[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.{sh,bash}]\nindent_style       = space\nindent_size        = 2\nsimplify           = true\nbinary_next_line   = false\nswitch_case_indent = true\nspace_redirects    = true\nfunction_next_line = false\n\n# also bash scripts.\n[{install,uninstall,bin/fzf-preview.sh,bin/fzf-tmux}]\nindent_style       = space\nindent_size        = 2\nsimplify           = true\nbinary_next_line   = false\nswitch_case_indent = true\nspace_redirects    = true\nfunction_next_line = false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: junegunn\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue_template.yml",
    "content": "---\nname: Issue Template\ndescription: Report a problem or bug related to fzf to help us improve\n\nbody:\n  - type: markdown\n    attributes:\n      value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED\n\n  - type: checkboxes\n    attributes:\n      label: Checklist\n      options:\n        - label: I have read through the manual page (`man fzf`)\n          required: true\n        - label: I have searched through the existing issues\n          required: true\n        - label: For bug reports, I have checked if the bug is reproducible in the latest version of fzf\n          required: false\n\n  - type: input\n    attributes:\n      label: Output of `fzf --version`\n      placeholder: e.g. 0.48.1 (d579e33)\n    validations:\n      required: true\n\n  - type: checkboxes\n    attributes:\n      label: OS\n      options:\n        - label: Linux\n        - label: macOS\n        - label: Windows\n        - label: Etc.\n\n  - type: checkboxes\n    attributes:\n      label: Shell\n      options:\n        - label: bash\n        - label: zsh\n        - label: fish\n\n  - type: textarea\n    attributes:\n      label: Problem / Steps to reproduce\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n        interval: \"weekly\"\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "go:\n  - changed-files:\n      - any-glob-to-any-file:\n          - src/**\n          - main.go\n          - go.mod\n          - go.sum\n\nshell:\n  - changed-files:\n      - any-glob-to-any-file:\n          - shell/**\n\nbash:\n  - changed-files:\n      - any-glob-to-any-file:\n          - shell/**/*.bash\n\nzsh:\n  - changed-files:\n      - any-glob-to-any-file:\n          - shell/**/*.zsh\n\nfish:\n  - changed-files:\n      - any-glob-to-any-file:\n          - shell/**/*.fish\n\nvim:\n  - changed-files:\n      - any-glob-to-any-file:\n          - plugin/**\n\ndocs:\n  - changed-files:\n      - any-glob-to-any-file:\n          - '*.md'\n          - doc/**\n          - man/**\n\nci:\n  - changed-files:\n      - any-glob-to-any-file:\n          - .github/**\n\nbuild:\n  - changed-files:\n      - any-glob-to-any-file:\n          - Makefile\n          - .goreleaser.yml\n          - Dockerfile\n\ntest:\n  - changed-files:\n      - any-glob-to-any-file:\n          - test/**\n          - src/**/*_test.go\n\ninstall:\n  - changed-files:\n      - any-glob-to-any-file:\n          - install\n          - install.ps1\n          - uninstall\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning\nname: CodeQL\n\non:\n  push:\n    branches: [ master, devel ]\n  pull_request:\n    branches: [ master ]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  analyze:\n    permissions:\n      actions: read  # for github/codeql-action/init to get workflow details\n      contents: read  # for actions/checkout to fetch code\n      security-events: write  # for github/codeql-action/autobuild to send a status report\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: ['go']\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v5\n      with:\n        fetch-depth: 0\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: ${{ matrix.language }}\n\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v4\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/depsreview.yaml",
    "content": "name: 'Dependency Review'\non: [pull_request]\n\npermissions:\n  contents: read\n\njobs:\n  dependency-review:\n    runs-on: ubuntu-latest\n    steps:\n      - name: 'Checkout Repository'\n        uses: actions/checkout@v5\n      - name: 'Dependency Review'\n        uses: actions/dependency-review-action@v4\n"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "content": "name: Label PRs\n\non:\n  pull_request_target:\n    types: [opened, synchronize, reopened]\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  label:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/labeler@v6\n        with:\n          configuration-path: .github/labeler.yml\n"
  },
  {
    "path": ".github/workflows/linux.yml",
    "content": "---\nname: build\n\non:\n  push:\n    branches: [ master, devel ]\n  pull_request:\n    branches: [ master ]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nenv:\n  LANG: C.UTF-8\n\njobs:\n  build:\n    runs-on: ubuntu-24.04\n    steps:\n    - uses: actions/checkout@v5\n      with:\n        fetch-depth: 0\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version: \"1.23\"\n\n    - name: Setup Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 3.4.6\n\n    - name: Install packages\n      run: sudo apt-get install --yes zsh fish tmux shfmt\n\n    - name: Install Ruby gems\n      run: bundle install\n\n    - name: Rubocop\n      run: make lint\n\n    - name: Unit test\n      run: make test\n\n    - name: Fuzz test\n      run: |\n        go test ./src/algo/ -fuzz=FuzzIndexByteTwo -fuzztime=5s\n        go test ./src/algo/ -fuzz=FuzzLastIndexByteTwo -fuzztime=5s\n\n    - name: Integration test\n      run: make install && ./install --all && tmux new-session -d && ruby test/runner.rb --verbose\n"
  },
  {
    "path": ".github/workflows/macos.yml",
    "content": "---\nname: Test fzf on macOS\n\non:\n  push:\n    branches: [ master, devel ]\n  pull_request:\n    branches: [ master ]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: macos-latest\n    steps:\n    - uses: actions/checkout@v5\n      with:\n        fetch-depth: 0\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version: \"1.23\"\n\n    - name: Setup Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 3.0.0\n\n    - name: Install packages\n      run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux shfmt\n\n    - name: Install Ruby gems\n      run: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1\n\n    - name: Rubocop\n      run: rubocop --require rubocop-minitest --require rubocop-performance\n\n    - name: Unit test\n      run: make test\n\n    - name: Integration test\n      run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose\n"
  },
  {
    "path": ".github/workflows/sponsors.yml",
    "content": "---\nname: Generate Sponsors README\non:\n  workflow_dispatch:\n  schedule:\n    - cron: 0 15 * * 6\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v5\n\n      - name: Generate Sponsors 💖\n        uses: JamesIves/github-sponsors-readme-action@v1\n        with:\n          token: ${{ secrets.SPONSORS_TOKEN }}\n          file: 'README.md'\n\n      - name: Deploy to GitHub Pages 🚀\n        uses: JamesIves/github-pages-deploy-action@v4\n        with:\n          branch: master\n          folder: '.'\n"
  },
  {
    "path": ".github/workflows/typos.yml",
    "content": "name: \"Spell Check\"\non: [pull_request]\n\njobs:\n  typos:\n    name: Spell Check with Typos\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v5\n    - uses: crate-ci/typos@v1.29.4\n"
  },
  {
    "path": ".github/workflows/winget.yml",
    "content": "name: Publish to Winget\non:\n  release:\n    types: [released]\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: vedantmgoyal2009/winget-releaser@v2\n        with:\n          identifier: junegunn.fzf\n          installers-regex: '-windows_(armv7|arm64|amd64)\\.zip$'\n          token: ${{ secrets.WINGET_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "bin/fzf\nbin/fzf.exe\ndist\ntarget\npkg\n.DS_Store\ndoc/tags\nvendor\ngopath\n*.zwc\nfzf\ntmp\n*.patch\n.idea\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "---\nversion: 2\nproject_name: fzf\n\nbefore:\n  hooks:\n    - go mod download\n\nbuilds:\n  - id: fzf\n    goos:\n      - darwin\n      - linux\n      - windows\n      - freebsd\n      - openbsd\n      - android\n    goarch:\n      - amd64\n      - arm\n      - arm64\n      - loong64\n      - ppc64le\n      - s390x\n      - riscv64\n    goarm:\n      - \"5\"\n      - \"6\"\n      - \"7\"\n    flags:\n      - -trimpath\n    ldflags:\n      - \"-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}\"\n    ignore:\n      - goos: freebsd\n        goarch: arm\n      - goos: openbsd\n        goarch: arm\n      - goos: freebsd\n        goarch: arm64\n      - goos: openbsd\n        goarch: arm64\n      - goos: openbsd\n        goarch: riscv64\n      - goos: android\n        goarch: amd64\n      - goos: android\n        goarch: arm\n\n# .goreleaser.yaml\nnotarize:\n  macos:\n    - # Whether this configuration is enabled or not.\n      #\n      # Default: false.\n      # Templates: allowed.\n      enabled: \"{{ not .IsSnapshot }}\"\n\n      # Before notarizing, we need to sign the binary.\n      # This blocks defines the configuration for doing so.\n      sign:\n        # The .p12 certificate file path or its base64'd contents.\n        certificate: \"{{.Env.MACOS_SIGN_P12}}\"\n\n        # The password to be used to open the certificate.\n        password: \"{{.Env.MACOS_SIGN_PASSWORD}}\"\n\n      # Then, we notarize the binaries.\n      notarize:\n        # The issuer ID.\n        # Its the UUID you see when creating the App Store Connect key.\n        issuer_id: \"{{.Env.MACOS_NOTARY_ISSUER_ID}}\"\n\n        # Key ID.\n        # You can see it in the list of App Store Connect Keys.\n        # It will also be in the ApiKey filename.\n        key_id: \"{{.Env.MACOS_NOTARY_KEY_ID}}\"\n\n        # The .p8 key file path or its base64'd contents.\n        key: \"{{.Env.MACOS_NOTARY_KEY}}\"\n\n        # Whether to wait for the notarization to finish.\n        # Not recommended, as it could take a really long time.\n        wait: true\n\narchives:\n  - name_template: \"{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}\"\n    ids:\n      - fzf\n    formats:\n      - tar.gz\n    format_overrides:\n      - goos: windows\n        formats:\n          - zip\n    files:\n      - non-existent*\n\nrelease:\n  github:\n    owner: junegunn\n    name: fzf\n  prerelease: auto\n  name_template: '{{ .Version }}'\n\nsnapshot:\n  version_template: \"{{ .Version }}-devel\"\n\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n      - README\n      - test\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "AllCops:\n  NewCops: enable\nLayout/LineLength:\n  Enabled: false\nMetrics:\n  Enabled: false\nLint/ShadowingOuterLocalVariable:\n  Enabled: false\nLint/NestedMethodDefinition:\n  Enabled: false\nStyle/MethodCallWithArgsParentheses:\n  Enabled: true\n  AllowedMethods:\n    - assert\n    - exit\n    - paste\n    - puts\n    - raise\n    - refute\n    - require\n    - send_keys\n  AllowedPatterns:\n    - ^assert_\n    - ^refute_\nStyle/NumericPredicate:\n  Enabled: false\nStyle/StringConcatenation:\n  Enabled: false\nStyle/OptionalBooleanParameter:\n  Enabled: false\nStyle/WordArray:\n  MinSize: 1\nMinitest/AssertEqual:\n  Enabled: false\nMinitest/EmptyLineBeforeAssertionMethods:\n  Enabled: false\nNaming/VariableNumber:\n  Enabled: false\nLint/EmptyBlock:\n  Enabled: false\nStyle/SafeNavigationChainLength:\n  Enabled: false\n"
  },
  {
    "path": ".tool-versions",
    "content": "golang 1.23\nruby 3.4\nshfmt 3.12\n"
  },
  {
    "path": "ADVANCED.md",
    "content": "Advanced fzf examples\n======================\n\n* *Last update: 2025/02/02*\n* *Requires fzf 0.59.0 or later*\n\n---\n\n<!-- vim-markdown-toc GFM -->\n\n* [Introduction](#introduction)\n* [Display modes](#display-modes)\n    * [`--height`](#--height)\n    * [`--tmux`](#--tmux)\n* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)\n    * [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)\n    * [Toggling between data sources](#toggling-between-data-sources)\n    * [Toggling with a single key binding](#toggling-with-a-single-key-binding)\n* [Ripgrep integration](#ripgrep-integration)\n    * [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)\n    * [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)\n    * [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)\n    * [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)\n    * [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)\n    * [Controlling Ripgrep search and fzf search simultaneously](#controlling-ripgrep-search-and-fzf-search-simultaneously)\n* [Log tailing](#log-tailing)\n* [Key bindings for git objects](#key-bindings-for-git-objects)\n    * [Files listed in `git status`](#files-listed-in-git-status)\n    * [Branches](#branches)\n    * [Commit hashes](#commit-hashes)\n* [Color themes](#color-themes)\n    * [fzf Theme Playground](#fzf-theme-playground)\n    * [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)\n\n<!-- vim-markdown-toc -->\n\nIntroduction\n------------\n\nfzf is an interactive [Unix filter][filter] program that is designed to be\nused with other Unix tools. It reads a list of items from the standard input,\nallows you to select a subset of the items, and prints the selected ones to\nthe standard output. You can think of it as an interactive version of *grep*,\nand it's already useful even if you don't know any of its options.\n\n```sh\n# 1. ps:   Feed the list of processes to fzf\n# 2. fzf:  Interactively select a process using fuzzy matching algorithm\n# 3. awk:  Take the PID from the selected line\n# 3. kill: Kill the process with the PID\nps -ef | fzf | awk '{print $2}' | xargs kill -9\n```\n\n[filter]: https://en.wikipedia.org/wiki/Filter_(software)\n\nWhile the above example succinctly summarizes the fundamental concept of fzf,\nyou can build much more sophisticated interactive workflows using fzf once you\nlearn its wide variety of features.\n\n- To see the full list of options and features, see `man fzf`\n- To see the latest additions, see [CHANGELOG.md](CHANGELOG.md)\n\nThis document will guide you through some examples that will familiarize you\nwith the advanced features of fzf.\n\nDisplay modes\n-------------\n\n### `--height`\n\nfzf by default opens in fullscreen mode, but it's not always desirable.\nOftentimes, you want to see the current context of the terminal while using\nfzf. `--height` is an option for opening fzf below the cursor in\nnon-fullscreen mode so you can still see the previous commands and their\nresults above it.\n\n```sh\nfzf --height=40%\n```\n\n![image](https://user-images.githubusercontent.com/700826/113379893-c184c680-93b5-11eb-9676-c7c0a2f01748.png)\n\nYou might also want to experiment with other layout options such as\n`--layout=reverse`, `--info=inline`, `--border`, `--margin`, etc.\n\n```sh\nfzf --height=40% --layout=reverse\nfzf --height=40% --layout=reverse --info=inline\nfzf --height=40% --layout=reverse --info=inline --border\nfzf --height=40% --layout=reverse --info=inline --border --margin=1\nfzf --height=40% --layout=reverse --info=inline --border --margin=1 --padding=1\n```\n\n![image](https://user-images.githubusercontent.com/700826/113379932-dfeac200-93b5-11eb-9e28-df1a2ee71f90.png)\n\n*(See man page to see the full list of options)*\n\nBut you definitely don't want to repeat `--height=40% --layout=reverse\n--info=inline --border --margin=1 --padding=1` every time you use fzf. You\ncould write a wrapper script or shell alias, but there is an easier option.\nDefine `$FZF_DEFAULT_OPTS` like so:\n\n```sh\nexport FZF_DEFAULT_OPTS=\"--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1\"\n```\n\n### `--tmux`\n\n(Requires tmux 3.3 or later)\n\nIf you're using tmux, you can open fzf in a tmux popup using `--tmux` option.\n\n```sh\n# Open fzf in a tmux popup at the center of the screen with 70% width and height\nfzf --tmux 70%\n```\n\n![image](https://github.com/junegunn/fzf/assets/700826/9c365405-c700-49b2-8985-60d822ed4cff)\n\n`--tmux` option is silently ignored if you're not on tmux. So if you're trying\nto avoid opening fzf in fullscreen, try specifying both `--height` and `--tmux`.\n\n```sh\n# --tmux is specified later so it takes precedence over --height when on tmux.\n# If you're not on tmux, --tmux is ignored and --height is used instead.\nfzf  --height 70% --tmux 70%\n```\n\nYou can also specify the position, width, and height of the popup window in\nthe following format:\n\n* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]]`\n\n```sh\n# 100% width and 60% height\nfzf --tmux 100%,60% --border horizontal\n```\n\n![image](https://github.com/junegunn/fzf/assets/700826/f80d3514-d69f-42f2-a8de-a392a562bfcf)\n\n```sh\n# On the right (50% width)\nfzf --tmux right\n```\n\n![image](https://github.com/junegunn/fzf/assets/700826/4033ade4-7efa-421b-a3fb-a430d197098a)\n\n```sh\n# On the left (40% width and 70% height)\nfzf --tmux left,40%,70%\n```\n\n![image](https://github.com/junegunn/fzf/assets/700826/efe43881-2bf0-49ea-ab2e-1377f778cd52)\n\n> [!TIP]\n> You might also want to check out my tmux plugins which support this popup\n> window layout.\n>\n> - https://github.com/junegunn/tmux-fzf-url\n> - https://github.com/junegunn/tmux-fzf-maccy\n\nDynamic reloading of the list\n-----------------------------\n\nfzf can dynamically update the candidate list using an arbitrary program with\n`reload` bindings (The design document for `reload` can be found\n[here][reload]).\n\n[reload]: https://github.com/junegunn/fzf/issues/1750\n\n### Updating the list of processes by pressing CTRL-R\n\nThis example shows how you can set up a binding for dynamically updating the\nlist without restarting fzf.\n\n```sh\n(date; ps -ef) |\n  fzf --bind='ctrl-r:reload(date; ps -ef)' \\\n      --header=$'Press CTRL-R to reload\\n\\n' --header-lines=2 \\\n      --preview='echo {}' --preview-window=down,3,wrap \\\n      --layout=reverse --height=80% | awk '{print $2}' | xargs kill -9\n```\n\n![image](https://user-images.githubusercontent.com/700826/113465047-200c7c00-946c-11eb-918c-268f37a900c8.png)\n\n- The initial command is `(date; ps -ef)`. It prints the current date and\n  time, and the list of the processes.\n- With `--header` option, you can show any message as the fixed header.\n- To disallow selecting the first two lines (`date` and `ps` header), we use\n  `--header-lines=2` option.\n- `--bind='ctrl-r:reload(date; ps -ef)'` binds CTRL-R to `reload` action that\n  runs `date; ps -ef`, so we can update the list of the processes by pressing\n  CTRL-R.\n- We use simple `echo {}` preview option, so we can see the entire line on the\n  preview window below even if it's too long\n\n### Toggling between data sources\n\nYou're not limited to just one reload binding. Set up multiple bindings so\nyou can switch between data sources.\n\n```sh\nfind * | fzf --prompt 'All> ' \\\n             --header 'CTRL-D: Directories / CTRL-F: Files' \\\n             --bind 'ctrl-d:change-prompt(Directories> )+reload(find * -type d)' \\\n             --bind 'ctrl-f:change-prompt(Files> )+reload(find * -type f)'\n```\n\n![image](https://user-images.githubusercontent.com/700826/113465073-4af6d000-946c-11eb-858f-2372c0955f67.png)\n\n![image](https://user-images.githubusercontent.com/700826/113465072-46321c00-946c-11eb-9b6f-cda3951df579.png)\n\n### Toggling with a single key binding\n\nThe above example uses two different key bindings to toggle between two modes,\nbut can we just use a single key binding?\n\nTo make a key binding behave differently each time it is pressed, we need:\n\n1. a way to store the current state. i.e. \"which mode are we in?\"\n2. and a way to dynamically perform different actions depending on the state.\n\nThe following example shows how to 1. store the current mode in the prompt\nstring, 2. and use this information (`$FZF_PROMPT`) to determine which\nactions to perform using the `transform` action.\n\n```sh\nfd --type file |\n  fzf --prompt 'Files> ' \\\n      --header 'CTRL-T: Switch between Files/Directories' \\\n      --bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ Files ]] &&\n              echo \"change-prompt(Files> )+reload(fd --type file)\" ||\n              echo \"change-prompt(Directories> )+reload(fd --type directory)\"' \\\n      --preview '[[ $FZF_PROMPT =~ Files ]] && bat --color=always {} || tree -C {}'\n```\n\nRipgrep integration\n-------------------\n\n### Using fzf as the secondary filter\n\n* Requires [bat][bat]\n* Requires [Ripgrep][rg]\n\n[bat]: https://github.com/sharkdp/bat\n[rg]: https://github.com/BurntSushi/ripgrep\n\nfzf is pretty fast for filtering a list that you will rarely have to think\nabout its performance. But it is not the right tool for searching for text\ninside many large files, and in that case you should definitely use something\nlike [Ripgrep][rg].\n\nIn the next example, Ripgrep is the primary filter that searches for the given\ntext in files, and fzf is used as the secondary fuzzy filter that adds\ninteractivity to the workflow. And we use [bat][bat] to show the matching line in\nthe preview window.\n\nThis is a bash script and it will not run as expected on other non-compliant\nshells. To avoid the compatibility issue, let's save this snippet as a script\nfile called `rfv`.\n\n```bash\n#!/usr/bin/env bash\n\n# 1. Search for text in files using Ripgrep\n# 2. Interactively narrow down the list using fzf\n# 3. Open the file in Vim\nrg --color=always --line-number --no-heading --smart-case \"${*:-}\" |\n  fzf --ansi \\\n      --color \"hl:-1:underline,hl+:-1:underline:reverse\" \\\n      --delimiter : \\\n      --preview 'bat --color=always {1} --highlight-line {2}' \\\n      --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \\\n      --bind 'enter:become(vim {1} +{2})'\n```\n\nAnd run it with an initial query string.\n\n```sh\n# Make the script executable\nchmod +x rfv\n\n# Run it with the initial query \"algo\"\n./rfv algo\n```\n\n> Ripgrep will perform the initial search and list all the lines that contain\n`algo`. Then we further narrow down the list on fzf.\n\n![image](https://user-images.githubusercontent.com/700826/113683873-a42a6200-96ff-11eb-9666-26ce4091b0e4.png)\n\nI know it's a lot to digest, let's try to break down the code.\n\n- Ripgrep prints the matching lines in the following format\n  ```\n  man/man1/fzf.1:54:.BI \"--algo=\" TYPE\n  man/man1/fzf.1:55:Fuzzy matching algorithm (default: v2)\n  man/man1/fzf.1:58:.BR v2 \"     Optimal scoring algorithm (quality)\"\n  src/pattern_test.go:7:  \"github.com/junegunn/fzf/src/algo\"\n  ```\n  The first token delimited by `:` is the file path, and the second token is\n  the line number of the matching line. They respectively correspond to `{1}`\n  and `{2}` in the preview command.\n    - `--preview 'bat --color=always {1} --highlight-line {2}'`\n- As we run `rg` with `--color=always` option, we should tell fzf to parse\n  ANSI color codes in the input by setting `--ansi`.\n- We customize how fzf colors various text elements using `--color` option.\n  `-1` tells fzf to keep the original color from the input. See `man fzf` for\n  available color options.\n- The value of `--preview-window` option consists of 5 components delimited\n  by `,`\n    1. `up` — Position of the preview window\n    1. `60%` — Size of the preview window\n    1. `border-bottom` — Preview window border only on the bottom side\n    1. `+{2}+3/3` — Scroll offset of the preview contents\n    1. `~3` — Fixed header\n- Let's break down the latter two. We want to display the bat output in the\n  preview window with a certain scroll offset so that the matching line is\n  positioned near the center of the preview window.\n    - `+{2}` — The base offset is extracted from the second token\n    - `+3` — We add 3 lines to the base offset to compensate for the header\n      part of `bat` output\n        - ```\n          ───────┬──────────────────────────────────────────────────────────\n                 │ File: CHANGELOG.md\n          ───────┼──────────────────────────────────────────────────────────\n             1   │ CHANGELOG\n             2   │ =========\n             3   │\n             4   │ 0.26.0\n             5   │ ------\n          ```\n    - `/3` adjusts the offset so that the matching line is shown at a third\n      position in the window\n    - `~3` makes the top three lines fixed header so that they are always\n      visible regardless of the scroll offset\n- Instead of using shell script to process the final output of fzf, we use\n  `become(...)` action which was added in [fzf 0.38.0][0.38.0] to turn fzf\n  into a new process that opens the file with `vim` (`vim {1}`) and move the\n  cursor to the line (`+{2}`).\n\n[0.38.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0380\n\n### Using fzf as interactive Ripgrep launcher\n\nWe have learned that we can bind `reload` action to a key (e.g.\n`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind\n`reload` action to `change` event** so that whenever the user *changes* the\nquery string on fzf, `reload` action is triggered.\n\nHere is a variation of the above `rfv` script. fzf will restart Ripgrep every\ntime the user updates the query string on fzf. Searching and filtering is\ncompletely done by Ripgrep, and fzf merely provides the interactive interface.\nSo we lose the \"fuzziness\", but the performance will be better on larger\nprojects, and it will free up memory as you narrow down the results.\n\n```bash\n#!/usr/bin/env bash\n\n# 1. Search for text in files using Ripgrep\n# 2. Interactively restart Ripgrep with reload action\n# 3. Open the file in Vim\nRG_PREFIX=\"rg --column --line-number --no-heading --color=always --smart-case \"\nINITIAL_QUERY=\"${*:-}\"\nfzf --ansi --disabled --query \"$INITIAL_QUERY\" \\\n    --bind \"start:reload:$RG_PREFIX {q} || true\" \\\n    --bind \"change:reload:sleep 0.1; $RG_PREFIX {q} || true\" \\\n    --delimiter : \\\n    --preview 'bat --color=always {1} --highlight-line {2}' \\\n    --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \\\n    --bind 'enter:become(vim {1} +{2})'\n```\n\n![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png)\n\n- Instead of starting fzf in the usual `rg ... | fzf` form, we make it start\n  the initial Ripgrep process immediately via `start:reload` binding for the\n  consistency of the code.\n- Filtering is no longer a responsibility of fzf; hence `--disabled`\n- `{q}` in the reload command evaluates to the query string on fzf prompt.\n- `sleep 0.1` in the reload command is for \"debouncing\". This small delay will\n  reduce the number of intermediate Ripgrep processes while we're typing in\n  a query.\n\n### Switching to fzf-only search mode\n\nIn the previous example, we lost fuzzy matching capability as we completely\ndelegated search functionality to Ripgrep. But we can dynamically switch to\nfzf-only search mode by *\"unbinding\"* `reload` action from `change` event.\n\n```sh\n#!/usr/bin/env bash\n\n# Two-phase filtering with Ripgrep and fzf\n#\n# 1. Search for text in files using Ripgrep\n# 2. Interactively restart Ripgrep with reload action\n#    * Press alt-enter to switch to fzf-only filtering\n# 3. Open the file in Vim\nRG_PREFIX=\"rg --column --line-number --no-heading --color=always --smart-case \"\nINITIAL_QUERY=\"${*:-}\"\nfzf --ansi --disabled --query \"$INITIAL_QUERY\" \\\n    --bind \"start:reload:$RG_PREFIX {q}\" \\\n    --bind \"change:reload:sleep 0.1; $RG_PREFIX {q} || true\" \\\n    --bind \"alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query\" \\\n    --color \"hl:-1:underline,hl+:-1:underline:reverse\" \\\n    --prompt '1. ripgrep> ' \\\n    --delimiter : \\\n    --preview 'bat --color=always {1} --highlight-line {2}' \\\n    --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \\\n    --bind 'enter:become(vim {1} +{2})'\n```\n\n* Phase 1. Filtering with Ripgrep\n![image](https://user-images.githubusercontent.com/700826/119213880-735e8a80-bafd-11eb-8493-123e4be24fbc.png)\n* Phase 2. Filtering with fzf\n![image](https://user-images.githubusercontent.com/700826/119213887-7e191f80-bafd-11eb-98c9-71a1af9d7aab.png)\n\n- We added `--prompt` option to show that fzf is initially running in \"Ripgrep\n  launcher mode\".\n- We added `alt-enter` binding that\n    1. unbinds `change` event, so Ripgrep is no longer restarted on key press\n    2. changes the prompt to `2. fzf>`\n    3. enables search functionality of fzf\n    4. clears the current query string that was used to start Ripgrep process\n    5. and unbinds `alt-enter` itself as this is a one-off event\n- We reverted `--color` option for customizing how the matching chunks are\n  displayed in the second phase\n\n### Switching between Ripgrep mode and fzf mode\n\n[fzf 0.30.0][0.30.0] added `rebind` action so we can \"rebind\" the bindings\nthat were previously \"unbound\" via `unbind`.\n\nThis is an improved version of the previous example that allows us to switch\nbetween Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and\nCTRL-F.\n\n```sh\n#!/usr/bin/env bash\n\n# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)\nrm -f /tmp/rg-fzf-{r,f}\nRG_PREFIX=\"rg --column --line-number --no-heading --color=always --smart-case \"\nINITIAL_QUERY=\"${*:-}\"\nfzf --ansi --disabled --query \"$INITIAL_QUERY\" \\\n    --bind \"start:reload($RG_PREFIX {q})+unbind(ctrl-r)\" \\\n    --bind \"change:reload:sleep 0.1; $RG_PREFIX {q} || true\" \\\n    --bind \"ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)\" \\\n    --bind \"ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)+transform-query(echo {q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r)\" \\\n    --color \"hl:-1:underline,hl+:-1:underline:reverse\" \\\n    --prompt '1. ripgrep> ' \\\n    --delimiter : \\\n    --header '╱ CTRL-R (ripgrep mode) ╱ CTRL-F (fzf mode) ╱' \\\n    --preview 'bat --color=always {1} --highlight-line {2}' \\\n    --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \\\n    --bind 'enter:become(vim {1} +{2})'\n```\n\n- To restore the query string when switching between modes, we store the\n  current query in `/tmp/rg-fzf-{r,f}` files and restore the query using\n  `transform-query` action which was added in [fzf 0.36.0][0.36.0].\n- Also note that we unbind `ctrl-r` binding on `start` event which is\n  triggered once when fzf starts.\n\n[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300\n[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360\n\n### Switching between Ripgrep mode and fzf mode using a single key binding\n\nIn contrast to the previous version, we use just one hotkey to toggle between\nripgrep and fzf mode. This is achieved by using the `$FZF_PROMPT` as a state\nwithin the `transform` action, a feature introduced in [fzf 0.45.0][0.45.0]. A\nmore detailed explanation of this feature can be found in a previous section -\n[Toggling with a single keybinding](#toggling-with-a-single-key-binding).\n\n[0.45.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0450\n\nWhen using the `transform` action, the placeholder (`\\{q}`) should be escaped to\nprevent immediate evaluation.\n\n```sh\n#!/usr/bin/env bash\n\n# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)\nrm -f /tmp/rg-fzf-{r,f}\nRG_PREFIX=\"rg --column --line-number --no-heading --color=always --smart-case \"\nINITIAL_QUERY=\"${*:-}\"\nfzf --ansi --disabled --query \"$INITIAL_QUERY\" \\\n    --bind \"start:reload:$RG_PREFIX {q}\" \\\n    --bind \"change:reload:sleep 0.1; $RG_PREFIX {q} || true\" \\\n    --bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&\n      echo \"rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \\{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r\" ||\n      echo \"unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \\{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f\"' \\\n    --color \"hl:-1:underline,hl+:-1:underline:reverse\" \\\n    --prompt '1. ripgrep> ' \\\n    --delimiter : \\\n    --header 'CTRL-T: Switch between ripgrep/fzf' \\\n    --preview 'bat --color=always {1} --highlight-line {2}' \\\n    --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \\\n    --bind 'enter:become(vim {1} +{2})'\n```\n\n### Controlling Ripgrep search and fzf search simultaneously\n\n`search` and `transform-search` action allow you to trigger an fzf search with\nan arbitrary query string. This frees fzf from strictly following the prompt\ninput, enabling custom search syntax.\n\nIn the example below, `transform` action is used to conditionally trigger\n`reload` for ripgrep, followed by `search` for fzf. The first word of the\nquery initiates the Ripgrep process to generate the initial results, while the\nremainder of the query is passed to fzf for secondary filtering.\n\n```sh\n#!/usr/bin/env bash\n\nexport TEMP=$(mktemp -u)\ntrap 'rm -f \"$TEMP\"' EXIT\n\nINITIAL_QUERY=\"${*:-}\"\nTRANSFORMER='\n  rg_pat={q:1}      # The first word is passed to ripgrep\n  fzf_pat={q:2..}   # The rest are passed to fzf\n\n  if ! [[ -r \"$TEMP\" ]] || [[ $rg_pat != $(cat \"$TEMP\") ]]; then\n    echo \"$rg_pat\" > \"$TEMP\"\n    printf \"reload:sleep 0.1; rg --column --line-number --no-heading --color=always --smart-case %q || true\" \"$rg_pat\"\n  fi\n  echo \"+search:$fzf_pat\"\n'\nfzf --ansi --disabled --query \"$INITIAL_QUERY\" \\\n    --with-shell 'bash -c' \\\n    --bind \"start,change:transform:$TRANSFORMER\" \\\n    --color \"hl:-1:underline,hl+:-1:underline:reverse\" \\\n    --delimiter : \\\n    --preview 'bat --color=always {1} --highlight-line {2}' \\\n    --preview-window 'up,60%,border-line,+{2}+3/3,~3' \\\n    --bind 'enter:become(vim {1} +{2})'\n```\n\nLog tailing\n-----------\n\nfzf can run long-running preview commands and render partial results before\ncompletion. And when you specify `follow` flag in `--preview-window` option,\nfzf will \"`tail -f`\" the result, automatically scrolling to the bottom.\n\n```bash\n# With \"follow\", preview window will automatically scroll to the bottom.\n# \"\\033[2J\" is an ANSI escape sequence for clearing the screen.\n# When fzf reads this code it clears the previous preview contents.\nfzf --preview-window follow --preview 'for i in $(seq 100000); do\n  echo \"$i\"\n  sleep 0.01\n  (( i % 300 == 0 )) && printf \"\\033[2J\"\ndone'\n```\n\n![image](https://user-images.githubusercontent.com/700826/113473303-dd669600-94a3-11eb-88a9-1f61b996bb0e.png)\n\nAdmittedly, that was a silly example. Here's a practical one for browsing\nKubernetes pods.\n\n```bash\npods() {\n  command='kubectl get pods --all-namespaces' fzf \\\n    --info=inline --layout=reverse --header-lines=1 \\\n    --prompt \"$(kubectl config current-context | sed 's/-context$//')> \" \\\n    --header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\\n\\n' \\\n    --bind 'start,ctrl-r:reload:$command' \\\n    --bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \\\n    --bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash' \\\n    --bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2})' \\\n    --preview-window up:follow \\\n    --preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' \"$@\"\n}\n```\n\n![image](https://user-images.githubusercontent.com/700826/113473547-1d7a4880-94a5-11eb-98ef-9aa6f0ed215a.png)\n\n- The preview window will *\"log tail\"* the pod\n    - Holding on to a large amount of log will consume a lot of memory. So we\n      limited the initial log amount with `--tail=10000`.\n- `execute` bindings allow you to run any command without leaving fzf\n    - Press enter key on a pod to `kubectl exec` into it\n    - Press CTRL-O to open the log in your editor\n- Press CTRL-R to reload the pod list\n- Press CTRL-/ repeatedly to rotate through a different sets of preview\n  window options\n    1. `80%,border-bottom`\n    1. `hidden`\n    1. Empty string after `|` translates to the default options from `--preview-window`\n\nKey bindings for git objects\n----------------------------\n\nOftentimes, you want to put the identifiers of various Git object to the\ncommand-line. For example, it is common to write commands like these:\n\n```sh\ngit checkout [SOME_COMMIT_HASH or BRANCH or TAG]\ngit diff [SOME_COMMIT_HASH or BRANCH or TAG] [SOME_COMMIT_HASH or BRANCH or TAG]\n```\n\n[fzf-git.sh](https://github.com/junegunn/fzf-git.sh) project defines a set of\nfzf-based key bindings for Git objects. I strongly recommend that you check\nthem out because they are seriously useful.\n\n### Files listed in `git status`\n\n<kbd>CTRL-G</kbd><kbd>CTRL-F</kbd>\n\n![image](https://user-images.githubusercontent.com/700826/113473779-a9d93b00-94a6-11eb-87b5-f62a8d0a0efc.png)\n\n### Branches\n\n<kbd>CTRL-G</kbd><kbd>CTRL-B</kbd>\n\n![image](https://user-images.githubusercontent.com/700826/113473758-87dfb880-94a6-11eb-82f4-9218103f10bd.png)\n\n### Commit hashes\n\n<kbd>CTRL-G</kbd><kbd>CTRL-H</kbd>\n\n![image](https://user-images.githubusercontent.com/700826/113473765-91692080-94a6-11eb-8d38-ed4d41f27ac1.png)\n\nColor themes\n------------\n\nYou can customize how fzf colors the text elements with `--color` option. Here\nare a few color themes. Note that you need a terminal emulator that can\ndisplay 24-bit colors.\n\n```sh\n# junegunn/seoul256.vim (dark)\nexport FZF_DEFAULT_OPTS='--color=bg+:#3F3F3F,bg:#4B4B4B,border:#6B6B6B,spinner:#98BC99,hl:#719872,fg:#D9D9D9,header:#719872,info:#BDBB72,pointer:#E12672,marker:#E17899,fg+:#D9D9D9,preview-bg:#3F3F3F,prompt:#98BEDE,hl+:#98BC99'\n```\n\n![seoul256](https://user-images.githubusercontent.com/700826/113475011-2c192d80-94ae-11eb-9d17-1e5867bae01f.png)\n\n```sh\n# junegunn/seoul256.vim (light)\nexport FZF_DEFAULT_OPTS='--color=bg+:#D9D9D9,bg:#E1E1E1,border:#C8C8C8,spinner:#719899,hl:#719872,fg:#616161,header:#719872,info:#727100,pointer:#E12672,marker:#E17899,fg+:#616161,preview-bg:#D9D9D9,prompt:#0099BD,hl+:#719899'\n```\n\n![seoul256-light](https://user-images.githubusercontent.com/700826/113475022-389d8600-94ae-11eb-905f-0939dd535837.png)\n\n```sh\n# morhetz/gruvbox\nexport FZF_DEFAULT_OPTS='--color=bg+:#3c3836,bg:#32302f,spinner:#fb4934,hl:#928374,fg:#ebdbb2,header:#928374,info:#8ec07c,pointer:#fb4934,marker:#fb4934,fg+:#ebdbb2,prompt:#fb4934,hl+:#fb4934'\n```\n\n![gruvbox](https://user-images.githubusercontent.com/700826/113475042-494dfc00-94ae-11eb-9322-cd03a027305a.png)\n\n```sh\n# arcticicestudio/nord-vim\nexport FZF_DEFAULT_OPTS='--color=bg+:#3B4252,bg:#2E3440,spinner:#81A1C1,hl:#616E88,fg:#D8DEE9,header:#616E88,info:#81A1C1,pointer:#81A1C1,marker:#81A1C1,fg+:#D8DEE9,prompt:#81A1C1,hl+:#81A1C1'\n```\n\n![nord](https://user-images.githubusercontent.com/700826/113475063-67b3f780-94ae-11eb-9b24-5f0d22b63399.png)\n\n```sh\n# tomasr/molokai\nexport FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#E6DB74,hl:#7E8E91,fg:#F8F8F2,header:#7E8E91,info:#A6E22E,pointer:#A6E22E,marker:#F92672,fg+:#F8F8F2,prompt:#F92672,hl+:#F92672'\n```\n\n![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png)\n\n### fzf Theme Playground\n\n[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by\n[Vitor Mello](https://github.com/vitormv) is a webpage where you can\ninteractively create fzf themes.\n\n### Generating fzf color theme from Vim color schemes\n\nThe Vim plugin of fzf can generate `--color` option from the current color\nscheme according to `g:fzf_colors` variable. You can find the detailed\nexplanation [here](https://github.com/junegunn/fzf/blob/master/README-VIM.md#explanation-of-gfzf_colors).\n\nHere is an example. Add this to your Vim configuration file.\n\n```vim\nlet g:fzf_colors =\n\\ { 'fg':         ['fg', 'Normal'],\n  \\ 'bg':         ['bg', 'Normal'],\n  \\ 'preview-bg': ['bg', 'NormalFloat'],\n  \\ 'hl':         ['fg', 'Comment'],\n  \\ 'fg+':        ['fg', 'CursorLine', 'CursorColumn', 'Normal'],\n  \\ 'bg+':        ['bg', 'CursorLine', 'CursorColumn'],\n  \\ 'hl+':        ['fg', 'Statement'],\n  \\ 'info':       ['fg', 'PreProc'],\n  \\ 'border':     ['fg', 'Ignore'],\n  \\ 'prompt':     ['fg', 'Conditional'],\n  \\ 'pointer':    ['fg', 'Exception'],\n  \\ 'marker':     ['fg', 'Keyword'],\n  \\ 'spinner':    ['fg', 'Label'],\n  \\ 'header':     ['fg', 'Comment'] }\n```\n\nThen you can see how the `--color` option is generated by printing the result\nof `fzf#wrap()`.\n\n```vim\n:echo fzf#wrap()\n```\n\nUse this command to append `export FZF_DEFAULT_OPTS=\"...\"` line to the end of\nthe current file.\n\n```vim\n:call append('$', printf('export FZF_DEFAULT_OPTS=\"%s\"', matchstr(fzf#wrap().options, \"--color[^']*\")))\n```\n"
  },
  {
    "path": "BUILD.md",
    "content": "Building fzf\n============\n\nBuild instructions\n------------------\n\n### Prerequisites\n\n- Go 1.23 or above\n\n### Using Makefile\n\n```sh\n# Build fzf binary for your platform in target\nmake\n\n# Build fzf binary and copy it to bin directory\nmake install\n\n# Build fzf binaries and archives for all platforms using goreleaser\nmake build\n\n# Publish GitHub release\nmake release\n```\n\n> [!WARNING]\n> Makefile uses git commands to determine the version and the revision\n> information for `fzf --version`. So if you're building fzf from an\n> environment where its git information is not available, you have to manually\n> set `$FZF_VERSION` and `$FZF_REVISION`.\n>\n> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`\n\n> [!TIP]\n> To build fzf with profiling options enabled, set `TAGS=pprof`\n>\n> ```sh\n> TAGS=pprof make clean install\n> fzf --profile-cpu /tmp/cpu.pprof --profile-mem /tmp/mem.pprof \\\n>     --profile-block /tmp/block.pprof --profile-mutex /tmp/mutex.pprof\n> ```\n\nRunning tests\n-------------\n\n```sh\n# Run go unit tests\nmake test\n\n# Run integration tests (requires to be on tmux)\nmake itest\n\n# Run a single test case\nruby test/runner.rb --name test_something\n```\n\nThird-party libraries used\n--------------------------\n\n- [rivo/uniseg](https://github.com/rivo/uniseg)\n    - Licensed under [MIT](https://raw.githubusercontent.com/rivo/uniseg/master/LICENSE.txt)\n- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)\n    - Licensed under [MIT](http://mattn.mit-license.org)\n- [mattn/go-isatty](https://github.com/mattn/go-isatty)\n    - Licensed under [MIT](http://mattn.mit-license.org)\n- [tcell](https://github.com/gdamore/tcell)\n    - Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)\n- [fastwalk](https://github.com/charlievieth/fastwalk)\n    - Licensed under [MIT](https://raw.githubusercontent.com/charlievieth/fastwalk/master/LICENSE)\n\nLicense\n-------\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "CHANGELOG\n=========\n\n0.71.0\n------\n- Cross-reload item identity with `--id-nth`\n    - Added `--id-nth=NTH` to define item identity fields for cross-reload operations\n    - When a `reload` is triggered with tracking enabled, fzf searches for the tracked item by its identity fields in the new list.\n        - `--track --id-nth ..` tracks by the entire line\n        - `--track --id-nth 1` tracks by the first field\n        - `--track` without `--id-nth` retains the existing index-based tracking behavior\n        - The UI is temporarily blocked (prompt dimmed, input disabled) until the item is found or loading completes.\n            - Press `Escape` or `Ctrl-C` to cancel the blocked state without quitting\n            - Info line shows `+T*` / `+t*` while searching\n    - With `--multi`, selected items are preserved across `reload-sync` by matching their identity fields\n- Performance improvements\n    - The search performance now scales linearly with the number of CPU cores, as we dropped static partitioning to allow better load balancing across threads.\n      ```\n      === query: 'linux' ===\n        [all]   baseline:    17.12ms  current:    14.28ms  (1.20x)  matches: 179966 (12.79%)\n        [1T]    baseline:   136.49ms  current:   137.25ms  (0.99x)  matches: 179966 (12.79%)\n        [2T]    baseline:    75.74ms  current:    68.75ms  (1.10x)  matches: 179966 (12.79%)\n        [4T]    baseline:    41.16ms  current:    34.97ms  (1.18x)  matches: 179966 (12.79%)\n        [8T]    baseline:    32.82ms  current:    17.79ms  (1.84x)  matches: 179966 (12.79%)\n      ```\n    - Improved the cache structure, reducing memory footprint per entry by 86x.\n        - With the reduced per-entry cost, the cache now has broader coverage.\n- fish: Improved command history (CTRL-R) (#4703) (@bitraid)\n- Bug fixes\n    - `--walker=follow` no longer follows symlinks whose target is an ancestor of the walker root, avoiding severe resource exhaustion when a symlink points outside the tree (e.g. Wine's `z:` → `/`) (#4710)\n    - Fixed AWK tokenizer not treating a new line character as whitespace\n    - Fixed `--{accept,with}-nth` removing trailing whitespaces with a non-default `--delimiter`\n    - Fixed OSC8 hyperlinks being mangled when the URL contains unicode characters (#4707)\n    - Fixed `--with-shell` not handling quoted arguments correctly (#4709)\n\n0.70.0\n------\n- Added `change-with-nth` action for dynamically changing the `--with-nth` option.\n    - Requires `--with-nth` to be set initially.\n    - Multiple options separated by `|` can be given to cycle through.\n  ```sh\n  echo -e \"a b c\\nd e f\\ng h i\" | fzf --with-nth .. \\\n    --bind 'space:change-with-nth(1|2|3|1,3|2,3|)'\n  ```\n- Added `change-header-lines` action for dynamically changing the `--header-lines` option\n- Performance improvements (1.3x to 1.9x faster filtering depending on query)\n  ```\n  === query: 'l' ===\n    [all]   baseline:   168.87ms  current:    95.21ms  (1.77x)  matches: 5069891 (94.78%)\n    [1T]    baseline:  1652.22ms  current:   841.40ms  (1.96x)  matches: 5069891 (94.78%)\n\n  === query: 'lin' ===\n    [all]   baseline:   343.27ms  current:   252.59ms  (1.36x)  matches: 3516507 (65.74%)\n    [1T]    baseline:  3199.89ms  current:  2230.64ms  (1.43x)  matches: 3516507 (65.74%)\n\n  === query: 'linux' ===\n    [all]   baseline:    85.47ms  current:    63.72ms  (1.34x)  matches: 307229 (5.74%)\n    [1T]    baseline:   774.64ms  current:   589.32ms  (1.31x)  matches: 307229 (5.74%)\n\n  === query: 'linuxlinux' ===\n    [all]   baseline:    55.13ms  current:    35.67ms  (1.55x)  matches: 12230 (0.23%)\n    [1T]    baseline:   461.99ms  current:   332.38ms  (1.39x)  matches: 12230 (0.23%)\n\n  === query: 'linuxlinuxlinux' ===\n    [all]   baseline:    51.77ms  current:    32.53ms  (1.59x)  matches: 865 (0.02%)\n    [1T]    baseline:   409.99ms  current:   296.33ms  (1.38x)  matches: 865 (0.02%)\n  ```\n- Fixed `nth` attribute merge order to respect precedence hierarchy (#4697)\n- bash: Replaced `printf` with builtin `printf` to bypass local indirections (#4684) (@DarrenBishop)\n\n0.68.0\n------\n- Implemented word wrapping in the list section\n    - Added `--wrap=word` (or `--wrap-word`) option and `toggle-wrap-word` action for word-level line wrapping in the list section\n    - Changed default binding of `ctrl-/` and `alt-/` from `toggle-wrap` to `toggle-wrap-word`\n  ```sh\n  fzf --wrap=word\n  ```\n- Implemented word wrapping in the preview window\n    - Added `wrap-word` flag for `--preview-window` to enable word-level wrapping\n    - Added `toggle-preview-wrap-word` action\n  ```sh\n  fzf --preview 'bat --style=plain --color=always {}' \\\n      --preview-window wrap-word \\\n      --bind space:toggle-preview-wrap-word\n  ```\n- Added support for underline style variants in `--color`: `underline-double`, `underline-curly`, `underline-dotted`, `underline-dashed`\n  ```sh\n  fzf --color 'fg:underline-curly,current-fg:underline-dashed'\n  ```\n- Added support for underline styles (`4:N`) and underline colors (SGR 58/59)\n  ```sh\n  # In the list section\n  printf '\\e[4:3;58;2;255;0;0mRed curly underline\\e[0m\\n' | fzf --ansi\n\n  # In the preview window\n  fzf --preview \"printf '\\e[4:3;58;2;255;0;0mRed curly underline\\e[0m\\n'\"\n  ```\n- Added `--preview-wrap-sign` to set a different wrap indicator for the preview window\n- Added `alt-gutter` color option (#4602) (@hedgieinsocks)\n- Added `$FZF_WRAP` environment variable to child processes (`char` or `word` when wrapping is enabled) (#4672) (@bitraid)\n- fish: Improved command history (CTRL-R) (#4672) (@bitraid)\n    - Enabled syntax highlighting in the list on fish 4.3.3+\n    - Added syntax-highlighted preview window that auto-shows for long or multi-line commands\n    - Added `ALT-ENTER` to reformat and insert selected commands\n    - Improved handling of bulk deletion of selected history entries (`SHIFT-DELETE`)\n- Added fish completion support (#4605) (@lalvarezt)\n- zsh: Handle multi-line history selection (#4595) (@LangLangBart)\n- Bug fixes\n    - Fixed `_fzf_compgen_{path,dir}` to respect `FZF_COMPLETION_{PATH,DIR}_OPTS` (#4592) (@shtse8, @LangLangBart)\n    - Fixed `--preview-window follow` not working correctly with wrapping (#3243, #4258)\n    - Fixed symlinks to directories being returned as files (#4676) (@skk64)\n    - Fixed SIGHUP signal handling (#4668) (@LangLangBart)\n    - Fixed preview process not killed on exit (#4667)\n    - Fixed coloring of items with zero-width characters (#4620)\n    - Fixed `track-current` unset after a combined movement action (#4649)\n    - Fixed `--accept-nth` being ignored in filter mode (#4636) (@charemma)\n    - Fixed display width calculation with `maxWidth` (#4596) (@LangLangBart)\n    - Fixed clearing of the rest of the current line on start (#4652)\n    - Fixed `x-api-key` header not required for GET requests (#4627)\n    - Fixed key reading not cancelled when `execute` triggered via a server request (#4653)\n    - Fixed rebind of readline command `redraw-current-line` (#4635) (@jameslazo)\n    - Fixed `fzf-tmux` `TERM` quoting and added `mktemp` usage (#4664) (@Goofygiraffe06)\n    - Do not allow very long queries in `FuzzyMatchV2` (#4608)\n\n0.67.0\n------\n- Added `--freeze-left=N` option to keep the leftmost N columns always visible.\n  ```sh\n  # Keep the file name column fixed and always visible\n  git grep --line-number --color=always -- '' |\n      fzf --ansi --delimiter : --freeze-left 1\n\n  # Can be used with --keep-right\n  git grep --line-number --color=always -- '' |\n      fzf --ansi --delimiter : --freeze-left 1 --keep-right\n  ```\n- Also added `--freeze-right=N` option to keep the rightmost N columns always visible.\n  ```sh\n  # Stronger version of --keep-right that always keeps the right-end visible\n  fd | fzf --freeze-right 1\n\n  # Keep the base name always visible\n  fd | fzf --freeze-right 1 --delimiter /\n\n  # Keep both leftmost and rightmost components visible\n  fd | fzf --freeze-left 1 --freeze-right 1 --delimiter /\n  ```\n- Updated `--info=inline` to print the spinner (load indicator).\n- Bug fixes\n\n0.66.1\n------\n- Bug fixes\n    - Fixed a bug preventing 'ctrl-h' from being bound to an action (#4556)\n    - Fixed `--no-color` / `NO_COLOR` theme (#4561)\n\n0.66.0\n------\n\n### Quick summary\n\nThis version introduces many new features centered around the new \"raw\" mode.\n\n| Type        | Class   | Name                | Description                                        |\n| :--         | :--     | :--                 | :--                                                |\n| New         | Option  | `--raw`             | Enable raw mode by default                         |\n| New         | Option  | `--gutter CHAR`     | Set the gutter column character                    |\n| New         | Option  | `--gutter-raw CHAR` | Set the gutter column character in raw mode        |\n| Enhancement | Option  | `--listen SOCKET`   | Added support for Unix domain sockets              |\n| New         | Action  | `toggle-raw`        | Toggle raw mode                                    |\n| New         | Action  | `enable-raw`        | Enable raw mode                                    |\n| New         | Action  | `disable-raw`       | Disable raw mode                                   |\n| New         | Action  | `up-match`          | Move up to the matching item                       |\n| New         | Action  | `down-match`        | Move down to the matching item                     |\n| New         | Action  | `best`              | Move to the  matching item with the best score     |\n| New         | Color   | `nomatch`           | Color for non-matching items in raw mode           |\n| New         | Env Var | `FZF_RAW`           | Matching status in raw mode (0, 1, or undefined)   |\n| New         | Env Var | `FZF_DIRECTION`     | `up` or `down` depending on the layout             |\n| New         | Env Var | `FZF_SOCK`          | Path to the Unix domain socket fzf is listening on |\n| Enhancement | Key     | `CTRL-N`            | `down` -> `down-match`                             |\n| Enhancement | Key     | `CTRL-P`            | `up` -> `up-match`                                 |\n| Enhancement | Shell   | `CTRL-R` binding    | Toggle raw mode with `ALT-R`                       |\n| Enhancement | Shell   | `CTRL-R` binding    | Opt-out with an empty `FZF_CTRL_R_COMMAND`         |\n\n### 1. Introducing \"raw\" mode\n\n![](https://github.com/user-attachments/assets/9640ae11-b5f7-43fb-95f1-c29307fc17c2)\n\nThis version introduces a new \"raw\" mode (named so because it shows the list\n\"unfiltered\"). In raw mode, non-matching items stay in their original positions,\nbut appear dimmed. This allows you to see the surrounding items of a match and\nbetter understand the context of it. You can enable raw mode by default with\n`--raw`, but it's often more useful when toggled dynamically with the\n`toggle-raw` action.\n\n```sh\ntree | fzf --reverse --bind alt-r:toggle-raw\n```\n\nWhile non-matching items are displayed in a dimmed color, they are treated just\nlike matching items, so you place the cursor on them and perform any action. If\nyou prefer to navigate only through matching items, use the `down-match` and\n`up-match` actions, which are from now on bound to `CTRL-N` and `CTRL-P`\nrespectively, and also to `ALT-DOWN` and `ALT-UP`.\n\n| Key        | Action       | With `--history` |\n| :--        | :--          | :--              |\n| `down`     | `down`       |                  |\n| `up`       | `up`         |                  |\n| `ctrl-j`   | `down`       |                  |\n| `ctrl-k`   | `up`         |                  |\n| `ctrl-n`   | `down-match` | `next-history`   |\n| `ctrl-p`   | `up-match`   | `prev-history`   |\n| `alt-down` | `down-match` |                  |\n| `alt-up`   | `up-match`   |                  |\n\n> [!NOTE]\n> `CTRL-N` and `CTRL-P` are bound to `next-history` and `prev-history` when\n> `--history` option is enabled, so in that case, you'll need to manually bind\n> them, or use `ALT-DOWN` and `ALT-UP` instead.\n\n> [!TIP]\n> `up-match` and `down-match` are equivalent to `up` and `down` when not in\n> raw mode, so you can safely bind them to `up` and `arrow` keys if you prefer.\n> ```sh\n> fzf --bind up:up-match,down:down-match\n> ```\n\n#### Customizing the behavior\n\nIn raw mode, the input list is presented in its original order, unfiltered, and\nyour cursor will not move to the matching item automatically. Here are ways to\ncustomize the behavior.\n\n```sh\n# When the result list is updated, move the cursor to the item with the best score\n# (assuming sorting is not disabled)\nfzf --raw --bind result:best\n\n# Move to the first matching item in the original list\n# - $FZF_RAW is set to 0 when raw mode is enabled and the current item is a non-match\n# - $FZF_DIRECTION is set to either 'up' or 'down' depending on the layout direction\nfzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match'\n```\n\n#### Customizing the look\n\n##### Gutter\n\nTo make the mode visually distinct, the gutter column is rendered in a dashed\nline using `▖` character. But you can customize it with the `--gutter-raw CHAR`\noption.\n\n```sh\n# Use a thinner gutter instead of the default dashed line\nfzf --bind alt-r:toggle-raw --gutter-raw ▎\n```\n\n##### Color and style of non-matching items\n\nNon-matching items are displayed in a dimmed color by default, but you can\nchange it with the `--color nomatch:...` option.\n\n```sh\nfzf --raw --color nomatch:red\nfzf --raw --color nomatch:red:dim\nfzf --raw --color nomatch:red:dim:strikethrough\nfzf --raw --color nomatch:red:dim:strikethrough:italic\n```\n\nFor colored input, dimming alone may not be enough, and you may prefer to remove\ncolors entirely. For that case, a new special style attribute `strip` has been\nadded.\n\n```sh\nfd --color always | fzf --ansi --raw --color nomatch:dim:strip:strikethrough\n```\n\n#### Conditional actions for raw mode\n\nYou may want to perform different actions depending on whether the current item\nis a match or not. For that, fzf now exports `$FZF_RAW` environment variable.\n\nIt's:\n\n- Undefined if raw mode is disabled\n- `1` if the current item is a match\n- `0` otherwise\n\n```sh\n# Do not allow selecting non-matching items\nfzf --raw --bind 'enter:transform:[[ ${FZF_RAW-1} = 1 ]] && echo accept || echo bell'\n```\n\n#### Leveraging raw mode in shell integration\n\nThe `CTRL-R` binding (command history) now lets you toggle raw mode with `ALT-R`.\n\n### 2. Style changes\n\nThe screenshot on the right shows the updated gutter style:\n\n![](https://github.com/user-attachments/assets/8ea7b5ef-c99e-4686-905b-22eb078b700a)\n\nThis version includes a few minor updates to fzf's classic visual style:\n\n- The gutter column is now narrower, rendered with the left-half block character (`▌`).\n- Markers no longer use background colors.\n- The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes.\n\n### 3. `--listen` now supports Unix domain sockets\n\nIf an argument to `--listen` ends with `.sock`, fzf will listen on a Unix\ndomain socket at the specified path.\n\n```sh\nfzf --listen /tmp/fzf.sock --no-tmux\n\n# GET\ncurl --unix-socket /tmp/fzf.sock http\n\n# POST\ncurl --unix-socket /tmp/fzf.sock http -d up\n```\n\nNote that any existing file at the given path will be removed before creating\nthe socket, so avoid using an important file path.\n\n### 4. Added options\n\n#### `--gutter CHAR`\n\nThe gutter column can now be customized using `--gutter CHAR` and styled with\n`--color gutter:...`. Examples:\n\n```sh\n# Right-aligned gutter\nfzf --gutter '▐'\n\n# Even thinner gutter\nfzf --gutter '▎'\n\n# Yellow checker pattern\nfzf --gutter '▚' --color gutter:yellow\n\n# Classic style\nfzf --gutter ' ' --color gutter:reverse\n```\n\n#### `--gutter-raw CHAR`\n\nAs noted above, the `--gutter-raw CHAR` option was also added for customizing the gutter column in raw mode.\n\n### 5. Added actions\n\nThe following actions were introduced to support working with raw mode:\n\n| Action        | Description                                                                                 |\n| :--           | :--                                                                                         |\n| `toggle-raw`  | Toggle raw mode                                                                             |\n| `enable-raw`  | Enable raw mode                                                                             |\n| `disable-raw` | Disable raw mode                                                                            |\n| `up-match`    | Move up to the matching item; identical to `up` if raw mode is disabled                     |\n| `down-match`  | Move down to the matching item; identical to `down` if raw mode is disabled                 |\n| `best`        | Move to the matching item with the best score; identical to `first` if raw mode is disabled |\n\n### 6. Added environment variables\n\n#### `$FZF_DIRECTION`\n\n`$FZF_DIRECTION` is now exported to child processes, indicating the list direction of the current layout:\n\n- `up` for the default layout\n- `down` for `reverse` or `reverse-list`\n\nThis simplifies writing transform actions involving layout-dependent actions\nlike `{up,down}-match`, `{up,down}-selected`, and `toggle+{up,down}`.\n\n```sh\nfzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match'\n```\n\n#### `$FZF_SOCK`\n\nWhen fzf is listening on a Unix domain socket using `--listen`, the path to the\nsocket is exported as `$FZF_SOCK`, analogous to `$FZF_PORT` for TCP sockets.\n\n#### `$FZF_RAW`\n\nAs described above, `$FZF_RAW` is now exported to child processes in raw mode,\nindicating whether the current item is a match (`1`) or not (`0`). It is not\ndefined when not in raw mode.\n\n#### `$FZF_CTRL_R_COMMAND`\n\nYou can opt-out `CTRL-R` binding from the shell integration by setting\n`FZF_CTRL_R_COMMAND` to an empty string. Setting it to any other value is not\nsupported and will result in a warning.\n\n```sh\n# Disable the CTRL-R binding from the shell integration\nFZF_CTRL_R_COMMAND= eval \"$(fzf --bash)\"\n```\n\n### 7. Added key support for `--bind`\n\nPull request [#3996](https://github.com/junegunn/fzf/pull/3996) added support\nfor many additional keys for `--bind` option, such as `ctrl-backspace`.\n\n### 8. Breaking changes\n\n#### Hiding the gutter column\n\nIn the previous versions, the recommended way to hide the gutter column was to\nset `--color gutter:-1`. That's because the gutter column was just a space\ncharacter, reversed. But now that it's using a visible character (`▌`), applying\nthe default color is no longer enough to hide it. Instead, you can set it to\na space character.\n\n```sh\n# Hide the gutter column\nfzf --gutter ' '\n\n# Classic style\nfzf --gutter ' ' --color gutter:reverse\n```\n\n#### `--color` option\n\nIn the previous versions, some elements had default style attributes applied and\nyou would have to explicitly unset them with `regular` attribute if you wanted\nto reset them. This is no longer needed now, as the default style attributes\nare applied only when you do not specify any color or style for that element.\n\n```sh\n# No 'dim', just red and italic.\nfzf --ghost 'Type to search' --color ghost:red:italic\n```\n\n#### Compatibility changes\n\nStarting with this release, fzf is built with Go 1.23. Support for some old OS versions has been dropped.\n\nSee https://go.dev/wiki/MinimumRequirements.\n\n0.65.2\n------\n- Bug fixes and improvements\n    - Fix incorrect truncation of `--info-command` with `--info=inline-right` (#4479)\n    - [install] Support old uname in macOS (#4492)\n    - [bash 3] Fix `CTRL-T` and `ALT-C` to preserve the last yank (#4496)\n    - Do not unset `FZF_DEFAULT_*` variables when using winpty (#4497) (#4400)\n    - Fix rendering of items with tabs when using a non-default ellipsis (#4505)\n- **This is the final release to support Windows 7.**\n    - Future versions will be built with the latest Go toolchain, which has dropped support for Windows 7.\n\n0.65.1\n------\n- Fixed incorrect `$FZF_CLICK_HEADER_WORD` and `$FZF_CLICK_FOOTER_WORD` when the header or footer contains ANSI escape sequences and tab characters.\n- Fixed a bug where you cannot unset the default `--nth` using `change-nth` action.\n- Fixed a highlighting bug when using `--color fg:dim,nth:regular` pattern over ANSI-colored items.\n\n0.65.0\n------\n- Added `click-footer` event that is triggered when the footer section is clicked. When the event is triggered, the following environment variables are set:\n    - `$FZF_CLICK_FOOTER_COLUMN` - clicked column (1-based)\n    - `$FZF_CLICK_FOOTER_LINE` - clicked line (1-based)\n    - `$FZF_CLICK_FOOTER_WORD` - the word under the cursor\n  ```sh\n  fzf --footer $'[Edit] [View]\\n[Copy to clipboard]' \\\n      --with-shell 'bash -c' \\\n      --bind 'click-footer:transform:\n        [[ $FZF_CLICK_FOOTER_WORD =~ Edit ]] && echo \"execute:vim \\{}\"\n        [[ $FZF_CLICK_FOOTER_WORD =~ View ]] && echo \"execute:view \\{}\"\n        (( FZF_CLICK_FOOTER_LINE == 2 )) && (( FZF_CLICK_FOOTER_COLUMN < 20 )) &&\n            echo \"execute-silent(echo -n \\{} | pbcopy)+bell\"\n      '\n  ```\n- Added `trigger(...)` action that triggers events bound to another key or event.\n  ```sh\n  # You can click on each key name to trigger the actions bound to that key\n  fzf --footer 'Ctrl-E: Edit / Ctrl-V: View / Ctrl-Y: Copy to clipboard' \\\n      --with-shell 'bash -c' \\\n      --bind 'ctrl-e:execute:vim {}' \\\n      --bind 'ctrl-v:execute:view {}' \\\n      --bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell' \\\n      --bind 'click-footer:transform:\n        [[ $FZF_CLICK_FOOTER_WORD =~ Ctrl ]] && echo \"trigger(${FZF_CLICK_FOOTER_WORD%:})\"\n      '\n  ```\n    - You can specify a series of keys and events\n      ```sh\n      fzf --bind 'a:up,b:trigger(a,a,a)'\n      ```\n- Added support for `{*n}` and `{*nf}` placeholder.\n    - `{*n}` evaluates to the zero-based ordinal index of all matched items.\n    - `{*nf}` evaluates to the temporary file containing that.\n- Bug fixes and improvements\n    - [neovim] Fixed margin background color when `&winborder` is used (#4453)\n    - Fixed rendering error when hiding a preview window without border (#4465)\n    - fix(shell): check for mawk existence before version check (#4468)\n        - Thanks to @LangLangBart and @akinomyoga\n    - Fixed `--no-header-lines-border` behavior (08027e7a)\n\n0.64.0\n------\n- Added `multi` event that is triggered when the multi-selection has changed.\n  ```sh\n  fzf --multi \\\n      --bind 'ctrl-a:select-all,ctrl-d:deselect-all' \\\n      --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo \"Selected $FZF_SELECT_COUNT item(s)\"'\n  ```\n- [Halfwidth and fullwidth alphanumeric and punctuation characters](https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)) are now internally normalized to their ASCII equivalents to allow matching with ASCII queries.\n  ```sh\n  echo ＡＢＣ| fzf -q abc\n  ```\n- Renamed `clear-selection` action to `clear-multi` for consistency.\n    - `clear-selection` remains supported as an alias for backward compatibility.\n- Bug fixes\n    - Fixed a bug that could cause fzf to abort due to incorrect update ordering.\n    - Fixed a bug where some multi-selections were lost when using `exclude` or `change-nth`.\n\n0.63.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.63.0/_\n\n- Added footer. The default border style for footer is `line`, which draws a single separator line.\n  ```sh\n  fzf --reverse --footer \"fzf: friend zone forever\"\n  ```\n  - Options\n      - `--footer[=STRING]`\n      - `--footer-border[=STYLE]`\n      - `--footer-label=LABEL`\n      - `--footer-label-pos=COL[:bottom]`\n  - Colors\n      - `footer`\n      - `footer-bg`\n      - `footer-border`\n      - `footer-label`\n  - Actions\n      - `change-footer`\n      - `transform-footer`\n      - `bg-transform-footer`\n      - `change-footer-label`\n      - `transform-footer-label`\n      - `bg-transform-footer-label`\n- `line` border style is now allowed for all types of border except for `--list-border`.\n  ```sh\n  fzf --height 50% --style full:line --preview 'cat {}' \\\n      --bind 'focus:bg-transform-header(file {})+bg-transform-footer(wc {})'\n  ```\n- Added `{*}` placeholder flag that evaluates to all matched items.\n  ```bash\n  seq 10000 | fzf --preview \"awk '{sum += \\$1} END {print sum}' {*f}\"\n  ```\n  - Use this with caution, as it can make fzf sluggish for large lists.\n- Added asynchronous transform actions with `bg-` prefix that run asynchronously in the background, along with `bg-cancel` action to cancel currently running `bg-transform` actions.\n  ```sh\n  # Implement popup that disappears after 1 second\n  #   * Use footer as the popup\n  #   * Use `bell` to ring the terminal bell\n  #   * Use `bg-transform-footer` to clear the footer after 1 second\n  #   * Use `bg-cancel` to cancel currently running background transform actions\n  fzf --multi --list-border \\\n      --bind 'enter:execute-silent(echo -n {+} | pbcopy)+bell' \\\n      --bind 'enter:+transform-footer(echo Copied {} to clipboard)' \\\n      --bind 'enter:+bg-cancel+bg-transform-footer(sleep 1)'\n\n  # It's okay for the commands to take a little while because they run in the background\n  GETTER='curl -s http://metaphorpsum.com/sentences/1'\n  fzf --style full --border --preview : \\\n      --bind \"focus:bg-transform-header:$GETTER\" \\\n      --bind \"focus:+bg-transform-footer:$GETTER\" \\\n      --bind \"focus:+bg-transform-border-label:$GETTER\" \\\n      --bind \"focus:+bg-transform-preview-label:$GETTER\" \\\n      --bind \"focus:+bg-transform-input-label:$GETTER\" \\\n      --bind \"focus:+bg-transform-list-label:$GETTER\" \\\n      --bind \"focus:+bg-transform-header-label:$GETTER\" \\\n      --bind \"focus:+bg-transform-footer-label:$GETTER\" \\\n      --bind \"focus:+bg-transform-ghost:$GETTER\" \\\n      --bind \"focus:+bg-transform-prompt:$GETTER\"\n  ```\n- Added support for full-line background color in the list section\n  ```sh\n  for i in $(seq 16 255); do\n    echo -e \"\\x1b[48;5;${i}m\\x1b[0Khello\"\n  done | fzf --ansi\n  ```\n- SSH completion enhancements by @akinomyoga\n- Bug fixes and improvements\n\n0.62.0\n------\n- Relaxed the `--color` option syntax to allow whitespace-separated entries (in addition to commas), making multi-line definitions easier to write and read\n  ```sh\n  # seoul256-light\n  fzf --style full --color='\n    fg:#616161 fg+:#616161\n    bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1\n    hl:#719872 hl+:#719899\n    pointer:#e12672 marker:#e17899\n    header:#719872\n    spinner:#719899 info:#727100\n    prompt:#0099bd query:#616161\n    border:#e1e1e1\n  '\n  ```\n- Added `alt-bg` color to create striped lines to visually separate rows\n  ```sh\n  fzf --color bg:237,alt-bg:238,current-bg:236 --highlight-line\n\n  declare -f | perl -0777 -pe 's/^}\\n/}\\0/gm' |\n    bat --plain --language bash --color always |\n    fzf --read0 --ansi --reverse --multi \\\n        --color bg:237,alt-bg:238,current-bg:236 --highlight-line\n  ```\n- [fish] Improvements in CTRL-R binding (@bitraid)\n    - You can trigger CTRL-R in the middle of a command to insert the selected item\n    - You can delete history items with SHIFT-DEL\n- Bug fixes and improvements\n    - Fixed unnecessary 100ms delay after `reload` (#4364)\n    - Fixed `selected-bg` not applied to colored items (#4372)\n\n0.61.3\n------\n- Reverted #4351 as it caused `tmux run-shell 'fzf --tmux'` to fail (#4559 #4560)\n- More environment variables for child processes (#4356)\n\n0.61.2\n------\n- Fixed panic when using header border without pointer/marker (@phanen)\n- Fixed `--tmux` option when already inside a tmux popup (@peikk0)\n- Bug fixes and improvements in CTRL-T binding of fish (#4334) (@bitraid)\n- Added `--no-tty-default` option to make fzf search for the current TTY device instead of defaulting to `/dev/tty` (#4242)\n\n0.61.1\n------\n- Disable bracketed-paste mode on exit. This fixes issue where pasting breaks after running fzf on old bash versions that don't support the mode.\n\n0.61.0\n------\n- Added `--ghost=TEXT` to display a ghost text when the input is empty\n  ```sh\n  # Display \"Type to search\" when the input is empty\n  fzf --ghost \"Type to search\"\n  ```\n- Added `change-ghost` and `transform-ghost` actions for dynamically changing the ghost text\n- Added `change-pointer` and `transform-pointer` actions for dynamically changing the pointer sign\n- Added `r` flag for placeholder expression (raw mode) for unquoted output\n- Bug fixes and improvements\n\n0.60.3\n------\n- Bug fixes and improvements\n    - [fish] Enable multiple history commands insertion (#4280) (@bitraid)\n    - [walker] Append '/' to directory entries on MSYS2 (#4281)\n    - Trim trailing whitespaces after processing ANSI sequences (#4282)\n    - Remove temp files before `become` when using `--tmux` option (#4283)\n    - Fix condition for using item numlines cache (#4285) (@alex-huff)\n    - Make `--accept-nth` compatible with `--select-1` (#4287)\n    - Increase the query length limit from 300 to 1000 (#4292)\n    - [windows] Prevent fzf from consuming user input while paused (#4260)\n\n0.60.2\n------\n- Template for `--with-nth` and `--accept-nth` now supports `{n}` which evaluates to the zero-based ordinal index of the item\n- Fixed a regression that caused the last field in the \"nth\" expression to be trimmed when a regular expression delimiter is used\n    - Thanks to @phanen for the fix\n- Fixed 'jump' action when the pointer is an empty string\n\n0.60.1\n------\n- Bug fixes and minor improvements\n    - Built-in walker now prints directory entries with a trailing slash\n    - Fixed a bug causing unexpected behavior with [fzf-tab](https://github.com/Aloxaf/fzf-tab). Please upgrade if you use it.\n- Thanks to @alexeisersun, @bitraid, @Lompik, and @fsc0 for the contributions\n\n0.60.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.60.0/_\n\n- Added `--accept-nth` for choosing output fields\n  ```sh\n  ps -ef | fzf --multi --header-lines 1 | awk '{print $2}'\n  # Becomes\n  ps -ef | fzf --multi --header-lines 1 --accept-nth 2\n\n  git branch | fzf | cut -c3-\n  # Can be rewritten as\n  git branch | fzf --accept-nth -1\n  ```\n- `--accept-nth` and `--with-nth` now support a template that includes multiple field index expressions in curly braces\n  ```sh\n  echo foo,bar,baz | fzf --delimiter , --accept-nth '{1}, {3}, {2}'\n    # foo, baz, bar\n\n  echo foo,bar,baz | fzf --delimiter , --with-nth '{1},{3},{2},{1..2}'\n    # foo,baz,bar,foo,bar\n  ```\n- Added `exclude` and `exclude-multi` actions for dynamically excluding items\n  ```sh\n  seq 100 | fzf --bind 'ctrl-x:exclude'\n\n  # 'exclude-multi' will exclude the selected items or the current item\n  seq 100 | fzf --multi --bind 'ctrl-x:exclude-multi'\n  ```\n- Preview window now prints wrap indicator when wrapping is enabled\n  ```sh\n  seq 100 | xargs | fzf --wrap --preview 'echo {}' --preview-window wrap\n  ```\n- Bug fixes and improvements\n\n0.59.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.59.0/_\n\n- Prioritizing file name matches (#4192)\n    - Added a new tiebreak option `pathname` for prioritizing file name matches\n    - `--scheme=path` now sets `--tiebreak=pathname,length`\n    - fzf will automatically choose `path` scheme\n        * when the input is a TTY device, where fzf would start its built-in walker or run `$FZF_DEFAULT_COMMAND` which is usually a command for listing files,\n        * but not when `reload` or `transform` action is bound to `start` event, because in that case, fzf can't be sure of the input type.\n- Added `--header-lines-border` to display header from `--header-lines` with a separate border\n  ```sh\n  # Use --header-lines-border to separate two headers\n  ps -ef | fzf --style full --layout reverse --header-lines 1 \\\n               --bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \\\n               --header-lines-border bottom --no-list-border\n  ```\n- `click-header` event now sets `$FZF_CLICK_HEADER_WORD` and `$FZF_CLICK_HEADER_NTH`. You can use them to implement a clickable header for changing the search scope using the new `transform-nth` action.\n  ```sh\n  # Click on the header line to limit search scope\n  ps -ef | fzf --style full --layout reverse --header-lines 1 \\\n               --header-lines-border bottom --no-list-border \\\n               --color fg:dim,nth:regular \\\n               --bind 'click-header:transform-nth(\n                         echo $FZF_CLICK_HEADER_NTH\n                       )+transform-prompt(\n                         echo \"$FZF_CLICK_HEADER_WORD> \"\n                       )'\n  ```\n    - `$FZF_KEY` was updated to expose the type of the click. e.g. `click`, `ctrl-click`, etc. You can use it to implement a more sophisticated behavior.\n    - `kill` completion for bash and zsh were updated to use this feature\n- Added `--no-input` option to completely disable and hide the input section\n  ```sh\n  # Click header to trigger search\n  fzf --header '[src] [test]' --no-input --layout reverse \\\n      --header-border bottom --input-border \\\n      --bind 'click-header:transform-search:echo ${FZF_CLICK_HEADER_WORD:1:-1}'\n\n  # Vim-like mode switch\n  fzf --layout reverse-list --no-input \\\n      --bind 'j:down,k:up,/:show-input+unbind(j,k,/)' \\\n      --bind 'enter,esc,ctrl-c:transform:\n        if [[ $FZF_INPUT_STATE = enabled ]]; then\n          echo \"rebind(j,k,/)+hide-input\"\n        elif [[ $FZF_KEY = enter ]]; then\n          echo accept\n        else\n          echo abort\n        fi\n      '\n  ```\n    - You can later show the input section using `show-input` or `toggle-input` action, and hide it again using `hide-input`, or `toggle-input`.\n- Extended `{q}` placeholder to support ranges. e.g. `{q:1}`, `{q:2..}`, etc.\n- Added `search(...)` and `transform-search(...)` action to trigger an fzf search with an arbitrary query string. This can be used to extend the search syntax of fzf. In the following example, fzf will use the first word of the query to trigger ripgrep search, and use the rest of the query to perform fzf search within the result.\n  ```sh\n  export TEMP=$(mktemp -u)\n  trap 'rm -f \"$TEMP\"' EXIT\n\n  TRANSFORMER='\n    rg_pat={q:1}      # The first word is passed to ripgrep\n    fzf_pat={q:2..}   # The rest are passed to fzf\n\n    if ! [[ -r \"$TEMP\" ]] || [[ $rg_pat != $(cat \"$TEMP\") ]]; then\n      echo \"$rg_pat\" > \"$TEMP\"\n      printf \"reload:sleep 0.1; rg --column --line-number --no-heading --color=always --smart-case %q || true\" \"$rg_pat\"\n    fi\n    echo \"+search:$fzf_pat\"\n  '\n  fzf --ansi --disabled \\\n    --with-shell 'bash -c' \\\n    --bind \"start,change:transform:$TRANSFORMER\"\n  ```\n- You can now bind actions to multiple keys and events at once by writing a comma-separated list of keys and events before the colon\n  ```sh\n  # Load 'ps -ef' output on start and reload it on CTRL-R\n  fzf --bind 'start,ctrl-r:reload:ps -ef'\n  ```\n- `--min-height` option now takes a number followed by `+`, which tells fzf to show at least that many items in the list section. The default value is now changed to `10+`.\n  ```sh\n  # You will only see the input section which takes 3 lines\n  fzf --style=full --height 1% --min-height 3\n\n  # You will see 3 items in the list section\n  fzf --style full --height 1% --min-height 3+\n  ```\n    - Shell integration scripts were updated to use `--min-height 20+` by default\n- `--header-lines` will be displayed at the top in `reverse-list` layout\n- Added `bell` action to ring the terminal bell\n  ```sh\n  # Press CTRL-Y to copy the current line to the clipboard and ring the bell\n  fzf --bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell'\n  ```\n- Added `toggle-bind` action\n- Bug fixes and improvements\n- Fixed fish script to support fish 3.1.2 or later (@bitraid)\n\n0.58.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.58.0/_\n\nThis version introduces three new border types, `--list-border`, `--input-border`, and `--header-border`, offering much greater flexibility for customizing the user interface.\n\n<img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-4-borders.png\" />\n\nAlso, fzf now offers \"style presets\" for quick customization, which can be activated using the `--style` option.\n\n| Preset    | Screenshot                                                                             |\n| :---      | :---                                                                                   |\n| `default` | <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-style-default.png\"/> |\n| `full`    | <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-style-full.png\"/>    |\n| `minimal` | <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-style-minimal.png\"/> |\n\n- Style presets (#4160)\n    - `--style=full[:BORDER_STYLE]`\n    - `--style=default`\n    - `--style=minimal`\n- Border and label for the list section (#4148)\n    - Options\n        - `--list-border[=STYLE]`\n        - `--list-label=LABEL`\n        - `--list-label-pos=COL[:bottom]`\n    - Colors\n        - `list-fg`\n        - `list-bg`\n        - `list-border`\n        - `list-label`\n    - Actions\n        - `change-list-label`\n        - `transform-list-label`\n- Border and label for the input section (prompt line and info line) (#4154)\n    - Options\n        - `--input-border[=STYLE]`\n        - `--input-label=LABEL`\n        - `--input-label-pos=COL[:bottom]`\n    - Colors\n        - `input-fg` (`query`)\n        - `input-bg`\n        - `input-border`\n        - `input-label`\n    - Actions\n        - `change-input-label`\n        - `transform-input-label`\n- Border and label for the header section (#4159)\n    - Options\n        - `--header-border[=STYLE]`\n        - `--header-label=LABEL`\n        - `--header-label-pos=COL[:bottom]`\n    - Colors\n        - `header-fg` (`header`)\n        - `header-bg`\n        - `header-border`\n        - `header-label`\n    - Actions\n        - `change-header-label`\n        - `transform-header-label`\n- Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]`\n- Added new preview border style `line` which draws a single separator line between the preview window and the rest of the interface\n- fzf will now render a dashed line (`┈┈`) in each `--gap` for better visual separation.\n  ```sh\n  # All bash/zsh functions, highlighted\n  declare -f |\n    perl -0 -pe 's/^}\\n/}\\0/gm' |\n    bat --plain --language bash --color always |\n    fzf --read0 --ansi --layout reverse --multi --highlight-line --gap\n  ```\n    * You can customize the line using `--gap-line[=STR]`.\n- You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup.\n  ```sh\n  fzf --tmux border-native --bind 'enter:execute:less {}'\n  ```\n- Added `toggle-multi-line` action\n- Added `toggle-hscroll` action\n- Added `change-nth` action for dynamically changing the value of the `--nth` option\n  ```sh\n  # Start with --nth 1, then 2, then 3, then back to the default, 1\n  echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo\n  ```\n- `--nth` parts of each line can now be rendered in a different text style\n  ```sh\n  # nth in a different style\n  ls -al | fzf --nth -1 --color nth:italic\n  ls -al | fzf --nth -1 --color nth:reverse\n  ls -al | fzf --nth -1 --color nth:reverse:bold\n\n  # Dim the other parts\n  ls -al | fzf --nth -1 --color nth:regular,fg:dim\n\n  # With 'change-nth'. The current nth option is exported as $FZF_NTH.\n  ps -ef | fzf --reverse --header-lines 1 --header-border bottom --input-border \\\n             --color nth:regular,fg:dim \\\n             --bind 'ctrl-n:change-nth(8..|1|2|3|4|5|6|7|)' \\\n             --bind 'result:transform-prompt:echo \"${FZF_NTH}> \"'\n  ```\n- A single-character delimiter is now treated as a plain string delimiter rather than a regular expression delimiter, even if it's a regular expression meta-character.\n    - This means you can just write `--delimiter '|'` instead of escaping it as `--delimiter '\\|'`\n- Bug fixes\n- Bug fixes and improvements in fish scripts (thanks to @bitraid)\n\n0.57.0\n------\n- You can now resize the preview window by dragging the border\n- Built-in walker improvements\n    - `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib`\n    - `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build`\n- Removed long processing delay when displaying images in the preview window\n- `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098)\n- Bug fixes in fish scripts\n\n0.56.3\n------\n- Bug fixes in zsh scripts\n    - fix(zsh): handle backtick trigger edge case (#4090)\n    - revert(zsh): remove 'fc -RI' call in the history widget (#4093)\n    - Thanks to @LangLangBart for the contributions\n\n0.56.2\n------\n- Bug fixes\n    - Fixed abnormal scrolling behavior when `--wrap` is set (#4083)\n    - [zsh] Fixed warning message when `ksh_arrays` is set (#4084)\n\n0.56.1\n------\n- Bug fixes and improvements\n    - Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)\n    - `page-up` and `page-down` actions now work correctly with multi-line items (#4069)\n    - `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)\n    - [zsh] Fixed regression in history loading with shared option (#4071)\n    - [zsh] Better command extraction in zsh completion (#4082)\n- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions\n\n0.56.0\n------\n- Added `--gap[=N]` option to display empty lines between items.\n    - This can be useful to visually separate adjacent multi-line items.\n      ```sh\n      # All bash functions, highlighted\n      declare -f | perl -0777 -pe 's/^}\\n/}\\0/gm' |\n        bat --plain --language bash --color always |\n        fzf --read0 --ansi --reverse --multi --highlight-line --gap\n      ```\n    - Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.\n      ```sh\n      fzf --info inline-right --gap --color gutter:-1\n      ```\n- Added `noinfo` option to `--preview-window` to hide the scroll indicator in the preview window\n- Bug fixes\n    - Thanks to @LangLangBart, @akinomyoga, and @charlievieth for fixing the bugs\n\n0.55.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_\n\n- Added `exact-boundary-match` type to the search syntax. When a search term is single-quoted, fzf will search for the exact occurrences of the string with both ends at word boundaries.\n  ```sh\n  fzf --query \"'here'\" << EOF\n  come here\n  not there\n  EOF\n  ```\n- [bash] Fuzzy path completion is enabled for all commands\n    - 1. If the default completion is not already set\n    - 2. And if the current bash supports `complete -D` option\n    - However, fuzzy completion for some commands can be \"dynamically\" disabled by the dynamic completion loader\n    - See the comment in `__fzf_default_completion` function for more information\n- Comments are now allowed in `$FZF_DEFAULT_OPTS` and `$FZF_DEFAULT_OPTS_FILE`\n  ```sh\n  export FZF_DEFAULT_OPTS='\n    # Layout options\n    --layout=reverse\n    --info=inline-right   # Show info on the right side of the prompt line\n    # ...\n  '\n  ```\n- Hyperlinks (OSC 8) are now supported in the preview window and in the main window\n  ```sh\n  printf '<< \\e]8;;http://github.com/junegunn/fzf\\e\\\\Link to \\e[32mfz\\e[0mf\\e]8;;\\e\\\\ >>' | fzf --ansi\n\n  fzf --preview \"printf '<< \\e]8;;http://github.com/junegunn/fzf\\e\\\\Link to \\e[32mfz\\e[0mf\\e]8;;\\e\\\\ >>'\"\n  ```\n- The default `--ellipsis` is now `··` instead of `..`.\n- [vim] A spec can have `exit` callback that is called with the exit status of fzf\n    - This can be used to clean up temporary resources or restore the original state when fzf is closed without a selection\n- Fixed `--tmux bottom` when the status line is not at the bottom\n- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)\n- Added fallback `ps` command for `kill` completion on Cygwin\n\n0.54.3\n------\n- Fixed incompatibility of adaptive height specification and 'start:reload'\n  ```sh\n  # A regression in 0.54.0 would cause this to fail\n  fzf --height '~100%' --bind 'start:reload:seq 10'\n  ```\n- Environment variables are now available to `$FZF_DEFAULT_COMMAND`\n  ```sh\n  FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo\n  ```\n\n0.54.2\n------\n- Fixed incorrect syntax highlighting of truncated multi-line entries\n- Updated GoReleaser to 2.1.0 to simplify notarization of macOS binaries\n    - macOS archives will be in `tar.gz` format instead of `zip` format since we no longer notarize the zip files but binaries\n- (Windows) Reverted a mintty fix in 0.54.0\n    - As a result, mouse may not work on mintty in fullscreen mode. However, fzf will correctly read non-ASCII input in fullscreen mode (`--no-height`).\n    - fzf unfortunately cannot read non-ASCII input when not in fullscreen mode on Windows. So if you need to input non-ASCII characters, add `--no-height` to your `$FZF_DEFAULT_OPTS`.\n    - Any help in fixing this issue will be appreciated (#3799, #3847).\n\n0.54.1\n------\n- Updated [fastwalk](https://github.com/charlievieth/fastwalk) dependency for built-in directory walker\n    - [fastwalk: add optional sorting and improve documentation](https://github.com/charlievieth/fastwalk/pull/27)\n    - [fastwalk: only check if MSYSTEM is set during MSYS/MSYS2](https://github.com/charlievieth/fastwalk/pull/28)\n    - Thanks to @charlievieth\n- Reverted ALT-C binding of fish to use `cd` instead of `builtin cd`\n    - `builtin cd` was introduced to work around a bug of `cd` coming from `zoxide init --cmd cd fish` where it cannot handle `--` argument.\n    - However, the default `cd` of fish is actually a wrapper function for supporting `cd -`, so we want to use it instead.\n    - See [#3928](https://github.com/junegunn/fzf/pull/3928) for more information and consider helping zoxide fix the bug.\n\n0.54.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.54.0/_\n\n- Implemented line wrap of long items\n    - `--wrap` option enables line wrap\n    - `--wrap-sign` customizes the sign for wrapped lines (default: `↳ `)\n    - `toggle-wrap` action toggles line wrap\n      ```sh\n      history | fzf --tac --wrap --bind 'ctrl-/:toggle-wrap' --wrap-sign $'\\t↳ '\n      ```\n    - fzf by default binds `CTRL-/` and `ALT-/` to `toggle-wrap`\n- Updated shell integration scripts to leverage line wrap\n    - CTRL-R binding includes `--wrap-sign $'\\t↳ '` to indent wrapped lines\n    - `kill **` completion uses `--wrap` to show the whole line by default\n      instead of showing it in the preview window\n- Added `--info-command` option for customizing the info line\n  ```sh\n  # Prepend the current cursor position in yellow\n  fzf --info-command='echo -e \"\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛\"'\n  ```\n    - `$FZF_INFO` is set to the original info text\n    - ANSI color codes are supported\n- Pointer and marker signs can be set to empty strings\n  ```sh\n  # Minimal style\n  fzf --pointer '' --marker '' --prompt '' --info hidden\n  ```\n- Better cache management and improved rendering for `--tail`\n- Improved `--sync` behavior\n    - When `--sync` is provided, fzf will not render the interface until the initial filtering and the associated actions (bound to any of `start`, `load`, `result`, or `focus`) are complete.\n      ```sh\n      # fzf will not render intermediate states\n      (sleep 1; seq 1000000; sleep 1) |\n        fzf --sync --query 5 --listen --bind start:up,load:up,result:up,focus:change-header:Ready\n      ```\n- GET endpoint is now available from `execute` and `transform` actions (it used to timeout due to lock conflict)\n  ```sh\n  fzf --listen --sync --bind 'focus:transform-header:curl -s localhost:$FZF_PORT?limit=0 | jq .'\n  ```\n- Added `offset-middle` action to place the current item is in the middle of the screen\n- fzf will not start the initial reader when `reload` or `reload-sync` is bound to `start` event. `fzf < /dev/null` or `: | fzf` are no longer required and extraneous `load` event will not fire due to the empty list.\n  ```sh\n  # Now this will work as expected. Previously, this would print an invalid header line.\n  # `fzf < /dev/null` or `: | fzf` would fix the problem, but then an extraneous\n  # `load` event would fire and the header would be prematurely updated.\n  fzf --header 'Loading ...' --header-lines 1 \\\n      --bind 'start:reload:sleep 1; ps -ef' \\\n      --bind 'load:change-header:Loaded!'\n  ```\n- Fixed mouse support on Windows\n- Fixed crash when using `--tiebreak=end` with very long items\n- zsh 5.0 compatibility (thanks to @LangLangBart)\n- Fixed `--walker-skip` to also skip symlinks to directories\n- Fixed `result` event not fired when input stream is not complete\n- New tags will have `v` prefix so that they are available on https://proxy.golang.org/\n\n0.53.0\n------\n_Release highlights: https://junegunn.github.io/fzf/releases/0.53.0/_\n\n- Multi-line display\n    - See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/)\n    - fzf can now display multi-line items\n      ```sh\n      # All bash functions, highlighted\n      declare -f | perl -0777 -pe 's/^}\\n/}\\0/gm' |\n        bat --plain --language bash --color always |\n        fzf --read0 --ansi --reverse --multi --highlight-line\n\n      # Ripgrep multi-line output\n      rg --pretty bash | perl -0777 -pe 's/\\n\\n/\\n\\0/gm' |\n        fzf --read0 --ansi --multi --highlight-line --reverse --tmux 70%\n      ```\n        - To disable multi-line display, use `--no-multi-line`\n    - CTRL-R bindings of bash, zsh, and fish have been updated to leverage multi-line display\n    - The default `--pointer` and `--marker` have been changed from `>` to Unicode bar characters as they look better with multi-line items\n    - Added `--marker-multi-line` to customize the select marker for multi-line entries with the default set to `╻┃╹`\n      ```\n      ╻First line\n      ┃...\n      ╹Last line\n      ```\n- Native tmux integration\n    - Added `--tmux` option to replace fzf-tmux script and simplify distribution\n      ```sh\n      # --tmux [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]\n      # Center, 100% width and 70% height\n      fzf --tmux 100%,70% --border horizontal --padding 1,2\n\n      # Left, 30% width\n      fzf --tmux left,30%\n\n      # Bottom, 50% height\n      fzf --tmux bottom,50%\n      ```\n        - To keep the implementation simple, it only uses popups. You need tmux 3.3 or later.\n    - To use `--tmux` in Vim plugin:\n      ```vim\n      let g:fzf_layout = { 'tmux': '100%,70%' }\n      ```\n- Added support for endless input streams\n    - See [Browsing log stream with fzf](https://junegunn.github.io/fzf/tips/browsing-log-streams/)\n    - Added `--tail=NUM` option to limit the number of items to keep in memory. This is useful when you want to browse an endless stream of data (e.g. log stream) with fzf while limiting memory usage.\n      ```sh\n      # Interactive filtering of a log stream\n      tail -f *.log | fzf --tail 100000 --tac --no-sort --exact\n      ```\n- Better Windows Support\n    - fzf now works on Git bash (mintty) out of the box via winpty integration\n    - Many fixes and improvements for Windows\n- man page is now embedded in the binary; `fzf --man` to see it\n- Changed the default `--scroll-off` to 3, as we think it's a better default\n- Process started by `execute` action now directly writes to and reads from `/dev/tty`. Manual `/dev/tty` redirection for interactive programs is no longer required.\n  ```sh\n  # Vim will work fine without /dev/tty redirection\n  ls | fzf --bind 'space:execute:vim {}' > selected\n  ```\n- Added `print(...)` action to queue an arbitrary string to be printed on exit. This was mainly added to work around the limitation of `--expect` where it's not compatible with `--bind` on the same key and it would ignore other actions bound to it.\n  ```sh\n  # This doesn't work as expected because --expect is not compatible with --bind\n  fzf --multi --expect ctrl-y --bind 'ctrl-y:select-all'\n\n  # This is something you can do instead\n  fzf --multi --bind 'enter:print()+accept,ctrl-y:select-all+print(ctrl-y)+accept'\n  ```\n    - We also considered making them compatible, but realized that some users may have been relying on the current behavior.\n- [`NO_COLOR`](https://no-color.org/) environment variable is now respected. If the variable is set, fzf defaults to `--no-color` unless otherwise specified.\n\n0.52.1\n------\n- Fixed a critical bug in the Windows version\n    - Windows users are strongly encouraged to upgrade to this version\n\n0.52.0\n------\n- Added `--highlight-line` to highlight the whole current line (à la `set cursorline` of Vim)\n- Added color names for selected lines: `selected-fg`, `selected-bg`, and `selected-hl`\n  ```sh\n  fzf --border --multi --info inline-right --layout reverse --marker ▏ --pointer ▌ --prompt '▌ '  \\\n      --highlight-line --color gutter:-1,selected-bg:238,selected-fg:146,current-fg:189\n  ```\n- Added `click-header` event that is triggered when the header section is clicked. When the event is triggered, `$FZF_CLICK_HEADER_COLUMN` and `$FZF_CLICK_HEADER_LINE` are set.\n  ```sh\n  fd --type f |\n    fzf --header $'[Files] [Directories]' --header-first \\\n        --bind 'click-header:transform:\n          (( FZF_CLICK_HEADER_COLUMN <= 7 )) && echo \"reload(fd --type f)\"\n          (( FZF_CLICK_HEADER_COLUMN >= 9 )) && echo \"reload(fd --type d)\"\n        '\n  ```\n- Add `$FZF_COMPLETION_{DIR,PATH}_OPTS` for separately customizing the behavior of fuzzy completion\n  ```sh\n  # Set --walker options without 'follow' not to follow symbolic links\n  FZF_COMPLETION_PATH_OPTS=\"--walker=file,dir,hidden\"\n  FZF_COMPLETION_DIR_OPTS=\"--walker=dir,hidden\"\n  ```\n- Fixed Windows argument escaping\n- Bug fixes and improvements\n- The code was heavily refactored to allow using fzf as a library in Go programs. The API is still experimental and subject to change.\n    - https://gist.github.com/junegunn/193990b65be48a38aac6ac49d5669170\n\n0.51.0\n------\n- Added a new environment variable `$FZF_POS` exported to the child processes. It's the vertical position of the cursor in the list starting from 1.\n  ```sh\n  # Toggle selection to the top or to the bottom\n  seq 30 | fzf --multi --bind 'load:pos(10)' \\\n    --bind 'shift-up:transform:for _ in $(seq $FZF_POS $FZF_MATCH_COUNT); do echo -n +toggle+up; done' \\\n    --bind 'shift-down:transform:for _ in $(seq 1 $FZF_POS); do echo -n +toggle+down; done'\n  ```\n- Added `--with-shell` option to start child processes with a custom shell command and flags\n  ```sh\n  gem list | fzf --with-shell 'ruby -e' \\\n    --preview 'pp Gem::Specification.find_by_name({1})' \\\n    --bind 'ctrl-o:execute-silent:\n        spec = Gem::Specification.find_by_name({1})\n        [spec.homepage, *spec.metadata.filter { _1.end_with?(\"uri\") }.values].uniq.each do\n          system \"open\", _1\n        end\n    '\n  ```\n- Added `change-multi` action for dynamically changing `--multi` option\n    - `change-multi` - enable multi-select mode with no limit\n    - `change-multi(NUM)` - enable multi-select mode with a limit\n    - `change-multi(0)` - disable multi-select mode\n- Windows improvements\n    - `become` action is now supported on Windows\n        - Unlike in *nix, this does not use `execve(2)`. Instead it spawns a new process and waits for it to finish, so the exact behavior may differ.\n    - Fixed argument escaping for Windows cmd.exe. No redundant escaping of backslashes.\n- Bug fixes and improvements\n\n0.50.0\n------\n- Search performance optimization. You can observe 50%+ improvement in some scenarios.\n  ```\n  $ rg --line-number --no-heading --smart-case . > $DATA\n\n  $ wc < $DATA\n   5520118 26862362 897487793\n\n  $ hyperfine -w 1 -L bin fzf-0.49.0,fzf-7ce6452,fzf-a5447b8,fzf '{bin} --filter \"///\" < $DATA | head -30'\n  Summary\n    fzf --filter \"///\" < $DATA | head -30 ran\n      1.16 ± 0.03 times faster than fzf-a5447b8 --filter \"///\" < $DATA | head -30\n      1.23 ± 0.03 times faster than fzf-7ce6452 --filter \"///\" < $DATA | head -30\n      1.52 ± 0.03 times faster than fzf-0.49.0 --filter \"///\" < $DATA | head -30\n  ```\n- Added `jump` and `jump-cancel` events that are triggered when leaving `jump` mode\n  ```sh\n  # Default behavior\n  fzf --bind space:jump\n\n  # Same as jump-accept action\n  fzf --bind space:jump,jump:accept\n\n  # Accept on jump, abort on cancel\n  fzf --bind space:jump,jump:accept,jump-cancel:abort\n\n  # Change header on jump-cancel\n  fzf --bind 'space:change-header(Type jump label)+jump,jump-cancel:change-header:Jump cancelled'\n  ```\n- Added a new environment variable `$FZF_KEY` exported to the child processes. It's the name of the last key pressed.\n  ```sh\n  fzf --bind 'space:jump,jump:accept,jump-cancel:transform:[[ $FZF_KEY =~ ctrl-c ]] && echo abort'\n  ```\n- fzf can be built with profiling options. See [BUILD.md](BUILD.md) for more information.\n- Bug fixes\n\n0.49.0\n------\n- Ingestion performance improved by around 40% (more or less depending on options)\n- `--info=hidden` and `--info=inline-right` will no longer hide the horizontal separator by default. This gives you more flexibility in customizing the layout.\n    ```sh\n    fzf --border --info=inline-right\n    fzf --border --info=inline-right --separator ═\n    fzf --border --info=inline-right --no-separator\n    fzf --border --info=hidden\n    fzf --border --info=hidden --separator ━\n    fzf --border --info=hidden --no-separator\n    ```\n- Added two environment variables exported to the child processes\n    - `FZF_PREVIEW_LABEL`\n    - `FZF_BORDER_LABEL`\n    ```sh\n    # Use the current value of $FZF_PREVIEW_LABEL to determine which actions to perform\n    git ls-files |\n      fzf --header 'Press CTRL-P to change preview mode' \\\n          --bind='ctrl-p:transform:[[ $FZF_PREVIEW_LABEL =~ cat ]] \\\n          && echo \"change-preview(git log --color=always \\{})+change-preview-label([[ log ]])\" \\\n          || echo \"change-preview(bat --color=always \\{})+change-preview-label([[ cat ]])\"'\n    ```\n- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action\n    - `track` is still available as an alias\n- Added `untrack-current` and `toggle-track-current` actions\n    - `*-current` actions are no-op when the global tracking state is set\n- Bug fixes and minor improvements\n\n0.48.1\n------\n- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script\n    ```sh\n    # bash\n    FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval \"$(fzf --bash)\"\n\n    # zsh\n    FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval \"$(fzf --zsh)\"\n\n    # fish\n    fzf --fish | FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= source\n    ```\n    - Setting the variables after sourcing the script will have no effect\n- Bug fixes\n\n0.48.0\n------\n- Shell integration scripts are now embedded in the fzf binary. This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries.\n    - bash\n      ```sh\n      # Set up fzf key bindings and fuzzy completion\n      eval \"$(fzf --bash)\"\n      ```\n    - zsh\n      ```sh\n      # Set up fzf key bindings and fuzzy completion\n      eval \"$(fzf --zsh)\"\n      ```\n    - fish\n      ```fish\n      # Set up fzf key bindings\n      fzf --fish | source\n      ```\n- Added options for customizing the behavior of the built-in walker\n    | Option               | Description                                       | Default              |\n    | ---                  | ---                                               | ---                  |\n    | `--walker=OPTS`      | Walker options (`[file][,dir][,follow][,hidden]`) | `file,follow,hidden` |\n    | `--walker-root=DIR`  | Root directory from which to start walker         | `.`                  |\n    | `--walker-skip=DIRS` | Comma-separated list of directory names to skip   | `.git,node_modules`  |\n    - Examples\n        ```sh\n        # Built-in walker is only used by standalone fzf when $FZF_DEFAULT_COMMAND is not set\n        unset FZF_DEFAULT_COMMAND\n\n        fzf # default: --walker=file,follow,hidden --walker-root=. --walker-skip=.git,node_modules\n        fzf --walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target\n\n        # Walker options in $FZF_DEFAULT_OPTS\n        export FZF_DEFAULT_OPTS=\"--walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target\"\n        fzf\n\n        # Reading from STDIN; --walker is ignored\n        seq 100 | fzf --walker=dir\n\n        # Reading from $FZF_DEFAULT_COMMAND; --walker is ignored\n        export FZF_DEFAULT_COMMAND='seq 100'\n        fzf --walker=dir\n        ```\n- Shell integration scripts have been updated to use the built-in walker with these new options and they are now much faster out of the box.\n\n0.47.0\n------\n- Replaced [\"the default find command\"][find] with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms.\n  This doesn't affect you if you have `$FZF_DEFAULT_COMMAND` set.\n    - Breaking changes:\n        - Unlike [the previous \"find\" command][find], the new traversal code will list hidden files, but hidden directories will still be ignored\n        - No filtering of `devtmpfs` or `proc` types\n        - Traversal is parallelized, so the order of the entries will be different each time\n    - You may wonder why fzf implements directory walker anyway when it's a filter program following the [Unix philosophy][unix].\n      But fzf has had [the walker code for years][walker] to tackle the performance problem on Windows. And I decided to use the same approach on different platforms as well for the benefits listed above.\n    - Built-in walker is using the excellent [charlievieth/fastwalk][fastwalk] library, which easily outperforms its competitors and supports safely following symlinks.\n- Added `$FZF_DEFAULT_OPTS_FILE` to allow managing default options in a file\n    - See [#3618](https://github.com/junegunn/fzf/pull/3618)\n    - Option precedence from lower to higher\n        1. Options read from `$FZF_DEFAULT_OPTS_FILE`\n        1. Options from `$FZF_DEFAULT_OPTS`\n        1. Options from command-line arguments\n- Bug fixes and improvements\n\n[find]: https://github.com/junegunn/fzf/blob/0.46.1/src/constants.go#L60-L64\n[walker]: https://github.com/junegunn/fzf/pull/1847\n[fastwalk]: https://github.com/charlievieth/fastwalk\n[unix]: https://en.wikipedia.org/wiki/Unix_philosophy\n\n0.46.1\n------\n- Bug fixes and improvements\n- Fixed Windows binaries\n- Downgraded Go version to 1.20 to support older versions of Windows\n    - https://tip.golang.org/doc/go1.21#windows\n- Updated [rivo/uniseg](https://github.com/rivo/uniseg) dependency to v0.4.6\n\n0.46.0\n------\n- Added two new events\n    - `result` - triggered when the filtering for the current query is complete and the result list is ready\n    - `resize` - triggered when the terminal size is changed\n- fzf now exports the following environment variables to the child processes\n  | Variable           | Description                                                 |\n  | ---                | ---                                                         |\n  | `FZF_LINES`        | Number of lines fzf takes up excluding padding and margin   |\n  | `FZF_COLUMNS`      | Number of columns fzf takes up excluding padding and margin |\n  | `FZF_TOTAL_COUNT`  | Total number of items                                       |\n  | `FZF_MATCH_COUNT`  | Number of matched items                                     |\n  | `FZF_SELECT_COUNT` | Number of selected items                                    |\n  | `FZF_QUERY`        | Current query string                                        |\n  | `FZF_PROMPT`       | Prompt string                                               |\n  | `FZF_ACTION`       | The name of the last action performed                       |\n  - This allows you to write sophisticated transformations like so\n    ```sh\n    # Script to dynamically resize the preview window\n    transformer='\n      # 1 line for info, another for prompt, and 2 more lines for preview window border\n      lines=$(( FZF_LINES - FZF_MATCH_COUNT - 4 ))\n      if [[ $FZF_MATCH_COUNT -eq 0 ]]; then\n        echo \"change-preview-window:hidden\"\n      elif [[ $lines -gt 3 ]]; then\n        echo \"change-preview-window:$lines\"\n      elif [[ $FZF_PREVIEW_LINES -ne 3 ]]; then\n        echo \"change-preview-window:3\"\n      fi\n    '\n    seq 10000 | fzf --preview 'seq {} 10000' --preview-window up \\\n                    --bind \"result:transform:$transformer\" \\\n                    --bind \"resize:transform:$transformer\"\n    ```\n  - And we're phasing out `{fzf:prompt}` and `{fzf:action}`\n- Changed [mattn/go-runewidth](https://github.com/mattn/go-runewidth) dependency to [rivo/uniseg](https://github.com/rivo/uniseg) for accurate results\n    - Set `--ambidouble` if your terminal displays ambiguous width characters (e.g. box-drawing characters for borders) as 2 columns\n    - `RUNEWIDTH_EASTASIAN=1` is still respected for backward compatibility, but it's recommended that you use this new option instead\n- Bug fixes\n\n0.45.0\n------\n- Added `transform` action to conditionally perform a series of actions\n  ```sh\n  # Disallow selecting an empty line\n  echo -e \"1. Hello\\n2. Goodbye\\n\\n3. Exit\" |\n    fzf --height '~100%' --reverse --header 'Select one' \\\n        --bind 'enter:transform:[[ -n {} ]] && echo accept || echo \"change-header:Invalid selection\"'\n\n  # Move cursor past the empty line\n  echo -e \"1. Hello\\n2. Goodbye\\n\\n3. Exit\" |\n    fzf --height '~100%' --reverse --header 'Select one' \\\n        --bind 'enter:transform:[[ -n {} ]] && echo accept || echo \"change-header:Invalid selection\"' \\\n        --bind 'focus:transform:[[ -n {} ]] && exit; [[ {fzf:action} =~ up$ ]] && echo up || echo down'\n\n  # A single key binding to toggle between modes\n  fd --type file |\n    fzf --prompt 'Files> ' \\\n        --header 'CTRL-T: Switch between Files/Directories' \\\n        --bind 'ctrl-t:transform:[[ ! {fzf:prompt} =~ Files ]] &&\n                  echo \"change-prompt(Files> )+reload(fd --type file)\" ||\n                  echo \"change-prompt(Directories> )+reload(fd --type directory)\"'\n  ```\n- Added placeholder expressions\n    - `{fzf:action}` - The name of the last action performed\n    - `{fzf:prompt}` - Prompt string (including ANSI color codes)\n    - `{fzf:query}` - Synonym for `{q}`\n- Added support for negative height\n  ```sh\n  # Terminal height minus 1, so you can still see the command line\n  fzf --height=-1\n  ```\n  - This handles a terminal resize better than `--height=$(($(tput lines) - 1))`\n- Added `accept-or-print-query` action that acts like `accept` but prints the\n  current query when there's no match for the query\n  ```sh\n  # You can make CTRL-R paste the current query when there's no match\n  export FZF_CTRL_R_OPTS='--bind enter:accept-or-print-query'\n  ```\n  - Note that there are alternative ways to implement the same strategy\n    ```sh\n    # 'become' is apparently more versatile but it's not available on Windows.\n    export FZF_CTRL_R_OPTS='--bind \"enter:become:if [ -z {} ]; then echo {q}; else echo {}; fi\"'\n\n    # Using the new 'transform' action\n    export FZF_CTRL_R_OPTS='--bind \"enter:transform:[ -z {} ] && echo print-query || echo accept\"'\n    ```\n- Added `show-header` and `hide-header` actions\n- Bug fixes\n\n0.44.1\n------\n- Fixed crash when preview window is hidden on `focus` event\n\n0.44.0\n------\n- (Experimental) Sixel image support in preview window (not available on Windows)\n    - [bin/fzf-preview.sh](bin/fzf-preview.sh) is added to demonstrate how to\n      display an image using Kitty image protocol or Sixel. You can use it\n      like so:\n      ```sh\n      fzf --preview='fzf-preview.sh {}'\n      ```\n- (Experimental) iTerm2 inline image protocol support in preview window (not available on Windows)\n  ```sh\n  # Using https://iterm2.com/utilities/imgcat\n  fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}'\n  ```\n- HTTP server can be configured to accept remote connections\n  ```sh\n  # FZF_API_KEY is required for a non-localhost listen address\n  export FZF_API_KEY=\"$(head -c 32 /dev/urandom | base64)\"\n  fzf --listen 0.0.0.0:6266\n  ```\n    - To allow remote process execution, use `--listen-unsafe` instead\n      (`execute*`, `reload*`, `become`, `preview`, `change-preview`, `transform-*`)\n      ```sh\n      fzf --listen-unsafe 0.0.0.0:6266\n      ```\n- Bug fixes\n\n0.43.0\n------\n- (Experimental) Added support for Kitty image protocol in the preview window\n  (not available on Windows)\n  ```sh\n  fzf --preview='\n    if file --mime-type {} | grep -qF image/; then\n      # --transfer-mode=memory is the fastest option but if you want fzf to be able\n      # to redraw the image on terminal resize or on 'change-preview-window',\n      # you need to use --transfer-mode=stream.\n      kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \\$d\n    else\n      bat --color=always {}\n    fi\n  '\n  ```\n- (Experimental) `--listen` server can report program state in JSON format (`GET /`)\n  ```sh\n  # fzf server started in \"headless\" mode\n  fzf --listen 6266 2> /dev/null\n\n  # Get program state\n  curl localhost:6266 | jq .\n\n  # Increase the number of items returned (default: 100)\n  curl localhost:6266?limit=1000 | jq .\n  ```\n- `--listen` server can be secured by setting `$FZF_API_KEY` environment\n  variable.\n  ```sh\n  export FZF_API_KEY=\"$(head -c 32 /dev/urandom | base64)\"\n\n  # Server\n  fzf --listen 6266\n\n  # Client\n  curl localhost:6266 -H \"x-api-key: $FZF_API_KEY\" -d 'change-query(yo)'\n  ```\n- Added `toggle-header` action\n- Added mouse events for `--bind`\n    - `scroll-up` (bound to `up`)\n    - `scroll-down` (bound to `down`)\n    - `shift-scroll-up` (bound to `toggle+up`)\n    - `shift-scroll-down` (bound to `toggle+down`)\n    - `shift-left-click` (bound to `toggle`)\n    - `shift-right-click` (bound to `toggle`)\n    - `preview-scroll-up` (bound to `preview-up`)\n    - `preview-scroll-down` (bound to `preview-down`)\n    ```sh\n    # Twice faster scrolling both in the main window and the preview window\n    fzf --bind 'scroll-up:up+up,scroll-down:down+down' \\\n        --bind 'preview-scroll-up:preview-up+preview-up' \\\n        --bind 'preview-scroll-down:preview-down+preview-down' \\\n        --preview 'cat {}'\n    ```\n- Added `offset-up` and `offset-down` actions\n  ```sh\n  # Scrolling will behave similarly to CTRL-E and CTRL-Y of vim\n  fzf --bind scroll-up:offset-up,scroll-down:offset-down \\\n      --bind ctrl-y:offset-up,ctrl-e:offset-down \\\n      --scroll-off=5\n  ```\n- Shell extensions\n    - Updated bash completion for fzf options\n    - bash key bindings no longer requires perl; it will use awk or mawk\n      instead if perl is not found\n    - Basic context-aware completion for ssh command\n    - Applied `--scheme=path` for better ordering of the result\n- Bug fixes and improvements\n\n0.42.0\n------\n- Added new info style: `--info=right`\n- Added new info style: `--info=inline-right`\n- Added new border style `thinblock` which uses symbols for legacy computing\n  [one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)\n    - Similarly to `block`, this style is suitable when using a different\n      background color because the window is completely contained within the border.\n      ```sh\n      BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \\\n          --margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \\\n          --color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253\n      ```\n    - This style may not render correctly depending on the font and the\n      terminal emulator.\n\n0.41.1\n------\n- Fixed a bug where preview window is not updated when `--disabled` is set and\n  a reload is triggered by `change:reload` binding\n\n0.41.0\n------\n- Added color name `preview-border` and `preview-scrollbar`\n- Added new border style `block` which uses [block elements](https://en.wikipedia.org/wiki/Block_Elements)\n- `--scrollbar` can take two characters, one for the main window, the other\n  for the preview window\n- Putting it altogether:\n  ```sh\n  fzf-tmux -p 80% --padding 1,2 --preview 'bat --style=plain --color=always {}' \\\n      --color 'bg:237,bg+:235,gutter:237,border:238,scrollbar:236' \\\n      --color 'preview-bg:235,preview-border:236,preview-scrollbar:234' \\\n      --preview-window 'border-block' --border block --scrollbar '▌▐'\n  ```\n- Bug fixes and improvements\n\n0.40.0\n------\n- Added `zero` event that is triggered when there's no match\n  ```sh\n  # Reload the candidate list when there's no match\n  echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3\n  ```\n- New actions\n    - Added `track` action which makes fzf track the current item when the\n      search result is updated. If the user manually moves the cursor, or the\n      item is not in the updated search result, tracking is automatically\n      disabled. Tracking is useful when you want to see the surrounding items\n      by deleting the query string.\n      ```sh\n      # Narrow down the list with a query, point to a command,\n      # and hit CTRL-T to see its surrounding commands.\n      export FZF_CTRL_R_OPTS=\"\n        --preview 'echo {}' --preview-window up:3:hidden:wrap\n        --bind 'ctrl-/:toggle-preview'\n        --bind 'ctrl-t:track+clear-query'\n        --bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'\n        --color header:italic\n        --header 'Press CTRL-Y to copy command into clipboard'\"\n      ```\n    - Added `change-header(...)`\n    - Added `transform-header(...)`\n    - Added `toggle-track` action\n- Fixed `--track` behavior when used with `--tac`\n    - However, using `--track` with `--tac` is not recommended. The resulting\n      behavior can be very confusing.\n- Bug fixes and improvements\n\n0.39.0\n------\n- Added `one` event that is triggered when there's only one match\n  ```sh\n  # Automatically select the only match\n  seq 10 | fzf --bind one:accept\n  ```\n- Added `--track` option that makes fzf track the current selection when the\n  result list is updated. This can be useful when browsing logs using fzf with\n  sorting disabled.\n  ```sh\n  git log --oneline --graph --color=always | nl |\n      fzf --ansi --track --no-sort --layout=reverse-list\n  ```\n- If you use `--listen` option without a port number fzf will automatically\n  allocate an available port and export it as `$FZF_PORT` environment\n  variable.\n  ```sh\n  # Automatic port assignment\n  fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'\n\n  # Say hello\n  curl \"localhost:$(cat /tmp/fzf-port)\" -d 'preview:echo Hello, fzf is listening on $FZF_PORT.'\n  ```\n- A carriage return and a line feed character will be rendered as dim ␍ and\n  ␊ respectively.\n  ```sh\n  printf \"foo\\rbar\\nbaz\" | fzf --read0 --preview 'echo {}'\n  ```\n- fzf will stop rendering a non-displayable characters as a space. This will\n  likely cause less glitches in the preview window.\n  ```sh\n  fzf --preview 'head -1000 /dev/random'\n  ```\n- Bug fixes and improvements\n\n0.38.0\n------\n- New actions\n    - `become(...)` - Replace the current fzf process with the specified\n      command using `execve(2)` system call.\n      See https://github.com/junegunn/fzf#turning-into-a-different-process for\n      more information.\n      ```sh\n      # Open selected files in Vim\n      fzf --multi --bind 'enter:become(vim {+})'\n\n      # Open the file in Vim and go to the line\n      git grep --line-number . |\n          fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'\n      ```\n        - This action is not supported on Windows\n    - `show-preview`\n    - `hide-preview`\n- Bug fixes\n    - `--preview-window 0,hidden` should not execute the preview command until\n      `toggle-preview` action is triggered\n\n0.37.0\n------\n- Added a way to customize the separator of inline info\n  ```sh\n  fzf --info 'inline: ╱ ' --prompt '╱ ' --color prompt:bright-yellow\n  ```\n- New event\n    - `focus` - Triggered when the focus changes due to a vertical cursor\n      movement or a search result update\n      ```sh\n      fzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'\n\n      # Any action bound to the event runs synchronously and thus can make the interface sluggish\n      # e.g. lolcat isn't one of the fastest programs, and every cursor movement in\n      #      fzf will be noticeably affected by its execution time\n      fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'\n\n      # Beware not to introduce an infinite loop\n      seq 10 | fzf --bind 'focus:up' --cycle\n      ```\n- New actions\n    - `change-border-label`\n    - `change-preview-label`\n    - `transform-border-label`\n    - `transform-preview-label`\n- Bug fixes and improvements\n\n0.36.0\n------\n- Added `--listen=HTTP_PORT` option to start HTTP server. It allows external\n  processes to send actions to perform via POST method.\n  ```sh\n  # Start HTTP server on port 6266\n  fzf --listen 6266\n\n  # Send actions to the server\n  curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'\n  ```\n- Added draggable scrollbar to the main search window and the preview window\n  ```sh\n  # Hide scrollbar\n  fzf --no-scrollbar\n\n  # Customize scrollbar\n  fzf --scrollbar ┆ --color scrollbar:blue\n  ```\n- New event\n    - Added `load` event that is triggered when the input stream is complete\n      and the initial processing of the list is complete.\n      ```sh\n      # Change the prompt to \"loaded\" when the input stream is complete\n      (seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\n\n      # You can use it instead of 'start' event without `--sync` if asynchronous\n      # trigger is not an issue.\n      (seq 10; sleep 1; seq 11 20) | fzf --bind 'load:last'\n      ```\n- New actions\n    - Added `pos(...)` action to move the cursor to the numeric position\n        - `first` and `last` are equivalent to `pos(1)` and `pos(-1)` respectively\n      ```sh\n      # Put the cursor on the 10th item\n      seq 100 | fzf --sync --bind 'start:pos(10)'\n\n      # Put the cursor on the 10th to last item\n      seq 100 | fzf --sync --bind 'start:pos(-10)'\n      ```\n    - Added `reload-sync(...)` action which replaces the current list only after\n      the reload process is complete. This is useful when the command takes\n      a while to produce the initial output and you don't want fzf to run against\n      an empty list while the command is running.\n      ```sh\n      # You can still filter and select entries from the initial list for 3 seconds\n      seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\n      ```\n    - Added `next-selected` and `prev-selected` actions to move between selected\n      items\n      ```sh\n      # `next-selected` will move the pointer to the next selected item below the current line\n      # `prev-selected` will move the pointer to the previous selected item above the current line\n      seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected\n\n      # Both actions respect --layout option\n      seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected --layout reverse\n      ```\n    - Added `change-query(...)` action that simply changes the query string to the\n      given static string. This can be useful when used with `--listen`.\n      ```sh\n      curl localhost:6266 -d \"change-query:$(date)\"\n      ```\n    - Added `transform-prompt(...)` action for transforming the prompt string\n      using an external command\n      ```sh\n      # Press space to change the prompt string using an external command\n      # (only the first line of the output is taken)\n      fzf --bind 'space:reload(ls),load:transform-prompt(printf \"%s> \" \"$(date)\")'\n      ```\n    - Added `transform-query(...)` action for transforming the query string using\n      an external command\n      ```sh\n      # Press space to convert the query to uppercase letters\n      fzf --bind 'space:transform-query(tr \"[:lower:]\" \"[:upper:]\" <<< {q})'\n\n      # Bind it to 'change' event for automatic conversion\n      fzf --bind 'change:transform-query(tr \"[:lower:]\" \"[:upper:]\" <<< {q})'\n\n      # Can only type numbers\n      fzf --bind 'change:transform-query(sed \"s/[^0-9]//g\" <<< {q})'\n      ```\n    - `put` action can optionally take an argument string\n      ```sh\n      # a will put 'alpha' on the prompt, ctrl-b will put 'bravo'\n      fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)'\n      ```\n- Added color name `preview-label` for `--preview-label` (defaults to `label`\n  for `--border-label`)\n- Better support for (Windows) terminals where each box-drawing character\n  takes 2 columns. Set `RUNEWIDTH_EASTASIAN` environment variable to `0` or `1`.\n    - On Vim, the variable will be automatically set if `&ambiwidth` is `double`\n- Behavior changes\n    - fzf will always execute the preview command if the command template\n      contains `{q}` even when it's empty. If you prefer the old behavior,\n      you'll have to check if `{q}` is empty in your command.\n      ```sh\n      # This will show // even when the query is empty\n      : | fzf --preview 'echo /{q}/'\n\n      # But if you don't want it,\n      : | fzf --preview '[ -n {q} ] || exit; echo /{q}/'\n      ```\n    - `double-click` will behave the same as `enter` unless otherwise specified,\n      so you don't have to repeat the same action twice in `--bind` in most cases.\n      ```sh\n      # No need to bind 'double-click' to the same action\n      fzf --bind 'enter:execute:less {}' # --bind 'double-click:execute:less {}'\n      ```\n    - If the color for `separator` is not specified, it will default to the\n      color for `border`. Same holds true for `scrollbar`. This is to reduce\n      the number of configuration items required to achieve a consistent color\n      scheme.\n    - If `follow` flag is specified in `--preview-window` option, fzf will\n      automatically scroll to the bottom of the streaming preview output. But\n      when the user manually scrolls the window, the following stops. With\n      this version, fzf will resume following if the user scrolls the window\n      to the bottom.\n    - Default border style on Windows is changed to `sharp` because some\n      Windows terminals are not capable of displaying `rounded` border\n      characters correctly.\n- Minor bug fixes and improvements\n\n0.35.1\n------\n- Fixed a bug where fzf with `--tiebreak=chunk` crashes on inverse match query\n- Fixed a bug where clicking above fzf would paste escape sequences\n\n0.35.0\n------\n- Added `start` event that is triggered only once when fzf finder starts.\n  Since fzf consumes the input stream asynchronously, the input list is not\n  available unless you use `--sync`.\n  ```sh\n  seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)'\n  ```\n- Added `--border-label` and `--border-label-pos` for putting label on the border\n  ```sh\n  # ANSI color codes are supported\n  # (with https://github.com/busyloop/lolcat)\n  label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)\n\n  # Border label at the center\n  fzf --height=10 --border --border-label=\"╢ $label ╟\" --color=label:italic:black\n\n  # Left-aligned (positive integer)\n  fzf --height=10 --border --border-label=\"╢ $label ╟\" --border-label-pos=3 --color=label:italic:black\n\n  # Right-aligned (negative integer) on the bottom line (:bottom)\n  fzf --height=10 --border --border-label=\"╢ $label ╟\" --border-label-pos=-3:bottom --color=label:italic:black\n  ```\n- Also added `--preview-label` and `--preview-label-pos` for the border of the\n  preview window\n  ```sh\n  fzf --preview 'cat {}' --border --preview-label=' Preview ' --preview-label-pos=2\n  ```\n- Info panel (match counter) will be followed by a horizontal separator by\n  default\n    - Use `--no-separator` or `--separator=''` to hide the separator\n    - You can specify an arbitrary string that is repeated to form the\n      horizontal separator. e.g. `--separator=╸`\n    - The color of the separator can be customized via `--color=separator:...`\n    - ANSI color codes are also supported\n  ```sh\n  fzf --separator=╸ --color=separator:green\n  fzf --separator=$(lolcat -f -F 1.4 <<< ▁▁▂▃▄▅▆▆▅▄▃▂▁▁) --info=inline\n  ```\n- Added `--border=bold` and `--border=double` along with\n  `--preview-window=border-bold` and `--preview-window=border-double`\n\n0.34.0\n------\n- Added support for adaptive `--height`. If the `--height` value is prefixed\n  with `~`, fzf will automatically determine the height in the range according\n  to the input size.\n  ```sh\n  seq 1 | fzf --height ~70% --border --padding 1 --margin 1\n  seq 10 | fzf --height ~70% --border --padding 1 --margin 1\n  seq 100 | fzf --height ~70% --border --padding 1 --margin 1\n  ```\n    - There are a few limitations\n        - Not compatible with percent top/bottom margin/padding\n          ```sh\n          # This is not allowed (top/bottom margin in percent value)\n          fzf --height ~50% --border --margin 5%,10%\n\n          # This is allowed (top/bottom margin in fixed value)\n          fzf --height ~50% --border --margin 2,10%\n          ```\n        - fzf will not start until it can determine the right height for the input\n          ```sh\n          # fzf will open immediately\n          (sleep 2; seq 10) | fzf --height 50%\n\n          # fzf will open after 2 seconds\n          (sleep 2; seq 10) | fzf --height ~50%\n          (sleep 2; seq 1000) | fzf --height ~50%\n          ```\n- Fixed tcell renderer used to render full-screen fzf on Windows\n- ~~`--no-clear` is deprecated. Use `reload` action instead.~~\n\n0.33.0\n------\n- Added `--scheme=[default|path|history]` option to choose scoring scheme\n    - (Experimental)\n    - We updated the scoring algorithm in 0.32.0, however we have learned that\n      this new scheme (`default`) is not always giving the optimal result\n    - `path`: Additional bonus point is only given to the characters after\n      path separator. You might want to choose this scheme if you have many\n      files with spaces in their paths.\n    - `history`: No additional bonus points are given so that we give more\n      weight to the chronological ordering. This is equivalent to the scoring\n      scheme before 0.32.0. This also sets `--tiebreak=index`.\n- ANSI color sequences with colon delimiters are now supported.\n  ```sh\n  printf \"\\e[38;5;208mOption 1\\e[m\\nOption 2\" | fzf --ansi\n  printf \"\\e[38:5:208mOption 1\\e[m\\nOption 2\" | fzf --ansi\n  ```\n- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in\n  `--preview-window`\n- Added support for ANSI `strikethrough`\n  ```sh\n  printf \"\\e[9mdeleted\" | fzf --ansi\n  fzf --color fg+:strikethrough\n  ```\n\n0.32.1\n------\n- Fixed incorrect ordering of `--tiebreak=chunk`\n- fzf-tmux will show fzf border instead of tmux popup border (requires tmux 3.3)\n  ```sh\n  fzf-tmux -p70%\n  fzf-tmux -p70% --color=border:bright-red\n  fzf-tmux -p100%,60% --color=border:bright-yellow --border=horizontal --padding 1,5 --margin 1,0\n  fzf-tmux -p70%,100% --color=border:bright-green --border=vertical\n\n  # Key bindings (CTRL-T, CTRL-R, ALT-C) will use these options\n  export FZF_TMUX_OPTS='-p100%,60% --color=border:green --border=horizontal --padding 1,5 --margin 1,0'\n  ```\n\n0.32.0\n------\n- Updated the scoring algorithm\n    - Different bonus points to different categories of word boundaries\n      (listed higher to lower bonus point)\n        - Word after whitespace characters or beginning of the string\n        - Word after common delimiter characters (`/,:;|`)\n        - Word after other non-word characters\n      ```sh\n      # foo/bar.sh` is preferred over `foo-bar.sh` on `bar`\n      fzf --query=bar --height=4 << EOF\n      foo-bar.sh\n      foo/bar.sh\n      EOF\n      ```\n- Added a new tiebreak `chunk`\n    - Favors the line with shorter matched chunk. A chunk is a set of\n      consecutive non-whitespace characters.\n    - Unlike the default `length`, this scheme works well with tabular input\n      ```sh\n      # length prefers item #1, because the whole line is shorter,\n      # chunk prefers item #2, because the matched chunk (\"foo\") is shorter\n      fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << \"EOF\"\n      N | Field1 | Field2 | Field3\n      - | ------ | ------ | ------\n      1 | hello  | foobar | baz\n      2 | world  | foo    | bazbaz\n      EOF\n      ```\n    - If the input does not contain any spaces, `chunk` is equivalent to\n      `length`. But we're not going to set it as the default because it is\n      computationally more expensive.\n- Bug fixes and improvements\n\n0.31.0\n------\n- Added support for an alternative preview window layout that is activated\n  when the size of the preview window is smaller than a certain threshold.\n  ```sh\n  # If the width of the preview window is smaller than 50 columns,\n  # it will be displayed above the search window.\n  fzf --preview 'cat {}' --preview-window 'right,50%,border-left,<50(up,30%,border-bottom)'\n\n  # Or you can just hide it like so\n  fzf --preview 'cat {}' --preview-window '<50(hidden)'\n  ```\n- fzf now uses SGR mouse mode to properly support mouse on larger terminals\n- You can now use characters that do not satisfy `unicode.IsGraphic` constraint\n  for `--marker`, `--pointer`, and `--ellipsis`. Allows Nerd Fonts and stuff.\n  Use at your own risk.\n- Bug fixes and improvements\n- Shell extension\n    - `kill` completion now requires trigger sequence (`**`) for consistency\n\n0.30.0\n------\n- Fixed cursor flickering over the screen by hiding it during rendering\n- Added `--ellipsis` option. You can take advantage of it to make fzf\n  effectively search non-visible parts of the item.\n  ```sh\n  # Search against hidden line numbers on the far right\n  nl /usr/share/dict/words                  |\n    awk '{printf \"%s%1000s\\n\", $2, $1}'     |\n    fzf --nth=-1 --no-hscroll --ellipsis='' |\n    awk '{print $2}'\n  ```\n- Added `rebind` action for restoring bindings after `unbind`\n- Bug fixes and improvements\n\n0.29.0\n------\n- Added `change-preview(...)` action to change the `--preview` command\n    - cf. `preview(...)` is a one-off action that doesn't change the default\n      preview command\n- Added `change-preview-window(...)` action\n    - You can rotate through the different options separated by `|`\n      ```sh\n      fzf --preview 'cat {}' --preview-window right:40% \\\n          --bind 'ctrl-/:change-preview-window(right,70%|down,40%,border-top|hidden|)'\n      ```\n- Fixed rendering of the prompt line when overflow occurs with `--info=inline`\n\n0.28.0\n------\n- Added `--header-first` option to print header before the prompt line\n  ```sh\n  fzf --header $'Welcome to fzf\\n▔▔▔▔▔▔▔▔▔▔▔▔▔▔' --reverse --height 30% --border --header-first\n  ```\n- Added `--scroll-off=LINES` option (similar to `scrolloff` option of Vim)\n    - You can set it to a very large number so that the cursor stays in the\n      middle of the screen while scrolling\n      ```sh\n      fzf --scroll-off=5\n      fzf --scroll-off=999\n      ```\n- Fixed bug where preview window is not updated on `reload` (#2644)\n- fzf on Windows will also use `$SHELL` to execute external programs\n    - See #2638 and #2647\n    - Thanks to @rashil2000, @vovcacik, and @janlazo\n\n0.27.3\n------\n- Preview window is `hidden` by default when there are `preview` bindings but\n  `--preview` command is not given\n- Fixed bug where `{n}` is not properly reset on `reload`\n- Fixed bug where spinner is not displayed on `reload`\n- Enhancements in tcell renderer for Windows (#2616)\n- Vim plugin\n    - `sinklist` is added as a synonym to `sink*` so that it's easier to add\n      a function to a spec dictionary\n      ```vim\n      let spec = { 'source': 'ls', 'options': ['--multi', '--preview', 'cat {}'] }\n      function spec.sinklist(matches)\n        echom string(a:matches)\n      endfunction\n\n      call fzf#run(fzf#wrap(spec))\n      ```\n    - Vim 7 compatibility\n\n0.27.2\n------\n- 16 base ANSI colors can be specified by their names\n  ```sh\n  fzf --color fg:3,fg+:11\n  fzf --color fg:yellow,fg+:bright-yellow\n  ```\n- Fix bug where `--read0` not properly displaying long lines\n\n0.27.1\n------\n- Added `unbind` action. In the following Ripgrep launcher example, you can\n  use `unbind(reload)` to switch to fzf-only filtering mode.\n    - See https://github.com/junegunn/fzf/blob/master/ADVANCED.md#switching-to-fzf-only-search-mode\n- Vim plugin\n    - Vim plugin will stop immediately even when the source command hasn't finished\n      ```vim\n      \" fzf will read the stream file while allowing other processes to append to it\n      call fzf#run({'source': 'cat /dev/null > /tmp/stream; tail -f /tmp/stream'})\n      ```\n    - It is now possible to open popup window relative to the current window\n      ```vim\n      let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }\n      ```\n\n0.27.0\n------\n- More border options for `--preview-window`\n  ```sh\n  fzf --preview 'cat {}' --preview-window border-left\n  fzf --preview 'cat {}' --preview-window border-left --border horizontal\n  fzf --preview 'cat {}' --preview-window top:border-bottom\n  fzf --preview 'cat {}' --preview-window top:border-horizontal\n  ```\n- Automatically set `/dev/tty` as STDIN on execute action\n  ```sh\n  # Redirect /dev/tty to suppress \"Vim: Warning: Input is not from a terminal\"\n  # ls | fzf --bind \"enter:execute(vim {} < /dev/tty)\"\n\n  # \"< /dev/tty\" part is no longer needed\n  ls | fzf --bind \"enter:execute(vim {})\"\n  ```\n- Bug fixes and improvements\n- Signed and notarized macOS binaries\n  (Huge thanks to [BACKERS.md](https://github.com/junegunn/junegunn/blob/main/BACKERS.md)!)\n\n0.26.0\n------\n- Added support for fixed header in preview window\n  ```sh\n  # Display top 3 lines as the fixed header\n  fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'\n  ```\n- More advanced preview offset expression to better support the fixed header\n  ```sh\n  # Preview with bat, matching line in the middle of the window below\n  # the fixed header of the top 3 lines\n  #\n  #   ~3    Top 3 lines as the fixed header\n  #   +{2}  Base scroll offset extracted from the second field\n  #   +3    Extra offset to compensate for the 3-line header\n  #   /2    Put in the middle of the preview area\n  #\n  git grep --line-number '' |\n    fzf --delimiter : \\\n        --preview 'bat --style=full --color=always --highlight-line {2} {1}' \\\n        --preview-window '~3:+{2}+3/2'\n  ```\n- Added `select` and `deselect` action for unconditionally selecting or\n  deselecting a single item in `--multi` mode. Complements `toggle` action.\n- Significant performance improvement in ANSI code processing\n- Bug fixes and improvements\n- Built with Go 1.16\n\n0.25.1\n------\n- Added `close` action\n    - Close preview window if open, abort fzf otherwise\n- Bug fixes and improvements\n\n0.25.0\n------\n- Text attributes set in `--color` are not reset when fzf sees another\n  `--color` option for the same element. This allows you to put custom text\n  attributes in your `$FZF_DEFAULT_OPTS` and still have those attributes\n  even when you override the colors.\n\n  ```sh\n  # Default colors and attributes\n  fzf\n\n  # Apply custom text attributes\n  export FZF_DEFAULT_OPTS='--color fg+:italic,hl:-1:underline,hl+:-1:reverse:underline'\n\n  fzf\n\n  # Different colors but you still have the attributes\n  fzf --color hl:176,hl+:177\n\n  # Write \"regular\" if you want to clear the attributes\n  fzf --color hl:176:regular,hl+:177:regular\n  ```\n- Renamed `--phony` to `--disabled`\n- You can dynamically enable and disable the search functionality using the\n  new `enable-search`, `disable-search`, and `toggle-search` actions\n- You can assign a different color to the query string for when search is disabled\n  ```sh\n  fzf --color query:#ffffff,disabled:#999999 --bind space:toggle-search\n  ```\n- Added `last` action to move the cursor to the last match\n    - The opposite action `top` is renamed to `first`, but `top` is still\n      recognized as a synonym for backward compatibility\n- Added `preview-top` and `preview-bottom` actions\n- Extended support for alt key chords: alt with any case-sensitive single character\n  ```sh\n  fzf --bind alt-,:first,alt-.:last\n  ```\n\n0.24.4\n------\n- Added `--preview-window` option `follow`\n  ```sh\n  # Preview window will automatically scroll to the bottom\n  fzf --preview-window follow --preview 'for i in $(seq 100000); do\n    echo \"$i\"\n    sleep 0.01\n    (( i % 300 == 0 )) && printf \"\\033[2J\"\n  done'\n  ```\n- Added `change-prompt` action\n  ```sh\n  fzf --prompt 'foo> ' --bind $'a:change-prompt:\\x1b[31mbar> '\n  ```\n- Bug fixes and improvements\n\n0.24.3\n------\n- Added `--padding` option\n  ```sh\n  fzf --margin 5% --padding 5% --border --preview 'cat {}' \\\n      --color bg:#222222,preview-bg:#333333\n  ```\n\n0.24.2\n------\n- Bug fixes and improvements\n\n0.24.1\n------\n- Fixed broken `--color=[bw|no]` option\n\n0.24.0\n------\n- Real-time rendering of preview window\n  ```sh\n  # fzf can render preview window before the command completes\n  fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'\n\n  # Preview window can process ANSI escape sequence (CSI 2 J) for clearing the display\n  fzf --preview 'for i in $(seq 100000); do\n    (( i % 200 == 0 )) && printf \"\\033[2J\"\n    echo \"$i\"\n    sleep 0.01\n  done'\n  ```\n- Updated `--color` option to support text styles\n  - `regular` / `bold` / `dim` / `underline` / `italic` / `reverse` / `blink`\n    ```sh\n    # * Set -1 to keep the original color\n    # * Multiple style attributes can be combined\n    # * Italic style may not be supported by some terminals\n    rg --line-number --no-heading --color=always \"\" |\n      fzf --ansi --prompt \"Rg: \" \\\n          --color fg+:italic,hl:underline:-1,hl+:italic:underline:reverse:-1 \\\n          --color pointer:reverse,prompt:reverse,input:159 \\\n          --pointer '  '\n    ```\n- More `--border` options\n  - `vertical`, `top`, `bottom`, `left`, `right`\n  - Updated Vim plugin to use these new `--border` options\n    ```vim\n    \" Floating popup window in the center of the screen\n    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n\n    \" Popup with 100% width\n    let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'border': 'horizontal' } }\n\n    \" Popup with 100% height\n    let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'border': 'vertical' } }\n\n    \" Similar to 'down' layout, but it uses a popup window and doesn't affect the window layout\n    let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'yoffset': 1.0, 'border': 'top' } }\n\n    \" Opens on the right;\n    \"   'highlight' option is still supported but it will only take the foreground color of the group\n    let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'xoffset': 1.0, 'border': 'left', 'highlight': 'Comment' } }\n    ```\n- To indicate if `--multi` mode is enabled, fzf will print the number of\n  selected items even when no item is selected\n  ```sh\n  seq 100 | fzf\n    # 100/100\n  seq 100 | fzf --multi\n    # 100/100 (0)\n  seq 100 | fzf --multi 5\n    # 100/100 (0/5)\n  ```\n- Since 0.24.0, release binaries will be uploaded to https://github.com/junegunn/fzf/releases\n\n0.23.1\n------\n- Added `--preview-window` options for disabling flags\n    - `nocycle`\n    - `nohidden`\n    - `nowrap`\n    - `default`\n- Built with Go 1.14.9 due to performance regression\n    - https://github.com/golang/go/issues/40727\n\n0.23.0\n------\n- Support preview scroll offset relative to window height\n  ```sh\n  git grep --line-number '' |\n    fzf --delimiter : \\\n        --preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\\n        --preview-window +{2}-/2\n  ```\n- Added `--preview-window` option for sharp edges (`--preview-window sharp`)\n- Added `--preview-window` option for cyclic scrolling (`--preview-window cycle`)\n- Reduced vertical padding around the preview window when `--preview-window\n  noborder` is used\n- Added actions for preview window\n    - `preview-half-page-up`\n    - `preview-half-page-down`\n- Vim\n    - Popup width and height can be given in absolute integer values\n    - Added `fzf#exec()` function for getting the path of fzf executable\n        - It also downloads the latest binary if it's not available by running\n          `./install --bin`\n- Built with Go 1.15.2\n    - We no longer provide 32-bit binaries\n\n0.22.0\n------\n- Added more options for `--bind`\n    - `backward-eof` event\n      ```sh\n      # Aborts when you delete backward when the query prompt is already empty\n      fzf --bind backward-eof:abort\n      ```\n    - `refresh-preview` action\n      ```sh\n      # Rerun preview command when you hit '?'\n      fzf --preview 'echo $RANDOM' --bind '?:refresh-preview'\n      ```\n    - `preview` action\n      ```sh\n      # Default preview command with an extra preview binding\n      fzf --preview 'file {}' --bind '?:preview:cat {}'\n\n      # A preview binding with no default preview command\n      # (Preview window is initially empty)\n      fzf --bind '?:preview:cat {}'\n\n      # Preview window hidden by default, it appears when you first hit '?'\n      fzf --bind '?:preview:cat {}' --preview-window hidden\n      ```\n- Added preview window option for setting the initial scroll offset\n  ```sh\n  # Initial scroll offset is set to the line number of each line of\n  # git grep output *minus* 5 lines\n  git grep --line-number '' |\n    fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5\n  ```\n- Added support for ANSI colors in `--prompt` string\n- Smart match of accented characters\n    - An unaccented character in the query string will match both accented and\n      unaccented characters, while an accented character will only match\n      accented characters. This is similar to how \"smart-case\" match works.\n- Vim plugin\n    - `tmux` layout option for using fzf-tmux\n      ```vim\n      let g:fzf_layout = { 'tmux': '-p90%,60%' }\n      ```\n\n0.21.1\n------\n- Shell extension\n    - CTRL-R will remove duplicate commands\n- fzf-tmux\n    - Supports tmux popup window (require tmux 3.2 or above)\n        - ```sh\n          # 50% width and height\n          fzf-tmux -p\n\n          # 80% width and height\n          fzf-tmux -p 80%\n\n          # 80% width and 40% height\n          fzf-tmux -p 80%,40%\n          fzf-tmux -w 80% -h 40%\n\n          # Window position\n          fzf-tmux -w 80% -h 40% -x 0 -y 0\n          fzf-tmux -w 80% -h 40% -y 1000\n\n          # Write ordinary fzf options after --\n          fzf-tmux -p -- --reverse --info=inline --margin 2,4 --border\n          ```\n        - On macOS, you can build the latest tmux from the source with\n          `brew install tmux --HEAD`\n- Bug fixes\n    - Fixed Windows file traversal not to include directories\n    - Fixed ANSI colors with `--keep-right`\n    - Fixed _fzf_complete for zsh\n- Built with Go 1.14.1\n\n0.21.0\n------\n- `--height` option is now available on Windows as well (@kelleyma49)\n- Added `--pointer` and `--marker` options\n- Added `--keep-right` option that keeps the right end of the line visible\n  when it's too long\n- Style changes\n    - `--border` will now print border with rounded corners around the\n      finder instead of printing horizontal lines above and below it.\n      The previous style is available via `--border=horizontal`\n    - Unicode spinner\n- More keys and actions for `--bind`\n- Added PowerShell script for downloading Windows binary\n- Vim plugin: Built-in floating windows support\n  ```vim\n  let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n  ```\n- bash: Various improvements in key bindings (CTRL-T, CTRL-R, ALT-C)\n    - CTRL-R will start with the current command-line as the initial query\n    - CTRL-R properly supports multi-line commands\n- Fuzzy completion API changed\n  ```sh\n  # Previous: fzf arguments given as a single string argument\n  # - This style is still supported, but it's deprecated\n  _fzf_complete \"--multi --reverse --prompt=\\\"doge> \\\"\" \"$@\" < <(\n    echo foo\n  )\n\n  # New API: multiple fzf arguments before \"--\"\n  # - Easier to write multiple options\n  _fzf_complete --multi --reverse --prompt=\"doge> \" -- \"$@\" < <(\n    echo foo\n  )\n  ```\n- Bug fixes and improvements\n\n0.20.0\n------\n- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)\n  ```sh\n  fzf --preview 'cat {}' \\\n      --color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899' \\\n      --border --height 20 --layout reverse --info inline\n  ```\n- Removed the immediate flicking of the screen on `reload` action.\n  ```sh\n  : | fzf --bind 'change:reload:seq {q}' --phony\n  ```\n- Added `clear-query` and `clear-selection` actions for `--bind`\n- It is now possible to split a composite bind action over multiple `--bind`\n  expressions by prefixing the later ones with `+`.\n  ```sh\n  fzf --bind 'ctrl-a:up+up'\n\n  # Can be now written as\n  fzf --bind 'ctrl-a:up' --bind 'ctrl-a:+up'\n\n  # This is useful when you need to write special execute/reload form (i.e. `execute:...`)\n  # to avoid parse errors and add more actions to the same key\n  fzf --multi --bind 'ctrl-l:select-all+execute:less {+f}' --bind 'ctrl-l:+deselect-all'\n  ```\n- Fixed parse error of `--bind` expression where concatenated execute/reload\n  action contains `+` character.\n  ```sh\n  fzf --multi --bind 'ctrl-l:select-all+execute(less {+f})+deselect-all'\n  ```\n- Fixed bugs of reload action\n    - Not triggered when there's no match even when the command doesn't have\n      any placeholder expressions\n    - Screen not properly cleared when `--header-lines` not filled on reload\n\n0.19.0\n------\n\n- Added `--phony` option which completely disables search functionality.\n  Useful when you want to use fzf only as a selector interface. See below.\n- Added \"reload\" action for dynamically updating the input list without\n  restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn\n  more about it.\n  ```sh\n  # Using fzf as the selector interface for ripgrep\n  RG_PREFIX=\"rg --column --line-number --no-heading --color=always --smart-case \"\n  INITIAL_QUERY=\"foo\"\n  FZF_DEFAULT_COMMAND=\"$RG_PREFIX '$INITIAL_QUERY' || true\" \\\n    fzf --bind \"change:reload:$RG_PREFIX {q} || true\" \\\n        --ansi --phony --query \"$INITIAL_QUERY\"\n  ```\n- `--multi` now takes an optional integer argument which indicates the maximum\n  number of items that can be selected\n  ```sh\n  seq 100 | fzf --multi 3 --reverse --height 50%\n  ```\n- If a placeholder expression for `--preview` and `execute` action (and the\n  new `reload` action) contains `f` flag, it is replaced to the\n  path of a temporary file that holds the evaluated list. This is useful\n  when you multi-select a large number of items and the length of the\n  evaluated string may exceed [`ARG_MAX`][argmax].\n  ```sh\n  # Press CTRL-A to select 100K items and see the sum of all the numbers\n  seq 100000 | fzf --multi --bind ctrl-a:select-all \\\n                   --preview \"awk '{sum+=\\$1} END {print sum}' {+f}\"\n  ```\n- `deselect-all` no longer deselects unmatched items. It is now consistent\n  with `select-all` and `toggle-all` in that it only affects matched items.\n- Due to the limitation of bash, fuzzy completion is enabled by default for\n  a fixed set of commands. A helper function for easily setting up fuzzy\n  completion for any command is now provided.\n  ```sh\n  # usage: _fzf_setup_completion path|dir COMMANDS...\n  _fzf_setup_completion path git kubectl\n  ```\n- Info line style can be changed by `--info=STYLE`\n    - `--info=default`\n    - `--info=inline` (same as old `--inline-info`)\n    - `--info=hidden`\n- Preview window border can be disabled by adding `noborder` to\n  `--preview-window`.\n- When you transform the input with `--with-nth`, the trailing white spaces\n  are removed.\n- `ctrl-\\`, `ctrl-]`, `ctrl-^`, and `ctrl-/` can now be used with `--bind`\n- See https://github.com/junegunn/fzf/milestone/15?closed=1 for more details\n\n[argmax]: https://unix.stackexchange.com/questions/120642/what-defines-the-maximum-size-for-a-command-single-argument\n\n0.18.0\n------\n\n- Added placeholder expression for zero-based item index: `{n}` and `{+n}`\n    - `fzf --preview 'echo {n}: {}'`\n- Added color option for the gutter: `--color gutter:-1`\n- Added `--no-unicode` option for drawing borders in non-Unicode, ASCII\n  characters\n- `FZF_PREVIEW_LINES` and `FZF_PREVIEW_COLUMNS` are exported to preview process\n    - fzf still overrides `LINES` and `COLUMNS` as before, but they may be\n      reset by the default shell.\n- Bug fixes and improvements\n    - See https://github.com/junegunn/fzf/milestone/14?closed=1\n- Built with Go 1.12.1\n\n0.17.5\n------\n\n- Bug fixes and improvements\n    - See https://github.com/junegunn/fzf/milestone/13?closed=1\n- Search query longer than the screen width is allowed (up to 300 chars)\n- Built with Go 1.11.1\n\n0.17.4\n------\n\n- Added `--layout` option with a new layout called `reverse-list`.\n    - `--layout=reverse` is a synonym for `--reverse`\n    - `--layout=default` is a synonym for `--no-reverse`\n- Preview window will be updated even when there is no match for the query\n  if any of the placeholder expressions (e.g. `{q}`, `{+}`) evaluates to\n  a non-empty string.\n- More keys for binding: `shift-{up,down}`, `alt-{up,down,left,right}`\n- fzf can now start even when `/dev/tty` is not available by making an\n  educated guess.\n- Updated the default command for Windows.\n- Fixes and improvements on bash/zsh completion\n- install and uninstall scripts now supports generating files under\n  `XDG_CONFIG_HOME` on `--xdg` flag.\n\nSee https://github.com/junegunn/fzf/milestone/12?closed=1 for the full list of\nchanges.\n\n0.17.3\n------\n- `$LINES` and `$COLUMNS` are exported to preview command so that the command\n  knows the exact size of the preview window.\n- Better error messages when the default command or `$FZF_DEFAULT_COMMAND`\n  fails.\n- Reverted #1061 to avoid having duplicate entries in the list when find\n  command detected a file system loop (#1120). The default command now\n  requires that find supports `-fstype` option.\n- fzf now distinguishes mouse left click and right click (#1130)\n    - Right click is now bound to `toggle` action by default\n    - `--bind` understands `left-click` and `right-click`\n- Added `replace-query` action (#1137)\n    - Replaces query string with the current selection\n- Added `accept-non-empty` action (#1162)\n    - Same as accept, except that it prevents fzf from exiting without any\n      selection\n\n0.17.1\n------\n\n- Fixed custom background color of preview window (#1046)\n- Fixed background color issues of Windows binary\n- Fixed Windows binary to execute command using cmd.exe with no parsing and\n  escaping (#1072)\n- Added support for `window` layout on Vim 8 using Vim 8 terminal (#1055)\n\n0.17.0-2\n--------\n\nA maintenance release for auxiliary scripts. fzf binaries are not updated.\n\n- Experimental support for the builtin terminal of Vim 8\n    - fzf can now run inside GVim\n- Updated Vim plugin to better handle `&shell` issue on fish\n- Fixed a bug of fzf-tmux where invalid output is generated\n- Fixed fzf-tmux to work even when `tput` does not work\n\n0.17.0\n------\n- Performance optimization\n- One can match literal spaces in extended-search mode with a space prepended\n  by a backslash.\n- `--expect` is now additive and can be specified multiple times.\n\n0.16.11\n-------\n- Performance optimization\n- Fixed missing preview update\n\n0.16.10\n-------\n- Fixed invalid handling of ANSI colors in preview window\n- Further improved `--ansi` performance\n\n0.16.9\n------\n- Memory and performance optimization\n    - Around 20% performance improvement for general use cases\n    - Up to 5x faster processing of `--ansi`\n    - Up to 50% reduction of memory usage\n- Bug fixes and usability improvements\n    - Fixed handling of bracketed paste mode\n    - [ERROR] on info line when the default command failed\n    - More efficient rendering of preview window\n    - `--no-clear` updated for repetitive relaunching scenarios\n\n0.16.8\n------\n- New `change` event and `top` action for `--bind`\n    - `fzf --bind change:top`\n        - Move cursor to the top result whenever the query string is changed\n    - `fzf --bind 'ctrl-w:unix-word-rubout+top,ctrl-u:unix-line-discard+top'`\n        - `top` combined with `unix-word-rubout` and `unix-line-discard`\n- Fixed inconsistent tiebreak scores when `--nth` is used\n- Proper display of tab characters in `--prompt`\n- Fixed not to `--cycle` on page-up/page-down to prevent overshoot\n- Git revision in `--version` output\n- Basic support for Cygwin environment\n- Many fixes in Vim plugin on Windows/Cygwin (thanks to @janlazo)\n\n0.16.7\n------\n- Added support for `ctrl-alt-[a-z]` key chords\n- CTRL-Z (SIGSTOP) now works with fzf\n- fzf will export `$FZF_PREVIEW_WINDOW` so that the scripts can use it\n- Bug fixes and improvements in Vim plugin and shell extensions\n\n0.16.6\n------\n- Minor bug fixes and improvements\n- Added `--no-clear` option for scripting purposes\n\n0.16.5\n------\n- Minor bug fixes\n- Added `toggle-preview-wrap` action\n- Built with Go 1.8\n\n0.16.4\n------\n- Added `--border` option to draw border above and below the finder\n- Bug fixes and improvements\n\n0.16.3\n------\n- Fixed a bug where fzf incorrectly display the lines when straddling tab\n  characters are trimmed\n- Placeholder expression used in `--preview` and `execute` action can\n  optionally take `+` flag to be used with multiple selections\n    - e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`\n- Added `execute-silent` action for executing a command silently without\n  switching to the alternate screen. This is useful when the process is\n  short-lived and you're not interested in its output.\n    - e.g. `fzf --bind 'ctrl-y:execute!(echo -n {} | pbcopy)'`\n- `ctrl-space` is allowed in `--bind`\n\n0.16.2\n------\n- Dropped ncurses dependency\n- Binaries for freebsd, openbsd, arm5, arm6, arm7, and arm8\n- Official 24-bit color support\n- Added support for composite actions in `--bind`. Multiple actions can be\n  chained using `+` separator.\n    - e.g. `fzf --bind 'ctrl-y:execute(echo -n {} | pbcopy)+abort'`\n- `--preview-window` with size 0 is allowed. This is used to make fzf execute\n  preview command in the background without displaying the result.\n- Minor bug fixes and improvements\n\n0.16.1\n------\n- Fixed `--height` option to properly fill the window with the background\n  color\n- Added `half-page-up` and `half-page-down` actions\n- Added `-L` flag to the default find command\n\n0.16.0\n------\n- *Added `--height HEIGHT[%]` option*\n    - fzf can now display finder without occupying the full screen\n- Preview window will truncate long lines by default. Line wrap can be enabled\n  by `:wrap` flag in `--preview-window`.\n- Latin script letters will be normalized before matching so that it's easier\n  to match against accented letters. e.g. `sodanco` can match `Só Danço Samba`.\n    - Normalization can be disabled via `--literal`\n- Added `--filepath-word` to make word-wise movements/actions (`alt-b`,\n  `alt-f`, `alt-bs`, `alt-d`) respect path separators\n\n0.15.9\n------\n- Fixed rendering glitches introduced in 0.15.8\n- The default escape delay is reduced to 50ms and is configurable via\n  `$ESCDELAY`\n- Scroll indicator at the top-right corner of the preview window is always\n  displayed when there's overflow\n- Can now be built with ncurses 6 or tcell to support extra features\n    - *ncurses 6*\n        - Supports more than 256 color pairs\n        - Supports italics\n    - *tcell*\n        - 24-bit color support\n    - See https://github.com/junegunn/fzf/blob/master/BUILD.md\n\n0.15.8\n------\n- Updated ANSI processor to handle more VT-100 escape sequences\n- Added `--no-bold` (and `--bold`) option\n- Improved escape sequence processing for WSL\n- Added support for `alt-[0-9]`, `f11`, and `f12` for `--bind` and `--expect`\n\n0.15.7\n------\n- Fixed panic when color is disabled and header lines contain ANSI colors\n\n0.15.6\n------\n- Windows binaries! (@kelleyma49)\n- Fixed the bug where header lines are cleared when preview window is toggled\n- Fixed not to display ^N and ^O on screen\n- Fixed cursor keys (or any key sequence that starts with ESC) on WSL by\n  making fzf wait for additional keystrokes after ESC for up to 100ms\n\n0.15.5\n------\n- Setting foreground color will no longer set background color to black\n    - e.g. `fzf --color fg:153`\n- `--tiebreak=end` will consider relative position instead of absolute distance\n- Updated `fzf#wrap` function to respect `g:fzf_colors`\n\n0.15.4\n------\n- Added support for range expression in preview and execute action\n    - e.g. `ls -l | fzf --preview=\"echo user={3} when={-4..-2}; cat {-1}\" --header-lines=1`\n    - `{q}` will be replaced to the single-quoted string of the current query\n- Fixed to properly handle unicode whitespace characters\n- Display scroll indicator in preview window\n- Inverse search term will use exact matcher by default\n    - This is a breaking change, but I believe it makes much more sense. It is\n      almost impossible to predict which entries will be filtered out due to\n      a fuzzy inverse term. You can still perform inverse-fuzzy-match by\n      prepending `!'` to the term.\n\n0.15.3\n------\n- Added support for more ANSI attributes: dim, underline, blink, and reverse\n- Fixed race condition in `toggle-preview`\n\n0.15.2\n------\n- Preview window is now scrollable\n    - With mouse scroll or with bindable actions\n        - `preview-up`\n        - `preview-down`\n        - `preview-page-up`\n        - `preview-page-down`\n- Updated ANSI processor to support high intensity colors and ignore\n  some VT100-related escape sequences\n\n0.15.1\n------\n- Fixed panic when the pattern occurs after 2^15-th column\n- Fixed rendering delay when displaying extremely long lines\n\n0.15.0\n------\n- Improved fuzzy search algorithm\n    - Added `--algo=[v1|v2]` option so one can still choose the old algorithm\n      which values the search performance over the quality of the result\n- Advanced scoring criteria\n- `--read0` to read input delimited by ASCII NUL character\n- `--print0` to print output delimited by ASCII NUL character\n\n0.13.5\n------\n- Memory and performance optimization\n    - Up to 2x performance with half the amount of memory\n\n0.13.4\n------\n- Performance optimization\n    - Memory footprint for ascii string is reduced by 60%\n    - 15 to 20% improvement of query performance\n    - Up to 45% better performance of `--nth` with non-regex delimiters\n- Fixed invalid handling of `hidden` property of `--preview-window`\n\n0.13.3\n------\n- Fixed duplicate rendering of the last line in preview window\n\n0.13.2\n------\n- Fixed race condition where preview window is not properly cleared\n\n0.13.1\n------\n- Fixed UI issue with large `--preview` output with many ANSI codes\n\n0.13.0\n------\n- Added preview feature\n    - `--preview CMD`\n    - `--preview-window POS[:SIZE][:hidden]`\n- `{}` in execute action is now replaced to the single-quoted (instead of\n  double-quoted) string of the current line\n- Fixed to ignore control characters for bracketed paste mode\n\n0.12.2\n------\n\n- 256-color capability detection does not require `256` in `$TERM`\n- Added `print-query` action\n- More named keys for binding; <kbd>F1</kbd> ~ <kbd>F10</kbd>,\n  <kbd>ALT-/</kbd>, <kbd>ALT-space</kbd>, and <kbd>ALT-enter</kbd>\n- Added `jump` and `jump-accept` actions that implement [EasyMotion][em]-like\n  movement\n  ![][jump]\n\n[em]: https://github.com/easymotion/vim-easymotion\n[jump]: https://cloud.githubusercontent.com/assets/700826/15367574/b3999dc4-1d64-11e6-85da-28ceeb1a9bc2.png\n\n0.12.1\n------\n\n- Ranking algorithm introduced in 0.12.0 is now universally applied\n- Fixed invalid cache reference in exact mode\n- Fixes and improvements in Vim plugin and shell extensions\n\n0.12.0\n------\n\n- Enhanced ranking algorithm\n- Minor bug fixes\n\n0.11.4\n------\n\n- Added `--hscroll-off=COL` option (default: 10) (#513)\n- Some fixes in Vim plugin and shell extensions\n\n0.11.3\n------\n\n- Graceful exit on SIGTERM (#482)\n- `$SHELL` instead of `sh` for `execute` action and `$FZF_DEFAULT_COMMAND` (#481)\n- Changes in fuzzy completion API\n    - [`_fzf_compgen_{path,dir}`](https://github.com/junegunn/fzf/commit/9617647)\n    - [`_fzf_complete_COMMAND_post`](https://github.com/junegunn/fzf/commit/8206746)\n      for post-processing\n\n0.11.2\n------\n\n- `--tiebreak` now accepts comma-separated list of sort criteria\n    - Each criterion should appear only once in the list\n    - `index` is only allowed at the end of the list\n    - `index` is implicitly appended to the list when not specified\n    - Default is `length` (or equivalently `length,index`)\n- `begin` criterion will ignore leading whitespaces when calculating the index\n- Added `toggle-in` and `toggle-out` actions\n    - Switch direction depending on `--reverse`-ness\n    - `export FZF_DEFAULT_OPTS=\"--bind tab:toggle-out,shift-tab:toggle-in\"`\n- Reduced the initial delay when `--tac` is not given\n    - fzf defers the initial rendering of the screen up to 100ms if the input\n      stream is ongoing to prevent unnecessary redraw during the initial\n      phase. However, 100ms delay is quite noticeable and might give the\n      impression that fzf is not snappy enough. This commit reduces the\n      maximum delay down to 20ms when `--tac` is not specified, in which case\n      the input list quickly fills the entire screen.\n\n0.11.1\n------\n\n- Added `--tabstop=SPACES` option\n\n0.11.0\n------\n\n- Added OR operator for extended-search mode\n- Added `--execute-multi` action\n- Fixed incorrect cursor position when unicode wide characters are used in\n  `--prompt`\n- Fixes and improvements in shell extensions\n\n0.10.9\n------\n\n- Extended-search mode is now enabled by default\n    - `--extended-exact` is deprecated and instead we have `--exact` for\n      orthogonally controlling \"exactness\" of search\n- Fixed not to display non-printable characters\n- Added `double-click` for `--bind` option\n- More robust handling of SIGWINCH\n\n0.10.8\n------\n\n- Fixed panic when trying to set colors after colors are disabled (#370)\n\n0.10.7\n------\n\n- Fixed unserialized interrupt handling during execute action which often\n  caused invalid memory access and crash\n- Changed `--tiebreak=length` (default) to use trimmed length when `--nth` is\n  used\n\n0.10.6\n------\n\n- Replaced `--header-file` with `--header` option\n- `--header` and `--header-lines` can be used together\n- Changed exit status\n    - 0: Okay\n    - 1: No match\n    - 2: Error\n    - 130: Interrupted\n- 64-bit linux binary is statically-linked with ncurses to avoid\n  compatibility issues.\n\n0.10.5\n------\n\n- `'`-prefix to unquote the term in `--extended-exact` mode\n- Backward scan when `--tiebreak=end` is set\n\n0.10.4\n------\n\n- Fixed to remove ANSI code from output when `--with-nth` is set\n\n0.10.3\n------\n\n- Fixed slow performance of `--with-nth` when used with `--delimiter`\n    - Regular expression engine of Golang as of now is very slow, so the fixed\n      version will treat the given delimiter pattern as a plain string instead\n      of a regular expression unless it contains special characters and is\n      a valid regular expression.\n    - Simpler regular expression for delimiter for better performance\n\n0.10.2\n------\n\n### Fixes and improvements\n\n- Improvement in perceived response time of queries\n    - Eager, efficient rune array conversion\n- Graceful exit when failed to initialize ncurses (invalid $TERM)\n- Improved ranking algorithm when `--nth` option is set\n- Changed the default command not to fail when there are files whose names\n  start with dash\n\n0.10.1\n------\n\n### New features\n\n- Added `--margin` option\n- Added options for sticky header\n    - `--header-file`\n    - `--header-lines`\n- Added `cancel` action which clears the input or closes the finder when the\n  input is already empty\n    - e.g. `export FZF_DEFAULT_OPTS=\"--bind esc:cancel\"`\n- Added `delete-char/eof` action to differentiate `CTRL-D` and `DEL`\n\n### Minor improvements/fixes\n\n- Fixed to allow binding colon and comma keys\n- Fixed ANSI processor to handle color regions spanning multiple lines\n\n0.10.0\n------\n\n### New features\n\n- More actions for `--bind`\n    - `select-all`\n    - `deselect-all`\n    - `toggle-all`\n    - `ignore`\n- `execute(...)` action for running arbitrary command without leaving fzf\n    - `fzf --bind \"ctrl-m:execute(less {})\"`\n    - `fzf --bind \"ctrl-t:execute(tmux new-window -d 'vim {}')\"`\n    - If the command contains parentheses, use any of the follows alternative\n      notations to avoid parse errors\n        - `execute[...]`\n        - `execute~...~`\n        - `execute!...!`\n        - `execute@...@`\n        - `execute#...#`\n        - `execute$...$`\n        - `execute%...%`\n        - `execute^...^`\n        - `execute&...&`\n        - `execute*...*`\n        - `execute;...;`\n        - `execute/.../`\n        - `execute|...|`\n        - `execute:...`\n            - This is the special form that frees you from parse errors as it\n              does not expect the closing character\n            - The catch is that it should be the last one in the\n              comma-separated list\n- Added support for optional search history\n    - `--history HISTORY_FILE`\n        - When used, `CTRL-N` and `CTRL-P` are automatically remapped to\n          `next-history` and `previous-history`\n    - `--history-size MAX_ENTRIES` (default: 1000)\n- Cyclic scrolling can be enabled with `--cycle`\n- Fixed the bug where the spinner was not spinning on idle input stream\n    - e.g. `sleep 100 | fzf`\n\n### Minor improvements/fixes\n\n- Added synonyms for key names that can be specified for `--bind`,\n  `--toggle-sort`, and `--expect`\n- Fixed the color of multi-select marker on the current line\n- Fixed to allow `^pattern$` in extended-search mode\n\n\n0.9.13\n------\n\n### New features\n\n- Color customization with the extended `--color` option\n\n### Bug fixes\n\n- Fixed premature termination of Reader in the presence of a long line which\n  is longer than 64KB\n\n0.9.12\n------\n\n### New features\n\n- Added `--bind` option for custom key bindings\n\n### Bug fixes\n\n- Fixed to update \"inline-info\" immediately after terminal resize\n- Fixed ANSI code offset calculation\n\n0.9.11\n------\n\n### New features\n\n- Added `--inline-info` option for saving screen estate (#202)\n     - Useful inside Neovim\n     - e.g. `let $FZF_DEFAULT_OPTS = $FZF_DEFAULT_OPTS.' --inline-info'`\n\n### Bug fixes\n\n- Invalid mutation of input on case conversion (#209)\n- Smart-case for each term in extended-search mode (#208)\n- Fixed double-click result when scroll offset is positive\n\n0.9.10\n------\n\n### Improvements\n\n- Performance optimization\n- Less aggressive memoization to limit memory usage\n\n### New features\n\n- Added color scheme for light background: `--color=light`\n\n0.9.9\n-----\n\n### New features\n\n- Added `--tiebreak` option (#191)\n- Added `--no-hscroll` option (#193)\n- Visual indication of `--toggle-sort` (#194)\n\n0.9.8\n-----\n\n### Bug fixes\n\n- Fixed Unicode case handling (#186)\n- Fixed to terminate on RuneError (#185)\n\n0.9.7\n-----\n\n### New features\n\n- Added `--toggle-sort` option (#173)\n    - `--toggle-sort=ctrl-r` is applied to `CTRL-R` shell extension\n\n### Bug fixes\n\n- Fixed to print empty line if `--expect` is set and fzf is completed by\n  `--select-1` or `--exit-0` (#172)\n- Fixed to allow comma character as an argument to `--expect` option\n\n0.9.6\n-----\n\n### New features\n\n#### Added `--expect` option (#163)\n\nIf you provide a comma-separated list of keys with `--expect` option, fzf will\nallow you to select the match and complete the finder when any of the keys is\npressed. Additionally, fzf will print the name of the key pressed as the first\nline of the output so that your script can decide what to do next based on the\ninformation.\n\n```sh\nfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\n```\n\nThe updated vim plugin uses this option to implement\n[ctrlp](https://github.com/kien/ctrlp.vim)-compatible key bindings.\n\n### Bug fixes\n\n- Fixed to ignore ANSI escape code `\\e[K` (#162)\n\n0.9.5\n-----\n\n### New features\n\n#### Added `--ansi` option (#150)\n\nIf you give `--ansi` option to fzf, fzf will interpret ANSI color codes from\nthe input, display the item with the ANSI colors (true colors are not\nsupported), and strips the codes from the output. This option is off by\ndefault as it entails some overhead.\n\n### Improvements\n\n#### Reduced initial memory footprint (#151)\n\nBy removing unnecessary copy of pointers, fzf will use significantly smaller\namount of memory when it's started. The difference is hugely noticeable when\nthe input is extremely large. (e.g. `locate / | fzf`)\n\n### Bug fixes\n\n- Fixed panic on `--no-sort --filter ''` (#149)\n\n0.9.4\n-----\n\n### New features\n\n#### Added `--tac` option to reverse the order of the input.\n\nOne might argue that this option is unnecessary since we can already put `tac`\nor `tail -r` in the command pipeline to achieve the same result. However, the\nadvantage of `--tac` is that it does not block until the input is complete.\n\n### *Backward incompatible changes*\n\n#### Changed behavior on `--no-sort`\n\n`--no-sort` option will no longer reverse the display order within finder. You\nmay want to use the new `--tac` option with `--no-sort`.\n\n```\nhistory | fzf +s --tac\n```\n\n### Improvements\n\n#### `--filter` will not block when sort is disabled\n\nWhen fzf works in filtering mode (`--filter`) and sort is disabled\n(`--no-sort`), there's no need to block until input is complete. The new\nversion of fzf will print the matches on-the-fly when the following condition\nis met:\n\n    --filter TERM --no-sort [--no-tac --no-sync]\n\nor simply:\n\n    -f TERM +s\n\nThis change removes unnecessary delay in the use cases like the following:\n\n    fzf -f xxx +s | head -5\n\nHowever, in this case, fzf processes the lines sequentially, so it cannot\nutilize multiple cores, and fzf will run slightly slower than the previous\nmode of execution where filtering is done in parallel after the entire input\nis loaded. If the user is concerned about this performance problem, one can\nadd `--sync` option to re-enable buffering.\n\n0.9.3\n-----\n\n### New features\n- Added `--sync` option for multi-staged filtering\n\n### Improvements\n- `--select-1` and `--exit-0` will start finder immediately when the condition\n  cannot be met\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM rubylang/ruby:3.4.1-noble\nRUN apt-get update -y && apt install -y git make golang zsh fish tmux\nRUN gem install --no-document -v 5.22.3 minitest\nRUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc\nRUN echo '. ~/.bashrc' >> ~/.bash_profile\n\n# Do not set default PS1\nRUN rm -f /etc/bash.bashrc\nCOPY . /fzf\nRUN cd /fzf && make install && ./install --all\nENV LANG=C.UTF-8\nCMD [\"bash\", \"-ic\", \"tmux new 'set -o pipefail; ruby /fzf/test/runner.rb | tee out && touch ok' && cat out && [ -e ok ]\"]\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\n\ngem 'minitest', '5.25.4'\ngem 'rubocop', '1.71.0'\ngem 'rubocop-minitest', '0.36.0'\ngem 'rubocop-performance', '1.23.1'\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "GO             ?= go\nDOCKER         ?= docker\nGOOS           ?= $(shell $(GO) env GOOS)\n\nMAKEFILE       := $(realpath $(lastword $(MAKEFILE_LIST)))\nROOT_DIR       := $(shell dirname $(MAKEFILE))\nSOURCES        := $(wildcard *.go src/*.go src/*/*.go shell/*sh man/man1/*.1) $(MAKEFILE)\n\nBASH_SCRIPTS   := $(ROOT_DIR)/bin/fzf-preview.sh \\\n\t\t\t\t\t$(ROOT_DIR)/bin/fzf-tmux \\\n\t\t\t\t\t$(ROOT_DIR)/install \\\n\t\t\t\t\t$(ROOT_DIR)/uninstall \\\n\t\t\t\t\t$(ROOT_DIR)/shell/common.sh \\\n\t\t\t\t\t$(ROOT_DIR)/shell/update.sh \\\n\t\t\t\t\t$(ROOT_DIR)/shell/completion.bash \\\n\t\t\t\t\t$(ROOT_DIR)/shell/key-bindings.bash\n\nifdef FZF_VERSION\nVERSION        := $(FZF_VERSION)\nelse\nVERSION        := $(shell git describe --abbrev=0 2> /dev/null | sed \"s/^v//\")\nendif\nifeq ($(VERSION),)\n$(error Not on git repository; cannot determine $$FZF_VERSION)\nendif\nVERSION_TRIM   := $(shell echo $(VERSION) | sed \"s/^v//; s/-.*//\")\nVERSION_REGEX  := $(subst .,\\.,$(VERSION_TRIM))\n\nifdef FZF_REVISION\nREVISION       := $(FZF_REVISION)\nelse\nREVISION       := $(shell git log -n 1 --pretty=format:%h --abbrev=8 -- $(SOURCES) 2> /dev/null)\nendif\nifeq ($(REVISION),)\n$(error Not on git repository; cannot determine $$FZF_REVISION)\nendif\nBUILD_FLAGS    := -a -ldflags \"-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)\" -tags \"$(TAGS)\" -trimpath\n\nBINARY32       := fzf-$(GOOS)_386\nBINARY64       := fzf-$(GOOS)_amd64\nBINARYS390     := fzf-$(GOOS)_s390x\nBINARYARM5     := fzf-$(GOOS)_arm5\nBINARYARM6     := fzf-$(GOOS)_arm6\nBINARYARM7     := fzf-$(GOOS)_arm7\nBINARYARM8     := fzf-$(GOOS)_arm8\nBINARYPPC64LE  := fzf-$(GOOS)_ppc64le\nBINARYRISCV64  := fzf-$(GOOS)_riscv64\nBINARYLOONG64  := fzf-$(GOOS)_loong64\n\n# https://en.wikipedia.org/wiki/Uname\nUNAME_M := $(shell uname -m)\nifeq ($(UNAME_M),x86_64)\n\tBINARY := $(BINARY64)\nelse ifeq ($(UNAME_M),amd64)\n\tBINARY := $(BINARY64)\nelse ifeq ($(UNAME_M),s390x)\n\tBINARY := $(BINARYS390)\nelse ifeq ($(UNAME_M),i686)\n\tBINARY := $(BINARY32)\nelse ifeq ($(UNAME_M),i386)\n\tBINARY := $(BINARY32)\nelse ifeq ($(UNAME_M),armv5l)\n\tBINARY := $(BINARYARM5)\nelse ifeq ($(UNAME_M),armv6l)\n\tBINARY := $(BINARYARM6)\nelse ifeq ($(UNAME_M),armv7l)\n\tBINARY := $(BINARYARM7)\nelse ifeq ($(UNAME_M),armv8l)\n\t# armv8l is always 32-bit and should implement the armv7 ISA, so\n\t# just use the same filename as for armv7.\n\tBINARY := $(BINARYARM7)\nelse ifeq ($(UNAME_M),arm64)\n\tBINARY := $(BINARYARM8)\nelse ifeq ($(UNAME_M),aarch64)\n\tBINARY := $(BINARYARM8)\nelse ifeq ($(UNAME_M),ppc64le)\n\tBINARY := $(BINARYPPC64LE)\nelse ifeq ($(UNAME_M),riscv64)\n\tBINARY := $(BINARYRISCV64)\nelse ifeq ($(UNAME_M),loongarch64)\n\tBINARY := $(BINARYLOONG64)\nelse\n$(error Build on $(UNAME_M) is not supported, yet.)\nendif\n\nall: target/$(BINARY)\n\ntest: $(SOURCES)\n\tSHELL=/bin/sh GOOS= $(GO) test -v -tags \"$(TAGS)\" \\\n\t\t\t\tgithub.com/junegunn/fzf/src \\\n\t\t\t\tgithub.com/junegunn/fzf/src/algo \\\n\t\t\t\tgithub.com/junegunn/fzf/src/tui \\\n\t\t\t\tgithub.com/junegunn/fzf/src/util\n\nitest:\n\truby test/runner.rb\n\nbench:\n\tcd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags \"$(TAGS)\" -run=Bench -bench=. -benchmem\n\nlint: $(SOURCES) test/*.rb test/lib/*.rb ${BASH_SCRIPTS}\n\t[ -z \"$$(gofmt -s -d src)\" ] || (gofmt -s -d src; exit 1)\n\tbundle exec rubocop -a --require rubocop-minitest --require rubocop-performance\n\tshell/update.sh --check ${BASH_SCRIPTS}\n\nfmt: $(SOURCES) $(BASH_SCRIPTS)\n\tgofmt -s -w src\n\tshell/update.sh ${BASH_SCRIPTS}\n\ninstall: bin/fzf\n\ngenerate:\n\tPATH=$(PATH):$(GOPATH)/bin $(GO) generate ./...\n\nbuild:\n\tgoreleaser build --clean --snapshot --skip=post-hooks\n\nrelease:\n\t# Make sure that the tests pass and the build works\n\tTAGS=tcell make test\n\tmake test build clean\n\nifndef GITHUB_TOKEN\n\t$(error GITHUB_TOKEN is not defined)\nendif\n\n\t# Check if we are on master branch\nifneq ($(shell git symbolic-ref --short HEAD),master)\n\t$(error Not on master branch)\nendif\n\n\t# Check if version numbers are properly updated\n\tgrep -q ^$(VERSION_REGEX)$$ CHANGELOG.md\n\tgrep -qF '\"fzf $(VERSION_TRIM)\"' man/man1/fzf.1\n\tgrep -qF '\"fzf $(VERSION_TRIM)\"' man/man1/fzf-tmux.1\n\tgrep -qF $(VERSION) install\n\tgrep -qF $(VERSION) install.ps1\n\n\t# Make release note out of CHANGELOG.md\n\tmkdir -p tmp\n\tsed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \\\n\t\tsed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note\n\n\t# Push to temp branch first so that install scripts always works on master branch\n\tgit checkout -B temp master\n\tgit push origin temp --follow-tags --force\n\n\t# Make a GitHub release\n\tgoreleaser --clean --release-notes tmp/release-note\n\n\t# Push to master\n\tgit checkout master\n\tgit push origin master\n\n\t# Delete temp branch\n\tgit push origin --delete temp\n\nclean:\n\t$(RM) -r dist target\n\ntarget/$(BINARY32): $(SOURCES)\n\tGOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARY64): $(SOURCES)\n\tGOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYS390): $(SOURCES)\n\tGOARCH=s390x $(GO) build $(BUILD_FLAGS) -o $@\n# https://github.com/golang/go/wiki/GoArm\ntarget/$(BINARYARM5): $(SOURCES)\n\tGOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYARM6): $(SOURCES)\n\tGOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYARM7): $(SOURCES)\n\tGOARCH=arm GOARM=7 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYARM8): $(SOURCES)\n\tGOARCH=arm64 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYPPC64LE): $(SOURCES)\n\tGOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYRISCV64): $(SOURCES)\n\tGOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@\n\ntarget/$(BINARYLOONG64): $(SOURCES)\n\tGOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@\n\nbin/fzf: target/$(BINARY) | bin\n\t-rm -f bin/fzf\n\tcp -f target/$(BINARY) bin/fzf\n\ndocker:\n\t$(DOCKER) build -t fzf-ubuntu .\n\t$(DOCKER) run -it fzf-ubuntu tmux\n\ndocker-test:\n\t$(DOCKER) build -t fzf-ubuntu .\n\t$(DOCKER) run -it fzf-ubuntu\n\nupdate:\n\t$(GO) get -u\n\t$(GO) mod tidy\n\n.PHONY: all generate build release test itest bench lint install clean docker docker-test update fmt\n"
  },
  {
    "path": "README-VIM.md",
    "content": "FZF Vim integration\n===================\n\nInstallation\n------------\n\nOnce you have fzf installed, you can enable it inside Vim simply by adding the\ndirectory to `&runtimepath` in your Vim configuration file. The path may\ndiffer depending on the package manager.\n\n```vim\n\" If installed using Homebrew\nset rtp+=/usr/local/opt/fzf\n\n\" If installed using Homebrew on Apple Silicon\nset rtp+=/opt/homebrew/opt/fzf\n\n\" If you have cloned fzf on ~/.fzf directory\nset rtp+=~/.fzf\n```\n\nIf you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be\nwritten as:\n\n```vim\n\" If installed using Homebrew\nPlug '/usr/local/opt/fzf'\n\n\" If installed using Homebrew on Apple Silicon\nPlug '/opt/homebrew/opt/fzf'\n\n\" If you have cloned fzf on ~/.fzf directory\nPlug '~/.fzf'\n```\n\nBut if you want the latest Vim plugin file from GitHub rather than the one\nincluded in the package, write:\n\n```vim\nPlug 'junegunn/fzf'\n```\n\nThe Vim plugin will pick up fzf binary available on the system. If fzf is not\nfound on `$PATH`, it will ask you if it should download the latest binary for\nyou.\n\nTo make sure that you have the latest version of the binary, set up\npost-update hook like so:\n\n```vim\nPlug 'junegunn/fzf', { 'do': { -> fzf#install() } }\n```\n\nSummary\n-------\n\nThe Vim plugin of fzf provides two core functions, and `:FZF` command which is\nthe basic file selector command built on top of them.\n\n1. **`fzf#run([spec dict])`**\n    - Starts fzf inside Vim with the given spec\n    - `:call fzf#run({'source': 'ls'})`\n2. **`fzf#wrap([spec dict]) -> (dict)`**\n    - Takes a spec for `fzf#run` and returns an extended version of it with\n      additional options for addressing global preferences (`g:fzf_xxx`)\n        - `:echo fzf#wrap({'source': 'ls'})`\n    - We usually *wrap* a spec with `fzf#wrap` before passing it to `fzf#run`\n        - `:call fzf#run(fzf#wrap({'source': 'ls'}))`\n3. **`:FZF [fzf_options string] [path string]`**\n    - Basic fuzzy file selector\n    - A reference implementation for those who don't want to write VimScript\n      to implement custom commands\n    - If you're looking for more such commands, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.\n\nThe most important of all is `fzf#run`, but it would be easier to understand\nthe whole if we start off with `:FZF` command.\n\n`:FZF[!]`\n---------\n\n```vim\n\" Look for files under current directory\n:FZF\n\n\" Look for files under your home directory\n:FZF ~\n\n\" With fzf command-line options\n:FZF --reverse --info=inline /tmp\n\n\" Bang version starts fzf in fullscreen mode\n:FZF!\n```\n\nSimilarly to [ctrlp.vim](https://github.com/kien/ctrlp.vim), use enter key,\n`CTRL-T`, `CTRL-X` or `CTRL-V` to open selected files in the current window,\nin new tabs, in horizontal splits, or in vertical splits respectively.\n\nNote that the environment variables `FZF_DEFAULT_COMMAND` and\n`FZF_DEFAULT_OPTS` also apply here.\n\n### Configuration\n\n- `g:fzf_action`\n    - Customizable extra key bindings for opening selected files in different ways\n- `g:fzf_layout`\n    - Determines the size and position of fzf window\n- `g:fzf_colors`\n    - Customizes fzf colors to match the current color scheme\n- `g:fzf_history_dir`\n    - Enables history feature\n\n#### Examples\n\n```vim\n\" This is the default extra key bindings\nlet g:fzf_action = {\n  \\ 'ctrl-t': 'tab split',\n  \\ 'ctrl-x': 'split',\n  \\ 'ctrl-v': 'vsplit' }\n\n\" An action can be a reference to a function that processes selected lines\nfunction! s:build_quickfix_list(lines)\n  call setqflist(map(copy(a:lines), '{ \"filename\": v:val, \"lnum\": 1 }'))\n  copen\n  cc\nendfunction\n\nlet g:fzf_action = {\n  \\ 'ctrl-q': function('s:build_quickfix_list'),\n  \\ 'ctrl-t': 'tab split',\n  \\ 'ctrl-x': 'split',\n  \\ 'ctrl-v': 'vsplit' }\n\n\" Default fzf layout\n\" - Popup window (center of the screen)\nlet g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n\n\" - Popup window (center of the current window)\nlet g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }\n\n\" - Popup window (anchored to the bottom of the current window)\nlet g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }\n\n\" - down / up / left / right\nlet g:fzf_layout = { 'down': '40%' }\n\n\" - Window using a Vim command\nlet g:fzf_layout = { 'window': 'enew' }\nlet g:fzf_layout = { 'window': '-tabnew' }\nlet g:fzf_layout = { 'window': '10new' }\n\n\" Customize fzf colors to match your color scheme\n\" - fzf#wrap translates this to a set of `--color` options\nlet g:fzf_colors =\n\\ { 'fg':      ['fg', 'Normal'],\n  \\ 'bg':      ['bg', 'Normal'],\n  \\ 'query':   ['fg', 'Normal'],\n  \\ 'hl':      ['fg', 'Comment'],\n  \\ 'fg+':     ['fg', 'CursorLine', 'CursorColumn', 'Normal'],\n  \\ 'bg+':     ['bg', 'CursorLine', 'CursorColumn'],\n  \\ 'hl+':     ['fg', 'Statement'],\n  \\ 'info':    ['fg', 'PreProc'],\n  \\ 'border':  ['fg', 'Ignore'],\n  \\ 'prompt':  ['fg', 'Conditional'],\n  \\ 'pointer': ['fg', 'Exception'],\n  \\ 'marker':  ['fg', 'Keyword'],\n  \\ 'spinner': ['fg', 'Label'],\n  \\ 'header':  ['fg', 'Comment'] }\n\n\" Enable per-command history\n\" - History files will be stored in the specified directory\n\" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and\n\"   'previous-history' instead of 'down' and 'up'.\nlet g:fzf_history_dir = '~/.local/share/fzf-history'\n```\n\n##### Explanation of `g:fzf_colors`\n\n`g:fzf_colors` is a dictionary mapping fzf elements to a color specification\nlist:\n\n    element: [ component, group1 [, group2, ...] ]\n\n- `element` is an fzf element to apply a color to:\n\n  | Element                     | Description                                           |\n  | ---                         | ---                                                   |\n  | `fg`  / `bg`  / `hl`        | Item (foreground / background / highlight)            |\n  | `fg+` / `bg+` / `hl+`       | Current item (foreground / background / highlight)    |\n  | `preview-fg` / `preview-bg` | Preview window text and background                    |\n  | `hl`  / `hl+`               | Highlighted substrings (normal / current)             |\n  | `gutter`                    | Background of the gutter on the left                  |\n  | `pointer`                   | Pointer to the current line (`>`)                     |\n  | `marker`                    | Multi-select marker (`>`)                             |\n  | `border`                    | Border around the window (`--border` and `--preview`) |\n  | `header`                    | Header (`--header` or `--header-lines`)               |\n  | `info`                      | Info line (match counters)                            |\n  | `spinner`                   | Streaming input indicator                             |\n  | `query`                     | Query string                                          |\n  | `disabled`                  | Query string when search is disabled                  |\n  | `prompt`                    | Prompt before query (`> `)                            |\n  | `pointer`                   | Pointer to the current line (`>`)                     |\n\n- `component` specifies the component (`fg` / `bg`) from which to extract the\n  color when considering each of the following highlight groups\n\n- `group1 [, group2, ...]` is a list of highlight groups that are searched (in\n  order) for a matching color definition\n\nFor example, consider the following specification:\n\n```vim\n  'prompt':  ['fg', 'Conditional', 'Comment'],\n```\n\nThis means we color the **prompt**\n- using the `fg` attribute of the `Conditional` if it exists,\n- otherwise use the `fg` attribute of the `Comment` highlight group if it exists,\n- otherwise fall back to the default color settings for the **prompt**.\n\nYou can examine the color option generated according the setting by printing\nthe result of `fzf#wrap()` function like so:\n\n```vim\n:echo fzf#wrap()\n```\n\n`fzf#run`\n---------\n\n`fzf#run()` function is the core of Vim integration. It takes a single\ndictionary argument, *a spec*, and starts fzf process accordingly. At the very\nleast, specify `sink` option to tell what it should do with the selected\nentry.\n\n```vim\ncall fzf#run({'sink': 'e'})\n```\n\nWe haven't specified the `source`, so this is equivalent to starting fzf on\ncommand line without standard input pipe; fzf will traverse the file system\nunder the current directory to get the list of files. (If\n`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command\ninstead.) When you select one, it will open it with the sink, `:e` command. If\nyou want to open it in a new tab, you can pass `:tabedit` command instead as\nthe sink.\n\n```vim\ncall fzf#run({'sink': 'tabedit'})\n```\n\nYou can use any shell command as the source to generate the list. The\nfollowing example will list the files managed by git. It's equivalent to\nrunning `git ls-files | fzf` on shell.\n\n```vim\ncall fzf#run({'source': 'git ls-files', 'sink': 'e'})\n```\n\nfzf options can be specified as `options` entry in spec dictionary.\n\n```vim\ncall fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})\n```\n\nYou can also pass a layout option if you don't want fzf window to take up the\nentire screen.\n\n```vim\n\" up / down / left / right / window are allowed\ncall fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})\ncall fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})\n```\n\n`source` doesn't have to be an external shell command, you can pass a Vim\narray as the source. In the next example, we pass the names of color\nschemes as the source to implement a color scheme selector.\n\n```vim\ncall fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),\n            \\               'fnamemodify(v:val, \":t:r\")'),\n            \\ 'sink': 'colo', 'left': '25%'})\n```\n\nThe following table summarizes the available options.\n\n| Option name                | Type          | Description                                                           |\n| -------------------------- | ------------- | ----------------------------------------------------------------      |\n| `source`                   | string        | External command to generate input to fzf (e.g. `find .`)             |\n| `source`                   | list          | Vim list as input to fzf                                              |\n| `sink`                     | string        | Vim command to handle the selected item (e.g. `e`, `tabe`)            |\n| `sink`                     | funcref       | Function to be called with each selected item                         |\n| `sinklist` (or `sink*`)    | funcref       | Similar to `sink`, but takes the list of output lines at once         |\n| `exit`                     | funcref       | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130) |\n| `options`                  | string/list   | Options to fzf                                                        |\n| `dir`                      | string        | Working directory                                                     |\n| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`)                  |\n| `tmux`                     | string        | (Layout) `--tmux` options (e.g. `90%,70%`)                            |\n| `window` (Vim 8 / Neovim)  | string        | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |\n| `window` (Vim 8 / Neovim)  | dict          | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |\n\n`options` entry can be either a string or a list. For simple cases, string\nshould suffice, but prefer to use list type to avoid escaping issues.\n\n```vim\ncall fzf#run({'options': '--reverse --prompt \"C:\\\\Program Files\\\\\"'})\ncall fzf#run({'options': ['--reverse', '--prompt', 'C:\\Program Files\\']})\n```\n\nWhen `window` entry is a dictionary, fzf will start in a popup window. The\nfollowing options are allowed:\n\n- Required:\n    - `width` [float range [0 ~ 1]] or [integer range [8 ~ ]]\n    - `height` [float range [0 ~ 1]] or [integer range [4 ~ ]]\n- Optional:\n    - `yoffset` [float default 0.5 range [0 ~ 1]]\n    - `xoffset` [float default 0.5 range [0 ~ 1]]\n    - `relative` [boolean default v:false]\n    - `border` [string default `rounded` (`sharp` on Windows)]: Border style\n        - `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`\n\n`fzf#wrap`\n----------\n\nWe have seen that several aspects of `:FZF` command can be configured with\na set of global option variables; different ways to open files\n(`g:fzf_action`), window position and size (`g:fzf_layout`), color palette\n(`g:fzf_colors`), etc.\n\nSo how can we make our custom `fzf#run` calls also respect those variables?\nSimply by *\"wrapping\"* the spec dictionary with `fzf#wrap` before passing it\nto `fzf#run`.\n\n- **`fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`**\n    - All arguments are optional. Usually we only need to pass a spec dictionary.\n    - `name` is for managing history files. It is ignored if\n      `g:fzf_history_dir` is not defined.\n    - `fullscreen` can be either `0` or `1` (default: 0).\n\n`fzf#wrap` takes a spec and returns an extended version of it (also\na dictionary) with additional options for addressing global preferences. You\ncan examine the return value of it like so:\n\n```vim\necho fzf#wrap({'source': 'ls'})\n```\n\nAfter we *\"wrap\"* our spec, we pass it to `fzf#run`.\n\n```vim\ncall fzf#run(fzf#wrap({'source': 'ls'}))\n```\n\nNow it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings (configurable\nvia `g:fzf_action`) and it opens fzf window according to `g:fzf_layout`\nsetting.\n\nTo make it easier to use, let's define `LS` command.\n\n```vim\ncommand! LS call fzf#run(fzf#wrap({'source': 'ls'}))\n```\n\nType `:LS` and see how it works.\n\nWe would like to make `:LS!` (bang version) open fzf in fullscreen, just like\n`:FZF!`. Add `-bang` to command definition, and use `<bang>` value to set\nthe last `fullscreen` argument of `fzf#wrap` (see `:help <bang>`).\n\n```vim\n\" On :LS!, <bang> evaluates to '!', and '!0' becomes 1\ncommand! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))\n```\n\nOur `:LS` command will be much more useful if we can pass a directory argument\nto it, so that something like `:LS /tmp` is possible.\n\n```vim\ncommand! -bang -complete=dir -nargs=? LS\n    \\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))\n```\n\nLastly, if you have enabled `g:fzf_history_dir`, you might want to assign\na unique name to our command and pass it as the first argument to `fzf#wrap`.\n\n```vim\n\" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.\n\" The name is ignored if g:fzf_history_dir is not defined.\ncommand! -bang -complete=dir -nargs=? LS\n    \\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))\n```\n\n### Global options supported by `fzf#wrap`\n\n- `g:fzf_layout`\n- `g:fzf_action`\n    - **Works only when no custom `sink` (or `sinklist`) is provided**\n        - Having custom sink usually means that each entry is not an ordinary\n          file path (e.g. name of color scheme), so we can't blindly apply the\n          same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)\n- `g:fzf_colors`\n- `g:fzf_history_dir`\n\nTips\n----\n\n### fzf inside terminal buffer\n\nOn the latest versions of Vim and Neovim, fzf will start in a terminal buffer.\nIf you find the default ANSI colors to be different, consider configuring the\ncolors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`\nin Neovim.\n\n```vim\n\" Terminal colors for seoul256 color scheme\nif has('nvim')\n  let g:terminal_color_0 = '#4e4e4e'\n  let g:terminal_color_1 = '#d68787'\n  let g:terminal_color_2 = '#5f865f'\n  let g:terminal_color_3 = '#d8af5f'\n  let g:terminal_color_4 = '#85add4'\n  let g:terminal_color_5 = '#d7afaf'\n  let g:terminal_color_6 = '#87afaf'\n  let g:terminal_color_7 = '#d0d0d0'\n  let g:terminal_color_8 = '#626262'\n  let g:terminal_color_9 = '#d75f87'\n  let g:terminal_color_10 = '#87af87'\n  let g:terminal_color_11 = '#ffd787'\n  let g:terminal_color_12 = '#add4fb'\n  let g:terminal_color_13 = '#ffafaf'\n  let g:terminal_color_14 = '#87d7d7'\n  let g:terminal_color_15 = '#e4e4e4'\nelse\n  let g:terminal_ansi_colors = [\n    \\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',\n    \\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',\n    \\ '#626262', '#d75f87', '#87af87', '#ffd787',\n    \\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'\n  \\ ]\nendif\n```\n\n### Starting fzf in a popup window\n\n```vim\n\" Required:\n\" - width [float range [0 ~ 1]] or [integer range [8 ~ ]]\n\" - height [float range [0 ~ 1]] or [integer range [4 ~ ]]\n\"\n\" Optional:\n\" - xoffset [float default 0.5 range [0 ~ 1]]\n\" - yoffset [float default 0.5 range [0 ~ 1]]\n\" - relative [boolean default v:false]\n\" - border [string default 'rounded']: Border style\n\"   - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'\nlet g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n```\n\nAlternatively, you can make fzf open in a tmux popup window (requires tmux 3.2\nor above) by putting `--tmux` option value in `tmux` key.\n\n```vim\n\" See `--tmux` option in `man fzf` for available options\n\" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]\nif exists('$TMUX')\n  let g:fzf_layout = { 'tmux': '90%,70%' }\nelse\n  let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\nendif\n```\n\n### Hide statusline\n\nWhen fzf starts in a terminal buffer, the file type of the buffer is set to\n`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of\nthe window.\n\nFor example, if you open fzf on the bottom on the screen (e.g. `{'down':\n'40%'}`), you might want to temporarily disable the statusline for a cleaner\nlook.\n\n```vim\nlet g:fzf_layout = { 'down': '30%' }\nautocmd! FileType fzf\nautocmd  FileType fzf set laststatus=0 noshowmode noruler\n  \\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler\n```\n\n[License](LICENSE)\n------------------\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-color.png\" alt=\"fzf - a command-line fuzzy finder\">\n  <a href=\"https://github.com/junegunn/fzf/actions\"><img src=\"https://github.com/junegunn/fzf/actions/workflows/linux.yml/badge.svg?branch=master\" alt=\"Build Status\"></a>\n  <a href=\"http://github.com/junegunn/fzf/releases\"><img src=\"https://img.shields.io/github/v/tag/junegunn/fzf\" alt=\"Version\"></a>\n  <a href=\"https://github.com/junegunn/fzf?tab=MIT-1-ov-file#readme\"><img src=\"https://img.shields.io/github/license/junegunn/fzf\" alt=\"License\"></a>\n  <a href=\"https://github.com/junegunn/fzf/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors/junegunn/fzf\" alt=\"Contributors\"></a>\n  <a href=\"https://github.com/sponsors/junegunn\"><img src=\"https://img.shields.io/github/sponsors/junegunn\" alt=\"Sponsors\"></a>\n  <a href=\"https://github.com/junegunn/fzf/stargazers\"><img src=\"https://img.shields.io/github/stars/junegunn/fzf?style=flat\" alt=\"Stars\"></a>\n</div>\n\n---\n\nfzf is a general-purpose command-line fuzzy finder.\n\n<img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png\" width=640>\n\nIt's an interactive filter program for any kind of list; files, command\nhistory, processes, hostnames, bookmarks, git commits, etc. It implements\na \"fuzzy\" matching algorithm, so you can quickly type in patterns with omitted\ncharacters and still get the results you want.\n\nHighlights\n----------\n\n- **Portable** — Distributed as a single binary for easy installation\n- **Fast** — Optimized to process millions of items instantly\n- **Versatile** — Fully customizable through an event-action binding mechanism\n- **All-inclusive** — Comes with integrations for Bash, Zsh, Fish, Vim, and Neovim\n\nTable of Contents\n-----------------\n\n<!-- vim-markdown-toc GFM -->\n\n* [Installation](#installation)\n    * [Using Homebrew](#using-homebrew)\n    * [Using Mise](#using-mise)\n    * [Linux packages](#linux-packages)\n    * [Windows packages](#windows-packages)\n    * [Using git](#using-git)\n    * [Binary releases](#binary-releases)\n    * [Setting up shell integration](#setting-up-shell-integration)\n    * [Vim/Neovim plugin](#vimneovim-plugin)\n* [Upgrading fzf](#upgrading-fzf)\n* [Building fzf](#building-fzf)\n* [Usage](#usage)\n    * [Using the finder](#using-the-finder)\n    * [Display modes](#display-modes)\n        * [`--height` mode](#--height-mode)\n        * [`--tmux` mode](#--tmux-mode)\n    * [Search syntax](#search-syntax)\n    * [Environment variables](#environment-variables)\n    * [Customizing the look](#customizing-the-look)\n    * [Options](#options)\n    * [Demo](#demo)\n* [Examples](#examples)\n* [Key bindings for command-line](#key-bindings-for-command-line)\n* [Fuzzy completion](#fuzzy-completion)\n    * [Files and directories](#files-and-directories)\n    * [Process IDs](#process-ids)\n    * [Host names](#host-names)\n    * [Environment variables / Aliases](#environment-variables--aliases)\n    * [Customizing fuzzy completion for bash and zsh](#customizing-fuzzy-completion-for-bash-and-zsh)\n        * [Customizing fzf options for completion](#customizing-fzf-options-for-completion)\n        * [Customizing completion source for paths and directories](#customizing-completion-source-for-paths-and-directories)\n        * [Supported commands (bash)](#supported-commands-bash)\n        * [Custom fuzzy completion](#custom-fuzzy-completion)\n    * [Fuzzy completion for fish](#fuzzy-completion-for-fish)\n* [Vim plugin](#vim-plugin)\n* [Advanced topics](#advanced-topics)\n    * [Customizing for different types of input](#customizing-for-different-types-of-input)\n    * [Performance](#performance)\n    * [Executing external programs](#executing-external-programs)\n    * [Turning into a different process](#turning-into-a-different-process)\n    * [Reloading the candidate list](#reloading-the-candidate-list)\n        * [1. Update the list of processes by pressing CTRL-R](#1-update-the-list-of-processes-by-pressing-ctrl-r)\n        * [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)\n        * [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)\n    * [Preview window](#preview-window)\n    * [Previewing an image](#previewing-an-image)\n* [Tips](#tips)\n    * [Respecting `.gitignore`](#respecting-gitignore)\n    * [Fish shell](#fish-shell)\n    * [fzf Theme Playground](#fzf-theme-playground)\n* [Related projects](#related-projects)\n* [License](#license)\n* [Goods](#goods)\n* [Sponsors :heart:](#sponsors-heart)\n\n<!-- vim-markdown-toc -->\n\nInstallation\n------------\n\n### Using Homebrew\n\nYou can use [Homebrew](https://brew.sh/) (on macOS or Linux) to install fzf.\n\n```sh\nbrew install fzf\n```\n\n> [!IMPORTANT]\n> To set up shell integration (key bindings and fuzzy completion),\n> see [the instructions below](#setting-up-shell-integration).\n\nfzf is also available [via MacPorts][portfile]: `sudo port install fzf`\n\n[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile\n\n### Using Mise\n\nYou can use [mise](https://github.com/jdx/mise) to install fzf.\n\n```sh\nmise use -g fzf@latest\n```\n\n### Linux packages\n\n| Package Manager | Linux Distribution      | Command                            |\n| --------------- | ----------------------- | ---------------------------------- |\n| APK             | Alpine Linux            | `sudo apk add fzf`                 |\n| APT             | Debian 9+/Ubuntu 19.10+ | `sudo apt install fzf`             |\n| Conda           |                         | `conda install -c conda-forge fzf` |\n| DNF             | Fedora                  | `sudo dnf install fzf`             |\n| Nix             | NixOS, etc.             | `nix-env -iA nixpkgs.fzf`          |\n| Pacman          | Arch Linux              | `sudo pacman -S fzf`               |\n| pkg             | FreeBSD                 | `pkg install fzf`                  |\n| pkgin           | NetBSD                  | `pkgin install fzf`                |\n| pkg_add         | OpenBSD                 | `pkg_add fzf`                      |\n| Portage         | Gentoo                  | `emerge --ask app-shells/fzf`      |\n| Spack           |                         | `spack install fzf`                |\n| XBPS            | Void Linux              | `sudo xbps-install -S fzf`         |\n| Zypper          | openSUSE                | `sudo zypper install fzf`          |\n\n> [!IMPORTANT]\n> To set up shell integration (key bindings and fuzzy completion),\n> see [the instructions below](#setting-up-shell-integration).\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/fzf.svg?columns=3)](https://repology.org/project/fzf/versions)\n\n### Windows packages\n\nOn Windows, fzf is available via [Chocolatey][choco], [Scoop][scoop],\n[Winget][winget], and [MSYS2][msys2]:\n\n| Package manager | Command                               |\n| --------------- | ------------------------------------- |\n| Chocolatey      | `choco install fzf`                   |\n| Scoop           | `scoop install fzf`                   |\n| Winget          | `winget install fzf`                  |\n| MSYS2 (pacman)  | `pacman -S $MINGW_PACKAGE_PREFIX-fzf` |\n\n[choco]: https://chocolatey.org/packages/fzf\n[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json\n[winget]: https://github.com/microsoft/winget-pkgs/tree/master/manifests/j/junegunn/fzf\n[msys2]: https://packages.msys2.org/base/mingw-w64-fzf\n\n### Using git\n\nAlternatively, you can \"git clone\" this repository to any directory and run\n[install](https://github.com/junegunn/fzf/blob/master/install) script.\n\n```sh\ngit clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf\n~/.fzf/install\n```\n\nThe install script will add lines to your shell configuration file to modify\n`$PATH` and set up shell integration.\n\n### Binary releases\n\nYou can download the official fzf binaries from the releases page.\n\n* https://github.com/junegunn/fzf/releases\n\n### Setting up shell integration\n\nAdd the following line to your shell configuration file.\n\n* bash\n  ```sh\n  # Set up fzf key bindings and fuzzy completion\n  eval \"$(fzf --bash)\"\n  ```\n* zsh\n  ```sh\n  # Set up fzf key bindings and fuzzy completion\n  source <(fzf --zsh)\n  ```\n* fish\n  ```fish\n  # Set up fzf key bindings\n  fzf --fish | source\n  ```\n\n> [!NOTE]\n> `--bash`, `--zsh`, and `--fish` options are only available in fzf 0.48.0 or\n> later. If you have an older version of fzf, or want finer control, you can\n> source individual script files in the [/shell](/shell) directory. The\n> location of the files may vary depending on the package manager you use.\n> Please refer to the package documentation for more information.\n> (e.g. `apt show fzf`)\n\n> [!TIP]\n> You can disable CTRL-T, CTRL-R, or ALT-C bindings by setting the\n> corresponding `*_COMMAND` variable to an empty string when sourcing the\n> script. For example, to disable CTRL-R and ALT-C:\n>\n> * bash: `FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= eval \"$(fzf --bash)\"`\n> * zsh: `FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= source <(fzf --zsh)`\n> * fish: `fzf --fish | FZF_CTRL_R_COMMAND= FZF_ALT_C_COMMAND= source`\n>\n> Setting the variables after sourcing the script will have no effect.\n\n### Vim/Neovim plugin\n\nIf you use [vim-plug](https://github.com/junegunn/vim-plug), add this to\nyour Vim configuration file:\n\n```vim\nPlug 'junegunn/fzf', { 'do': { -> fzf#install() } }\nPlug 'junegunn/fzf.vim'\n```\n\n* `junegunn/fzf` provides the basic library functions\n    * `fzf#install()` makes sure that you have the latest binary\n* `junegunn/fzf.vim` is [a separate project](https://github.com/junegunn/fzf.vim)\n  that provides a variety of useful commands\n\nTo learn more about the Vim integration, see [README-VIM.md](README-VIM.md).\n\n> [!TIP]\n> If you use Neovim and prefer Lua-based plugins, check out\n> [fzf-lua](https://github.com/ibhagwan/fzf-lua).\n\nUpgrading fzf\n-------------\n\nfzf is being actively developed, and you might want to upgrade it once in a\nwhile. Please follow the instruction below depending on the installation\nmethod used.\n\n- git: `cd ~/.fzf && git pull && ./install`\n- brew: `brew update; brew upgrade fzf`\n- macports: `sudo port upgrade fzf`\n- chocolatey: `choco upgrade fzf`\n- vim-plug: `:PlugUpdate fzf`\n\nBuilding fzf\n------------\n\nSee [BUILD.md](BUILD.md).\n\nUsage\n-----\n\nfzf will launch interactive finder, read the list from STDIN, and write the\nselected item to STDOUT.\n\n```sh\nfind * -type f | fzf > selected\n```\n\nWithout STDIN pipe, fzf will traverse the file system under the current\ndirectory to get the list of files.\n\n```sh\nvim $(fzf)\n```\n\n> [!NOTE]\n> You can override the default behavior\n> * Either by setting `$FZF_DEFAULT_COMMAND` to a command that generates the desired list\n> * Or by setting `--walker`, `--walker-root`, and `--walker-skip` options in `$FZF_DEFAULT_OPTS`\n\n> [!WARNING]\n> A more robust solution would be to use `xargs` but we've presented\n> the above as it's easier to grasp\n> ```sh\n> fzf --print0 | xargs -0 -o vim\n> ```\n\n> [!TIP]\n> fzf also has the ability to turn itself into a different process.\n>\n> ```sh\n> fzf --bind 'enter:become(vim {})'\n> ```\n>\n> *See [Turning into a different process](#turning-into-a-different-process)\n> for more information.*\n\n### Using the finder\n\n- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down\n- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit\n- On multi-select mode (`-m`), `TAB` and `Shift-TAB` to mark multiple items\n- Emacs style key bindings\n- Mouse: scroll, click, double-click; shift-click and shift-scroll on\n  multi-select mode\n\n### Display modes\n\nfzf by default runs in fullscreen mode, but there are other display modes.\n\n#### `--height` mode\n\nWith `--height HEIGHT[%]`, fzf will start below the cursor with the given height.\n\n```sh\nfzf --height 40%\n```\n\n`reverse` layout and `--border` goes well with this option.\n\n```sh\nfzf --height 40% --layout reverse --border\n```\n\nBy prepending `~` to the height, you're setting the maximum height.\n\n```sh\n# Will take as few lines as possible to display the list\nseq 3 | fzf --height ~100%\nseq 3000 | fzf --height ~100%\n```\n\nHeight value can be a negative number.\n\n```sh\n# Screen height - 3\nfzf --height -3\n```\n\n#### `--tmux` mode\n\nWith `--tmux` option, fzf will start in a tmux popup.\n\n```sh\n# --tmux [center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]]\n\nfzf --tmux center         # Center, 50% width and height\nfzf --tmux 80%            # Center, 80% width and height\nfzf --tmux 100%,50%       # Center, 100% width and 50% height\nfzf --tmux left,40%       # Left, 40% width\nfzf --tmux left,40%,90%   # Left, 40% width, 90% height\nfzf --tmux top,40%        # Top, 40% height\nfzf --tmux bottom,80%,40% # Bottom, 80% width, 40% height\n```\n\n`--tmux` is silently ignored when you're not on tmux.\n\n> [!NOTE]\n> If you're stuck with an old version of tmux that doesn't support popup,\n> or if you want to open fzf in a regular tmux pane, check out\n> [fzf-tmux](bin/fzf-tmux) script.\n\n> [!TIP]\n> You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by\n> default. For example,\n>\n> ```sh\n> # Open in tmux popup if on tmux, otherwise use --height mode\n> export FZF_DEFAULT_OPTS='--height 40% --tmux bottom,40% --layout reverse --border top'\n> ```\n\n### Search syntax\n\nUnless otherwise specified, fzf starts in \"extended-search mode\" where you can\ntype in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt\n!fire`\n\n| Token     | Match type                              | Description                                  |\n| --------- | --------------------------------------  | ------------------------------------------   |\n| `sbtrkt`  | fuzzy-match                             | Items that match `sbtrkt`                    |\n| `'wild`   | exact-match (quoted)                    | Items that include `wild`                    |\n| `'wild'`  | exact-boundary-match (quoted both ends) | Items that include `wild` at word boundaries |\n| `^music`  | prefix-exact-match                      | Items that start with `music`                |\n| `.mp3$`   | suffix-exact-match                      | Items that end with `.mp3`                   |\n| `!fire`   | inverse-exact-match                     | Items that do not include `fire`             |\n| `!^music` | inverse-prefix-exact-match              | Items that do not start with `music`         |\n| `!.mp3$`  | inverse-suffix-exact-match              | Items that do not end with `.mp3`            |\n\nIf you don't prefer fuzzy matching and do not wish to \"quote\" every word,\nstart fzf with `-e` or `--exact` option. Note that when  `--exact` is set,\n`'`-prefix \"unquotes\" the term.\n\nA single bar character term acts as an OR operator. For example, the following\nquery matches entries that start with `core` and end with either `go`, `rb`,\nor `py`.\n\n```\n^core go$ | rb$ | py$\n```\n\n### Environment variables\n\n- `FZF_DEFAULT_COMMAND`\n    - Default command to use when input is tty\n    - e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`\n- `FZF_DEFAULT_OPTS`\n    - Default options\n    - e.g. `export FZF_DEFAULT_OPTS=\"--layout=reverse --inline-info\"`\n- `FZF_DEFAULT_OPTS_FILE`\n    - If you prefer to manage default options in a file, set this variable to\n      point to the location of the file\n    - e.g. `export FZF_DEFAULT_OPTS_FILE=~/.fzfrc`\n\n> [!WARNING]\n> `FZF_DEFAULT_COMMAND` is not used by shell integration due to the\n> slight difference in requirements.\n>\n> * `CTRL-T` runs `$FZF_CTRL_T_COMMAND` to get a list of files and directories\n> * `ALT-C` runs `$FZF_ALT_C_COMMAND` to get a list of directories\n> * `vim ~/**<tab>` runs `fzf_compgen_path()` with the prefix (`~/`) as the first argument\n> * `cd foo**<tab>` runs `fzf_compgen_dir()` with the prefix (`foo`) as the first argument\n>\n> The available options are described later in this document.\n\n### Customizing the look\n\nThe user interface of fzf is fully customizable with a large number of\nconfiguration options. For a quick setup, you can start with one of the style\npresets — `default`, `full`, or `minimal` — using the `--style` option.\n\n```sh\nfzf --style full \\\n    --preview 'fzf-preview.sh {}' --bind 'focus:transform-header:file --brief {}'\n```\n\n| Preset    | Screenshot                                                                             |\n| :---      | :---                                                                                   |\n| `default` | <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-style-default.png\"/> |\n| `full`    | <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-style-full.png\"/>    |\n| `minimal` | <img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-style-minimal.png\"/> |\n\nHere's an example based on the `full` preset:\n\n<img src=\"https://raw.githubusercontent.com/junegunn/i/master/fzf-4-borders.png\"/>\n\n<details>\n\n```sh\ngit ls-files | fzf --style full \\\n    --border --padding 1,2 \\\n    --border-label ' Demo ' --input-label ' Input ' --header-label ' File Type ' \\\n    --preview 'fzf-preview.sh {}' \\\n    --bind 'result:transform-list-label:\n        if [[ -z $FZF_QUERY ]]; then\n          echo \" $FZF_MATCH_COUNT items \"\n        else\n          echo \" $FZF_MATCH_COUNT matches for [$FZF_QUERY] \"\n        fi\n        ' \\\n    --bind 'focus:transform-preview-label:[[ -n {} ]] && printf \" Previewing [%s] \" {}' \\\n    --bind 'focus:+transform-header:file --brief {} || echo \"No file selected\"' \\\n    --bind 'ctrl-r:change-list-label( Reloading the list )+reload(sleep 2; git ls-files)' \\\n    --color 'border:#aaaaaa,label:#cccccc' \\\n    --color 'preview-border:#9999cc,preview-label:#ccccff' \\\n    --color 'list-border:#669966,list-label:#99cc99' \\\n    --color 'input-border:#996666,input-label:#ffcccc' \\\n    --color 'header-border:#6699cc,header-label:#99ccff'\n```\n\n</details>\n\n### Options\n\nSee the man page (`fzf --man` or `man fzf`) for the full list of options.\n\n### Demo\nIf you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.\n\n<a title=\"fzf - command-line fuzzy finder\" href=\"https://www.youtube.com/watch?v=qgG5Jhi_Els\">\n  <img src=\"https://i.imgur.com/vtG8olE.png\" width=\"640\">\n</a>\n\nExamples\n--------\n\n* [Wiki page of examples](https://github.com/junegunn/fzf/wiki/examples)\n    * *Disclaimer: The examples on this page are maintained by the community\n      and are not thoroughly tested*\n* [Advanced fzf examples](https://github.com/junegunn/fzf/blob/master/ADVANCED.md)\n\nKey bindings for command-line\n-----------------------------\n\nBy [setting up shell integration](#setting-up-shell-integration), you can use\nthe following key bindings in bash, zsh, and fish.\n\n- `CTRL-T` - Paste the selected files and directories onto the command-line\n    - The list is generated using `--walker file,dir,follow,hidden` option\n        - You can override the behavior by setting `FZF_CTRL_T_COMMAND` to a custom command that generates the desired list\n        - Or you can set `--walker*` options in `FZF_CTRL_T_OPTS`\n    - Set `FZF_CTRL_T_OPTS` to pass additional options to fzf\n      ```sh\n      # Preview file content using bat (https://github.com/sharkdp/bat)\n      export FZF_CTRL_T_OPTS=\"\n        --walker-skip .git,node_modules,target\n        --preview 'bat -n --color=always {}'\n        --bind 'ctrl-/:change-preview-window(down|hidden|)'\"\n      ```\n    - Can be disabled by setting `FZF_CTRL_T_COMMAND` to an empty string when\n      sourcing the script\n- `CTRL-R` - Paste the selected command from history onto the command-line\n    - If you want to see the commands in chronological order, press `CTRL-R`\n      again which toggles sorting by relevance\n    - Press `ALT-R` to toggle \"raw\" mode where you can see the surrounding items\n      of a match. In this mode, you can press `CTRL-N` and `CTRL-P` to move\n      between the matching items only.\n    - Press `CTRL-/` or `ALT-/` to toggle line wrapping\n    - Set `FZF_CTRL_R_OPTS` to pass additional options to fzf\n      ```sh\n      # CTRL-Y to copy the command into clipboard using pbcopy\n      export FZF_CTRL_R_OPTS=\"\n        --bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'\n        --color header:italic\n        --header 'Press CTRL-Y to copy command into clipboard'\"\n      ```\n    - Can be disabled by setting `FZF_CTRL_R_COMMAND` to an empty string when\n      sourcing the script\n    - Custom override via a non-empty `FZF_CTRL_R_COMMAND` is not yet supported and will emit a warning\n- `ALT-C` - cd into the selected directory\n    - The list is generated using `--walker dir,follow,hidden` option\n    - Set `FZF_ALT_C_COMMAND` to override the default command\n        - Or you can set `--walker-*` options in `FZF_ALT_C_OPTS`\n    - Set `FZF_ALT_C_OPTS` to pass additional options to fzf\n      ```sh\n      # Print tree structure in the preview window\n      export FZF_ALT_C_OPTS=\"\n        --walker-skip .git,node_modules,target\n        --preview 'tree -C {}'\"\n      ```\n    - Can be disabled by setting `FZF_ALT_C_COMMAND` to an empty string when\n      sourcing the script\n\nDisplay modes for these bindings can be separately configured via\n`FZF_{CTRL_T,CTRL_R,ALT_C}_OPTS` or globally via `FZF_DEFAULT_OPTS`.\n(e.g. `FZF_CTRL_R_OPTS='--tmux bottom,60% --height 60% --border top'`)\n\nMore tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).\n\nFuzzy completion\n----------------\n\nShell integration also provides fuzzy completion for bash, zsh, and fish.\n\n### Files and directories\n\nFuzzy completion for files and directories can be triggered if the word before\nthe cursor ends with the trigger sequence, which is by default `**`.\n\n- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`\n\n```sh\n# Files under the current directory\n# - You can select multiple items with TAB key\nvim **<TAB>\n\n# Files under parent directory\nvim ../**<TAB>\n\n# Files under parent directory that match `fzf`\nvim ../fzf**<TAB>\n\n# Files under your home directory\nvim ~/**<TAB>\n\n\n# Directories under current directory (single-selection)\ncd **<TAB>\n\n# Directories under ~/github that match `fzf`\ncd ~/github/fzf**<TAB>\n```\n\n### Process IDs\n\nFuzzy completion for PIDs is provided for kill command.\n\n```sh\n# Can select multiple processes with <TAB> or <Shift-TAB> keys\nkill -9 **<TAB>\n```\n\n### Host names\n\nFor ssh command, fuzzy completion for hostnames is provided. The names are\nextracted from /etc/hosts and ~/.ssh/config.\n\n```sh\nssh **<TAB>\n```\n\n### Environment variables / Aliases\n\n```sh\n# bash and zsh\nunset **<TAB>\nexport **<TAB>\nunalias **<TAB>\n\n# fish\nset <SHIFT-TAB>\n```\n\n### Customizing fuzzy completion for bash and zsh\n\n#### Customizing fzf options for completion\n\n```sh\n# Use ~~ as the trigger sequence instead of the default **\nexport FZF_COMPLETION_TRIGGER='~~'\n\n# Options to fzf command\nexport FZF_COMPLETION_OPTS='--border --info=inline'\n\n# Options for path completion (e.g. vim **<TAB>)\nexport FZF_COMPLETION_PATH_OPTS='--walker file,dir,follow,hidden'\n\n# Options for directory completion (e.g. cd **<TAB>)\nexport FZF_COMPLETION_DIR_OPTS='--walker dir,follow'\n\n# Advanced customization of fzf options via _fzf_comprun function\n# - The first argument to the function is the name of the command.\n# - You should make sure to pass the rest of the arguments ($@) to fzf.\n_fzf_comprun() {\n  local command=$1\n  shift\n\n  case \"$command\" in\n    cd)           fzf --preview 'tree -C {} | head -200'   \"$@\" ;;\n    export|unset) fzf --preview \"eval 'echo \\$'{}\"         \"$@\" ;;\n    ssh)          fzf --preview 'dig {}'                   \"$@\" ;;\n    *)            fzf --preview 'bat -n --color=always {}' \"$@\" ;;\n  esac\n}\n```\n\n#### Customizing completion source for paths and directories\n\n```sh\n# Use fd (https://github.com/sharkdp/fd) for listing path candidates.\n# - The first argument to the function ($1) is the base path to start traversal\n# - See the source code (completion.{bash,zsh}) for the details.\n_fzf_compgen_path() {\n  fd --hidden --follow --exclude \".git\" . \"$1\"\n}\n\n# Use fd to generate the list for directory completion\n_fzf_compgen_dir() {\n  fd --type d --hidden --follow --exclude \".git\" . \"$1\"\n}\n```\n\n#### Supported commands (bash)\n\nOn bash, fuzzy completion is enabled only for a predefined set of commands\n(`complete | grep _fzf` to see the list). But you can enable it for other\ncommands as well by using `_fzf_setup_completion` helper function.\n\n```sh\n# usage: _fzf_setup_completion path|dir|var|alias|host COMMANDS...\n_fzf_setup_completion path ag git kubectl\n_fzf_setup_completion dir tree\n```\n\n#### Custom fuzzy completion\n\n_**(Custom completion API is experimental and subject to change)**_\n\nFor a command named _\"COMMAND\"_, define `_fzf_complete_COMMAND` function using\n`_fzf_complete` helper.\n\n```sh\n# Custom fuzzy completion for \"doge\" command\n#   e.g. doge **<TAB>\n_fzf_complete_doge() {\n  _fzf_complete --multi --reverse --prompt=\"doge> \" -- \"$@\" < <(\n    echo very\n    echo wow\n    echo such\n    echo doge\n  )\n}\n```\n\n- The arguments before `--` are the options to fzf.\n- After `--`, simply pass the original completion arguments unchanged (`\"$@\"`).\n- Then, write a set of commands that generates the completion candidates and\n  feed its output to the function using process substitution (`< <(...)`).\n\nzsh will automatically pick up the function using the naming convention but in\nbash you have to manually associate the function with the command using the\n`complete` command.\n\n```sh\n[ -n \"$BASH\" ] && complete -F _fzf_complete_doge -o default -o bashdefault doge\n```\n\nIf you need to post-process the output from fzf, define\n`_fzf_complete_COMMAND_post` as follows.\n\n```sh\n_fzf_complete_foo() {\n  _fzf_complete --multi --reverse --header-lines=3 -- \"$@\" < <(\n    ls -al\n  )\n}\n\n_fzf_complete_foo_post() {\n  awk '{print $NF}'\n}\n\n[ -n \"$BASH\" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo\n```\n\n### Fuzzy completion for fish\n\n(Available in 0.68.0 or later)\n\nFuzzy completion for fish differs from bash and zsh in that:\n\n- It doesn't require a trigger sequence like `**`. Instead, if activates\n  on `Shift-TAB`, while `TAB` preserves fish's native completion behavior.\n- It relies on fish's native completion system to populate the candidate list,\n  rather than performing a recursive file system traversal. For recursive\n  searching, use the `CTRL-T` binding instead.\n- The only supported configuration variable is `FZF_COMPLETION_OPTS`.\n\nThat said, just like in bash and zsh, you can implement custom completion for\na specific command by defining an `_fzf_complete_COMMAND` function. For example:\n\n```fish\nfunction _fzf_complete_foo\n  function _fzf_complete_foo_post\n    awk '{print $NF}'\n  end\n  _fzf_complete --multi --reverse --header-lines=3 -- $argv < (ls -al | psub)\n\n  functions -e _fzf_complete_foo_post\nend\n```\n\nAnd here's a more complex example for customizing `git`\n\n```fish\nfunction _fzf_complete_git\n  switch $argv[2]\n    case checkout switch\n      _fzf_complete --reverse --no-preview -- $argv < (git branch --all --format='%(refname:short)' | psub)\n\n    case add\n      function _fzf_complete_git_post\n        awk '{print $NF}'\n      end\n      _fzf_complete --multi --reverse -- $argv < (git status --short | psub)\n\n    case show log diff\n      function _fzf_complete_git_post\n        awk '{print $1}'\n      end\n      _fzf_complete --reverse --no-sort --preview='git show --color=always {1}' -- $argv < (git log --oneline | psub)\n\n    case ''\n      __fzf_complete_native \"$argv[1] \" --query=(commandline -t | string escape)\n\n    case '*'\n      set -l -- current_token (commandline -t)\n      __fzf_complete_native \"$argv $current_token\" --query=(string escape -- $current_token) --multi\n  end\n\n  functions -e _fzf_complete_git_post\nend\n```\n\nVim plugin\n----------\n\nSee [README-VIM.md](README-VIM.md).\n\nAdvanced topics\n---------------\n\n### Customizing for different types of input\n\nSince fzf is a general-purpose text filter, its algorithm was designed to\n\"generally\" work well with any kind of input. However, admittedly, there is no\ntrue one-size-fits-all solution, and you may want to tweak the algorithm and\nsome of the settings depending on the type of the input. To make this process\neasier, fzf provides a set of \"scheme\"s for some common input types.\n\n| Scheme             | Description                                                                         |\n| :---               | :---                                                                                |\n| `--scheme=default` | Generic scheme designed to work well with any kind of input                         |\n| `--scheme=path`    | Suitable for file paths                                                             |\n| `--scheme=history` | Suitable for command history or any input where chronological ordering is important |\n\n(See `fzf --man` for the details)\n\n### Performance\n\nfzf is fast. Performance should not be a problem in most use cases. However,\nyou might want to be aware of the options that can affect performance.\n\n- `--ansi` tells fzf to extract and parse ANSI color codes in the input, and it\n  makes the initial scanning slower. So it's not recommended that you add it\n  to your `$FZF_DEFAULT_OPTS`.\n- `--nth` makes fzf slower because it has to tokenize each line.\n- A plain string `--delimiter` should be preferred over a regular expression\n  delimiter.\n- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each\n  line.\n\n### Executing external programs\n\nYou can set up key bindings for starting external processes without leaving\nfzf (`execute`, `execute-silent`).\n\n```bash\n# Press F1 to open the file with less without leaving fzf\n# Press CTRL-Y to copy the line to clipboard and aborts fzf (requires pbcopy)\nfzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort'\n```\n\nSee *KEY/EVENT BINDINGS* section of the man page for details.\n\n### Turning into a different process\n\n`become(...)` is similar to `execute(...)`/`execute-silent(...)` described\nabove, but instead of executing the command and coming back to fzf on\ncomplete, it turns fzf into a new process for the command.\n\n```sh\nfzf --bind 'enter:become(vim {})'\n```\n\nCompared to the seemingly equivalent command substitution `vim \"$(fzf)\"`, this\napproach has several advantages:\n\n* Vim will not open an empty file when you terminate fzf with\n  <kbd>CTRL-C</kbd>\n* Vim will not open an empty file when you press <kbd>ENTER</kbd> on an empty\n  result\n* Can handle multiple selections even when they have whitespaces\n  ```sh\n  fzf --multi --bind 'enter:become(vim {+})'\n  ```\n\nTo be fair, running `fzf --print0 | xargs -0 -o vim` instead of `vim \"$(fzf)\"`\nresolves all of the issues mentioned. Nonetheless, `become(...)` still offers\nadditional benefits in different scenarios.\n\n* You can set up multiple bindings to handle the result in different ways\n  without any wrapping script\n  ```sh\n  fzf --bind 'enter:become(vim {}),ctrl-e:become(emacs {})'\n  ```\n  * Previously, you would have to use `--expect=ctrl-e` and check the first\n    line of the output of fzf\n* You can easily build the subsequent command using the field index\n  expressions of fzf\n  ```sh\n  # Open the file in Vim and go to the line\n  git grep --line-number . |\n      fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'\n  ```\n\n### Reloading the candidate list\n\nBy binding `reload` action to a key or an event, you can make fzf dynamically\nreload the candidate list. See https://github.com/junegunn/fzf/issues/1750 for\nmore details.\n\n#### 1. Update the list of processes by pressing CTRL-R\n\n```sh\nps -ef |\n  fzf --bind 'ctrl-r:reload(ps -ef)' \\\n      --header 'Press CTRL-R to reload' --header-lines=1 \\\n      --height=50% --layout=reverse\n```\n\n#### 2. Switch between sources by pressing CTRL-D or CTRL-F\n\n```sh\nFZF_DEFAULT_COMMAND='find . -type f' \\\n  fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload(eval \"$FZF_DEFAULT_COMMAND\")' \\\n      --height=50% --layout=reverse\n```\n\n#### 3. Interactive ripgrep integration\n\nThe following example uses fzf as the selector interface for ripgrep. We bound\n`reload` action to `change` event, so every time you type on fzf, the ripgrep\nprocess will restart with the updated query string denoted by the placeholder\nexpression `{q}`. Also, note that we used `--disabled` option so that fzf\ndoesn't perform any secondary filtering.\n\n```sh\n: | rg_prefix='rg --column --line-number --no-heading --color=always --smart-case' \\\n    fzf --bind 'start:reload:$rg_prefix \"\"' \\\n        --bind 'change:reload:$rg_prefix {q} || true' \\\n        --bind 'enter:become(vim {1} +{2})' \\\n        --ansi --disabled \\\n        --height=50% --layout=reverse\n```\n\nIf ripgrep doesn't find any matches, it will exit with a non-zero exit status,\nand fzf will warn you about it. To suppress the warning message, we added\n`|| true` to the command, so that it always exits with 0.\n\nSee [\"Using fzf as interactive Ripgrep launcher\"](https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interactive-ripgrep-launcher)\nfor more sophisticated examples.\n\n### Preview window\n\nWhen the `--preview` option is set, fzf automatically starts an external process\nwith the current line as the argument and shows the result in the split window.\nYour `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.\nThe window can be scrolled using the mouse or custom key bindings.\n\n```bash\n# {} is replaced with the single-quoted string of the focused line\nfzf --preview 'cat {}'\n```\n\nPreview window supports ANSI colors, so you can use any program that\nsyntax-highlights the content of a file, such as\n[Bat](https://github.com/sharkdp/bat) or\n[Highlight](https://gitlab.com/saalen/highlight):\n\n```bash\nfzf --preview 'bat --color=always {}' --preview-window '~3'\n```\n\nYou can customize the size, position, and border of the preview window using\n`--preview-window` option, and the foreground and background color of it with\n`--color` option. For example,\n\n```bash\nfzf --height 40% --layout reverse --info inline --border \\\n    --preview 'file {}' --preview-window up,1,border-horizontal \\\n    --bind 'ctrl-/:change-preview-window(50%|hidden|)' \\\n    --color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'\n```\n\nSee the man page (`man fzf`) for the full list of options.\n\nMore advanced examples can be found [here](https://github.com/junegunn/fzf/blob/master/ADVANCED.md).\n\n> [!WARNING]\n> Since fzf is a general-purpose text filter rather than a file finder, **it is\n> not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**.\n>\n> ```sh\n> # *********************\n> # ** DO NOT DO THIS! **\n> # *********************\n> export FZF_DEFAULT_OPTS='--preview \"bat --style=numbers --color=always --line-range :500 {}\"'\n>\n> # bat doesn't work with any input other than the list of files\n> ps -ef | fzf\n> seq 100 | fzf\n> history | fzf\n> ```\n\n### Previewing an image\n\nfzf can display images in the preview window using one of the following protocols:\n\n* [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/)\n* [iTerm2 inline images protocol](https://iterm2.com/documentation-images.html)\n* [Sixel](https://en.wikipedia.org/wiki/Sixel)\n\nSee [bin/fzf-preview.sh](bin/fzf-preview.sh) script for more information.\n\n```sh\nfzf --preview 'fzf-preview.sh {}'\n```\n\nTips\n----\n\n### Respecting `.gitignore`\n\nYou can use [fd](https://github.com/sharkdp/fd),\n[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver\nsearcher](https://github.com/ggreer/the_silver_searcher) to traverse the file\nsystem while respecting `.gitignore`.\n\n```sh\n# Feed the output of fd into fzf\nfd --type f --strip-cwd-prefix | fzf\n\n# Setting fd as the default source for fzf\nexport FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix'\n\n# Now fzf (w/o pipe) will use the fd command to generate the list\nfzf\n\n# To apply the command to CTRL-T as well\nexport FZF_CTRL_T_COMMAND=\"$FZF_DEFAULT_COMMAND\"\n```\n\nIf you want the command to follow symbolic links and don't want it to exclude\nhidden files, use the following command:\n\n```sh\nexport FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix --hidden --follow --exclude .git'\n```\n\n### Fish shell\n\n`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last\ntoken on the command-line as the root directory for the recursive search. For\ninstance, hitting `CTRL-T` at the end of the following command-line\n\n```sh\nls /var/\n```\n\nwill list all files and directories under `/var/`.\n\nWhen using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to\nmake use of this feature. `$dir` defaults to `.` when the last token is not a\nvalid directory. Example:\n\n```sh\nset -g FZF_CTRL_T_COMMAND \"command find -L \\$dir -type f 2> /dev/null | sed '1d; s#^\\./##'\"\n```\n\n### fzf Theme Playground\n\n[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by\n[Vitor Mello](https://github.com/vitormv) is a webpage where you can\ninteractively create fzf themes.\n\nRelated projects\n----------------\n\nhttps://github.com/junegunn/fzf/wiki/Related-projects\n\n[License](LICENSE)\n------------------\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n\nGoods\n-----\n\nGrab fzf T-shirts, mugs, and stickers here: https://commitgoods.com/collections/fzf\n\nSponsors :heart:\n----------------\n\nI would like to thank all the sponsors of this project who make it possible for me to continue to improve fzf.\n\nIf you'd like to sponsor this project, please visit https://github.com/sponsors/junegunn.\n\n<!-- sponsors --><a href=\"https://github.com/miyanokomiya\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;miyanokomiya.png\" width=\"60px\" alt=\"User avatar: miyanokomiya\" /></a><a href=\"https://github.com/AceofSpades5757\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;AceofSpades5757.png\" width=\"60px\" alt=\"User avatar: Kyle L. Davis\" /></a><a href=\"https://github.com/Frederick888\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;Frederick888.png\" width=\"60px\" alt=\"User avatar: Frederick Zhang\" /></a><a href=\"https://github.com/moritzdietz\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;moritzdietz.png\" width=\"60px\" alt=\"User avatar: Moritz Dietz\" /></a><a href=\"https://github.com/pldubouilh\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;pldubouilh.png\" width=\"60px\" alt=\"User avatar: Pierre Dubouilh\" /></a><a href=\"https://github.com/trantor\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;trantor.png\" width=\"60px\" alt=\"User avatar: Fulvio Scapin\" /></a><a href=\"https://github.com/rcorre\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;rcorre.png\" width=\"60px\" alt=\"User avatar: Ryan Roden-Corrent\" /></a><a href=\"https://github.com/blissdev\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;blissdev.png\" width=\"60px\" alt=\"User avatar: Jordan Arentsen\" /></a><a href=\"https://github.com/aexvir\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;aexvir.png\" width=\"60px\" alt=\"User avatar: Alex Viscreanu\" /></a><a href=\"https://github.com/moobar\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;moobar.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/benelan\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;benelan.png\" width=\"60px\" alt=\"User avatar: Ben Elan\" /></a><a href=\"https://github.com/pawelduda\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;pawelduda.png\" width=\"60px\" alt=\"User avatar: Paweł Kolonko-Duda\" /></a><a href=\"https://github.com/mckellygit\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;mckellygit.png\" width=\"60px\" alt=\"User avatar: M Kelly\" /></a><a href=\"https://github.com/ArtBIT\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;ArtBIT.png\" width=\"60px\" alt=\"User avatar: ArtBIT\" /></a><a href=\"https://github.com/hovissimo\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;hovissimo.png\" width=\"60px\" alt=\"User avatar: Hovis\" /></a><a href=\"https://github.com/eliangcs\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;eliangcs.png\" width=\"60px\" alt=\"User avatar: Chang-Hung Liang\" /></a><a href=\"https://github.com/asphaltbuffet\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;asphaltbuffet.png\" width=\"60px\" alt=\"User avatar: Ben Lechlitner\" /></a><a href=\"https://github.com/kg8m\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;kg8m.png\" width=\"60px\" alt=\"User avatar: Takumi KAGIYAMA\" /></a><a href=\"https://github.com/polm\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;polm.png\" width=\"60px\" alt=\"User avatar: Paul OLeary McCann\" /></a><a href=\"https://github.com/rbeeger\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;rbeeger.png\" width=\"60px\" alt=\"User avatar: Robert Beeger\" /></a><a href=\"https://github.com/scalisi\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;scalisi.png\" width=\"60px\" alt=\"User avatar: Josh Scalisi\" /></a><a href=\"https://github.com/alecbcs\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;alecbcs.png\" width=\"60px\" alt=\"User avatar: Alec Scott\" /></a><a href=\"https://github.com/artursapek\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;artursapek.png\" width=\"60px\" alt=\"User avatar: Artur Sapek\" /></a><a href=\"https://github.com/ramnes\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;ramnes.png\" width=\"60px\" alt=\"User avatar: Guillaume Gelin\" /></a><a href=\"https://github.com/jyc\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;jyc.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/roblevy\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;roblevy.png\" width=\"60px\" alt=\"User avatar: Rob Levy\" /></a><a href=\"https://github.com/toupeira\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;toupeira.png\" width=\"60px\" alt=\"User avatar: Markus Koller\" /></a><a href=\"https://github.com/rkpatel33\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;rkpatel33.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/jamesob\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;jamesob.png\" width=\"60px\" alt=\"User avatar: jamesob\" /></a><a href=\"https://github.com/jlebray\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;jlebray.png\" width=\"60px\" alt=\"User avatar: Johan Le Bray\" /></a><a href=\"https://github.com/panosl1\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;panosl1.png\" width=\"60px\" alt=\"User avatar: Panos Lampropoulos\" /></a><a href=\"https://github.com/bespinian\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;bespinian.png\" width=\"60px\" alt=\"User avatar: bespinian\" /></a><a href=\"https://github.com/scosu\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;scosu.png\" width=\"60px\" alt=\"User avatar: Markus Schneider-Pargmann\" /></a><a href=\"https://github.com/charlieegan3\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;charlieegan3.png\" width=\"60px\" alt=\"User avatar: Charlie Egan\" /></a><a href=\"https://github.com/thobbs\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;thobbs.png\" width=\"60px\" alt=\"User avatar: Tyler Hobbs\" /></a><a href=\"https://github.com/neilparikh\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;neilparikh.png\" width=\"60px\" alt=\"User avatar: Neil Parikh\" /></a><a href=\"https://github.com/BasedScience\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;BasedScience.png\" width=\"60px\" alt=\"User avatar: dockien\" /></a><a href=\"https://github.com/RussellGilmore\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;RussellGilmore.png\" width=\"60px\" alt=\"User avatar: Russell Gilmore\" /></a><a href=\"https://github.com/meribold\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;meribold.png\" width=\"60px\" alt=\"User avatar: Lukas Waymann\" /></a><a href=\"https://github.com/terminaldweller\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;terminaldweller.png\" width=\"60px\" alt=\"User avatar: Farzad Sadeghi\" /></a><a href=\"https://github.com/jaydee-coder\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;jaydee-coder.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/brpaz\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;brpaz.png\" width=\"60px\" alt=\"User avatar: Bruno Paz\" /></a><a href=\"https://github.com/timobenn\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;timobenn.png\" width=\"60px\" alt=\"User avatar: Timothy Bennett\" /></a><a href=\"https://github.com/danhorner\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;danhorner.png\" width=\"60px\" alt=\"User avatar: Daniel Horner\" /></a><a href=\"https://github.com/syeo66\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;syeo66.png\" width=\"60px\" alt=\"User avatar: Red Ochsenbein\" /></a><a href=\"https://github.com/nekhaevskiy\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;nekhaevskiy.png\" width=\"60px\" alt=\"User avatar: Yury\" /></a><a href=\"https://github.com/lajarre\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;lajarre.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/NightsPaladin\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;NightsPaladin.png\" width=\"60px\" alt=\"User avatar: Chris G.\" /></a><a href=\"https://github.com/lzell\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;lzell.png\" width=\"60px\" alt=\"User avatar: Lou Zell\" /></a><a href=\"https://github.com/3ximus\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;3ximus.png\" width=\"60px\" alt=\"User avatar: Fabio\" /></a><a href=\"https://github.com/justinlubin\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;justinlubin.png\" width=\"60px\" alt=\"User avatar: Justin Lubin\" /></a><a href=\"https://github.com/mieubrisse\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;mieubrisse.png\" width=\"60px\" alt=\"User avatar: Kevin Today\" /></a><a href=\"https://github.com/coko7\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;coko7.png\" width=\"60px\" alt=\"User avatar: Coko\" /></a><a href=\"https://github.com/neogeographica\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;neogeographica.png\" width=\"60px\" alt=\"User avatar: Joel B\" /></a><a href=\"https://github.com/fabridamicelli\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;fabridamicelli.png\" width=\"60px\" alt=\"User avatar: Fabrizio Damicelli\" /></a><a href=\"https://github.com/petercool\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;petercool.png\" width=\"60px\" alt=\"User avatar: Sonami\" /></a><a href=\"https://github.com/jksolbakken\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;jksolbakken.png\" width=\"60px\" alt=\"User avatar: Jan-Kåre Solbakken\" /></a><a href=\"https://github.com/Trash-Nothing\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;Trash-Nothing.png\" width=\"60px\" alt=\"User avatar: Trash Nothing\" /></a><a href=\"https://github.com/justrajdeep\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;justrajdeep.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/jpc\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;jpc.png\" width=\"60px\" alt=\"User avatar: Jakub Piotr Cłapa\" /></a><a href=\"https://github.com/DANIII3L\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;DANIII3L.png\" width=\"60px\" alt=\"User avatar: DANIII3L\" /></a><a href=\"https://github.com/HestHub\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;HestHub.png\" width=\"60px\" alt=\"User avatar: Hest\" /></a><a href=\"https://github.com/sideshowbarker\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;sideshowbarker.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/guttermonk\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;guttermonk.png\" width=\"60px\" alt=\"User avatar: guttermonk\" /></a><a href=\"https://github.com/wromaszkan\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;wromaszkan.png\" width=\"60px\" alt=\"User avatar: \" /></a><a href=\"https://github.com/umglurf\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;umglurf.png\" width=\"60px\" alt=\"User avatar: Håvard Moen\" /></a><a href=\"https://github.com/DanielYang59\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;DanielYang59.png\" width=\"60px\" alt=\"User avatar: Haoyu YANG 杨浩宇\" /></a><a href=\"https://github.com/GrantL10\"><img src=\"https:&#x2F;&#x2F;github.com&#x2F;GrantL10.png\" width=\"60px\" alt=\"User avatar: Gang Li\" /></a><!-- sponsors -->\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Reporting\n\nIf you wish to report a security vulnerability privately, we appreciate your diligence. Please follow the guidelines below to submit your report.\n\n## Reporting\n\nTo report a security vulnerability, please provide the following information:\n\n1. **PROJECT**\n   - https://github.com/junegunn/fzf\n\n2. **PUBLIC**\n   - Indicate whether this vulnerability has already been publicly discussed or disclosed.\n   - If so, provide relevant links.\n\n3. **DESCRIPTION**\n   - Provide a detailed description of the security vulnerability.\n   - Include as much information as possible to help us understand and address the issue.\n\nSend this information, along with any additional relevant details, to <junegunn.c AT gmail DOT com>.\n\n## Confidentiality\n\nWe kindly ask you to keep the report confidential until a public announcement is made.\n\n## Notes\n\n- Vulnerabilities will be handled on a best-effort basis.\n- You may request an advance copy of the patched release, but we cannot guarantee early access before the public release.\n- You will be notified via email simultaneously with the public announcement.\n- We will respond within a few weeks to confirm whether your report has been accepted or rejected.\n\nThank you for helping to improve the security of our project!\n"
  },
  {
    "path": "bin/fzf-preview.sh",
    "content": "#!/usr/bin/env bash\n#\n# The purpose of this script is to demonstrate how to preview a file or an\n# image in the preview window of fzf.\n#\n# Dependencies:\n# - https://github.com/sharkdp/bat\n# - https://github.com/hpjansson/chafa\n# - https://iterm2.com/utilities/imgcat\n\nif [[ $# -ne 1 ]]; then\n  >&2 echo \"usage: $0 FILENAME[:LINENO][:IGNORED]\"\n  exit 1\nfi\n\nfile=${1/#\\~\\//$HOME/}\n\ncenter=0\nif [[ ! -r $file ]]; then\n  if [[ $file =~ ^(.+):([0-9]+)\\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then\n    file=${BASH_REMATCH[1]}\n    center=${BASH_REMATCH[2]}\n  elif [[ $file =~ ^(.+):([0-9]+):[0-9]+\\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then\n    file=${BASH_REMATCH[1]}\n    center=${BASH_REMATCH[2]}\n  fi\nfi\n\ntype=$(file --brief --dereference --mime -- \"$file\")\n\nif [[ ! $type =~ image/ ]]; then\n  if [[ $type =~ =binary ]]; then\n    file \"$1\"\n    exit\n  fi\n\n  # Sometimes bat is installed as batcat.\n  if command -v batcat > /dev/null; then\n    batname=\"batcat\"\n  elif command -v bat > /dev/null; then\n    batname=\"bat\"\n  else\n    cat \"$1\"\n    exit\n  fi\n\n  ${batname} --style=\"${BAT_STYLE:-numbers}\" --color=always --pager=never --highlight-line=\"${center:-0}\" -- \"$file\"\n  exit\nfi\n\ndim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}\nif [[ $dim == x ]]; then\n  dim=$(stty size < /dev/tty | awk '{print $2 \"x\" $1}')\nelif ! [[ $KITTY_WINDOW_ID ]] && ((FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}'))); then\n  # Avoid scrolling issue when the Sixel image touches the bottom of the screen\n  # * https://github.com/junegunn/fzf/issues/2544\n  dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))\nfi\n\n# 1. Use icat (from Kitty) if kitten is installed\nif [[ $KITTY_WINDOW_ID ]] || [[ $GHOSTTY_RESOURCES_DIR ]] && command -v kitten > /dev/null; then\n  # 1. 'memory' is the fastest option but if you want the image to be scrollable,\n  #    you have to use 'stream'.\n  #\n  # 2. The last line of the output is the ANSI reset code without newline.\n  #    This confuses fzf and makes it render scroll offset indicator.\n  #    So we remove the last line and append the reset code to its previous line.\n  kitten icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place=\"$dim@0x0\" \"$file\" | sed '$d' | sed $'$s/$/\\e[m/'\n\n# 2. Use chafa with Sixel output\nelif command -v chafa > /dev/null; then\n  chafa -s \"$dim\" \"$file\"\n  # Add a new line character so that fzf can display multiple images in the preview window\n  echo\n\n# 3. If chafa is not found but imgcat is available, use it on iTerm2\nelif command -v imgcat > /dev/null; then\n  # NOTE: We should use https://iterm2.com/utilities/it2check to check if the\n  # user is running iTerm2. But for the sake of simplicity, we just assume\n  # that's the case here.\n  imgcat -W \"${dim%%x*}\" -H \"${dim##*x}\" \"$file\"\n\n# 4. Cannot find any suitable method to preview the image\nelse\n  file \"$file\"\nfi\n"
  },
  {
    "path": "bin/fzf-tmux",
    "content": "#!/usr/bin/env bash\n# fzf-tmux: starts fzf in a tmux pane\n# usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]\n\nfail() {\n  >&2 echo \"$1\"\n  exit 2\n}\n\nfzf=\"$(command which fzf)\" || fzf=\"$(dirname \"$0\")/fzf\"\n[[ -x $fzf ]] || fail 'fzf executable not found'\n\nargs=()\nopt=\"\"\nskip=\"\"\nswap=\"\"\nclose=\"\"\nterm=\"\"\n[[ -n $LINES ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p \"#{pane_height}\")\n[[ -n $COLUMNS ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p \"#{pane_width}\")\n\ntmux_version=$(tmux -V | sed 's/[^0-9.]//g')\ntmux_32=$(awk '{print ($1 >= 3.2)}' <<< \"$tmux_version\" 2> /dev/null || bc -l <<< \"$tmux_version >= 3.2\")\n\nhelp() {\n  >&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]\n\n  LAYOUT OPTIONS:\n    (default layout: -d 50%)\n\n    Popup window (requires tmux 3.2 or above):\n      -p [WIDTH[%][,HEIGHT[%]]]  (default: 50%)\n      -w WIDTH[%]\n      -h HEIGHT[%]\n      -x COL\n      -y ROW\n\n    Split pane:\n      -u [HEIGHT[%]]             Split above (up)\n      -d [HEIGHT[%]]             Split below (down)\n      -l [WIDTH[%]]              Split left\n      -r [WIDTH[%]]              Split right\n'\n  exit\n}\n\nwhile [[ $# -gt 0 ]]; do\n  arg=\"$1\"\n  shift\n  [[ -z $skip ]] && case \"$arg\" in\n    -)\n      term=1\n      ;;\n    --help)\n      help\n      ;;\n    --version)\n      echo \"fzf-tmux (with fzf $(\"$fzf\" --version))\"\n      exit\n      ;;\n    -p* | -w* | -h* | -x* | -y* | -d* | -u* | -r* | -l*)\n      if [[ $arg =~ ^-[pwhxy] ]]; then\n        [[ $opt =~ \"-E\" ]] || opt=\"-E\"\n      elif [[ $arg =~ ^.[lr] ]]; then\n        opt=\"-h\"\n        if [[ $arg =~ ^.l ]]; then\n          opt=\"$opt -d\"\n          swap=\"; swap-pane -D ; select-pane -L\"\n          close=\"; tmux swap-pane -D\"\n        fi\n      else\n        opt=\"\"\n        if [[ $arg =~ ^.u ]]; then\n          opt=\"$opt -d\"\n          swap=\"; swap-pane -D ; select-pane -U\"\n          close=\"; tmux swap-pane -D\"\n        fi\n      fi\n      if [[ ${#arg} -gt 2 ]]; then\n        size=\"${arg:2}\"\n      else\n        if [[ $1 =~ ^[0-9%,]+$ ]] || [[ $1 =~ ^[A-Z]$ ]]; then\n          size=\"$1\"\n          shift\n        else\n          continue\n        fi\n      fi\n\n      if [[ $arg =~ ^-p ]]; then\n        if [[ -n $size ]]; then\n          w=${size%%,*}\n          h=${size##*,}\n          opt=\"$opt -w$w -h$h\"\n        fi\n      elif [[ $arg =~ ^-[whxy] ]]; then\n        opt=\"$opt ${arg:0:2}$size\"\n      elif [[ $size =~ %$ ]]; then\n        size=${size:0:${#size}-1}\n        if [[ $tmux_32 == 1 ]]; then\n          if [[ -n $swap ]]; then\n            opt=\"$opt -l $((100 - size))%\"\n          else\n            opt=\"$opt -l $size%\"\n          fi\n        else\n          if [[ -n $swap ]]; then\n            opt=\"$opt -p $((100 - size))\"\n          else\n            opt=\"$opt -p $size\"\n          fi\n        fi\n      else\n        if [[ -n $swap ]]; then\n          if [[ $arg =~ ^.l ]]; then\n            max=$columns\n          else\n            max=$lines\n          fi\n          size=$((max - size))\n          [[ $size -lt 0 ]] && size=0\n          opt=\"$opt -l $size\"\n        else\n          opt=\"$opt -l $size\"\n        fi\n      fi\n      ;;\n    --)\n      # \"--\" can be used to separate fzf-tmux options from fzf options to\n      # avoid conflicts\n      skip=1\n      continue\n      ;;\n    *)\n      args+=(\"$arg\")\n      ;;\n  esac\n  [[ -n $skip ]] && args+=(\"$arg\")\ndone\n\nif [[ -z $TMUX ]]; then\n  \"$fzf\" \"${args[@]}\"\n  exit $?\nfi\n\n# * --height option is not allowed\n# * CTRL-Z is also disabled\n# * fzf-tmux script is not compatible with --tmux option in fzf 0.53.0 or later\nargs=(\"${args[@]}\" \"--no-height\" \"--bind=ctrl-z:ignore\" \"--no-tmux\")\n\n# Handle zoomed tmux pane without popup options by moving it to a temp window\nif [[ ! $opt =~ \"-E\" ]] && tmux list-panes -F '#F' | grep -q Z; then\n  zoomed_without_popup=1\n  original_window=$(tmux display-message -p \"#{window_id}\")\n  tmp_window=$(tmux new-window -d -P -F \"#{window_id}\" \"bash -c 'while :; do for c in \\\\| / - '\\\\;' do sleep 0.2; printf \\\"\\\\r\\$c fzf-tmux is running\\\\r\\\"; done; done'\")\n  tmux swap-pane -t $tmp_window \\; select-window -t $tmp_window\nfi\n\nset -e\n\n# Clean up named pipes on exit\ntmpdir=$(mktemp -d \"${TMPDIR:-/tmp}/fzf-tmux-XXXXXX\")\nargsf=\"$tmpdir/args\"\nfifo1=\"$tmpdir/fifo1\"\nfifo2=\"$tmpdir/fifo2\"\nfifo3=\"$tmpdir/fifo3\"\nif tmux_win_opts=$(tmux show-options -p remain-on-exit \\; show-options -p synchronize-panes 2> /dev/null); then\n  tmux_win_opts=($(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\\\;/' <<< \"$tmux_win_opts\"))\n  tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'\nelse\n  tmux_win_opts=($(tmux show-window-options remain-on-exit \\; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\\\;/'))\n  tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'\nfi\ncleanup() {\n  \\rm -rf \"$tmpdir\"\n  # Restore tmux window options\n  if [[ ${#tmux_win_opts[@]} -gt 1 ]]; then\n    eval \"tmux ${tmux_win_opts[*]}\"\n  fi\n\n  # Remove temp window if we were zoomed without popup options\n  if [[ -n $zoomed_without_popup ]]; then\n    tmux display-message -p \"#{window_id}\" > /dev/null\n    tmux swap-pane -t $original_window \\; \\\n      select-window -t $original_window \\; \\\n      kill-window -t $tmp_window \\; \\\n      resize-pane -Z\n  fi\n\n  if [[ $# -gt 0 ]]; then\n    trap - EXIT\n    exit 130\n  fi\n}\ntrap 'cleanup 1' SIGUSR1\ntrap 'cleanup' EXIT\n\nenvs=\"export TERM=$(printf %q \"$TERM\") \"\nif [[ $opt =~ \"-E\" ]]; then\n  if [[ $tmux_version == 3.2 ]]; then\n    FZF_DEFAULT_OPTS=\"--margin 0,1 $FZF_DEFAULT_OPTS\"\n  elif [[ $tmux_32 == 1 ]]; then\n    FZF_DEFAULT_OPTS=\"--border $FZF_DEFAULT_OPTS\"\n    opt=\"-B $opt\"\n  else\n    echo \"fzf-tmux: tmux 3.2 or above is required for popup mode\" >&2\n    exit 2\n  fi\nfi\nenvs=\"$envs FZF_DEFAULT_COMMAND=$(printf %q \"$FZF_DEFAULT_COMMAND\")\"\nenvs=\"$envs FZF_DEFAULT_OPTS=$(printf %q \"$FZF_DEFAULT_OPTS\")\"\nenvs=\"$envs FZF_DEFAULT_OPTS_FILE=$(printf %q \"$FZF_DEFAULT_OPTS_FILE\")\"\n[[ -n $RUNEWIDTH_EASTASIAN ]] && envs=\"$envs RUNEWIDTH_EASTASIAN=$(printf %q \"$RUNEWIDTH_EASTASIAN\")\"\n[[ -n $BAT_THEME ]] && envs=\"$envs BAT_THEME=$(printf %q \"$BAT_THEME\")\"\necho \"$envs;\" > \"$argsf\"\n\n# Build arguments to fzf\nopts=$(printf \"%q \" \"${args[@]}\")\n\npppid=$$\necho -n \"trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;\" >> $argsf\nclose=\"; trap - EXIT SIGINT SIGTERM $close\"\n\nexport TMUX=$(cut -d , -f 1,2 <<< \"$TMUX\")\nmkfifo -m o+w $fifo2\nif [[ $opt =~ \"-E\" ]]; then\n  cat $fifo2 &\n  if [[ -n $term ]] || [[ -t 0 ]]; then\n    cat <<< \"\\\"$fzf\\\" $opts > $fifo2; out=\\$? $close; exit \\$out\" >> $argsf\n  else\n    mkfifo $fifo1\n    cat <<< \"\\\"$fzf\\\" $opts < $fifo1 > $fifo2; out=\\$? $close; exit \\$out\" >> $argsf\n    cat <&0 > $fifo1 &\n  fi\n\n  tmux popup -d \"$PWD\" $opt \"bash $argsf\" > /dev/null 2>&1\n  exit $?\nfi\n\nmkfifo -m o+w $fifo3\nif [[ -n $term ]] || [[ -t 0 ]]; then\n  cat <<< \"\\\"$fzf\\\" $opts > $fifo2; echo \\$? > $fifo3 $close\" >> $argsf\nelse\n  mkfifo $fifo1\n  cat <<< \"\\\"$fzf\\\" $opts < $fifo1 > $fifo2; echo \\$? > $fifo3 $close\" >> $argsf\n  cat <&0 > $fifo1 &\nfi\ntmux \\\n  split-window -c \"$PWD\" $opt \"bash -c 'exec -a fzf bash $argsf'\" $swap \\\n  $tmux_off_opts \\\n  > /dev/null 2>&1 || {\n  \"$fzf\" \"${args[@]}\"\n  exit $?\n}\ncat $fifo2\nexit \"$(cat $fifo3)\"\n"
  },
  {
    "path": "doc/fzf.txt",
    "content": "fzf.txt\tfzf\tLast change: February 15 2024\nFZF - TABLE OF CONTENTS                                            *fzf* *fzf-toc*\n==============================================================================\n\n  FZF Vim integration                       |fzf-vim-integration|\n    Installation                            |fzf-installation|\n    Summary                                 |fzf-summary|\n    :FZF[!]                                 |:FZF|\n      Configuration                         |fzf-configuration|\n        Examples                            |fzf-examples|\n          Explanation of g:fzf_colors       |fzf-explanation-of-gfzfcolors|\n    fzf#run                                 |fzf#run|\n    fzf#wrap                                |fzf#wrap|\n      Global options supported by fzf#wrap  |fzf-global-options-supported-by-fzf#wrap|\n    Tips                                    |fzf-tips|\n      fzf inside terminal buffer            |fzf-inside-terminal-buffer|\n      Starting fzf in a popup window        |fzf-starting-fzf-in-a-popup-window|\n      Hide statusline                       |fzf-hide-statusline|\n    License                                 |fzf-license|\n\nFZF VIM INTEGRATION                                        *fzf-vim-integration*\n==============================================================================\n\n\nINSTALLATION                                                  *fzf-installation*\n==============================================================================\n\nOnce you have fzf installed, you can enable it inside Vim simply by adding the\ndirectory to 'runtimepath' in your Vim configuration file. The path may differ\ndepending on the package manager.\n>\n    \" If installed using Homebrew\n    set rtp+=/usr/local/opt/fzf\n\n    \" If installed using Homebrew on Apple Silicon\n    set rtp+=/opt/homebrew/opt/fzf\n\n    \" If you have cloned fzf on ~/.fzf directory\n    set rtp+=~/.fzf\n<\nIf you use {vim-plug}{1}, the same can be written as:\n>\n    \" If installed using Homebrew\n    Plug '/usr/local/opt/fzf'\n\n    \" If installed using Homebrew on Apple Silicon\n    Plug '/opt/homebrew/opt/fzf'\n\n    \" If you have cloned fzf on ~/.fzf directory\n    Plug '~/.fzf'\n<\nBut if you want the latest Vim plugin file from GitHub rather than the one\nincluded in the package, write:\n>\n    Plug 'junegunn/fzf'\n<\nThe Vim plugin will pick up fzf binary available on the system. If fzf is not\nfound on `$PATH`, it will ask you if it should download the latest binary for\nyou.\n\nTo make sure that you have the latest version of the binary, set up\npost-update hook like so:\n\n                                                                   *fzf#install*\n>\n    Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }\n<\n                                      {1} https://github.com/junegunn/vim-plug\n\n\nSUMMARY                                                            *fzf-summary*\n==============================================================================\n\nThe Vim plugin of fzf provides two core functions, and `:FZF` command which is\nthe basic file selector command built on top of them.\n\n 1. `fzf#run([spec dict])`\n   - Starts fzf inside Vim with the given spec\n   - `:call fzf#run({'source': 'ls'})`\n 2. `fzf#wrap([spec dict]) -> (dict)`\n   - Takes a spec for `fzf#run` and returns an extended version of it with\n     additional options for addressing global preferences (`g:fzf_xxx`)\n     - `:echo fzf#wrap({'source': 'ls'})`\n   - We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`\n     - `:call fzf#run(fzf#wrap({'source': 'ls'}))`\n 3. `:FZF [fzf_options string] [path string]`\n   - Basic fuzzy file selector\n   - A reference implementation for those who don't want to write VimScript to\n     implement custom commands\n   - If you're looking for more such commands, check out {fzf.vim}{2} project.\n\nThe most important of all is `fzf#run`, but it would be easier to understand\nthe whole if we start off with `:FZF` command.\n\n                                       {2} https://github.com/junegunn/fzf.vim\n\n\n:FZF[!]\n==============================================================================\n\n                                                                          *:FZF*\n>\n    \" Look for files under current directory\n    :FZF\n\n    \" Look for files under your home directory\n    :FZF ~\n\n    \" With fzf command-line options\n    :FZF --reverse --info=inline /tmp\n\n    \" Bang version starts fzf in fullscreen mode\n    :FZF!\n<\nSimilarly to {ctrlp.vim}{3}, use enter key, CTRL-T, CTRL-X or CTRL-V to open\nselected files in the current window, in new tabs, in horizontal splits, or in\nvertical splits respectively.\n\nNote that the environment variables `FZF_DEFAULT_COMMAND` and\n`FZF_DEFAULT_OPTS` also apply here.\n\n                                         {3} https://github.com/kien/ctrlp.vim\n\n\n< Configuration >_____________________________________________________________~\n                                                             *fzf-configuration*\n\n                      *g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir*\n\n - `g:fzf_action`\n   - Customizable extra key bindings for opening selected files in different\n     ways\n - `g:fzf_layout`\n   - Determines the size and position of fzf window\n - `g:fzf_colors`\n   - Customizes fzf colors to match the current color scheme\n - `g:fzf_history_dir`\n   - Enables history feature\n\n\nExamples~\n                                                                  *fzf-examples*\n>\n    \" This is the default extra key bindings\n    let g:fzf_action = {\n      \\ 'ctrl-t': 'tab split',\n      \\ 'ctrl-x': 'split',\n      \\ 'ctrl-v': 'vsplit' }\n\n    \" An action can be a reference to a function that processes selected lines\n    function! s:build_quickfix_list(lines)\n      call setqflist(map(copy(a:lines), '{ \"filename\": v:val, \"lnum\": 1 }'))\n      copen\n      cc\n    endfunction\n\n    let g:fzf_action = {\n      \\ 'ctrl-q': function('s:build_quickfix_list'),\n      \\ 'ctrl-t': 'tab split',\n      \\ 'ctrl-x': 'split',\n      \\ 'ctrl-v': 'vsplit' }\n\n    \" Default fzf layout\n    \" - Popup window (center of the screen)\n    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n\n    \" - Popup window (center of the current window)\n    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }\n\n    \" - Popup window (anchored to the bottom of the current window)\n    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }\n\n    \" - down / up / left / right\n    let g:fzf_layout = { 'down': '40%' }\n\n    \" - Window using a Vim command\n    let g:fzf_layout = { 'window': 'enew' }\n    let g:fzf_layout = { 'window': '-tabnew' }\n    let g:fzf_layout = { 'window': '10new' }\n\n    \" Customize fzf colors to match your color scheme\n    \" - fzf#wrap translates this to a set of `--color` options\n    let g:fzf_colors =\n    \\ { 'fg':      ['fg', 'Normal'],\n      \\ 'bg':      ['bg', 'Normal'],\n      \\ 'hl':      ['fg', 'Comment'],\n      \\ 'fg+':     ['fg', 'CursorLine', 'CursorColumn', 'Normal'],\n      \\ 'bg+':     ['bg', 'CursorLine', 'CursorColumn'],\n      \\ 'hl+':     ['fg', 'Statement'],\n      \\ 'info':    ['fg', 'PreProc'],\n      \\ 'border':  ['fg', 'Ignore'],\n      \\ 'prompt':  ['fg', 'Conditional'],\n      \\ 'pointer': ['fg', 'Exception'],\n      \\ 'marker':  ['fg', 'Keyword'],\n      \\ 'spinner': ['fg', 'Label'],\n      \\ 'header':  ['fg', 'Comment'] }\n\n    \" Enable per-command history\n    \" - History files will be stored in the specified directory\n    \" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and\n    \"   'previous-history' instead of 'down' and 'up'.\n    let g:fzf_history_dir = '~/.local/share/fzf-history'\n<\n\nExplanation of g:fzf_colors~\n                                                 *fzf-explanation-of-gfzfcolors*\n\n`g:fzf_colors` is a dictionary mapping fzf elements to a color specification\nlist:\n>\n    element: [ component, group1 [, group2, ...] ]\n<\n - `element` is an fzf element to apply a color to:\n\n ----------------------------+------------------------------------------------------\n Element                     | Description                                          ~\n ----------------------------+------------------------------------------------------\n  `fg`   /  `bg`   /  `hl`         | Item (foreground / background / highlight)\n  `fg+`  /  `bg+`  /  `hl+`        | Current item (foreground / background / highlight)\n  `preview-fg`  /  `preview-bg`  | Preview window text and background\n  `hl`   /  `hl+`                | Highlighted substrings (normal / current)\n  `gutter`                     | Background of the gutter on the left\n  `pointer`                    | Pointer to the current line ( `>` )\n  `marker`                     | Multi-select marker ( `>` )\n  `border`                     | Border around the window ( `--border`  and  `--preview` )\n  `header`                     | Header ( `--header`  or  `--header-lines` )\n  `info`                       | Info line (match counters)\n  `spinner`                    | Streaming input indicator\n  `query`                      | Query string\n  `disabled`                   | Query string when search is disabled\n  `prompt`                     | Prompt before query ( `> ` )\n  `pointer`                    | Pointer to the current line ( `>` )\n ----------------------------+------------------------------------------------------\n - `component` specifies the component (`fg` / `bg`) from which to extract the\n   color when considering each of the following highlight groups\n - `group1 [, group2, ...]` is a list of highlight groups that are searched (in\n   order) for a matching color definition\n\nFor example, consider the following specification:\n>\n      'prompt':  ['fg', 'Conditional', 'Comment'],\n<\nThis means we color the prompt - using the `fg` attribute of the `Conditional`\nif it exists, - otherwise use the `fg` attribute of the `Comment` highlight\ngroup if it exists, - otherwise fall back to the default color settings for\nthe prompt.\n\nYou can examine the color option generated according the setting by printing\nthe result of `fzf#wrap()` function like so:\n>\n    :echo fzf#wrap()\n<\n\nFZF#RUN\n==============================================================================\n\n                                                                       *fzf#run*\n\n`fzf#run()` function is the core of Vim integration. It takes a single\ndictionary argument, a spec, and starts fzf process accordingly. At the very\nleast, specify `sink` option to tell what it should do with the selected\nentry.\n>\n    call fzf#run({'sink': 'e'})\n<\nWe haven't specified the `source`, so this is equivalent to starting fzf on\ncommand line without standard input pipe; fzf will traverse the file system\nunder the current directory to get the list of files. (If\n`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command\ninstead.) When you select one, it will open it with the sink, `:e` command. If\nyou want to open it in a new tab, you can pass `:tabedit` command instead as\nthe sink.\n>\n    call fzf#run({'sink': 'tabedit'})\n<\nYou can use any shell command as the source to generate the list. The\nfollowing example will list the files managed by git. It's equivalent to\nrunning `git ls-files | fzf` on shell.\n>\n    call fzf#run({'source': 'git ls-files', 'sink': 'e'})\n<\nfzf options can be specified as `options` entry in spec dictionary.\n>\n    call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})\n<\nYou can also pass a layout option if you don't want fzf window to take up the\nentire screen.\n>\n    \" up / down / left / right / window are allowed\n    call fzf#run({'source': 'git ls-files', 'sink': 'e', 'left': '40%'})\n    call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})\n<\n`source` doesn't have to be an external shell command, you can pass a Vim\narray as the source. In the next example, we pass the names of color schemes\nas the source to implement a color scheme selector.\n>\n    call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),\n                \\               'fnamemodify(v:val, \":t:r\")'),\n                \\ 'sink': 'colo', 'left': '25%'})\n<\nThe following table summarizes the available options.\n\n ---------------------------+---------------+----------------------------------------------------------------------\n Option name                | Type          | Description                                                          ~\n ---------------------------+---------------+----------------------------------------------------------------------\n  `source`                    | string        | External command to generate input to fzf (e.g.  `find .` )\n  `source`                    | list          | Vim list as input to fzf\n  `sink`                      | string        | Vim command to handle the selected item (e.g.  `e` ,  `tabe` )\n  `sink`                      | funcref       | Function to be called with each selected item\n  `sinklist`  (or  `sink*` )    | funcref       | Similar to  `sink` , but takes the list of output lines at once\n  `exit`                      | funcref       | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130)\n  `options`                   | string/list   | Options to fzf\n  `dir`                       | string        | Working directory\n  `up` / `down` / `left` / `right`  | number/string | (Layout) Window position and size (e.g.  `20` ,  `50%` )\n  `tmux`                      | string        | (Layout) `--tmux` options (e.g.  `90%,70%` )\n  `window`  (Vim 8 / Neovim)  | string        | (Layout) Command to open fzf window (e.g.  `vertical aboveleft 30new` )\n  `window`  (Vim 8 / Neovim)  | dict          | (Layout) Popup window settings (e.g.  `{'width': 0.9, 'height': 0.6}` )\n ---------------------------+---------------+----------------------------------------------------------------------\n\n`options` entry can be either a string or a list. For simple cases, string\nshould suffice, but prefer to use list type to avoid escaping issues.\n>\n    call fzf#run({'options': '--reverse --prompt \"C:\\\\Program Files\\\\\"'})\n    call fzf#run({'options': ['--reverse', '--prompt', 'C:\\Program Files\\']})\n<\nWhen `window` entry is a dictionary, fzf will start in a popup window. The\nfollowing options are allowed:\n\n - Required:\n   - `width` [float range [0 ~ 1]] or [integer range [8 ~ ]]\n   - `height` [float range [0 ~ 1]] or [integer range [4 ~ ]]\n - Optional:\n   - `yoffset` [float default 0.5 range [0 ~ 1]]\n   - `xoffset` [float default 0.5 range [0 ~ 1]]\n   - `relative` [boolean default v:false]\n   - `border` [string default `rounded` (`sharp` on Windows)]: Border style\n     - `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`\n\n\nFZF#WRAP\n==============================================================================\n\n                                                                      *fzf#wrap*\n\nWe have seen that several aspects of `:FZF` command can be configured with a\nset of global option variables; different ways to open files (`g:fzf_action`),\nwindow position and size (`g:fzf_layout`), color palette (`g:fzf_colors`),\netc.\n\nSo how can we make our custom `fzf#run` calls also respect those variables?\nSimply by \"wrapping\" the spec dictionary with `fzf#wrap` before passing it to\n`fzf#run`.\n\n - `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`\n   - All arguments are optional. Usually we only need to pass a spec\n     dictionary.\n   - `name` is for managing history files. It is ignored if `g:fzf_history_dir`\n     is not defined.\n   - `fullscreen` can be either `0` or `1` (default: 0).\n\n`fzf#wrap` takes a spec and returns an extended version of it (also a\ndictionary) with additional options for addressing global preferences. You can\nexamine the return value of it like so:\n>\n    echo fzf#wrap({'source': 'ls'})\n<\nAfter we \"wrap\" our spec, we pass it to `fzf#run`.\n>\n    call fzf#run(fzf#wrap({'source': 'ls'}))\n<\nNow it supports CTRL-T, CTRL-V, and CTRL-X key bindings (configurable via\n`g:fzf_action`) and it opens fzf window according to `g:fzf_layout` setting.\n\nTo make it easier to use, let's define `LS` command.\n>\n    command! LS call fzf#run(fzf#wrap({'source': 'ls'}))\n<\nType `:LS` and see how it works.\n\nWe would like to make `:LS!` (bang version) open fzf in fullscreen, just like\n`:FZF!`. Add `-bang` to command definition, and use <bang> value to set the\nlast `fullscreen` argument of `fzf#wrap` (see :help <bang>).\n>\n    \" On :LS!, <bang> evaluates to '!', and '!0' becomes 1\n    command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))\n<\nOur `:LS` command will be much more useful if we can pass a directory argument\nto it, so that something like `:LS /tmp` is possible.\n>\n    command! -bang -complete=dir -nargs=? LS\n        \\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))\n<\nLastly, if you have enabled `g:fzf_history_dir`, you might want to assign a\nunique name to our command and pass it as the first argument to `fzf#wrap`.\n>\n    \" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.\n    \" The name is ignored if g:fzf_history_dir is not defined.\n    command! -bang -complete=dir -nargs=? LS\n        \\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))\n<\n\n< Global options supported by fzf#wrap >______________________________________~\n                                      *fzf-global-options-supported-by-fzf#wrap*\n\n - `g:fzf_layout`\n - `g:fzf_action`\n   - Works only when no custom `sink` (or `sinklist`) is provided\n     - Having custom sink usually means that each entry is not an ordinary\n       file path (e.g. name of color scheme), so we can't blindly apply the\n       same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)\n - `g:fzf_colors`\n - `g:fzf_history_dir`\n\n\nTIPS                                                                  *fzf-tips*\n==============================================================================\n\n\n< fzf inside terminal buffer >________________________________________________~\n                                                    *fzf-inside-terminal-buffer*\n\n\nOn the latest versions of Vim and Neovim, fzf will start in a terminal buffer.\nIf you find the default ANSI colors to be different, consider configuring the\ncolors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`\nin Neovim.\n\n>\n    \" Terminal colors for seoul256 color scheme\n    if has('nvim')\n      let g:terminal_color_0 = '#4e4e4e'\n      let g:terminal_color_1 = '#d68787'\n      let g:terminal_color_2 = '#5f865f'\n      let g:terminal_color_3 = '#d8af5f'\n      let g:terminal_color_4 = '#85add4'\n      let g:terminal_color_5 = '#d7afaf'\n      let g:terminal_color_6 = '#87afaf'\n      let g:terminal_color_7 = '#d0d0d0'\n      let g:terminal_color_8 = '#626262'\n      let g:terminal_color_9 = '#d75f87'\n      let g:terminal_color_10 = '#87af87'\n      let g:terminal_color_11 = '#ffd787'\n      let g:terminal_color_12 = '#add4fb'\n      let g:terminal_color_13 = '#ffafaf'\n      let g:terminal_color_14 = '#87d7d7'\n      let g:terminal_color_15 = '#e4e4e4'\n    else\n      let g:terminal_ansi_colors = [\n        \\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',\n        \\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',\n        \\ '#626262', '#d75f87', '#87af87', '#ffd787',\n        \\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'\n      \\ ]\n    endif\n<\n\n< Starting fzf in a popup window >____________________________________________~\n                                            *fzf-starting-fzf-in-a-popup-window*\n>\n    \" Required:\n    \" - width [float range [0 ~ 1]] or [integer range [8 ~ ]]\n    \" - height [float range [0 ~ 1]] or [integer range [4 ~ ]]\n    \"\n    \" Optional:\n    \" - xoffset [float default 0.5 range [0 ~ 1]]\n    \" - yoffset [float default 0.5 range [0 ~ 1]]\n    \" - relative [boolean default v:false]\n    \" - border [string default 'rounded']: Border style\n    \"   - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'\n    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n<\nAlternatively, you can make fzf open in a tmux popup window (requires tmux 3.2\nor above) by putting `--tmux` options in `tmux` key.\n>\n    \" See `--tmux` option in `man fzf` for available options\n    \" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]\n    if exists('$TMUX')\n      let g:fzf_layout = { 'tmux': '90%,70%' }\n    else\n      let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }\n    endif\n<\n\n< Hide statusline >___________________________________________________________~\n                                                           *fzf-hide-statusline*\n\nWhen fzf starts in a terminal buffer, the file type of the buffer is set to\n`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of\nthe window.\n\nFor example, if you open fzf on the bottom on the screen (e.g. `{'down':\n'40%'}`), you might want to temporarily disable the statusline for a cleaner\nlook.\n>\n    let g:fzf_layout = { 'down': '30%' }\n    autocmd! FileType fzf\n    autocmd  FileType fzf set laststatus=0 noshowmode noruler\n      \\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler\n<\n\nLICENSE                                                            *fzf-license*\n==============================================================================\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n\n==============================================================================\nvim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/junegunn/fzf\n\nrequire (\n\tgithub.com/charlievieth/fastwalk v1.0.14\n\tgithub.com/gdamore/tcell/v2 v2.9.0\n\tgithub.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/rivo/uniseg v0.4.7\n\tgolang.org/x/sys v0.35.0\n\tgolang.org/x/term v0.34.0\n)\n\nrequire (\n\tgithub.com/gdamore/encoding v1.0.1 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgolang.org/x/text v0.28.0 // indirect\n)\n\ngo 1.23.0\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/charlievieth/fastwalk v1.0.14 h1:3Eh5uaFGwHZd8EGwTjJnSpBkfwfsak9h6ICgnWlhAyg=\ngithub.com/charlievieth/fastwalk v1.0.14/go.mod h1:diVcUreiU1aQ4/Wu3NbxxH4/KYdKpLDojrQ1Bb2KgNY=\ngithub.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=\ngithub.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=\ngithub.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=\ngithub.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo=\ngithub.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 h1:7dYDtfMDfKzjT+DVfIS4iqknSEKtZpEcXtu6vuaasHs=\ngithub.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=\ngolang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=\ngolang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\n"
  },
  {
    "path": "install",
    "content": "#!/usr/bin/env bash\n\nset -u\n\nversion=0.70.0\nauto_completion=\nkey_bindings=\nupdate_config=2\nshells=\"bash zsh fish\"\nprefix='~/.fzf'\nprefix_expand=~/.fzf\nfish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish\n\nhelp() {\n  cat << EOF\nusage: $0 [OPTIONS]\n\n    --help               Show this message\n    --bin                Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}\n    --all                Download fzf binary and update configuration files\n                         to enable key bindings and fuzzy completion\n    --xdg                Generate files under \\$XDG_CONFIG_HOME/fzf\n    --[no-]key-bindings  Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)\n    --[no-]completion    Enable/disable fuzzy completion (bash & zsh)\n    --[no-]update-rc     Whether or not to update shell configuration files\n\n    --no-bash            Do not set up bash configuration\n    --no-zsh             Do not set up zsh configuration\n    --no-fish            Do not set up fish configuration\nEOF\n}\n\nfor opt in \"$@\"; do\n  case $opt in\n    --help)\n      help\n      exit 0\n      ;;\n    --all)\n      auto_completion=1\n      key_bindings=1\n      update_config=1\n      ;;\n    --xdg)\n      prefix='\"${XDG_CONFIG_HOME:-$HOME/.config}\"/fzf/fzf'\n      prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf\n      mkdir -p \"${XDG_CONFIG_HOME:-$HOME/.config}/fzf\"\n      ;;\n    --key-bindings) key_bindings=1 ;;\n    --no-key-bindings) key_bindings=0 ;;\n    --completion) auto_completion=1 ;;\n    --no-completion) auto_completion=0 ;;\n    --update-rc) update_config=1 ;;\n    --no-update-rc) update_config=0 ;;\n    --bin) ;;\n    --no-bash) shells=${shells/bash/} ;;\n    --no-zsh) shells=${shells/zsh/} ;;\n    --no-fish) shells=${shells/fish/} ;;\n    *)\n      echo \"unknown option: $opt\"\n      help\n      exit 1\n      ;;\n  esac\ndone\n\ncd \"$(dirname \"${BASH_SOURCE[0]}\")\"\nfzf_base=$(pwd)\nfzf_base_esc=$(printf %q \"$fzf_base\")\n\nask() {\n  while true; do\n    read -p \"$1 ([y]/n) \" -r\n    REPLY=${REPLY:-\"y\"}\n    if [[ $REPLY =~ ^[Yy]$ ]]; then\n      return 1\n    elif [[ $REPLY =~ ^[Nn]$ ]]; then\n      return 0\n    fi\n  done\n}\n\ncheck_binary() {\n  echo -n \"  - Checking fzf executable ... \"\n  local output\n  output=$(FZF_DEFAULT_OPTS= \"$fzf_base\"/bin/fzf --version 2>&1)\n  if [ $? -ne 0 ]; then\n    echo \"Error: $output\"\n    binary_error=\"Invalid binary\"\n  else\n    output=${output/ */}\n    if [ \"$version\" != \"$output\" ]; then\n      echo \"$output != $version\"\n      binary_error=\"Invalid version\"\n    else\n      echo \"$output\"\n      binary_error=\"\"\n      return 0\n    fi\n  fi\n  rm -f \"$fzf_base\"/bin/fzf\n  return 1\n}\n\nlink_fzf_in_path() {\n  if which_fzf=\"$(command -v fzf)\"; then\n    echo '  - Found in $PATH'\n    echo \"  - Creating symlink: bin/fzf -> $which_fzf\"\n    (cd \"$fzf_base\"/bin && rm -f fzf && ln -sf \"$which_fzf\" fzf)\n    check_binary && return\n  fi\n  return 1\n}\n\ntry_curl() {\n  command -v curl > /dev/null &&\n    if [[ $1 =~ tar.gz$ ]]; then\n      curl -fL $1 | tar --no-same-owner -xzf -\n    else\n      local temp=${TMPDIR:-/tmp}/fzf.zip\n      curl -fLo \"$temp\" $1 && unzip -o \"$temp\" && rm -f \"$temp\"\n    fi\n}\n\ntry_wget() {\n  command -v wget > /dev/null &&\n    if [[ $1 =~ tar.gz$ ]]; then\n      wget -O - $1 | tar --no-same-owner -xzf -\n    else\n      local temp=${TMPDIR:-/tmp}/fzf.zip\n      wget -O \"$temp\" $1 && unzip -o \"$temp\" && rm -f \"$temp\"\n    fi\n}\n\ndownload() {\n  echo \"Downloading bin/fzf ...\"\n  if [ -x \"$fzf_base\"/bin/fzf ]; then\n    echo \"  - Already exists\"\n    check_binary && return\n  fi\n  link_fzf_in_path && return\n  mkdir -p \"$fzf_base\"/bin && cd \"$fzf_base\"/bin\n  if [ $? -ne 0 ]; then\n    binary_error=\"Failed to create bin directory\"\n    return\n  fi\n\n  local url\n  url=https://github.com/junegunn/fzf/releases/download/v$version/${1}\n  set -o pipefail\n  if ! (try_curl $url || try_wget $url); then\n    set +o pipefail\n    binary_error=\"Failed to download with curl and wget\"\n    return\n  fi\n  set +o pipefail\n\n  if [ ! -f fzf ]; then\n    binary_error=\"Failed to download ${1}\"\n    return\n  fi\n\n  chmod +x fzf && check_binary\n}\n\n# Try to download binary executable\narchi=$(uname -smo 2> /dev/null || uname -sm)\nbinary_available=1\nbinary_error=\"\"\ncase \"$archi\" in\n  Darwin\\ arm64*) download fzf-$version-darwin_arm64.tar.gz ;;\n  Darwin\\ x86_64*) download fzf-$version-darwin_amd64.tar.gz ;;\n  Linux\\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;\n  Linux\\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;\n  Linux\\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;\n  Linux\\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;\n  Linux\\ aarch64\\ Android) download fzf-$version-android_arm64.tar.gz ;;\n  Linux\\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;\n  Linux\\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;\n  Linux\\ riscv64*) download fzf-$version-linux_riscv64.tar.gz ;;\n  Linux\\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;\n  Linux\\ *64*) download fzf-$version-linux_amd64.tar.gz ;;\n  Linux\\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;\n  FreeBSD\\ *64*) download fzf-$version-freebsd_amd64.tar.gz ;;\n  OpenBSD\\ *64*) download fzf-$version-openbsd_amd64.tar.gz ;;\n  CYGWIN*\\ *64*) download fzf-$version-windows_amd64.zip ;;\n  MINGW*\\ *64*) download fzf-$version-windows_amd64.zip ;;\n  MSYS*\\ *64*) download fzf-$version-windows_amd64.zip ;;\n  Windows*\\ *64*) download fzf-$version-windows_amd64.zip ;;\n  *) binary_available=0 binary_error=1 ;;\nesac\n\ncd \"$fzf_base\"\nif [ -n \"$binary_error\" ]; then\n  if [ $binary_available -eq 0 ]; then\n    echo \"No prebuilt binary for $archi ...\"\n  else\n    echo \"  - $binary_error !!!\"\n  fi\n  if command -v go > /dev/null; then\n    echo -n \"Building binary (go install github.com/junegunn/fzf) ... \"\n    if [ -z \"${GOPATH-}\" ]; then\n      export GOPATH=\"${TMPDIR:-/tmp}/fzf-gopath\"\n      mkdir -p \"$GOPATH\"\n    fi\n    if go install -ldflags \"-s -w -X main.version=$version -X main.revision=go-install\" github.com/junegunn/fzf; then\n      echo \"OK\"\n      cp \"$GOPATH/bin/fzf\" \"$fzf_base/bin/\"\n    else\n      echo \"Failed to build binary. Installation failed.\"\n      exit 1\n    fi\n  else\n    echo \"go executable not found. Installation failed.\"\n    exit 1\n  fi\nfi\n\n[[ $* =~ \"--bin\" ]] && exit 0\n\nfor s in $shells; do\n  if ! command -v \"$s\" > /dev/null; then\n    shells=${shells/$s/}\n  fi\ndone\n\nif [[ ${#shells} -lt 3 ]]; then\n  echo \"No shell configuration to be updated.\"\n  exit 0\nfi\n\n# Auto-completion\nif [ -z \"$auto_completion\" ]; then\n  ask \"Do you want to enable fuzzy auto-completion?\"\n  auto_completion=$?\nfi\n\n# Key-bindings\nif [ -z \"$key_bindings\" ]; then\n  ask \"Do you want to enable key bindings?\"\n  key_bindings=$?\nfi\n\necho\nfor shell in $shells; do\n  fzf_completion=\"source \\\"$fzf_base/shell/completion.${shell}\\\"\"\n  fzf_key_bindings=\"source \\\"$fzf_base/shell/key-bindings.${shell}\\\"\"\n  [[ $shell == fish ]] && continue\n  src=${prefix_expand}.${shell}\n  echo -n \"Generate $src ... \"\n\n  if [ $auto_completion -eq 0 ]; then\n    fzf_completion=\"# $fzf_completion\"\n  fi\n\n  if [ $key_bindings -eq 0 ]; then\n    fzf_key_bindings=\"# $fzf_key_bindings\"\n  fi\n\n  cat > \"$src\" << EOF\n# Setup fzf\n# ---------\nif [[ ! \"\\$PATH\" == *$fzf_base_esc/bin* ]]; then\n  PATH=\"\\${PATH:+\\${PATH}:}$fzf_base/bin\"\nfi\n\nEOF\n\n  if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then\n    if [[ $shell == zsh ]]; then\n      echo \"source <(fzf --$shell)\" >> \"$src\"\n    else\n      echo \"eval \\\"\\$(fzf --$shell)\\\"\" >> \"$src\"\n    fi\n  else\n    cat >> \"$src\" << EOF\n# Auto-completion\n# ---------------\n$fzf_completion\n\n# Key bindings\n# ------------\n$fzf_key_bindings\nEOF\n  fi\n  echo \"OK\"\ndone\n\n# fish\nif [[ $shells =~ fish ]]; then\n  echo -n \"Update fish_user_paths ... \"\n  fish << EOF\n  echo \\$fish_user_paths | \\grep \"$fzf_base\"/bin > /dev/null\n  or set --universal fish_user_paths \\$fish_user_paths \"$fzf_base\"/bin\nEOF\n  [ $? -eq 0 ] && echo \"OK\" || echo \"Failed\"\nfi\n\nappend_line() {\n  local update line file pat lines\n  update=\"$1\"\n  line=\"$2\"\n  file=\"$3\"\n  pat=\"${4:-}\"\n  at_lno=\"${5:-}\"\n  lines=\"\"\n\n  echo \"Update $file:\"\n  echo \"  - $line\"\n  if [ -f \"$file\" ]; then\n    if [[ -n $pat ]]; then\n      lines=$(\\grep -nF \"$pat\" \"$file\")\n    else\n      lines=$(\\grep -nF \"${line#\"${line%%[![:space:]]*}\"}\" \"$file\")\n    fi\n  fi\n\n  if [ -n \"$lines\" ]; then\n    echo \"    - Already exists:\"\n    sed 's/^/        Line /' <<< \"$lines\"\n\n    update=0\n    if ! \\grep -qv \"^[0-9]*:[[:space:]]*#\" <<< \"$lines\"; then\n      echo \"    - But they all seem to be commented\"\n      ask \"    - Continue modifying $file?\"\n      update=$?\n    fi\n  fi\n\n  set -e\n  if [ \"$update\" -eq 1 ]; then\n    if [[ -z $at_lno ]]; then\n      [ -f \"$file\" ] && echo >> \"$file\"\n      echo \"$line\" >> \"$file\"\n    else\n      sed -i.~fzf_bak \"${at_lno}a\\\\\"$'\\n'\"$line\" \"$file\" && rm \"$file.~fzf_bak\"\n    fi\n    echo \"    + Added\"\n  else\n    echo \"    ~ Skipped\"\n  fi\n\n  echo\n  set +e\n}\n\ncreate_file() {\n  local file=\"$1\"\n  shift\n  echo \"Create $file:\"\n  for line in \"$@\"; do\n    echo \"    $line\"\n    echo \"$line\" >> \"$file\"\n  done\n  echo\n}\n\nif [ $update_config -eq 2 ]; then\n  echo\n  ask \"Do you want to update your shell configuration files?\"\n  update_config=$?\nfi\necho\nfor shell in $shells; do\n  [[ $shell == fish ]] && continue\n  [ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc\n  append_line $update_config \"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}\" \"$dest\" \"${prefix}.${shell}\"\ndone\n\nif [[ $shells =~ fish ]]; then\n  bind_file=\"${fish_dir}/functions/fish_user_key_bindings.fish\"\n  if [ ! -e \"$bind_file\" ]; then\n    if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then\n      mkdir -p \"${fish_dir}/functions\"\n      if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then\n        create_file \"$bind_file\" \\\n          'function fish_user_key_bindings' \\\n          '  fzf --fish | source' \\\n          'end'\n      elif [[ $key_bindings -eq 1 ]]; then\n        create_file \"$bind_file\" \\\n          'function fish_user_key_bindings' \\\n          \"  $fzf_key_bindings\" \\\n          'end'\n      elif [[ $auto_completion -eq 1 ]]; then\n        create_file \"$bind_file\" \\\n          'function fish_user_key_bindings' \\\n          \"  $fzf_completion\" \\\n          'end'\n      fi\n      lno_func=$(\\grep -nF \"function fish_user_key_bindings\" \"$bind_file\" | sed 's/:.*//' | tr '\\n' ' ')\n    else\n      lno_func=0\n    fi\n  else\n    echo \"Check $bind_file:\"\n    lno_func=$(\\grep -nF \"function fish_user_key_bindings\" \"$bind_file\" | sed 's/:.*//' | tr '\\n' ' ')\n    if [[ -z $lno_func ]]; then\n      echo -e \"function fish_user_key_bindings\\nend\" >> \"$bind_file\"\n      lno_func=$(\\grep -nF \"function fish_user_key_bindings\" \"$bind_file\" | sed 's/:.*//' | tr '\\n' ' ')\n    fi\n    lno_keys=$(\\grep -nF \"fzf_key_bindings\" \"$bind_file\" | sed 's/:.*//' | tr '\\n' ' ')\n    if [[ -n $lno_keys ]]; then\n      echo \"  ** Found 'fzf_key_bindings' in line #$lno_keys\"\n      if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then\n        echo \"  ** You have to replace the line to 'fzf --fish | source'\"\n      elif [[ $key_bindings -eq 1 ]]; then\n        echo \"  ** You have to replace the line to '$fzf_key_bindings'\"\n      else\n        echo \"  ** You have to remove the line\"\n      fi\n      echo\n    else\n      echo \"  - Clear\"\n      echo\n      if [[ $key_bindings -eq 1 && $auto_completion -eq 1 ]]; then\n        sed -i.~fzf_bak \"\\#$fzf_completion#d\" \"$bind_file\" && rm \"$bind_file.~fzf_bak\"\n        sed -i.~fzf_bak \"\\#$fzf_key_bindings#d\" \"$bind_file\" && rm \"$bind_file.~fzf_bak\"\n        append_line $update_config \"  fzf --fish | source\" \"$bind_file\" \"\" \"$lno_func\"\n      else\n        sed -i.~fzf_bak '/fzf --fish \\| source/d' \"$bind_file\" && rm \"$bind_file.~fzf_bak\"\n        if [[ $key_bindings -eq 1 ]]; then\n          sed -i.~fzf_bak \"\\#$fzf_completion#d\" \"$bind_file\" && rm \"$bind_file.~fzf_bak\"\n          append_line $update_config \"  $fzf_key_bindings\" \"$bind_file\" \"\" \"$lno_func\"\n        elif [[ $auto_completion -eq 1 ]]; then\n          sed -i.~fzf_bak \"\\#$fzf_key_bindings#d\" \"$bind_file\" && rm \"$bind_file.~fzf_bak\"\n          append_line $update_config \"  $fzf_completion\" \"$bind_file\" \"\" \"$lno_func\"\n        fi\n      fi\n    fi\n  fi\nfi\n\nif [ $update_config -eq 1 ]; then\n  echo 'Finished. Restart your shell or reload config file.'\n  if [[ $shells =~ bash ]]; then\n    echo -n '   source ~/.bashrc  # bash'\n    [[ $archi =~ Darwin ]] && echo -n '  (.bashrc should be loaded from .bash_profile)'\n    echo\n  fi\n  [[ $shells =~ zsh ]] && echo \"   source ${ZDOTDIR:-~}/.zshrc   # zsh\"\n  [[ $shells =~ fish && $lno_func -ne 0 ]] && echo '   fzf_user_key_bindings  # fish'\n  echo\n  echo 'Use uninstall script to remove fzf.'\n  echo\nfi\necho 'For more information, see: https://github.com/junegunn/fzf'\n"
  },
  {
    "path": "install.ps1",
    "content": "$version=\"0.70.0\"\n\n$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition\n\nfunction check_binary () {\n  Write-Host \"  - Checking fzf executable ... \" -NoNewline\n  $output=cmd /c $fzf_base\\bin\\fzf.exe --version 2>&1\n  if (-not $?) {\n    Write-Host \"Error: $output\"\n    $binary_error=\"Invalid binary\"\n  } else {\n    $output=(-Split $output)[0]\n    if ($version -ne $output) {\n      Write-Host \"$output != $version\"\n      $binary_error=\"Invalid version\"\n    } else {\n      Write-Host \"$output\"\n      $binary_error=\"\"\n      return 1\n    }\n  }\n  Remove-Item \"$fzf_base\\bin\\fzf.exe\"\n  return 0\n}\n\nfunction download {\n  param($file)\n  Write-Host \"Downloading bin/fzf ...\"\n  if (Test-Path \"$fzf_base\\bin\\fzf.exe\") {\n    Write-Host \"  - Already exists\"\n    if (check_binary) {\n      return\n    }\n  }\n  if (-not (Test-Path \"$fzf_base\\bin\")) {\n    md \"$fzf_base\\bin\"\n  }\n  if (-not $?) {\n    $binary_error=\"Failed to create bin directory\"\n    return\n  }\n  cd \"$fzf_base\\bin\"\n  $url=\"https://github.com/junegunn/fzf/releases/download/v$version/$file\"\n  $temp=$env:TMP + \"\\fzf.zip\"\n  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n  if ($PSVersionTable.PSVersion.Major -ge 3) {\n    Invoke-WebRequest -Uri $url -OutFile $temp\n  } else {\n    (New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(\"$temp\"))\n  }\n  if ($?) {\n    (Microsoft.PowerShell.Archive\\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)\n  } else {\n    $binary_error=\"Failed to download with powershell\"\n  }\n  if (-not (Test-Path fzf.exe)) {\n    $binary_error=\"Failed to download $file\"\n    return\n  }\n  echo y | icacls $fzf_base\\bin\\fzf.exe /grant Administrator:F ; check_binary >$null\n}\n\ndownload \"fzf-$version-windows_amd64.zip\"\n\nWrite-Host 'For more information, see: https://github.com/junegunn/fzf'\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\tfzf \"github.com/junegunn/fzf/src\"\n\t\"github.com/junegunn/fzf/src/protector\"\n)\n\nvar version = \"0.70\"\nvar revision = \"devel\"\n\n//go:embed shell/key-bindings.bash\nvar bashKeyBindings []byte\n\n//go:embed shell/completion.bash\nvar bashCompletion []byte\n\n//go:embed shell/key-bindings.zsh\nvar zshKeyBindings []byte\n\n//go:embed shell/completion.zsh\nvar zshCompletion []byte\n\n//go:embed shell/key-bindings.fish\nvar fishKeyBindings []byte\n\n//go:embed shell/completion.fish\nvar fishCompletion []byte\n\n//go:embed man/man1/fzf.1\nvar manPage []byte\n\nfunc printScript(label string, content []byte) {\n\tfmt.Println(\"### \" + label + \" ###\")\n\tfmt.Println(strings.TrimSpace(string(content)))\n\tfmt.Println(\"### end: \" + label + \" ###\")\n}\n\nfunc exit(code int, err error) {\n\tif code == fzf.ExitError && err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t}\n\tos.Exit(code)\n}\n\nfunc main() {\n\tprotector.Protect()\n\n\toptions, err := fzf.ParseOptions(true, os.Args[1:])\n\tif err != nil {\n\t\texit(fzf.ExitError, err)\n\t\treturn\n\t}\n\tif options.Bash {\n\t\tprintScript(\"key-bindings.bash\", bashKeyBindings)\n\t\tprintScript(\"completion.bash\", bashCompletion)\n\t\treturn\n\t}\n\tif options.Zsh {\n\t\tprintScript(\"key-bindings.zsh\", zshKeyBindings)\n\t\tprintScript(\"completion.zsh\", zshCompletion)\n\t\treturn\n\t}\n\tif options.Fish {\n\t\tprintScript(\"key-bindings.fish\", fishKeyBindings)\n\t\tprintScript(\"completion.fish\", fishCompletion)\n\t\treturn\n\t}\n\tif options.Help {\n\t\tfmt.Print(fzf.Usage)\n\t\treturn\n\t}\n\tif options.Version {\n\t\tif len(revision) > 0 {\n\t\t\tfmt.Printf(\"%s (%s)\\n\", version, revision)\n\t\t} else {\n\t\t\tfmt.Println(version)\n\t\t}\n\t\treturn\n\t}\n\tif options.Man {\n\t\tfile := fzf.WriteTemporaryFile([]string{string(manPage)}, \"\\n\")\n\t\tif len(file) == 0 {\n\t\t\tfmt.Print(string(manPage))\n\t\t\treturn\n\t\t}\n\t\tdefer os.Remove(file)\n\t\tcmd := exec.Command(\"man\", file)\n\t\tcmd.Stdin = os.Stdin\n\t\tcmd.Stdout = os.Stdout\n\t\tif err := cmd.Run(); err != nil {\n\t\t\tfmt.Print(string(manPage))\n\t\t}\n\t\treturn\n\t}\n\n\tcode, err := fzf.Run(options)\n\texit(code, err)\n}\n"
  },
  {
    "path": "man/man1/fzf-tmux.1",
    "content": ".ig\nThe MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n..\n.TH fzf\\-tmux 1 \"Mar 2026\" \"fzf 0.70.0\" \"fzf\\-tmux - open fzf in tmux split pane\"\n\n.SH NAME\nfzf\\-tmux - open fzf in tmux split pane\n\n.SH SYNOPSIS\n.B fzf\\-tmux [\\fILAYOUT OPTIONS\\fR] [\\-\\-] [\\fIFZF OPTIONS\\fR]\n\n.SH DESCRIPTION\nfzf\\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in\na tmux popup window. It is designed to work just like fzf except that it does\nnot take up the whole screen. You can safely use fzf\\-tmux instead of fzf in\nyour scripts as the extra options will be silently ignored if you're not on\ntmux.\n\n.SH LAYOUT OPTIONS\n\n(default layout: \\fB\\-d 50%\\fR)\n\n.SS Popup window\n(requires tmux 3.2 or above)\n.TP\n.B \"\\-p [WIDTH[%][,HEIGHT[%]]]\"\n.TP\n.B \"\\-w WIDTH[%]\"\n.TP\n.B \"\\-h WIDTH[%]\"\n.TP\n.B \"\\-x COL\"\n.TP\n.B \"\\-y ROW\"\n\n.SS Split pane\n.TP\n.B \"\\-u [height[%]]\"\nSplit above (up)\n.TP\n.B \"\\-d [height[%]]\"\nSplit below (down)\n.TP\n.B \"\\-l [width[%]]\"\nSplit left\n.TP\n.B \"\\-r [width[%]]\"\nSplit right\n"
  },
  {
    "path": "man/man1/fzf.1",
    "content": ".ig\nThe MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n..\n.TH fzf 1 \"Mar 2026\" \"fzf 0.70.0\" \"fzf - a command-line fuzzy finder\"\n\n.SH NAME\nfzf - a command-line fuzzy finder\n\n.SH SYNOPSIS\nfzf [\\fIoptions\\fR]\n\n.SH DESCRIPTION\nfzf is an interactive filter program for any kind of list.\n\nIt implements a \"fuzzy\" matching algorithm, so you can quickly type in patterns\nwith omitted characters and still get the results you want.\n\n.SH OPTIONS\n.SS NOTE\n.TP\nMost long options have the opposite version with \\fB\\-\\-no\\-\\fR prefix.\n\n.SS SEARCH\n.TP\n.B \"\\-x, \\-\\-extended\"\nExtended-search mode. Enabled by default. You can disable it with \\fB+x\\fR or\n\\fB\\-\\-no\\-extended\\fR.\n.TP\n.B \"\\-e, \\-\\-exact\"\nEnable exact-match\n.TP\n.B \"\\-i, \\-\\-ignore\\-case\"\nCase-insensitive match (default: smart-case match)\n.TP\n.B \"+i, \\-\\-no\\-ignore\\-case\"\nCase-sensitive match\n.TP\n.B \"\\-\\-smart\\-case\"\nSmart-case match (default). In this mode, the search is case-insensitive by\ndefault, but it becomes case-sensitive if the query contains any uppercase\nletters.\n.TP\n.B \"\\-\\-literal\"\nDo not normalize latin script letters for matching.\n.TP\n.BI \"\\-\\-scheme=\" SCHEME\nChoose scoring scheme tailored for different types of input.\n\n.RS\n.B default\n.RS\nGeneric scoring scheme designed to work well with any type of input.\n.RE\n.RE\n\n.RS\n.B path\n.RS\nAdditional bonus point is only given to the characters after path separator.\nYou might want to choose this scheme over \\fBdefault\\fR if you have many files\nwith spaces in their paths. This also sets \\fB\\-\\-tiebreak=pathname,length\\fR,\nto prioritize matches occurring in the tail element of a file path.\n.RE\n.RE\n\n.RS\n.B history\n.RS\nScoring scheme well suited for command history or any input where chronological\nordering is important. No additional bonus points are given so that we give\nmore weight to the chronological ordering. This also sets\n\\fB\\-\\-tiebreak=index\\fR.\n.RE\n.RE\n\n.RS\nfzf chooses \\fBpath\\fR scheme when the input is a TTY device, where fzf would\nstart its built-in walker or run \\fB$FZF_DEFAULT_COMMAND\\fR, and there is no\n\\fBreload\\fR or \\fBtransform\\fR action bound to \\fBstart\\fR event. Otherwise,\nit chooses \\fBdefault\\fR scheme.\n.RE\n\n.TP\n.BI \"\\-\\-algo=\" TYPE\nFuzzy matching algorithm (default: v2)\n\n.br\n.BR v2 \"     Optimal scoring algorithm (quality)\"\n.br\n.BR v1 \"     Faster but not guaranteed to find the optimal result (performance)\"\n.br\n\n.TP\n.BI \"\\-n, \\-\\-nth=\" \"N[,..]\"\nComma-separated list of field index expressions for limiting search scope.\nSee \\fBFIELD INDEX EXPRESSION\\fR for the details. When you use this option with\n\\fB\\-\\-with\\-nth\\fR, the field index expressions are calculated against the\ntransformed lines (unlike in \\fB\\-\\-preview\\fR where fields are extracted from\nthe original lines) because fzf doesn't allow searching against the hidden\nfields.\n.TP\n.BI \"\\-\\-with\\-nth=\" \"N[,..] or TEMPLATE\"\nTransform the presentation of each line using the field index expressions.\nFor advanced transformation, you can provide a template containing field index\nexpressions in curly braces. When you use a template, the trailing delimiter is\nstripped from each expression, giving you more control over the output.\n\\fB{n}\\fR in template evaluates to the zero-based ordinal index of the line.\n\n.RS\ne.g.\n     # Single expression: drop the first field\n     echo foo bar baz | fzf --with-nth 2..\n\n     # Use template to rearrange fields\n     echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'\n.RE\n.RS\n\n\\fBchange\\-with\\-nth\\fR action is only available when \\fB\\-\\-with\\-nth\\fR is set.\nWhen \\fB\\-\\-with\\-nth\\fR is used, fzf retains the original input lines in memory\nso they can be re\\-transformed on the fly (e.g. \\fB\\-\\-with\\-nth ..\\fR to keep\nthe original presentation). This increases memory usage, so only use\n\\fB\\-\\-with\\-nth\\fR when you actually need field transformation.\n.RE\n.TP\n.BI \"\\-\\-accept\\-nth=\" \"N[,..] or TEMPLATE\"\nDefine which fields to print on accept. The last delimiter is stripped from the\noutput. For advanced transformation, you can provide a template containing\nfield index expressions in curly braces. When you use a template, the trailing\ndelimiter is stripped from each expression, giving you more control over the\noutput. \\fB{n}\\fR in template evaluates to the zero-based ordinal index of the\nline.\n\n.RS\ne.g.\n     # Single expression\n     echo foo bar baz | fzf --accept-nth 2\n\n     # Template\n     echo foo bar baz | fzf --accept-nth 'Index: {n}, 1st: {1}, 2nd: {2}, 3rd: {3}'\n.RE\n.TP\n.B \"+s, \\-\\-no\\-sort\"\nDo not sort the result\n.TP\n.BI \"\\-d, \\-\\-delimiter=\" \"STR\"\nField delimiter regex for \\fB\\-\\-nth\\fR, \\fB\\-\\-with\\-nth\\fR, and field index\nexpressions (default: AWK-style)\n.TP\n.B \"\\-\\-tail=NUM\"\nMaximum number of items to keep in memory. This is useful when you want to\nbrowse an endless stream of data (e.g. log stream) with fzf while limiting\nmemory usage.\n\n.RS\ne.g.\n     \\fB# Interactive filtering of a log stream\n     tail \\-f *.log | fzf \\-\\-tail 100000 \\-\\-tac \\-\\-no\\-sort \\-\\-exact\\fR\n.RE\n.TP\n.BI \"\\-\\-disabled\"\nDo not perform search. With this option, fzf becomes a simple selector\ninterface rather than a \"fuzzy finder\". You can later enable the search using\n\\fBenable\\-search\\fR or \\fBtoggle\\-search\\fR action.\n.TP\n.BI \"\\-\\-tiebreak=\" \"CRI[,..]\"\nComma-separated list of sort criteria to apply when the scores are tied.\n.br\n\n.br\n.BR length \"   Prefers line with shorter length\"\n.br\n.BR chunk \"    Prefers line with shorter matched chunk (delimited by whitespaces)\"\n.br\n.BR pathname \" Prefers line with matched substring in the file name of the path\"\n.br\n.BR begin \"    Prefers line with matched substring closer to the beginning\"\n.br\n.BR end \"      Prefers line with matched substring closer to the end\"\n.br\n.BR index \"    Prefers line that appeared earlier in the input stream\"\n.br\n\n.br\n- Each criterion should appear only once in the list\n.br\n- \\fBindex\\fR is only allowed at the end of the list\n.br\n- \\fBindex\\fR is implicitly appended to the list when not specified\n.br\n- Default is \\fBlength\\fR (or equivalently \\fBlength\\fR,index)\n.br\n- If \\fBend\\fR is found in the list, fzf will scan each line backwards\n.SS INPUT/OUTPUT\n.TP\n.B \"\\-\\-read0\"\nRead input delimited by ASCII NUL characters instead of newline characters\n.TP\n.B \"\\-\\-print0\"\nPrint output delimited by ASCII NUL characters instead of newline characters\n.TP\n.B \"\\-\\-ansi\"\nEnable processing of ANSI color codes\n.TP\n.B \"\\-\\-sync\"\nSynchronous search for multi-staged filtering. If specified, fzf will launch\nthe finder only after the input stream is complete and the initial filtering\nand the associated actions (bound to any of \\fBstart\\fR, \\fBload\\fR,\n\\fBresult\\fR, or \\fBfocus\\fR) are complete.\n\n.RS\ne.g. \\fB# Avoid rendering both fzf instances at the same time\n     fzf \\-\\-multi | fzf \\-\\-sync\n\n     # fzf will not render intermediate states\n     (sleep 1; seq 1000000; sleep 1) |\n       fzf \\-\\-sync \\-\\-query 5 \\-\\-listen \\-\\-bind start:up,load:up,result:up,focus:change\\-header:Ready\\fR\n.RE\n.TP\n.B \"\\-\\-no\\-tty\\-default\"\nMake fzf search for the current TTY device via standard error instead of\ndefaulting to \\fB/dev/tty\\fR. This option avoids issues when launching\nemacsclient from within fzf. Alternatively, you can change the default TTY\ndevice by setting \\fB--tty-default=DEVICE_NAME\\fR.\n\n.SS GLOBAL STYLE\n.TP\n.BI \"\\-\\-style=\" \"PRESET\"\nApply a style preset [default|minimal|full[:BORDER_STYLE]]\n.TP\n.BI \"\\-\\-color=\" \"[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]...\"\nColor configuration. The name of the base color scheme is followed by custom\ncolor mappings. Each entry is separated by a comma and/or whitespaces.\n\n.RS\n.B BASE SCHEME:\n    (default: \\fBdark\\fR on 256-color terminal, otherwise \\fBbase16\\fR; If \\fBNO_COLOR\\fR is set, \\fBbw\\fR)\n\n    \\fBdark    \\fRColor scheme for dark terminal\n    \\fBlight   \\fRColor scheme for light terminal\n    \\fBbase16  \\fRColor scheme using base 16 colors (alias: \\fB16\\fR)\n    \\fBbw      \\fRNo colors (equivalent to \\fB\\-\\-no\\-color\\fR)\n\n.B COLOR NAMES:\n    \\fBfg                    \\fRText\n      \\fBlist\\-fg             \\fRText in the list section\n        \\fBselected\\-fg       \\fRSelected line text\n      \\fBpreview\\-fg          \\fRPreview window text\n    \\fBbg                    \\fRBackground\n      \\fBlist\\-bg             \\fRList section background\n        \\fBselected\\-bg       \\fRSelected line background\n      \\fBpreview\\-bg          \\fRPreview window background\n      \\fBinput\\-bg            \\fRInput window background\n      \\fBheader\\-bg           \\fRHeader window background\n      \\fBfooter\\-bg           \\fRFooter window background\n    \\fBhl                    \\fRHighlighted substrings\n      \\fBselected\\-hl         \\fRHighlighted substrings in the selected line\n    \\fBcurrent\\-fg (fg+)      \\fRText (current line)\n    \\fBcurrent\\-bg (bg+)      \\fRBackground (current line)\n      \\fBgutter              \\fRGutter on the left\n    \\fBcurrent\\-hl (hl+)      \\fRHighlighted substrings (current line)\n    \\fBalt\\-bg                \\fRAlternate background color to create striped lines\n    \\fBalt\\-gutter            \\fRAlternate gutter color to create the striped pattern\n    \\fBquery (input\\-fg)      \\fRQuery string\n      \\fBghost               \\fRGhost text (\\fB\\-\\-ghost\\fR, \\fBdim\\fR applied by default)\n      \\fBdisabled            \\fRQuery string when search is disabled (\\fB\\-\\-disabled\\fR)\n    \\fBinfo                  \\fRInfo line (match counters)\n    \\fBborder                \\fRBorder around the window (\\fB\\-\\-border\\fR and \\fB\\-\\-preview\\fR)\n      \\fBlist\\-border         \\fRBorder around the list section (\\fB\\-\\-list\\-border\\fR)\n        \\fBscrollbar         \\fRScrollbar\n        \\fBseparator         \\fRHorizontal separator on info line\n        \\fBgap\\-line          \\fRHorizontal line on each gap\n      \\fBpreview\\-border      \\fRBorder around the preview window (\\fB\\-\\-preview\\fR)\n        \\fBpreview\\-scrollbar \\fRScrollbar\n      \\fBinput\\-border        \\fRBorder around the input window (\\fB\\-\\-input\\-border\\fR)\n      \\fBheader\\-border       \\fRBorder around the header window (\\fB\\-\\-header\\-border\\fR)\n      \\fBfooter\\-border       \\fRBorder around the footer window (\\fB\\-\\-footer\\-border\\fR)\n    \\fBlabel                 \\fRBorder label (\\fB\\-\\-border\\-label\\fR, \\fB\\-\\-list\\-label\\fR, \\fB\\-\\-input\\-label\\fR, and \\fB\\-\\-preview\\-label\\fR)\n      \\fBlist\\-label          \\fRBorder label of the list section (\\fB\\-\\-list\\-label\\fR)\n      \\fBpreview\\-label       \\fRBorder label of the preview window (\\fB\\-\\-preview\\-label\\fR)\n      \\fBinput\\-label         \\fRBorder label of the input window (\\fB\\-\\-input\\-label\\fR)\n      \\fBheader\\-label        \\fRBorder label of the header window (\\fB\\-\\-header\\-label\\fR)\n      \\fBfooter\\-label        \\fRBorder label of the footer window (\\fB\\-\\-footer\\-label\\fR)\n    \\fBprompt                \\fRPrompt\n    \\fBpointer               \\fRPointer to the current line\n    \\fBmarker                \\fRMulti\\-select marker\n    \\fBspinner               \\fRStreaming input indicator\n    \\fBheader (header\\-fg)   \\fRHeader\n    \\fBfooter (footer\\-fg)   \\fRFooter\n    \\fBnth                   \\fRParts of the line specified by \\fB\\-\\-nth\\fR (only supports attributes)\n    \\fBnomatch               \\fRNon-matching items in raw mode (default: \\fBdim\\fR)\n\n.B ANSI COLORS:\n    \\fB\\-1         \\fRDefault terminal foreground/background color\n    \\fB           \\fR(or the original color of the text)\n    \\fB0 ~ 15     \\fR16 base colors\n      \\fBblack\\fR\n      \\fBred\\fR\n      \\fBgreen\\fR\n      \\fByellow\\fR\n      \\fBblue\\fR\n      \\fBmagenta\\fR\n      \\fBcyan\\fR\n      \\fBwhite\\fR\n      \\fBbright\\-black\\fR (gray | grey)\n      \\fBbright\\-red\\fR\n      \\fBbright\\-green\\fR\n      \\fBbright\\-yellow\\fR\n      \\fBbright\\-blue\\fR\n      \\fBbright\\-magenta\\fR\n      \\fBbright\\-cyan\\fR\n      \\fBbright\\-white\\fR\n    \\fB16 ~ 255   \\fRANSI 256 colors\n    \\fB#rrggbb    \\fR24-bit colors\n\n.B ANSI ATTRIBUTES: (Only applies to foreground colors)\n    \\fBregular           \\fRClear previously set attributes; should precede the other ones\n    \\fBstrip             \\fRRemove colors\n    \\fBbold\\fR\n    \\fBunderline\\fR\n    \\fBunderline-double\\fR\n    \\fBunderline-curly\\fR\n    \\fBunderline-dotted\\fR\n    \\fBunderline-dashed\\fR\n    \\fBreverse\\fR\n    \\fBdim\\fR\n    \\fBitalic\\fR\n    \\fBstrikethrough\\fR\n\n.B EXAMPLES:\n\n     \\fB# Seoul256 theme with 8-bit colors\n     # (https://github.com/junegunn/seoul256.vim)\n     fzf \\-\\-color='bg:237,bg+:236,info:143,border:240,spinner:108' \\\\\n         \\-\\-color='hl:65,fg:252,header:65,fg+:252' \\\\\n         \\-\\-color='pointer:161,marker:168,prompt:110,hl+:108'\n\n     # Seoul256 theme with 24-bit colors\n     fzf \\-\\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\\\\n         \\-\\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\\\\n         \\-\\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\n\n     # Seoul256 light theme with 24-bit colors, each entry separated by whitespaces\n     fzf \\-\\-style full \\-\\-color='\n       fg:#616161 fg+:#616161\n       bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1\n       hl:#719872 hl+:#719899\n       pointer:#e12672 marker:#e17899\n       header:#719872\n       spinner:#719899 info:#727100\n       prompt:#0099bd query:#616161\n       border:#e1e1e1\n     '\\fR\n.RE\n.TP\n.B \"\\-\\-no\\-color\"\nDisable colors\n.TP\n.B \"\\-\\-no\\-bold\"\nDo not use bold text\n.TP\n.B \"\\-\\-black\"\nUse black background\n\n.SS DISPLAY MODE\n.TP\n.BI \"\\-\\-height=\" \"[~]HEIGHT[%]\"\nDisplay fzf window below the cursor with the given height instead of using\nthe full screen.\n\nIf a negative value is specified, the height is calculated as the terminal\nheight minus the given value.\n\n  fzf \\-\\-height=\\-1\n\nWhen prefixed with \\fB~\\fR, fzf will automatically determine the height in the\nrange according to the input size.\n\n  # Will not take up 100% of the screen\n  seq 5 | fzf \\-\\-height=~100%\n\nAdaptive height has the following limitations:\n.br\n* Cannot be used with top/bottom margin and padding given in percent size\n.br\n* Negative value is not allowed\n.br\n* It will not find the right size when there are multi-line items\n\n.TP\n.BI \"\\-\\-min\\-height=\" \"HEIGHT[+]\"\nMinimum height when \\fB\\-\\-height\\fR is given as a percentage.\nAdd \\fB+\\fR to automatically increase the value according to the other\nlayout options so that the specified number of items are visible in the list\nsection (default: \\fB10+\\fR).\nIgnored when \\fB\\-\\-height\\fR is not specified or set as an absolute value.\n.TP\n.BI \"\\-\\-tmux\" \"[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]\"\nStart fzf in a tmux popup (default \\fBcenter,50%\\fR). Requires tmux 3.3 or\nlater. This option is ignored if you are not running fzf inside tmux.\n\ne.g.\n  \\fB# Popup in the center with 70% width and height\n  fzf \\-\\-tmux 70%\n\n  # Popup on the left with 40% width and 100% height\n  fzf \\-\\-tmux right,40%\n\n  # Popup on the bottom with 100% width and 30% height\n  fzf \\-\\-tmux bottom,30%\n\n  # Popup on the top with 80% width and 40% height\n  fzf \\-\\-tmux top,80%,40%\n\n  # Popup with a native tmux border in the center with 80% width and height\n  fzf \\-\\-tmux center,80%,border\\-native\\fR\n\n.SS LAYOUT\n.TP\n.BI \"\\-\\-layout=\" \"LAYOUT\"\nChoose the layout (default: default)\n\n.br\n.BR default  \"       Display from the bottom of the screen\"\n.br\n.BR reverse  \"       Display from the top of the screen\"\n.br\n.BR reverse\\-list \"  Display from the top of the screen, prompt at the bottom\"\n.br\n\n.TP\n.B \"\\-\\-reverse\"\nA synonym for \\fB\\-\\-layout=reverse\\fB\n\n.TP\n.BI \"\\-\\-margin=\" MARGIN\nComma-separated expression for margins around the finder.\n.br\n\n.br\n.RS\n.BR TRBL \"     Same margin for top, right, bottom, and left\"\n.br\n.BR TB,RL \"    Vertical, horizontal margin\"\n.br\n.BR T,RL,B \"   Top, horizontal, bottom margin\"\n.br\n.BR T,R,B,L \"  Top, right, bottom, left margin\"\n.br\n\n.br\nEach part can be given in absolute number or in percentage relative to the\nterminal size with \\fB%\\fR suffix.\n.br\n\n.br\ne.g.\n     \\fBfzf \\-\\-margin 10%\n     fzf \\-\\-margin 1,5%\\fR\n.RE\n.TP\n.BI \"\\-\\-padding=\" PADDING\nComma-separated expression for padding inside the border. Padding is\ndistinguishable from margin only when \\fB\\-\\-border\\fR option is used.\n.br\n\n.br\ne.g.\n     \\fBfzf \\-\\-margin 5% \\-\\-padding 5% \\-\\-border \\-\\-preview 'cat {}' \\\\\n         \\-\\-color bg:#222222,preview\\-bg:#333333\\fR\n\n.br\n.RS\n.BR TRBL \"     Same padding for top, right, bottom, and left\"\n.br\n.BR TB,RL \"    Vertical, horizontal padding\"\n.br\n.BR T,RL,B \"   Top, horizontal, bottom padding\"\n.br\n.BR T,R,B,L \"  Top, right, bottom, left padding\"\n.br\n.RE\n\n.TP\n.BI \"\\-\\-border\" [=STYLE]\nDraw border around the finder\n\n.br\n.BR rounded \"       Border with rounded corners (default)\"\n.br\n.BR sharp \"         Border with sharp corners\"\n.br\n.BR bold \"          Border with bold lines\"\n.br\n.BR double \"        Border with double lines\"\n.br\n.BR block \"         Border using block elements; suitable when using different background colors\"\n.br\n.BR thinblock \"     Border using legacy computing symbols; may not be displayed on some terminals\"\n.br\n.BR horizontal \"    Horizontal lines above and below the finder\"\n.br\n.BR vertical \"      Vertical lines on each side of the finder\"\n.br\n.BR line \"          Single line border (position automatically determined)\"\n.br\n.BR top \" (up)\"\n.br\n.BR bottom \" (down)\"\n.br\n.BR left\n.br\n.BR right\n.br\n.BR none\n.br\n\nIf you use a terminal emulator where each box-drawing character takes\n2 columns, try setting \\fB\\-\\-ambidouble\\fR. If the border is still not properly\nrendered, set \\fB\\-\\-no\\-unicode\\fR.\n\n\\fBline\\fR style draws a single separator line at the top when \\fB\\-\\-height\\fR\nis used.\n\n.TP\n.BI \"\\-\\-border\\-label\" [=LABEL]\nLabel to print on the horizontal border line. Should be used with one of the\nfollowing \\fB\\-\\-border\\fR options.\n\n.br\n.B * rounded\n.br\n.B * sharp\n.br\n.B * bold\n.br\n.B * double\n.br\n.B * horizontal\n.br\n.BR \"* top\" \" (up)\"\n.br\n.BR \"* bottom\" \" (down)\"\n.br\n\n.br\ne.g.\n  \\fB# ANSI color codes are supported\n  # (with https://github.com/busyloop/lolcat)\n  label=$(curl \\-s http://metaphorpsum.com/sentences/1 | lolcat \\-f)\n\n  # Border label at the center\n  fzf \\-\\-height=10 \\-\\-border \\-\\-border\\-label=\"╢ $label ╟\" \\-\\-color=label:italic:black\n\n  # Left-aligned (positive integer)\n  fzf \\-\\-height=10 \\-\\-border \\-\\-border\\-label=\"╢ $label ╟\" \\-\\-border\\-label\\-pos=3 \\-\\-color=label:italic:black\n\n  # Right-aligned (negative integer) on the bottom line (:bottom)\n  fzf \\-\\-height=10 \\-\\-border \\-\\-border\\-label=\"╢ $label ╟\" \\-\\-border\\-label\\-pos=\\-3:bottom \\-\\-color=label:italic:black\\fR\n\n.TP\n.BI \"\\-\\-border\\-label\\-pos\" [=N[:top|bottom]]\nPosition of the border label on the border line. Specify a positive integer as\nthe column position from the left. Specify a negative integer to right-align\nthe label. Label is printed on the top border line by default, add\n\\fB:bottom\\fR to put it on the border line on the bottom. The default value\n\\fB0 (or \\fBcenter\\fR) will put the label at the center of the border line.\n\n.SS LIST SECTION\n.TP\n.BI \"\\-m, \\-\\-multi\" \"[=MAX]\"\nEnable multi-select with tab/shift\\-tab. It optionally takes an integer argument\nwhich denotes the maximum number of items that can be selected.\n.TP\n.B \"+m, \\-\\-no\\-multi\"\nDisable multi-select\n.TP\n.B \"\\-\\-highlight\\-line\"\nHighlight the whole current line\n.TP\n.B \"\\-\\-cycle\"\nEnable cyclic scroll\n.TP\n.BI \"\\-\\-wrap\" \"[=MODE]\"\nEnable line wrap. \\fIMODE\\fR can be \\fBchar\\fR (default) or \\fBword\\fR.\n\\fBword\\fR mode wraps lines at word boundaries (spaces and tabs) instead of\nat arbitrary character positions. \\fB\\-\\-wrap\\-word\\fR is a synonym for\n\\fB\\-\\-wrap=word\\fR.\n.TP\n.BI \"\\-\\-wrap\\-sign\" \"=INDICATOR\"\nIndicator for wrapped lines. The default is '↳ ' or '> ' depending on\n\\fB\\-\\-no\\-unicode\\fR.\n.TP\n.B \"\\-\\-no\\-multi\\-line\"\nDisable multi-line display of items when using \\fB\\-\\-read0\\fR\n.TP\n.B \"\\-\\-raw\"\nEnable raw mode where non-matching items are also displayed in a dimmed color.\n.TP\n.BI \"\\-\\-track\"\nMake fzf track the current selection when the result list is updated.\nThis can be useful when browsing logs using fzf with sorting disabled. It is\nnot recommended to use this option with \\fB\\-\\-tac\\fR as the resulting behavior\ncan be confusing.\n\nWhen \\fB\\-\\-id\\-nth\\fR is also set, fzf enables field\\-based tracking across\n\\fBreload\\fRs. See \\fB\\-\\-id\\-nth\\fR for details.\n\nWithout \\fB\\-\\-id\\-nth\\fR, \\fB\\-\\-track\\fR uses index\\-based tracking that\ndoes not persist across reloads.\n\n.RS\ne.g.\n     \\fB# Index\\-based tracking (does not persist across reloads)\n     git log \\-\\-oneline \\-\\-graph \\-\\-color=always | nl |\n         fzf \\-\\-ansi \\-\\-track \\-\\-no\\-sort \\-\\-layout=reverse\\-list\\fR\n\n     \\fB# Track by first field (e.g. pod name) across reloads\n     kubectl get pods | fzf \\-\\-track \\-\\-id\\-nth 1 \\-\\-header\\-lines=1 \\\\\n         \\-\\-bind 'ctrl\\-r:reload:kubectl get pods'\\fR\n.RE\n.TP\n.BI \"\\-\\-id\\-nth=\" \"N[,..]\"\nDefine item identity fields for cross\\-reload operations. When set, fzf\nuses the specified fields to identify items across \\fBreload\\fR and\n\\fBreload\\-sync\\fR.\n\nWith \\fB\\-\\-track\\fR, fzf extracts the tracking key from the current item\nusing the nth expression and searches for a matching item in the reloaded list.\nWhile searching, the UI is blocked (query input and cursor movement are\ndisabled, and the prompt is dimmed). With \\fBreload\\fR, the blocked state\nclears as soon as the match is found in the stream. With \\fBreload\\-sync\\fR,\nthe blocked state persists until the entire stream is complete. Press\n\\fBEscape\\fR or \\fBCtrl\\-C\\fR to cancel the blocked state without quitting fzf.\n\nThe info line shows \\fB+T*\\fR (or \\fB+t*\\fR for one\\-off tracking) while\nthe search is in progress.\n\nWith \\fB\\-\\-multi\\fR, selected items are preserved across \\fBreload\\-sync\\fR\nby matching their identity fields in the reloaded list.\n\n.RS\ne.g.\n     \\fB# Track and preserve selections by pod name across reloads\n     kubectl get pods | fzf \\-\\-multi \\-\\-track \\-\\-id\\-nth 1 \\-\\-header\\-lines=1 \\\\\n         \\-\\-bind 'ctrl\\-r:reload\\-sync:kubectl get pods'\\fR\n.RE\n.TP\n.B \"\\-\\-tac\"\nReverse the order of the input\n\n.RS\ne.g.\n     \\fBhistory | fzf \\-\\-tac \\-\\-no\\-sort\\fR\n.RE\n.TP\n.BI \"\\-\\-gap\" \"[=N]\"\nRender empty lines between each item\n.TP\n.BI \"\\-\\-gap\\-line\" \"[=STR]\"\nThe given string will be repeated to draw a horizontal line on each gap\n(default: '┈' or '\\-' depending on \\fB\\-\\-no\\-unicode\\fR).\n.TP\n.BI \"\\-\\-freeze\\-left=\" \"N\"\nNumber of fields to freeze on the left.\n.TP\n.BI \"\\-\\-freeze\\-right=\" \"N\"\nNumber of fields to freeze on the right.\n.TP\n.B \"\\-\\-keep\\-right\"\nKeep the right end of the line visible when it's too long. Effective only when\nthe query string is empty. Use \\fB\\-\\-freeze\\-right=1\\fR instead if you want\nthe last field to be always visible even with a non-empty query.\n.TP\n.BI \"\\-\\-scroll\\-off=\" \"LINES\"\nNumber of screen lines to keep above or below when scrolling to the top or to\nthe bottom (default: 3).\n.TP\n.B \"\\-\\-no\\-hscroll\"\nDisable horizontal scroll\n.TP\n.BI \"\\-\\-hscroll\\-off=\" \"COLS\"\nNumber of screen columns to keep to the right of the highlighted substring\n(default: 10). Setting it to a large value will cause the text to be positioned\non the center of the screen.\n.TP\n.BI \"\\-\\-jump\\-labels=\" \"CHARS\"\nLabel characters for \\fBjump\\fR mode.\n.TP\n.BI \"\\-\\-gutter=\" \"CHAR\"\nCharacter used for the gutter column (default: '▌' unless \\fB\\-\\-no\\-unicode\\fR is given)\n.TP\n.BI \"\\-\\-gutter\\-raw=\" \"CHAR\"\nCharacter used for the gutter column in raw mode (default: '▖' unless \\fB\\-\\-no\\-unicode\\fR is given)\n.TP\n.BI \"\\-\\-pointer=\" \"STR\"\nPointer to the current line (default: '▌' or '>' depending on \\fB\\-\\-no\\-unicode\\fR)\n.TP\n.BI \"\\-\\-marker=\" \"STR\"\nMulti-select marker (default: '┃' or '>' depending on \\fB\\-\\-no\\-unicode\\fR)\n.TP\n.BI \"\\-\\-marker\\-multi\\-line=\" \"STR\"\nMulti-select marker for multi-line entries. 3 elements for top, middle, and bottom.\n(default: '╻┃╹' or '.|'' depending on \\fB\\-\\-no\\-unicode\\fR)\n.TP\n.BI \"\\-\\-ellipsis=\" \"STR\"\nEllipsis to show when line is truncated (default: '··')\n.TP\n.BI \"\\-\\-tabstop=\" SPACES\nNumber of spaces for a tab character (default: 8)\n.TP\n.BI \"\\-\\-scrollbar=\" \"CHAR1[CHAR2]\"\nUse the given character to render scrollbar. (default: '│' or ':' depending on\n\\fB\\-\\-no\\-unicode\\fR). The optional \\fBCHAR2\\fR is used to render scrollbar of\nthe preview window.\n\n.TP\n.B \"\\-\\-no\\-scrollbar\"\nDo not display scrollbar. A synonym for \\fB\\-\\-scrollbar=''\\fB\n\n.TP\n.BI \"\\-\\-list\\-border\" [=STYLE]\nDraw border around the list section. \\fBline\\fR style is not supported for\nthis border.\n\n.TP\n.BI \"\\-\\-list\\-label\" [=LABEL]\nLabel to print on the list border\n\n.TP\n.BI \"\\-\\-list\\-label\\-pos\" [=N[:top|bottom]]\nPosition of the list label\n\n.SS INPUT SECTION\n\n.TP\n.B \"\\-\\-no\\-input\"\nDisable and hide the input section. You can no longer type in queries. To\ntrigger a search, use \\fBsearch\\fR action. You can later show the input section\nusing \\fBshow\\-input\\fR or \\fBtoggle\\-input\\fR action, and hide it again using\n\\fBhide\\-input\\fR, or \\fBtoggle\\-input\\fR.\n\n.TP\n.BI \"\\-\\-prompt=\" \"STR\"\nInput prompt (default: '> ')\n.TP\n.BI \"\\-\\-info=\" \"STYLE\"\nDetermines the display style of the finder info. (e.g. match counter, loading indicator, etc.)\n\n.BR default  \"              On the left end of the horizontal separator\"\n.br\n.BR right  \"                On the right end of the horizontal separator\"\n.br\n.BR hidden  \"               Do not display finder info\"\n.br\n.BR inline  \"               After the prompt with the default prefix ' < '\"\n.br\n.BR inline:PREFIX  \"        After the prompt with a non-default prefix\"\n.br\n.BR inline\\-right \"         On the right end of the prompt line\"\n.br\n.BR inline\\-right:PREFIX \"  On the right end of the prompt line with a custom prefix\"\n.br\n\n.TP\n.BI \"\\-\\-info\\-command=\" \"COMMAND\"\nCommand to generate the finder info line. The command runs synchronously and\nblocks the UI until completion, so make sure that it's fast. ANSI color codes\nare supported. \\fB$FZF_INFO\\fR variable is set to the original info text.\nFor additional environment variables available to the command, see the section\nENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.\n\ne.g.\n     \\fB# Prepend the current cursor position in yellow\n     fzf \\-\\-info\\-command='printf \"\\\\x1b[33;1m$FZF_POS\\\\x1b[m/$FZF_INFO 💛\"'\\fR\n\n.TP\n.B \"\\-\\-no\\-info\"\nA synonym for \\fB\\-\\-info=hidden\\fB\n\n.TP\n.BI \"\\-\\-separator=\" \"STR\"\nThe given string will be repeated to form the horizontal separator on the info\nline (default: '─' or '\\-' depending on \\fB\\-\\-no\\-unicode\\fR).\n\nANSI color codes are supported.\n\n.TP\n.B \"\\-\\-no\\-separator\"\nDo not display horizontal separator on the info line. A synonym for\n\\fB\\-\\-separator=''\\fB\n\n.TP\n.BI \"\\-\\-ghost=\" \"TEXT\"\nGhost text to display when the input is empty\n\n.TP\n.B \"\\-\\-filepath\\-word\"\nMake word-wise movements and actions respect path separators. The following\nactions are affected:\n\n\\fBbackward\\-kill\\-word\\fR\n.br\n\\fBbackward\\-word\\fR\n.br\n\\fBforward\\-word\\fR\n.br\n\\fBkill\\-word\\fR\n.TP\n.BI \"\\-\\-input\\-border\" [=STYLE]\nDraw border around the input section. \\fBline\\fR style draws a single separator\nline between the input section and the list section.\n\n.TP\n.BI \"\\-\\-input\\-label\" [=LABEL]\nLabel to print on the input border\n\n.TP\n.BI \"\\-\\-input\\-label\\-pos\" [=N[:top|bottom]]\nPosition of the input label\n\n.SS PREVIEW WINDOW\n.TP\n.BI \"\\-\\-preview=\" \"COMMAND\"\nExecute the given command for the current line and display the result on the\npreview window. \\fB{}\\fR in the command is the placeholder that is replaced to\nthe single-quoted string of the current line. To transform the replacement\nstring, specify field index expressions between the braces (See \\fBFIELD INDEX\nEXPRESSION\\fR for the details).\n\n.RS\ne.g.\n     \\fBfzf \\-\\-preview='head \\-$LINES {}'\n     ls \\-l | fzf \\-\\-preview=\"echo user={3} when={\\-4..\\-2}; cat {\\-1}\" \\-\\-header\\-lines=1\\fR\n\nfzf exports \\fB$FZF_PREVIEW_LINES\\fR and \\fB$FZF_PREVIEW_COLUMNS\\fR so that\nthey represent the exact size of the preview window. (It also overrides\n\\fB$LINES\\fR and \\fB$COLUMNS\\fR with the same values but they can be reset\nby the default shell, so prefer to refer to the ones with \\fBFZF_PREVIEW_\\fR\nprefix.)\n\nfzf also exports \\fB$FZF_PREVIEW_TOP\\fR and \\fB$FZF_PREVIEW_LEFT\\fR so that\nthe preview command can determine the position of the preview window.\n\nA placeholder expression starting with \\fB+\\fR flag will be replaced to the\nspace-separated list of the selected items (or the current item if no selection\nwas made) individually quoted.\n\ne.g.\n     \\fBfzf \\-\\-multi \\-\\-preview='head \\-10 {+}'\n     git log \\-\\-oneline | fzf \\-\\-multi \\-\\-preview 'git show {+1}'\\fR\n\nSimilarly, a placeholder expression starting with \\fB*\\fR flag will be replaced\nto the space-separated list of all matched items individually quoted.\n\nEach expression expands to a quoted string, so that it's safe to pass it as an\nargument to an external command. So you should not manually add quotes around\nthe curly braces. But if you don't want this behavior, you can put\n\\fBr\\fR flag (raw) in the expression (e.g. \\fB{r}\\fR, \\fB{r1}\\fR, etc).\nUse it with caution as unquoted output can lead to broken commands.\n\nWhen using a field index expression, leading and trailing whitespace is stripped\nfrom the replacement string. To preserve the whitespace, use the \\fBs\\fR flag.\n\nA placeholder expression with \\fBf\\fR flag is replaced to the path of\na temporary file that holds the evaluated list. This is useful when you\npass a large number of items and the length of the evaluated string may\nexceed \\fBARG_MAX\\fR.\n\ne.g.\n     \\fB# See the sum of all the matched numbers\n     # This won't work properly without 'f' flag due to ARG_MAX limit.\n     seq 100000 | fzf \\-\\-preview \"awk '{sum+=\\\\$1} END {print sum}' {*f}\"\\fR\n\n     \\fB# Use {+f} to get the selected items as a line-separated list\n     seq 100 | fzf \\-\\-multi \\-\\-bind 'enter:become:cat {+f}'\\fR\n\nAlso,\n\n* \\fB{q}\\fR is replaced to the current query string\n.br\n* \\fB{q}\\fR can contain field index expressions. e.g. \\fB{q:1}\\fR, \\fB{q:2..}\\fR, etc.\n.br\n* \\fB{n}\\fR is replaced to the zero-based ordinal index of the current item.\n  Use \\fB{+n}\\fR if you want all index numbers when multiple lines are selected.\n.br\n\nNote that you can escape a placeholder pattern by prepending a backslash.\n\nPreview window will be updated even when there is no match for the current\nquery if any of the placeholder expressions evaluates to a non-empty string\nor \\fB{q}\\fR is in the command template.\n\nSince 0.24.0, fzf can render partial preview content before the preview command\ncompletes. ANSI escape sequence for clearing the display (\\fBCSI 2 J\\fR) is\nsupported, so you can use it to implement preview window that is constantly\nupdating.\n\ne.g.\n      \\fBfzf \\-\\-preview 'for i in $(seq 100000); do\n        (( i % 200 == 0 )) && printf \"\\\\033[2J\"\n        echo \"$i\"\n        sleep 0.01\n      done'\\fR\n\nfzf has experimental support for Kitty graphics protocol and Sixel graphics.\nThe following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf\\-preview.sh\nscript to render an image using either of the protocols inside the preview window.\n\ne.g.\n      \\fBfzf \\-\\-preview='fzf\\-preview.sh {}'\\fR\n\n.RE\n\n.TP\n.BI \"\\-\\-preview\\-border\" [=STYLE]\nShort for \\fB\\-\\-preview\\-window=border\\-STYLE\\fR. \\fBline\\fR style draws\na single separator line between the preview window and the rest of the\ninterface.\n\n.TP\n.BI \"\\-\\-preview\\-label\" [=LABEL]\nLabel to print on the horizontal border line of the preview window.\nShould be used with one of the following \\fB\\-\\-preview\\-window\\fR options.\n\n.br\n.B * border\\-rounded (default on non-Windows platforms)\n.br\n.B * border\\-sharp (default on Windows)\n.br\n.B * border\\-bold\n.br\n.B * border\\-double\n.br\n.B * border\\-block\n.br\n.B * border\\-thinblock\n.br\n.B * border\\-horizontal\n.br\n.B * border\\-top\n.br\n.B * border\\-bottom\n.br\n\n.TP\n.BI \"\\-\\-preview\\-wrap\\-sign\" =INDICATOR\nIndicator for wrapped lines in the preview window. If not set, the value of\n\\fB\\-\\-wrap\\-sign\\fR is used.\n\n.TP\n.BI \"\\-\\-preview\\-label\\-pos\" [=N[:top|bottom]]\nPosition of the border label on the border line of the preview window. Specify\na positive integer as the column position from the left. Specify a negative\ninteger to right-align the label. Label is printed on the top border line by\ndefault, add \\fB:bottom\\fR to put it on the border line on the bottom. The\ndefault value 0 (or \\fBcenter\\fR) will put the label at the center of the\nborder line.\n\n.TP\n.BI \"\\-\\-preview\\-window=\" \"[POSITION][,SIZE[%]][,border\\-STYLE][,[no]wrap][,wrap\\-word][,[no]follow][,[no]cycle][,[no]info][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]\"\n\n.RS\n.B POSITION: (default: right)\n    \\fBup\n    \\fBdown\n    \\fBleft\n    \\fBright\n\n\\fRDetermines the layout of the preview window.\n\n* If the argument contains \\fB:hidden\\fR, the preview window will be hidden by\ndefault until \\fBtoggle\\-preview\\fR action is triggered.\n\n* If size is given as 0, preview window will not be visible, but fzf will still\nexecute the command in the background.\n\n* Long lines are truncated by default. Line wrap can be enabled with\n\\fBwrap\\fR flag. \\fBwrap\\-word\\fR flag enables word-level wrapping, which\nbreaks lines at word boundaries instead of mid-word.\n\n* Preview window will automatically scroll to the bottom when \\fBfollow\\fR\nflag is set, similarly to how \\fBtail \\-f\\fR works.\n\n.RS\ne.g.\n      \\fBfzf \\-\\-preview\\-window follow \\-\\-preview 'for i in $(seq 100000); do\n        echo \"$i\"\n        sleep 0.01\n        (( i % 300 == 0 )) && printf \"\\\\033[2J\"\n      done'\\fR\n.RE\n\n* Cyclic scrolling is enabled with \\fBcycle\\fR flag.\n\n* To hide the scroll offset information on the top right corner, specify\n\\fBnoinfo\\fR.\n\n* To change the style of the border of the preview window, specify one of\nthe options for \\fB\\-\\-border\\fR with \\fBborder\\-\\fR prefix.\ne.g. \\fBborder\\-rounded\\fR (border with rounded edges, default),\n\\fBborder\\-sharp\\fR (border with sharp edges), \\fBborder\\-left\\fR,\n\\fBborder\\-none\\fR, etc.\n\n* In addition to the other border styles, \\fBborder\\-line\\fR style is also\nsupported, which draws a single separator line between the preview window and\nthe rest of the interface.\n\n* \\fB[:+SCROLL[OFFSETS][/DENOM]]\\fR determines the initial scroll offset of the\npreview window.\n\n  - \\fBSCROLL\\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer or {n} to refer to the zero-based ordinal index of the current item.\n\n  - The optional \\fBOFFSETS\\fR part is for adjusting the base offset. It should be given as a series of signed integers (\\fB\\-INTEGER\\fR or \\fB+INTEGER\\fR).\n\n  - The final \\fB/DENOM\\fR part is for specifying a fraction of the preview window height.\n\n* \\fB~HEADER_LINES\\fR keeps the top N lines as the fixed header so that they\nare always visible.\n\n* \\fBdefault\\fR resets all options previously set to the default.\n\n.RS\ne.g.\n     \\fB# Non-default scroll window positions and sizes\n     fzf \\-\\-preview=\"head {}\" \\-\\-preview\\-window=up,30%\n     fzf \\-\\-preview=\"file {}\" \\-\\-preview\\-window=down,1\n\n     # Initial scroll offset is set to the line number of each line of\n     # git grep output *minus* 5 lines (\\-5)\n     git grep \\-\\-line\\-number '' |\n       fzf \\-\\-delimiter : \\-\\-preview 'nl {1}' \\-\\-preview\\-window '+{2}\\-5'\n\n     # Preview with bat, matching line in the middle of the window below\n     # the fixed header of the top 3 lines\n     #\n     #   ~3    Top 3 lines as the fixed header\n     #   +{2}  Base scroll offset extracted from the second field\n     #   +3    Extra offset to compensate for the 3-line header\n     #   /2    Put in the middle of the preview area\n     #\n     git grep \\-\\-line\\-number '' |\n       fzf \\-\\-delimiter : \\\\\n           \\-\\-preview 'bat \\-\\-style=full \\-\\-color=always \\-\\-highlight\\-line {2} {1}' \\\\\n           \\-\\-preview\\-window '~3,+{2}+3/2'\n\n     # Display top 3 lines as the fixed header\n     fzf \\-\\-preview 'bat \\-\\-style=full \\-\\-color=always {}' \\-\\-preview\\-window '~3'\\fR\n.RE\n\n* You can specify an alternative set of options that are used only when the size\n  of the preview window is below a certain threshold. Note that only one\n  alternative layout is allowed.\n\n.RS\ne.g.\n      \\fBfzf \\-\\-preview 'cat {}' \\-\\-preview\\-window 'right,border\\-left,<30(up,30%,border\\-bottom)'\\fR\n.RE\n\n.SS HEADER\n\n.TP\n.BI \"\\-\\-header=\" \"STR\"\nThe given string will be printed as the sticky header. The lines are displayed\nin the given order from top to bottom regardless of \\fB\\-\\-layout\\fR option, and\nare not affected by \\fB\\-\\-with\\-nth\\fR. ANSI color codes are processed even when\n\\fB\\-\\-ansi\\fR is not set.\n.TP\n.BI \"\\-\\-header\\-lines=\" \"N\"\nThe first N lines of the input are treated as the sticky header. When\n\\fB\\-\\-with\\-nth\\fR is set, the lines are transformed just like the other\nlines that follow.\n.TP\n.B \"\\-\\-header\\-first\"\nPrint header before the prompt line. When both normal header and header lines\n(\\fB\\-\\-header\\-lines\\fR) are present, this applies only to the normal header.\n.TP\n.BI \"\\-\\-header\\-border\" [=STYLE]\nDraw border around the header section. \\fBline\\fR style draws a single\nseparator line between the header window and the list section.\n\n.TP\n.BI \"\\-\\-header\\-label\" [=LABEL]\nLabel to print on the header border\n\n.TP\n.BI \"\\-\\-header\\-label\\-pos\" [=N[:top|bottom]]\nPosition of the header label\n\n.TP\n.BI \"\\-\\-header\\-lines\\-border\" [=STYLE]\nDisplay header from \\fB--header\\-lines\\fR with a separate border. Pass\n\\fBnone\\fR to still separate the header lines but without a border. To combine\ntwo headers, use \\fB\\-\\-no\\-header\\-lines\\-border\\fR. \\fBline\\fR style draws\na single separator line between the header lines and the list section.\n\n.SS FOOTER\n\n.TP\n.BI \"\\-\\-footer=\" \"STR\"\nThe given string will be printed as the sticky footer. The lines are displayed\nin the given order from top to bottom regardless of \\fB\\-\\-layout\\fR option, and\nare not affected by \\fB\\-\\-with\\-nth\\fR. ANSI color codes are processed even when\n\\fB\\-\\-ansi\\fR is not set.\n\n.TP\n.BI \"\\-\\-footer\\-border\" [=STYLE]\nDraw border around the footer section. \\fBline\\fR style draws a single\nseparator line between the footer and the list section.\n\n.TP\n.BI \"\\-\\-footer\\-label\" [=LABEL]\nLabel to print on the footer border\n\n.TP\n.BI \"\\-\\-footer\\-label\\-pos\" [=N[:top|bottom]]\nPosition of the footer label\n\n.SS SCRIPTING\n.TP\n.BI \"\\-q, \\-\\-query=\" \"STR\"\nStart the finder with the given query\n.TP\n.B \"\\-1, \\-\\-select\\-1\"\nIf there is only one match for the initial query (\\fB\\-\\-query\\fR), do not start\ninteractive finder and automatically select the only match\n.TP\n.B \"\\-0, \\-\\-exit\\-0\"\nIf there is no match for the initial query (\\fB\\-\\-query\\fR), do not start\ninteractive finder and exit immediately\n.TP\n.BI \"\\-f, \\-\\-filter=\" \"STR\"\nFilter mode. Do not start interactive finder. When used with \\fB\\-\\-no\\-sort\\fR,\nfzf becomes a fuzzy-version of grep.\n.TP\n.B \"\\-\\-print\\-query\"\nPrint query as the first line\n.TP\n.BI \"\\-\\-expect=\" \"KEY[,..]\"\nComma-separated list of keys that can be used to complete fzf in addition to\nthe default enter key. When this option is set, fzf will print the name of the\nkey pressed as the first line of its output (or as the second line if\n\\fB\\-\\-print\\-query\\fR is also used). The line will be empty if fzf is completed\nwith the default enter key. If \\fB\\-\\-expect\\fR option is specified multiple\ntimes, fzf will expect the union of the keys. \\fB\\-\\-no\\-expect\\fR will clear the\nlist.\n\ne.g.\n     \\fBfzf \\-\\-expect=ctrl\\-v,ctrl\\-t,alt\\-s \\-\\-expect=f1,f2,~,@\\fR\n\nThis option is not compatible with \\fB\\-\\-bind\\fR on the same key and will take\nprecedence over it. To combine the two, use \\fBprint\\fR action.\n\ne.g.\n     \\fBfzf \\-\\-multi \\\\\n         \\-\\-bind 'enter:print()+accept,ctrl\\-y:select\\-all+print(ctrl\\-y)+accept'\\fR\n.TP\n.B \"\\-\\-no\\-clear\"\nDo not clear finder interface on exit. If fzf was started in full screen mode,\nit will not switch back to the original screen, so you'll have to manually run\n\\fBtput rmcup\\fR to return. This option can be used to avoid flickering of the\nscreen when your application needs to start fzf multiple times in order. (Note\nthat in most cases, it is preferable to use \\fBreload\\fR action instead.)\n\ne.g.\n     \\fBfoo=$(seq 100 | fzf \\-\\-no\\-clear) || (\n       # Need to manually switch back to the main screen when cancelled\n       tput rmcup\n       exit 1\n     ) && seq \"$foo\" 100 | fzf\n\n.SS KEY/EVENT BINDING\n.TP\n.BI \"\\-\\-bind=\" \"BINDINGS\"\nComma-separated list of custom key/event bindings. See \\fBKEY/EVENT BINDINGS\\fR\nfor the details.\n\n.SS ADVANCED\n.TP\n.B \"\\-\\-with\\-shell=STR\"\nShell command and flags to start child processes with. On *nix Systems, the\ndefault value is \\fB$SHELL \\-c\\fR if \\fB$SHELL\\fR is set, otherwise \\fBsh \\-c\\fR.\nOn Windows, the default value is \\fBcmd /s/c\\fR when \\fB$SHELL\\fR is not\nset.\n\n.TP\n.B \"\\-\\-listen[=SOCKET_PATH|[ADDR:]PORT]\" \"\\-\\-listen\\-unsafe[=[ADDR:]PORT]\"\nStart HTTP server and listen on the given address or Unix socket. It allows\nexternal processes to send actions to perform via POST method and query the\nprogram state via GET method. For the argument to be recognized as a socket\npath, it must have \\fB.sock\\fR extension.\n\n- If the port number is omitted or given as 0, fzf will automatically choose\na port and export it as \\fBFZF_PORT\\fR environment variable to the child processes.\n\n- If a Unix socket path is given, fzf will create a Unix domain socket at the\n  given path. The existing file will be removed. The path to the socket file\n  is exported as \\fBFZF_SOCK\\fR environment variable.\n\n- If \\fBFZF_API_KEY\\fR environment variable is set, the server would require\n  sending an API key with the same value in the \\fBx\\-api\\-key\\fR HTTP header.\n\n- \\fBFZF_API_KEY\\fR is required for a non-localhost listen address.\n\n- To allow remote process execution, use \\fB\\-\\-listen\\-unsafe\\fR.\n\ne.g.\n     \\fB# Start HTTP server on port 6266\n     fzf \\-\\-listen 6266\n\n     # Send action to the server\n     curl \\-XPOST localhost:6266 \\-d 'reload(seq 100)+change\\-prompt(hundred> )'\n\n     # Start HTTP server on port 6266 with remote connections allowed\n     # * Listening on non-localhost address requires using an API key\n     export FZF_API_KEY=\"$(head \\-c 32 /dev/urandom | base64)\"\n     fzf \\-\\-listen 0.0.0.0:6266\n\n     # Send an authenticated action\n     curl \\-XPOST localhost:6266 \\-H \"x\\-api\\-key: $FZF_API_KEY\" \\-d 'change\\-query(yo)'\n\n     # Choose port automatically and export it as $FZF_PORT to the child process\n     fzf \\-\\-listen \\-\\-bind 'start:execute\\-silent:echo $FZF_PORT > /tmp/fzf\\-port'\n\n     # Get program state in JSON format (experimental)\n     # - GET Parameters:\n     #    - limit: number of items to return (default: 100)\n     #    - offset: number of items to skip (default: 0)\n     curl localhost:6266\n\n     # Automatically select items with .txt extension\n     fzf \\-\\-multi \\-\\-sync \\-\\-listen \\-\\-bind 'load:transform:\n       pos=1\n       curl \\-s localhost:$FZF_PORT?limit=1000 | jq \\-r .matches[].text | while read \\-r text; do\n         if [[ $text =~ \\\\.txt$ ]]; then\n           echo \\-n \"+pos($pos)+select\"\n         fi\n         pos=$((pos + 1))\n       done\n       echo +first\n     '\n     \\fR\n\nHere is an example script that uses a Unix socket instead of a TCP port.\n\n    \\fB\n    fzf --listen=/tmp/fzf.sock\n\n    # GET\n    curl --unix-socket /tmp/fzf.sock http\n\n    # POST\n    curl --unix-socket /tmp/fzf.sock http -d up\n    \\fR\n\n.TP\n.BI \"\\-\\-threads=\" \"N\"\nNumber of matcher threads to use. The default value is\n\\fBmin(8 * NUM_CPU, 32)\\fR.\n.TP\n.BI \"\\-\\-bench=\" \"DURATION\"\nRepeatedly run \\fB\\-\\-filter\\fR for the given duration and print timing\nstatistics. Must be used with \\fB\\-\\-filter\\fR.\n\ne.g.\n     \\fBcat /usr/share/dict/words | fzf \\-\\-filter abc \\-\\-bench 10s\\fR\n\n.SS DIRECTORY TRAVERSAL\n.TP\n.B \"\\-\\-walker=[file][,dir][,follow][,hidden]\"\nDetermines the behavior of the built-in directory walker that is used when\n\\fB$FZF_DEFAULT_COMMAND\\fR is not set. The default value is \\fBfile,follow,hidden\\fR.\n\n* \\fBfile\\fR: Include files in the search result\n.br\n* \\fBdir\\fR: Include directories in the search result\n.br\n* \\fBhidden\\fR: Include and follow hidden directories\n.br\n* \\fBfollow\\fR: Follow symbolic links\n.br\n\n.TP\n.B \"\\-\\-walker\\-root=DIR [...]\"\nList of directory names to start the built-in directory walker.\nThe default value is the current working directory.\n\n.TP\n.B \"\\-\\-walker\\-skip=DIRS\"\nComma-separated list of directory names to skip during the directory walk.\nThe default value is \\fB.git,node_modules\\fR.\n\n.SS HISTORY\n.TP\n.BI \"\\-\\-history=\" \"HISTORY_FILE\"\nLoad search history from the specified file and update the file on completion.\nWhen enabled, \\fBCTRL\\-N\\fR and \\fBCTRL\\-P\\fR are automatically remapped to\n\\fBnext\\-history\\fR and \\fBprev\\-history\\fR.\n.TP\n.BI \"\\-\\-history\\-size=\" \"N\"\nMaximum number of entries in the history file (default: 1000). The file is\nautomatically truncated when the number of the lines exceeds the value.\n\n.RS\ne.g. \\fBgem list | fzf \\-\\-with\\-shell 'ruby \\-e' \\-\\-preview 'pp Gem::Specification.find_by_name({1})'\\fR\n.RE\n\n.SS SHELL INTEGRATION\n.TP\n.B \"\\-\\-bash\"\nPrint script to set up Bash shell integration\n\ne.g. \\fBeval \"$(fzf \\-\\-bash)\"\\fR\n\n.TP\n.B \"\\-\\-zsh\"\nPrint script to set up Zsh shell integration\n\ne.g. \\fBsource <(fzf \\-\\-zsh)\\fR\n\n.TP\n.B \"\\-\\-fish\"\nPrint script to set up Fish shell integration\n\ne.g. \\fBfzf \\-\\-fish | source\\fR\n\n.SS OTHERS\n.TP\n.B \"\\-\\-no\\-mouse\"\nDisable mouse\n.TP\n.B \"\\-\\-no\\-unicode\"\nUse ASCII characters instead of Unicode drawing characters to draw borders,\nthe spinner and the horizontal separator.\n\n.TP\n.B \"\\-\\-ambidouble\"\nSet this option if your terminal displays ambiguous width characters (e.g.\nbox-drawing characters for borders) as 2 columns.\n\n.SS HELP\n.TP\n.B \"\\-\\-version\"\nDisplay version information and exit\n.TP\n.B \"\\-\\-help\"\nShow help message\n.TP\n.B \"\\-\\-man\"\nShow man page\n\n.SH ENVIRONMENT VARIABLES\n.TP\n.B FZF_DEFAULT_COMMAND\nDefault command to use when input is a TTY device. On *nix systems, fzf runs\nthe command with \\fB$SHELL \\-c\\fR if \\fBSHELL\\fR is set, otherwise with \\fBsh\n\\-c\\fR, so in this case make sure that the command is POSIX-compliant.\n.TP\n.B FZF_DEFAULT_OPTS\nDefault options.\n.br\ne.g. \\fBexport FZF_DEFAULT_OPTS=\"\\-\\-layout=reverse \\-\\-border \\-\\-cycle\"\\fR\n.TP\n.B FZF_DEFAULT_OPTS_FILE\nThe location of the file that contains the default options.\n.br\ne.g. \\fBexport FZF_DEFAULT_OPTS_FILE=~/.fzfrc\\fR\n.TP\n.B FZF_API_KEY\nCan be used to require an API key when using \\fB\\-\\-listen\\fR option. If not set,\nno authentication will be required by the server. You can set this value if\nyou need to protect against DNS rebinding and privilege escalation attacks.\n\n.SH EXIT STATUS\n.BR 0 \"      Normal exit\"\n.br\n.BR 1 \"      No match\"\n.br\n.BR 2 \"      Error\"\n.br\n.BR 126 \"    Permission denied error from \\fBbecome\\fR action\"\n.br\n.BR 127 \"    Invalid shell command for \\fBbecome\\fR action\"\n.br\n.BR 130 \"    Interrupted with \\fBCTRL\\-C\\fR or \\fBESC\\fR\"\n\n.SH FIELD INDEX EXPRESSION\n\nA field index expression can be a non-zero integer or a range expression\n([BEGIN]..[END]). \\fB\\-\\-nth\\fR and \\fB\\-\\-with\\-nth\\fR take a comma-separated list\nof field index expressions.\n\n.SS Examples\n.BR 1 \"      The 1st field\"\n.br\n.BR 2 \"      The 2nd field\"\n.br\n.BR \\-1 \"     The last field\"\n.br\n.BR \\-2 \"     The 2nd to last field\"\n.br\n.BR 3..5 \"   From the 3rd field to the 5th field\"\n.br\n.BR 2.. \"    From the 2nd field to the last field\"\n.br\n.BR ..\\-3 \"   From the 1st field to the 3rd to the last field\"\n.br\n.BR .. \"     All the fields\"\n.br\n\n.SH ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES\n\nfzf exports the following environment variables to its child processes.\n\n.BR FZF_LINES \"           Number of lines fzf takes up excluding padding and margin\"\n.br\n.BR FZF_COLUMNS \"         Number of columns fzf takes up excluding padding and margin\"\n.br\n.BR FZF_DIRECTION \"       Direction of the list (\\fBup\\fR or \\fBdown\\fR)\"\n.br\n.BR FZF_TOTAL_COUNT \"     Total number of items\"\n.br\n.BR FZF_MATCH_COUNT \"     Number of matched items\"\n.br\n.BR FZF_SELECT_COUNT \"    Number of selected items\"\n.br\n.BR FZF_POS \"             Vertical position of the cursor in the list starting from 1\"\n.br\n.BR FZF_WRAP \"            The line wrapping mode (char, word) when enabled\"\n.br\n.BR FZF_QUERY \"           Current query string\"\n.br\n.BR FZF_INPUT_STATE \"     Current input state (enabled, disabled, hidden)\"\n.br\n.BR FZF_NTH \"             Current \\-\\-nth option\"\n.br\n.BR FZF_WITH_NTH \"        Current \\-\\-with\\-nth option\"\n.br\n.BR FZF_PROMPT \"          Prompt string\"\n.br\n.BR FZF_GHOST \"           Ghost string\"\n.br\n.BR FZF_POINTER \"         Pointer string\"\n.br\n.BR FZF_PREVIEW_LABEL \"   Preview label string\"\n.br\n.BR FZF_BORDER_LABEL \"    Border label string\"\n.br\n.BR FZF_LIST_LABEL \"      List label string\"\n.br\n.BR FZF_INPUT_LABEL \"     Input label string\"\n.br\n.BR FZF_HEADER_LABEL \"    Header label string\"\n.br\n.BR FZF_ACTION \"          The name of the last action performed\"\n.br\n.BR FZF_KEY \"             The name of the last key pressed\"\n.br\n.BR FZF_PORT \"            Port number when \\-\\-listen option is used\"\n.br\n.BR FZF_SOCK \"            Unix socket path when \\-\\-listen option is used\"\n.br\n.BR FZF_PREVIEW_TOP \"     Top position of the preview window\"\n.br\n.BR FZF_PREVIEW_LEFT \"    Left position of the preview window\"\n.br\n.BR FZF_PREVIEW_LINES \"   Number of lines in the preview window\"\n.br\n.BR FZF_PREVIEW_COLUMNS \" Number of columns in the preview window\"\n.br\n.BR FZF_RAW \"             Only in raw mode. 1 if the current item matches, 0 otherwise\"\n\n.SH EXTENDED SEARCH MODE\n\nUnless specified otherwise, fzf will start in \"extended\\-search mode\". In this\nmode, you can specify multiple patterns delimited by spaces, such as: \\fB'wild\n^music .mp3$ sbtrkt !rmx\\fR\n\nYou can prepend a backslash to a space (\\fB\\\\ \\fR) to match a literal space\ncharacter.\n\n.SS Exact\\-match (quoted)\nA term that is prefixed by a single-quote character (\\fB'\\fR) is interpreted as\nan \"exact\\-match\" (or \"non\\-fuzzy\") term. fzf will search for the exact\noccurrences of the string.\n\n.SS Anchored\\-match\nA term can be prefixed by \\fB^\\fR, or suffixed by \\fB$\\fR to become an\nanchored-match term. Then fzf will search for the lines that start with or end\nwith the given string. An anchored-match term is also an exact-match term.\n\n.SS Exact\\-boundary\\-match (quoted both ends)\nA single-quoted term is interpreted as an \"exact\\-boundary\\-match\". fzf will\nsearch for the exact occurrences of the string with both ends at the word\nboundaries. Unlike in regular expressions, this also sees an underscore as\na word boundary. But the words around underscores are ranked lower and appear\nlater in the result than the other words around the other types of word\nboundaries.\n\n1. xxx foo xxx (highest score)\n.br\n2. xxx foo_xxx\n.br\n3. xxx_foo xxx\n.br\n4. xxx_foo_xxx (lowest score)\n\n.SS Negation\nIf a term is prefixed by \\fB!\\fR, fzf will exclude the lines that satisfy the\nterm from the result. In this case, fzf performs exact match by default.\n\n.SS Exact\\-match by default\nIf you don't prefer fuzzy matching and do not wish to \"quote\" (prefixing with\n\\fB'\\fR) every word, start fzf with \\fB\\-e\\fR or \\fB\\-\\-exact\\fR option. Note that\nwhen \\fB\\-\\-exact\\fR is set, \\fB'\\fR\\-prefix \"unquotes\" the term.\n\n.SS OR operator\nA single bar character term acts as an OR operator. For example, the following\nquery matches entries that start with \\fBcore\\fR and end with either \\fBgo\\fR,\n\\fBrb\\fR, or \\fBpy\\fR.\n\ne.g. \\fB^core go$ | rb$ | py$\\fR\n\n.SH KEY/EVENT BINDINGS\n\\fB\\-\\-bind\\fR option allows you to bind \\fBa key\\fR or \\fBan event\\fR to one or\nmore \\fBactions\\fR. You can use it to customize key bindings or implement\ndynamic behaviors.\n\n\\fB\\-\\-bind\\fR takes a comma-separated list of binding expressions. Each binding\nexpression is \\fBKEY:ACTION\\fR or \\fBEVENT:ACTION\\fR. You can bind actions to\nmultiple keys and events by writing comma-separated list of keys and events\nbefore the colon. e.g. \\fBKEY1,KEY2,EVENT1,EVENT2:ACTION\\fR.\n\ne.g.\n     \\fBfzf \\-\\-bind=ctrl\\-j:accept,ctrl\\-k:kill\\-line\n\n     # Load 'ps \\-ef' output on start and reload it on CTRL\\-R\n     fzf \\-\\-bind 'start,ctrl\\-r:reload:ps \\-ef'\\fR\n\n.SS AVAILABLE KEYS:    (SYNONYMS)\n\\fIctrl\\-[a\\-z]\\fR\n.br\n\\fIctrl\\-space\\fR\n.br\n\\fIctrl\\-delete\\fR\n.br\n\\fIctrl\\-\\\\\\fR\n.br\n\\fIctrl\\-]\\fR\n.br\n\\fIctrl\\-^\\fR         (\\fIctrl\\-6\\fR)\n.br\n\\fIctrl\\-/\\fR         (\\fIctrl\\-_\\fR)\n.br\n\\fIctrl\\-alt\\-[a\\-z]\\fR (\\fIctrl\\-alt\\-h\\fR is \\fIctrl\\-alt\\-backspace\\fR on non-Windows)\n.br\n\\fIalt\\-[*]\\fR        (Any case-sensitive single character is allowed)\n.br\n\\fIf[1\\-12]\\fR\n.br\n\\fIenter\\fR          (\\fIreturn\\fR \\fIctrl\\-m\\fR)\n.br\n\\fIspace\\fR\n.br\n\\fIbackspace\\fR      (\\fIbspace\\fR \\fIbs\\fR)\n.br\n\\fIalt\\-up\\fR\n.br\n\\fIalt\\-down\\fR\n.br\n\\fIalt\\-left\\fR\n.br\n\\fIalt\\-right\\fR\n.br\n\\fIalt\\-home\\fR\n.br\n\\fIalt\\-end\\fR\n.br\n\\fIalt\\-backspace\\fR  (\\fIalt\\-bspace\\fR \\fIalt\\-bs\\fR)\n.br\n\\fIalt\\-delete\\fR\n.br\n\\fIalt\\-page\\-up\\fR\n.br\n\\fIalt\\-page\\-down\\fR\n.br\n\\fIalt\\-enter\\fR\n.br\n\\fIalt\\-space\\fR\n.br\n\\fItab\\fR\n.br\n\\fIshift\\-tab\\fR      (\\fIbtab\\fR)\n.br\n\\fIesc\\fR\n.br\n\\fIdelete\\fR         (\\fIdel\\fR)\n.br\n\\fIup\\fR\n.br\n\\fIdown\\fR\n.br\n\\fIleft\\fR\n.br\n\\fIright\\fR\n.br\n\\fIhome\\fR\n.br\n\\fIend\\fR\n.br\n\\fIinsert\\fR\n.br\n\\fIpage\\-up\\fR        (\\fIpgup\\fR)\n.br\n\\fIpage\\-down\\fR      (\\fIpgdn\\fR)\n.br\n\\fIctrl\\-up\\fR\n.br\n\\fIctrl\\-down\\fR\n.br\n\\fIctrl\\-left\\fR\n.br\n\\fIctrl\\-right\\fR\n.br\n\\fIctrl\\-home\\fR\n.br\n\\fIctrl\\-end\\fR\n.br\n\\fIctrl\\-backspace\\fR  (\\fIctrl\\-bspace\\fR \\fIctrl\\-bs\\fR)\n.br\n\\fIctrl\\-delete\\fR\n.br\n\\fIctrl\\-page\\-up\\fR\n.br\n\\fIctrl\\-page\\-down\\fR\n.br\n\\fIshift\\-up\\fR\n.br\n\\fIshift\\-down\\fR\n.br\n\\fIshift\\-left\\fR\n.br\n\\fIshift\\-right\\fR\n.br\n\\fIshift\\-home\\fR\n.br\n\\fIshift\\-end\\fR\n.br\n\\fIshift\\-delete\\fR\n.br\n\\fIshift\\-page\\-up\\fR\n.br\n\\fIshift\\-page\\-down\\fR\n.br\n\\fIalt\\-shift\\-up\\fR\n.br\n\\fIalt\\-shift\\-down\\fR\n.br\n\\fIalt\\-shift\\-left\\fR\n.br\n\\fIalt\\-shift\\-right\\fR\n.br\n\\fIalt\\-shift\\-home\\fR\n.br\n\\fIalt\\-shift\\-end\\fR\n.br\n\\fIalt\\-shift\\-delete\\fR\n.br\n\\fIalt\\-shift\\-page\\-up\\fR\n.br\n\\fIalt\\-shift\\-page\\-down\\fR\n.br\n\\fIctrl\\-alt\\-up\\fR\n.br\n\\fIctrl\\-alt\\-down\\fR\n.br\n\\fIctrl\\-alt\\-left\\fR\n.br\n\\fIctrl\\-alt\\-right\\fR\n.br\n\\fIctrl\\-alt\\-home\\fR\n.br\n\\fIctrl\\-alt\\-end\\fR\n.br\n\\fIctrl\\-alt\\-backspace\\fR  (\\fIctrl\\-alt\\-bspace\\fR \\fIctrl\\-alt\\-bs\\fR) (\\fIctrl\\-alt\\-h\\fR (non-Windows))\n.br\n\\fIctrl\\-alt\\-delete\\fR\n.br\n\\fIctrl\\-alt\\-page\\-up\\fR\n.br\n\\fIctrl\\-alt\\-page\\-down\\fR\n.br\n\\fIctrl\\-shift\\-up\\fR\n.br\n\\fIctrl\\-shift\\-down\\fR\n.br\n\\fIctrl\\-shift\\-left\\fR\n.br\n\\fIctrl\\-shift\\-right\\fR\n.br\n\\fIctrl\\-shift\\-home\\fR\n.br\n\\fIctrl\\-shift\\-end\\fR\n.br\n\\fIctrl\\-shift\\-delete\\fR\n.br\n\\fIctrl\\-shift\\-page\\-up\\fR\n.br\n\\fIctrl\\-shift\\-page\\-down\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-up\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-down\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-left\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-right\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-home\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-end\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-delete\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-page\\-up\\fR\n.br\n\\fIctrl\\-alt\\-shift\\-page\\-down\\fR\n.br\n\\fIleft\\-click\\fR\n.br\n\\fIright\\-click\\fR\n.br\n\\fIdouble\\-click\\fR\n.br\n\\fIscroll\\-up\\fR\n.br\n\\fIscroll\\-down\\fR\n.br\n\\fIpreview\\-scroll\\-up\\fR\n.br\n\\fIpreview\\-scroll\\-down\\fR\n.br\n\\fIshift\\-left\\-click\\fR\n.br\n\\fIshift\\-right\\-click\\fR\n.br\n\\fIshift\\-scroll\\-up\\fR\n.br\n\\fIshift\\-scroll\\-down\\fR\n.br\nor any single character\n\nNote that some terminal emulators may not support \\fIctrl-*\\fR bindings.\n\n.SS AVAILABLE EVENTS:\n\\fIstart\\fR\n.RS\nTriggered only once when fzf finder starts. Since fzf consumes the input stream\nasynchronously, the input list is not available unless you use \\fI\\-\\-sync\\fR.\n\ne.g.\n     \\fB# Move cursor to the last item and select all items\n     seq 1000 | fzf \\-\\-multi \\-\\-sync \\-\\-bind start:last+select\\-all\\fR\n.RE\n\n\\fIload\\fR\n.RS\nTriggered when the input stream is complete and the initial processing of the\nlist is complete.\n\ne.g.\n     \\fB# Change the prompt to \"loaded\" when the input stream is complete\n     (seq 10; sleep 1; seq 11 20) | fzf \\-\\-prompt 'Loading> ' \\-\\-bind 'load:change\\-prompt:Loaded> '\\fR\n.RE\n\n\\fIresize\\fR\n.RS\nTriggered when the terminal size is changed.\n\ne.g.\n     \\fBfzf \\-\\-bind 'resize:transform\\-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\\fR\n.RE\n\n\\fIresult\\fR\n.RS\nTriggered when the filtering for the current query is complete and the result list is ready.\n\ne.g.\n     \\fB# Put the cursor on the second item when the query string is empty\n     # * Note that you can't use 'change' event in this case because the second position may not be available\n     fzf \\-\\-sync \\-\\-bind 'result:transform:[[ \\-z {q} ]] && echo \"pos(2)\"'\\fR\n.RE\n\\fIchange\\fR\n.RS\nTriggered whenever the query string is changed\n\ne.g.\n     \\fB# Move cursor to the first entry whenever the query is changed\n     fzf \\-\\-bind change:first\\fR\n.RE\n\\fIfocus\\fR\n.RS\nTriggered when the focus changes due to a vertical cursor movement or a search\nresult update.\n\ne.g.\n     \\fBfzf \\-\\-bind 'focus:transform\\-preview\\-label:echo [ {} ]' \\-\\-preview 'cat {}'\n\n     # Any action bound to the event runs synchronously and thus can make the interface sluggish\n     # e.g. lolcat isn't one of the fastest programs, and every cursor movement in\n     #      fzf will be noticeably affected by its execution time\n     fzf \\-\\-bind 'focus:transform\\-preview\\-label:echo [ {} ] | lolcat \\-f' \\-\\-preview 'cat {}'\n\n     # Beware not to introduce an infinite loop\n     seq 10 | fzf \\-\\-bind 'focus:up' \\-\\-cycle\\fR\n.RE\n\\fImulti\\fR\n.RS\nTriggered when the multi\\-selection has changed.\n.RE\n\n\\fIone\\fR\n.RS\nTriggered when there's only one match. \\fBone:accept\\fR binding is comparable\nto \\fB\\-\\-select\\-1\\fR option, but the difference is that \\fB\\-\\-select\\-1\\fR is only\neffective before the interactive finder starts but \\fBone\\fR event is triggered\nby the interactive finder.\n\ne.g.\n     \\fB# Automatically select the only match\n     seq 10 | fzf \\-\\-bind one:accept\\fR\n.RE\n\n\\fIzero\\fR\n.RS\nTriggered when there's no match. \\fBzero:abort\\fR binding is comparable to\n\\fB\\-\\-exit\\-0\\fR option, but the difference is that \\fB\\-\\-exit\\-0\\fR is only\neffective before the interactive finder starts but \\fBzero\\fR event is\ntriggered by the interactive finder.\n\ne.g.\n     \\fB# Reload the candidate list when there's no match\n     echo $RANDOM | fzf \\-\\-bind 'zero:reload(echo $RANDOM)+clear\\-query' \\-\\-height 3\\fR\n.RE\n\n\\fIbackward\\-eof\\fR\n.RS\nTriggered when the query string is already empty and you try to delete it\nbackward.\n\ne.g.\n     \\fBfzf \\-\\-bind backward\\-eof:abort\\fR\n.RE\n\n\\fIjump\\fR\n.RS\nTriggered when successfully jumped to the target item in \\fBjump\\fR mode.\n\ne.g.\n     \\fBfzf \\-\\-bind space:jump,jump:accept\\fR\n.RE\n\n\\fIjump\\-cancel\\fR\n.RS\nTriggered when \\fBjump\\fR mode is cancelled.\n\ne.g.\n     \\fBfzf \\-\\-bind space:jump,jump:accept,jump\\-cancel:abort\\fR\n.RE\n\n\\fIclick\\-header\\fR\n.RS\nTriggered when a mouse click occurs within the header. Sets\n\\fBFZF_CLICK_HEADER_LINE\\fR and \\fBFZF_CLICK_HEADER_COLUMN\\fR environment\nvariables starting from 1. It optionally sets \\fBFZF_CLICK_HEADER_WORD\\fR and\n\\fBFZF_CLICK_HEADER_NTH\\fR if clicked on a word.\n\ne.g.\n     \\fB# Click on the header line to limit search scope\n     ps \\-ef | fzf \\-\\-style full \\-\\-layout reverse \\-\\-header\\-lines 1 \\\\\n                  \\-\\-header\\-lines\\-border bottom \\-\\-no\\-list\\-border \\\\\n                  \\-\\-color fg:dim,nth:regular \\\\\n                  \\-\\-bind 'click\\-header:transform\\-nth(\n                            echo $FZF_CLICK_HEADER_NTH\n                          )+transform\\-prompt(\n                            echo \"$FZF_CLICK_HEADER_WORD> \"\n                          )'\\fR\n.RE\n\n\\fIclick\\-footer\\fR\n.RS\nTriggered when a mouse click occurs within the footer. Sets\n\\fBFZF_CLICK_FOOTER_LINE\\fR and \\fBFZF_CLICK_FOOTER_COLUMN\\fR environment\nvariables starting from 1. It optionally sets \\fBFZF_CLICK_FOOTER_WORD\\fR\nif clicked on a word.\n.RE\n\n.SS AVAILABLE ACTIONS:\nA key or an event can be bound to one or more of the following actions.\n\n  \\fBACTION:                      DEFAULT BINDINGS (NOTES):\n    \\fBabort\\fR                        \\fIctrl\\-c  ctrl\\-g  ctrl\\-q  esc\\fR\n    \\fBaccept\\fR                       \\fIenter   double\\-click\\fR\n    \\fBaccept\\-non\\-empty\\fR             (same as \\fBaccept\\fR except that it prevents fzf from exiting without selection)\n    \\fBaccept\\-or\\-print\\-query\\fR        (same as \\fBaccept\\fR except that it prints the query when there's no match)\n    \\fBbackward\\-char\\fR                \\fIctrl\\-b  left\\fR\n    \\fBbackward\\-delete\\-char\\fR         \\fIctrl\\-h ctrl\\-bspace bspace\\fR\n    \\fBbackward\\-delete\\-char/eof\\fR     (same as \\fBbackward\\-delete\\-char\\fR except aborts fzf if query is empty)\n    \\fBbackward\\-kill\\-subword\\fR\n    \\fBbackward\\-kill\\-word\\fR           \\fIalt\\-bs\\fR\n    \\fBbackward\\-subword\\fR\n    \\fBbackward\\-word\\fR                \\fIalt\\-b   shift\\-left\\fR\n    \\fBbecome(...)\\fR                  (replace fzf process with the specified command; see below for the details)\n    \\fBbeginning\\-of\\-line\\fR            \\fIctrl\\-a  home\\fR\n    \\fBbell\\fR                         (ring the terminal bell)\n    \\fBbest\\fR                         (move to the best match; same as \\fBfirst\\fR if raw mode is disabled)\n    \\fBbg\\-cancel\\fR                    (cancel background transform processes)\n    \\fBcancel\\fR                       (clear query string if not empty, abort fzf otherwise)\n    \\fBchange\\-border\\-label(...)\\fR     (change \\fB\\-\\-border\\-label\\fR to the given string)\n    \\fBchange\\-ghost(...)\\fR            (change ghost text to the given string)\n    \\fBchange\\-header(...)\\fR           (change header to the given string; doesn't affect \\fB\\-\\-header\\-lines\\fR)\n    \\fBchange\\-header\\-lines(N)\\fR       (change the number of \\fB\\-\\-header\\-lines\\fR)\n    \\fBchange\\-header\\-label(...)\\fR     (change \\fB\\-\\-header\\-label\\fR to the given string)\n    \\fBchange\\-input\\-label(...)\\fR      (change \\fB\\-\\-input\\-label\\fR to the given string)\n    \\fBchange\\-list\\-label(...)\\fR       (change \\fB\\-\\-list\\-label\\fR to the given string)\n    \\fBchange\\-multi\\fR                 (enable multi-select mode with no limit)\n    \\fBchange\\-multi(...)\\fR            (enable multi-select mode with a limit or disable it with 0)\n    \\fBchange\\-nth(...)\\fR              (change \\fB\\-\\-nth\\fR option; rotate through the multiple options separated by '|')\n    \\fBchange\\-with\\-nth(...)\\fR         (change \\fB\\-\\-with\\-nth\\fR option; rotate through the multiple options separated by '|')\n    \\fBchange\\-pointer(...)\\fR          (change \\fB\\-\\-pointer\\fR option)\n    \\fBchange\\-preview(...)\\fR          (change \\fB\\-\\-preview\\fR option)\n    \\fBchange\\-preview\\-label(...)\\fR    (change \\fB\\-\\-preview\\-label\\fR to the given string)\n    \\fBchange\\-preview\\-window(...)\\fR   (change \\fB\\-\\-preview\\-window\\fR option; rotate through the multiple option sets separated by '|')\n    \\fBchange\\-prompt(...)\\fR           (change prompt to the given string)\n    \\fBchange\\-query(...)\\fR            (change query string to the given string)\n    \\fBclear\\-screen\\fR                 \\fIctrl\\-l\\fR\n    \\fBclear\\-multi\\fR                  (clear multi\\-selection)\n    \\fBclose\\fR                        (close preview window if open, abort fzf otherwise)\n    \\fBclear\\-query\\fR                  (clear query string)\n    \\fBdelete\\-char\\fR                  \\fIdel\\fR\n    \\fBdelete\\-char/eof\\fR              \\fIctrl\\-d\\fR (same as \\fBdelete\\-char\\fR except aborts fzf if query is empty)\n    \\fBdeselect\\fR\n    \\fBdeselect\\-all\\fR                 (deselect all matches; to also clear non-matching selections, use \\fBclear\\-multi\\fR)\n    \\fBdisable\\-raw\\fR                  (disable raw mode)\n    \\fBdisable\\-search\\fR               (disable search functionality)\n    \\fBdown\\fR                         \\fIctrl\\-j  down\\fR\n    \\fBdown\\-match\\fR                   \\fIctrl\\-n\\fR  \\fIalt\\-down\\fR (move to the match below the cursor)\n    \\fBdown\\-selected\\fR                (move to the selected item below the cursor)\n    \\fBenable\\-raw\\fR                   (enable raw mode)\n    \\fBenable\\-search\\fR                (enable search functionality)\n    \\fBend\\-of\\-line\\fR                  \\fIctrl\\-e  end\\fR\n    \\fBexclude\\fR                      (exclude the current item from the result)\n    \\fBexclude\\-multi\\fR                (exclude the selected items or the current item from the result)\n    \\fBexecute(...)\\fR                 (see below for the details)\n    \\fBexecute\\-silent(...)\\fR          (see below for the details)\n    \\fBfirst\\fR                        (move to the first match; same as \\fBpos(1)\\fR)\n    \\fBforward\\-char\\fR                 \\fIctrl\\-f  right\\fR\n    \\fBforward\\-subword\\fR\n    \\fBforward\\-word\\fR                 \\fIalt\\-f   shift\\-right\\fR\n    \\fBignore\\fR\n    \\fBjump\\fR                         (EasyMotion-like 2-keystroke movement)\n    \\fBkill\\-line\\fR\n    \\fBkill\\-subword\\fR\n    \\fBkill\\-word\\fR                    \\fIalt\\-d\\fR\n    \\fBlast\\fR                         (move to the last match; same as \\fBpos(\\-1)\\fR)\n    \\fBnext\\-history\\fR                 (\\fIctrl\\-n\\fR on \\fB\\-\\-history\\fR)\n    \\fBnext\\-selected\\fR                (synonym to \\fBdown\\-selected\\fR)\n    \\fBpage\\-down\\fR                    \\fIpgdn\\fR\n    \\fBpage\\-up\\fR                      \\fIpgup\\fR\n    \\fBhalf\\-page\\-down\\fR\n    \\fBhalf\\-page\\-up\\fR\n    \\fBhide\\-header\\fR\n    \\fBhide\\-input\\fR\n    \\fBhide\\-preview\\fR\n    \\fBoffset\\-down\\fR                  (similar to CTRL\\-E of Vim)\n    \\fBoffset\\-up\\fR                    (similar to CTRL\\-Y of Vim)\n    \\fBoffset\\-middle\\fR                (place the current item is in the middle of the screen)\n    \\fBpos(...)\\fR                     (move cursor to the numeric position; negative number to count from the end)\n    \\fBprev\\-history\\fR                 (\\fIctrl\\-p\\fR on \\fB\\-\\-history\\fR)\n    \\fBprev\\-selected\\fR                (synonym to \\fBup\\-selected\\fR)\n    \\fBpreview(...)\\fR                 (see below for the details)\n    \\fBpreview\\-down\\fR                 \\fIshift\\-down\\fR\n    \\fBpreview\\-up\\fR                   \\fIshift\\-up\\fR\n    \\fBpreview\\-page\\-down\\fR\n    \\fBpreview\\-page\\-up\\fR\n    \\fBpreview\\-half\\-page\\-down\\fR\n    \\fBpreview\\-half\\-page\\-up\\fR\n    \\fBpreview\\-bottom\\fR\n    \\fBpreview\\-top\\fR\n    \\fBprint(...)\\fR                   (add string to the output queue and print on normal exit)\n    \\fBput\\fR                          (put the character to the prompt)\n    \\fBput(...)\\fR                     (put the given string to the prompt)\n    \\fBrefresh\\-preview\\fR\n    \\fBrebind(...)\\fR                  (rebind bindings after \\fBunbind\\fR)\n    \\fBreload(...)\\fR                  (see below for the details)\n    \\fBreload\\-sync(...)\\fR             (see below for the details)\n    \\fBreplace\\-query\\fR                (replace query string with the current selection)\n    \\fBsearch(...)\\fR                  (trigger fzf search with the given string)\n    \\fBselect\\fR\n    \\fBselect\\-all\\fR                   (select all matches)\n    \\fBshow\\-header\\fR\n    \\fBshow\\-input\\fR\n    \\fBshow\\-preview\\fR\n    \\fBtoggle\\fR                       (\\fIright\\-click\\fR)\n    \\fBtoggle\\-all\\fR                   (toggle all matches)\n    \\fBtoggle\\-in\\fR                    (\\fB\\-\\-layout=reverse*\\fR ? \\fBtoggle+up\\fR : \\fBtoggle+down\\fR)\n    \\fBtoggle\\-out\\fR                   (\\fB\\-\\-layout=reverse*\\fR ? \\fBtoggle+down\\fR : \\fBtoggle+up\\fR)\n    \\fBtoggle\\-bind\\fR\n    \\fBtoggle\\-header\\fR\n    \\fBtoggle\\-hscroll\\fR\n    \\fBtoggle\\-input\\fR\n    \\fBtoggle\\-multi\\-line\\fR\n    \\fBtoggle\\-preview\\fR\n    \\fBtoggle\\-preview\\-wrap\\fR\n    \\fBtoggle\\-preview\\-wrap\\-word\\fR\n    \\fBtoggle\\-raw\\fR                   (toggle raw mode for displaying non-matching items)\n    \\fBtoggle\\-search\\fR                (toggle search functionality)\n    \\fBtoggle\\-sort\\fR\n    \\fBtoggle\\-track\\fR                 (toggle global tracking option (\\fB\\-\\-track\\fR))\n    \\fBtoggle\\-track\\-current\\fR         (toggle tracking of the current item)\n    \\fBtoggle\\-wrap\\fR\n    \\fBtoggle\\-wrap\\-word\\fR             \\fIctrl\\-/\\fR  \\fIalt\\-/\\fR\n    \\fBtoggle+down\\fR                  \\fIctrl\\-i  (tab)\\fR\n    \\fBtoggle+up\\fR                    \\fIbtab    (shift\\-tab)\\fR\n    \\fBtrack\\-current\\fR                (track the current item; automatically disabled if focus changes)\n    \\fBtransform(...)\\fR               (transform states using the output of an external command)\n    \\fBtransform\\-border\\-label(...)\\fR  (transform border label using an external command)\n    \\fBtransform\\-ghost(...)\\fR         (transform ghost text using an external command)\n    \\fBtransform\\-header(...)\\fR        (transform header using an external command)\n    \\fBtransform\\-header\\-lines(...)\\fR  (transform the number of \\fB\\-\\-header\\-lines\\fR using an external command)\n    \\fBtransform\\-header\\-label(...)\\fR  (transform header label using an external command)\n    \\fBtransform\\-input\\-label(...)\\fR   (transform input label using an external command)\n    \\fBtransform\\-list\\-label(...)\\fR    (transform list label using an external command)\n    \\fBtransform\\-nth(...)\\fR           (transform nth using an external command)\n    \\fBtransform\\-with\\-nth(...)\\fR      (transform with-nth using an external command)\n    \\fBtransform\\-pointer(...)\\fR       (transform pointer using an external command)\n    \\fBtransform\\-preview\\-label(...)\\fR (transform preview label using an external command)\n    \\fBtransform\\-prompt(...)\\fR        (transform prompt string using an external command)\n    \\fBtransform\\-query(...)\\fR         (transform query string using an external command)\n    \\fBtransform\\-search(...)\\fR        (trigger fzf search with the output of an external command)\n    \\fBtrigger(...)\\fR                 (trigger actions bound to a comma-separated list of keys and events)\n    \\fBunbind(...)\\fR                  (unbind bindings)\n    \\fBunix\\-line\\-discard\\fR            \\fIctrl\\-u\\fR\n    \\fBunix\\-word\\-rubout\\fR             \\fIctrl\\-w\\fR\n    \\fBuntrack\\-current\\fR              (stop tracking the current item; no-op if global tracking is enabled)\n    \\fBup\\fR                           \\fIctrl\\-k  up\\fR\n    \\fBup\\-match\\fR                     \\fIctrl\\-p\\fR  \\fIalt\\-up\\fR (move to the match above the cursor)\n    \\fBup\\-selected\\fR                  (move to the selected item above the cursor)\n    \\fByank\\fR                         \\fIctrl\\-y\\fR\n\nEach \\fBtransform*\\fR action has a corresponding \\fBbg\\-transform*\\fR\nvariant that runs the command in the background.\n\n.SS ACTION COMPOSITION\n\nMultiple actions can be chained using \\fB+\\fR separator.\n\ne.g.\n     \\fBfzf \\-\\-multi \\-\\-bind 'ctrl\\-a:select\\-all+accept'\\fR\n     \\fBfzf \\-\\-multi \\-\\-bind 'ctrl\\-a:select\\-all' \\-\\-bind 'ctrl\\-a:+accept'\\fR\n\nAny action after a terminal action that exits fzf, such as \\fBaccept\\fR or\n\\fBabort\\fR, is ignored.\n\n.SS ACTION ARGUMENT\n\nAn action denoted with \\fB(...)\\fR suffix takes an argument.\n\ne.g.\n     \\fBfzf \\-\\-bind 'ctrl\\-a:change\\-prompt(NewPrompt> )'\\fR\n     \\fBfzf \\-\\-bind 'ctrl\\-v:preview(cat {})' \\-\\-preview\\-window hidden\\fR\n\nIf the argument contains parentheses, fzf may fail to parse the expression. In\nthat case, you can use any of the following alternative notations to avoid\nparse errors.\n\n    \\fBaction\\-name[...]\\fR\n    \\fBaction\\-name{...}\\fR\n    \\fBaction\\-name<...>\\fR\n    \\fBaction\\-name~...~\\fR\n    \\fBaction\\-name!...!\\fR\n    \\fBaction\\-name@...@\\fR\n    \\fBaction\\-name#...#\\fR\n    \\fBaction\\-name$...$\\fR\n    \\fBaction\\-name%...%\\fR\n    \\fBaction\\-name^...^\\fR\n    \\fBaction\\-name&...&\\fR\n    \\fBaction\\-name*...*\\fR\n    \\fBaction\\-name;...;\\fR\n    \\fBaction\\-name/.../\\fR\n    \\fBaction\\-name|...|\\fR\n    \\fBaction\\-name:...\\fR\n.RS\nThe last one is the special form that frees you from parse errors as it does\nnot expect the closing character. The catch is that it should be the last one\nin the comma-separated list of key-action pairs.\n.RE\n\n.SS COMMAND EXECUTION\n\nWith \\fBexecute(...)\\fR action, you can execute arbitrary commands without\nleaving fzf. For example, you can turn fzf into a simple file browser by\nbinding \\fBenter\\fR key to \\fBless\\fR command like follows.\n\n    \\fBfzf \\-\\-bind \"enter:execute(less {})\"\\fR\n\nYou can use the same placeholder expressions as in \\fB\\-\\-preview\\fR.\n\nfzf switches to the alternate screen when executing a command. However, if the\ncommand is expected to complete quickly, and you are not interested in its\noutput, you might want to use \\fBexecute\\-silent\\fR instead, which silently\nexecutes the command without the switching. Note that fzf will not be\nresponsive until the command is complete. For asynchronous execution, start\nyour command as a background process (i.e. appending \\fB&\\fR).\n\nOn *nix systems, fzf runs the command with \\fB$SHELL \\-c\\fR if \\fBSHELL\\fR is\nset, otherwise with \\fBsh \\-c\\fR, so in this case make sure that the command is\nPOSIX-compliant.\n\n\\fBbecome(...)\\fR action is similar to \\fBexecute(...)\\fR, but it replaces the\ncurrent fzf process with the specified command using \\fBexecve(2)\\fR system\ncall.\n\n    \\fBfzf \\-\\-bind \"enter:become(vim {})\"\\fR\n\n.SS RELOAD INPUT\n\n\\fBreload(...)\\fR action is used to dynamically update the input list\nwithout restarting fzf. It takes the same command template with placeholder\nexpressions as \\fBexecute(...)\\fR.\n\nSee \\fIhttps://github.com/junegunn/fzf/issues/1750\\fR for more info.\n\ne.g.\n     \\fB# Update the list of processes by pressing CTRL\\-R\n     ps \\-ef | fzf \\-\\-bind 'ctrl\\-r:reload(ps \\-ef)' \\-\\-header 'Press CTRL\\-R to reload' \\\\\n                  \\-\\-header\\-lines=1 \\-\\-layout=reverse\n\n     # Integration with ripgrep\n     RG_PREFIX=\"rg \\-\\-column \\-\\-line\\-number \\-\\-no\\-heading \\-\\-color=always \\-\\-smart\\-case \"\n     INITIAL_QUERY=\"foobar\"\n     FZF_DEFAULT_COMMAND=\"$RG_PREFIX '$INITIAL_QUERY'\" \\\\\n       fzf \\-\\-bind \"change:reload:$RG_PREFIX {q} || true\" \\\\\n           \\-\\-ansi \\-\\-disabled \\-\\-query \"$INITIAL_QUERY\"\\fR\n\n\\fBreload\\-sync(...)\\fR is a synchronous version of \\fBreload\\fR that replaces\nthe list only when the command is complete. This is useful when the command\ntakes a while to produce the initial output and you don't want fzf to run\nagainst an empty list while the command is running.\n\n\ne.g.\n     \\fB# You can still filter and select entries from the initial list for 3 seconds\n     seq 100 | fzf \\-\\-bind 'load:reload\\-sync(sleep 3; seq 1000)+unbind(load)'\\fR\n\n.SS TRANSFORM ACTIONS\n\nActions with \\fBtransform\\-\\fR prefix are used to transform the states of fzf\nusing the output of an external command. The output of these commands are\nexpected to be a single line of text.\n\ne.g.\n    \\fBfzf \\-\\-bind 'focus:transform\\-header:file \\-\\-brief {}'\\fR\n\n\\fBtransform(...)\\fR action runs an external command that should print a series\nof actions to be performed. The output should be in the same format as the\npayload of HTTP POST request to the \\fB\\-\\-listen\\fR server.\n\ne.g.\n    \\fB# Disallow selecting an empty line\n    printf \"1. Hello\\\\n2. Goodbye\\\\n\\\\n3. Exit\" |\n      fzf \\-\\-height '~100%' \\-\\-reverse \\-\\-header 'Select one' \\\\\n          \\-\\-bind 'enter:transform:[[ \\-n {} ]] &&\n                    echo accept ||\n                    echo \"change\\-header:Invalid selection\"'\n    \\fR\n\nA common mistake when writing a \\fBtransform\\fR action is not escaping\nplaceholder expressions when passing them back to fzf. In the following\nexample, if you don't escape \\fB{}\\fR, fzf will immediately replace it with the\nsingle-quoted string of the current item. This causes single quotes to appear\nin the header and footer, and the script will break if any item contains\ndouble-quote characters.\n\n    \\fBfzf \\-\\-bind 'focus:transform:[[ $FZF_ACTION =~ up ]] &&\n                echo \"change\\-header()+transform\\-footer:echo \\\\{}\" ||\n                echo \"change\\-footer()+transform\\-header:echo \\\\{}\"'\\fR\n\n.SS TRANSFORM IN THE BACKGROUND\n\nTransform actions are synchronous, meaning fzf becomes unresponsive while the\ncommand runs. To avoid this, each \\fBtransform*\\fR action has a corresponding\n\\fBbg\\-transform*\\fR variant that runs in the background. Unless you need to\nchain multiple transform actions where later ones depend on earlier results,\nprefer using the \\fBbg\\fR variant. To cancel currently running background\ntransform processes, use \\fBbg\\-cancel\\fR action.\n\n.SS PREVIEW BINDING\n\nWith \\fBpreview(...)\\fR action, you can specify multiple different preview\ncommands in addition to the default preview command given by \\fB\\-\\-preview\\fR\noption.\n\ne.g.\n     # Default preview command with an extra preview binding\n     fzf \\-\\-preview 'file {}' \\-\\-bind '?:preview:cat {}'\n\n     # A preview binding with no default preview command\n     # (Preview window is initially empty)\n     fzf \\-\\-bind '?:preview:cat {}'\n\n     # Preview window hidden by default, it appears when you first hit '?'\n     fzf \\-\\-bind '?:preview:cat {}' \\-\\-preview\\-window hidden\n\n.SS CHANGE PREVIEW WINDOW ATTRIBUTES\n\n\\fBchange\\-preview\\-window\\fR action can be used to change the properties of the\npreview window. Unlike the \\fB\\-\\-preview\\-window\\fR option, you can specify\nmultiple sets of options separated by '|' characters.\n\ne.g.\n     # Rotate through the options using CTRL\\-/\n     fzf \\-\\-preview 'cat {}' \\-\\-bind 'ctrl\\-/:change\\-preview\\-window(right,70%|down,40%,border\\-horizontal|hidden|right)'\n\n     # The default properties given by `\\-\\-preview\\-window` are inherited, so an empty string in the list is interpreted as the default\n     fzf \\-\\-preview 'cat {}' \\-\\-preview\\-window 'right,40%,border\\-left' \\-\\-bind 'ctrl\\-/:change\\-preview\\-window(70%|down,border\\-top|hidden|)'\n\n     # This is equivalent to toggle\\-preview action\n     fzf \\-\\-preview 'cat {}' \\-\\-bind 'ctrl\\-/:change\\-preview\\-window(hidden|)'\n\n.SH AUTHOR\nJunegunn Choi (\\fIjunegunn.c@gmail.com\\fR)\n\n.SH SEE ALSO\n.B Project homepage:\n.RS\n.I https://github.com/junegunn/fzf\n.RE\n.br\n\n.br\n.B Extra Vim plugin:\n.RS\n.I https://github.com/junegunn/fzf.vim\n.RE\n\n.SH LICENSE\nMIT\n"
  },
  {
    "path": "plugin/fzf.vim",
    "content": "\" Copyright (c) 2013-2026 Junegunn Choi\n\"\n\" MIT License\n\"\n\" Permission is hereby granted, free of charge, to any person obtaining\n\" a copy of this software and associated documentation files (the\n\" \"Software\"), to deal in the Software without restriction, including\n\" without limitation the rights to use, copy, modify, merge, publish,\n\" distribute, sublicense, and/or sell copies of the Software, and to\n\" permit persons to whom the Software is furnished to do so, subject to\n\" the following conditions:\n\"\n\" The above copyright notice and this permission notice shall be\n\" included in all copies or substantial portions of the Software.\n\"\n\" THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n\" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n\" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n\" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nif exists('g:loaded_fzf')\n  finish\nendif\nlet g:loaded_fzf = 1\n\nlet s:is_win = has('win32') || has('win64')\nif s:is_win && &shellslash\n  set noshellslash\n  let s:base_dir = expand('<sfile>:h:h')\n  set shellslash\nelse\n  let s:base_dir = expand('<sfile>:h:h')\nendif\nif s:is_win\n  let s:term_marker = '&::FZF'\n\n  function! s:fzf_call(fn, ...)\n    let shellslash = &shellslash\n    try\n      set noshellslash\n      return call(a:fn, a:000)\n    finally\n      let &shellslash = shellslash\n    endtry\n  endfunction\n\n  \" Use utf-8 for fzf.vim commands\n  \" Return array of shell commands for cmd.exe\n  function! s:enc_to_cp(str)\n    if !has('iconv')\n      return a:str\n    endif\n    if !exists('s:codepage')\n      let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)\n    endif\n    return iconv(a:str, &encoding, 'cp'.s:codepage)\n  endfunction\n  function! s:wrap_cmds(cmds)\n    return map(['@echo off']\n    \\ + (has('gui_running') ? ['set TERM= > nul'] : [])\n    \\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]),\n    \\ '<SID>enc_to_cp(v:val.\"\\r\")')\n  endfunction\nelse\n  let s:term_marker = \";#FZF\"\n\n  function! s:fzf_call(fn, ...)\n    return call(a:fn, a:000)\n  endfunction\n\n  function! s:wrap_cmds(cmds)\n    return a:cmds\n  endfunction\n\n  function! s:enc_to_cp(str)\n    return a:str\n  endfunction\nendif\n\nfunction! s:shellesc_cmd(arg)\n  let e = '\"'\n  let slashes = 0\n  for c in split(a:arg, '\\zs')\n    if c ==# '\\'\n      let slashes += 1\n    elseif c ==# '\"'\n      let e .= repeat('\\', slashes + 1)\n      let slashes = 0\n    else\n      let slashes = 0\n    endif\n    let e .= c\n  endfor\n  let e .= repeat('\\', slashes) .'\"'\n  return substitute(substitute(e, '[&|<>()^!\"]', '^&', 'g'), '%', '%%', 'g')\nendfunction\n\nfunction! fzf#shellescape(arg, ...)\n  let shell = get(a:000, 0, s:is_win ? 'cmd.exe' : 'sh')\n  if shell =~# 'cmd.exe$'\n    return s:shellesc_cmd(a:arg)\n  endif\n  try\n    let [shell, &shell] = [&shell, shell]\n    return s:fzf_call('shellescape', a:arg)\n  finally\n    let [shell, &shell] = [&shell, shell]\n  endtry\nendfunction\n\nfunction! s:fzf_getcwd()\n  return s:fzf_call('getcwd')\nendfunction\n\nfunction! s:fzf_fnamemodify(fname, mods)\n  return s:fzf_call('fnamemodify', a:fname, a:mods)\nendfunction\n\nfunction! s:fzf_expand(fmt)\n  return s:fzf_call('expand', a:fmt, 1)\nendfunction\n\nfunction! s:fzf_tempname()\n  return s:fzf_call('tempname')\nendfunction\n\nlet s:layout_keys = ['window', 'tmux', 'up', 'down', 'left', 'right']\nlet s:fzf_go = s:base_dir.'/bin/fzf'\nlet s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'\n\nlet s:cpo_save = &cpo\nset cpo&vim\n\nfunction! s:popup_support()\n  return has('nvim') ? has('nvim-0.4') : has('popupwin') && has('patch-8.2.191')\nendfunction\n\nfunction! s:default_layout()\n  return s:popup_support()\n        \\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }\n        \\ : { 'down': '~40%' }\nendfunction\n\nfunction! fzf#install()\n  if s:is_win && !has('win32unix')\n    let script = s:base_dir.'/install.ps1'\n    if !filereadable(script)\n      throw script.' not found'\n    endif\n    let script = 'powershell -ExecutionPolicy Bypass -file ' . shellescape(script)\n  else\n    let script = s:base_dir.'/install'\n    if !executable(script)\n      throw script.' not found'\n    endif\n    let script .= ' --bin'\n  endif\n\n  call s:warn('Running fzf installer ...')\n  call system(script)\n  if v:shell_error\n    throw 'Failed to download fzf: '.script\n  endif\nendfunction\n\nlet s:versions = {}\nfunction s:get_version(bin)\n  if has_key(s:versions, a:bin)\n    return s:versions[a:bin]\n  end\n  let command = (&shell =~ 'powershell\\|pwsh' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'\n  let output = systemlist(command)\n  if v:shell_error || empty(output)\n    return ''\n  endif\n  let ver = matchstr(output[-1], '[0-9.]\\+')\n  let s:versions[a:bin] = ver\n  return ver\nendfunction\n\nfunction! s:compare_versions(a, b)\n  let a = split(a:a, '\\.')\n  let b = split(a:b, '\\.')\n  for idx in range(0, max([len(a), len(b)]) - 1)\n    let v1 = str2nr(get(a, idx, 0))\n    let v2 = str2nr(get(b, idx, 0))\n    if     v1 < v2 | return -1\n    elseif v1 > v2 | return 1\n    endif\n  endfor\n  return 0\nendfunction\n\nfunction! s:compare_binary_versions(a, b)\n  return s:compare_versions(s:get_version(a:a), s:get_version(a:b))\nendfunction\n\nlet s:min_version = '0.53.0'\nlet s:checked = {}\nfunction! fzf#exec(...)\n  if !exists('s:exec')\n    let binaries = []\n    if executable('fzf')\n      call add(binaries, 'fzf')\n    endif\n    if executable(s:fzf_go)\n      call add(binaries, s:fzf_go)\n    endif\n\n    if empty(binaries)\n      if input('fzf executable not found. Download binary? (y/n) ') =~? '^y'\n        redraw\n        call fzf#install()\n        return fzf#exec()\n      else\n        redraw\n        throw 'fzf executable not found'\n      endif\n    elseif len(binaries) > 1\n      call sort(binaries, 's:compare_binary_versions')\n    endif\n\n    let s:exec = binaries[-1]\n  endif\n\n  let min_version = s:min_version\n  if a:0 && s:compare_versions(a:1, min_version) > 0\n    let min_version = a:1\n  endif\n  if !has_key(s:checked, min_version)\n    let fzf_version = s:get_version(s:exec)\n    if empty(fzf_version)\n      let message = printf('Failed to run \"%s --version\"', s:exec)\n      unlet s:exec\n      throw message\n    end\n\n    if s:compare_versions(fzf_version, min_version) >= 0\n      let s:checked[min_version] = 1\n      return s:exec\n    elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', min_version, fzf_version)) =~? '^y'\n      let s:versions = {}\n      unlet s:exec\n      redraw\n      call fzf#install()\n      return fzf#exec(min_version, 1)\n    else\n      throw printf('You need to upgrade fzf (required: %s or above)', min_version)\n    endif\n  endif\n\n  return s:exec\nendfunction\n\nfunction! s:tmux_enabled()\n  if has('gui_running') || !exists('$TMUX')\n    return 0\n  endif\n\n  if exists('s:tmux')\n    return s:tmux\n  endif\n\n  let s:tmux = 0\n  if !executable(s:fzf_tmux)\n    if executable('fzf-tmux')\n      let s:fzf_tmux = 'fzf-tmux'\n    else\n      return 0\n    endif\n  endif\n\n  let output = system('tmux -V')\n  let s:tmux = !v:shell_error && output >= 'tmux 1.7'\n  return s:tmux\nendfunction\n\nfunction! s:escape(path)\n  let path = fnameescape(a:path)\n  return s:is_win ? escape(path, '$') : path\nendfunction\n\nfunction! s:error(msg)\n  echohl ErrorMsg\n  echom a:msg\n  echohl None\nendfunction\n\nfunction! s:warn(msg)\n  echohl WarningMsg\n  echom a:msg\n  echohl None\nendfunction\n\nfunction! s:has_any(dict, keys)\n  for key in a:keys\n    if has_key(a:dict, key)\n      return 1\n    endif\n  endfor\n  return 0\nendfunction\n\nfunction! s:open(cmd, target)\n  if stridx('edit', a:cmd) == 0 && s:fzf_fnamemodify(a:target, ':p') ==# s:fzf_expand('%:p')\n    return\n  endif\n  execute a:cmd s:escape(a:target)\nendfunction\n\nfunction! s:common_sink(action, lines) abort\n  if len(a:lines) < 2\n    return\n  endif\n  let key = remove(a:lines, 0)\n  let Cmd = get(a:action, key, 'e')\n  if type(Cmd) == type(function('call'))\n    return Cmd(a:lines)\n  endif\n  if len(a:lines) > 1\n    augroup fzf_swap\n      autocmd SwapExists * let v:swapchoice='o'\n            \\| call s:warn('fzf: E325: swap file exists: '.s:fzf_expand('<afile>'))\n    augroup END\n  endif\n  try\n    let empty = empty(s:fzf_expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified\n    \" Preserve the current working directory in case it's changed during\n    \" the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)\n    let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')\n    for item in a:lines\n      if has('win32unix') && item !~ '/'\n        let item = substitute(item, '\\', '/', 'g')\n      end\n      if item[0] != '~' && item !~ (s:is_win ? '^\\([A-Z]:\\)\\?\\' : '^/')\n        let sep = s:is_win ? '\\' : '/'\n        let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)\n      endif\n      if empty\n        execute 'e' s:escape(item)\n        let empty = 0\n      else\n        call s:open(Cmd, item)\n      endif\n      if !has('patch-8.0.0177') && !has('nvim-0.2') && exists('#BufEnter')\n            \\ && isdirectory(item)\n        doautocmd BufEnter\n      endif\n    endfor\n  catch /^Vim:Interrupt$/\n  finally\n    silent! autocmd! fzf_swap\n  endtry\nendfunction\n\nfunction! s:get_color(attr, ...)\n  \" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)\n  let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && (has('gui_running') || has('termguicolors') && &termguicolors))\n  let fam = gui ? 'gui' : 'cterm'\n  let pat = gui ? '^#[a-f0-9]\\+' : '^[0-9]\\+$'\n  for group in a:000\n    let code = synIDattr(synIDtrans(hlID(group)), a:attr, fam)\n    if code =~? pat\n      return code\n    endif\n  endfor\n  return ''\nendfunction\n\nfunction! s:defaults()\n  let rules = copy(get(g:, 'fzf_colors', {}))\n  let colors = join(map(items(filter(map(rules, 'call(\"s:get_color\", v:val)'), '!empty(v:val)')), 'join(v:val, \":\")'), ',')\n  return empty(colors) ? '' : fzf#shellescape('--color='.colors)\nendfunction\n\nfunction! s:validate_layout(layout)\n  for key in keys(a:layout)\n    if index(s:layout_keys, key) < 0\n      throw printf('Invalid entry in g:fzf_layout: %s (allowed: %s)%s',\n            \\ key, join(s:layout_keys, ', '), key == 'options' ? '. Use $FZF_DEFAULT_OPTS.' : '')\n    endif\n  endfor\n  return a:layout\nendfunction\n\nfunction! s:evaluate_opts(options)\n  return type(a:options) == type([]) ?\n        \\ join(map(copy(a:options), 'fzf#shellescape(v:val)')) : a:options\nendfunction\n\n\" [name string,] [opts dict,] [fullscreen boolean]\nfunction! fzf#wrap(...)\n  let args = ['', {}, 0]\n  let expects = map(copy(args), 'type(v:val)')\n  let tidx = 0\n  for arg in copy(a:000)\n    let tidx = index(expects, type(arg) == 6 ? type(0) : type(arg), tidx)\n    if tidx < 0\n      throw 'Invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])'\n    endif\n    let args[tidx] = arg\n    let tidx += 1\n    unlet arg\n  endfor\n  let [name, opts, bang] = args\n\n  if len(name)\n    let opts.name = name\n  end\n\n  \" Layout: g:fzf_layout (and deprecated g:fzf_height)\n  if bang\n    for key in s:layout_keys\n      if has_key(opts, key)\n        call remove(opts, key)\n      endif\n    endfor\n  elseif !s:has_any(opts, s:layout_keys)\n    if !exists('g:fzf_layout') && exists('g:fzf_height')\n      let opts.down = g:fzf_height\n    else\n      let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout())))\n    endif\n  endif\n\n  \" Colors: g:fzf_colors\n  let opts.options = s:defaults() .' '. s:evaluate_opts(get(opts, 'options', ''))\n\n  \" History: g:fzf_history_dir\n  if len(name) && len(get(g:, 'fzf_history_dir', ''))\n    let dir = s:fzf_expand(g:fzf_history_dir)\n    if !isdirectory(dir)\n      call mkdir(dir, 'p')\n    endif\n    let history = fzf#shellescape(dir.'/'.name)\n    let opts.options = join(['--history', history, opts.options])\n  endif\n\n  \" Action: g:fzf_action\n  if !s:has_any(opts, ['sink', 'sinklist', 'sink*'])\n    let opts._action = get(g:, 'fzf_action', s:default_action)\n    let opts.options .= ' --expect='.join(keys(opts._action), ',')\n    function! opts.sinklist(lines) abort\n      return s:common_sink(self._action, a:lines)\n    endfunction\n    let opts['sink*'] = opts.sinklist \" For backward compatibility\n  endif\n\n  return opts\nendfunction\n\nfunction! s:use_sh()\n  let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]\n  if s:is_win\n    set shell=cmd.exe\n    set noshellslash\n    let &shellcmdflag = has('nvim') ? '/s /c' : '/c'\n    let &shellxquote = has('nvim') ? '\"' : '('\n  else\n    set shell=sh\n  endif\n  return [shell, shellslash, shellcmdflag, shellxquote]\nendfunction\n\nfunction! s:writefile(...)\n  if call('writefile', a:000) == -1\n    throw 'Failed to write temporary file. Check if you can write to the path tempname() returns.'\n  endif\nendfunction\n\nfunction! s:extract_option(opts, name)\n  let opt = ''\n  let expect = 0\n  \" There are a few cases where this function doesn't work as expected.\n  \" Let's just assume such cases are extremely unlikely in real world.\n  \"   e.g. --query --border\n  for word in split(a:opts)\n    if expect && word !~ '^\"\\=-'\n      let opt = opt . ' ' . word\n      let expect = 0\n    elseif word == '--no-'.a:name\n      let opt = ''\n    elseif word =~ '^--'.a:name.'='\n      let opt = word\n    elseif word =~ '^--'.a:name.'$'\n      let opt = word\n      let expect = 1\n    elseif expect\n      let expect = 0\n    endif\n  endfor\n  return opt\nendfunction\n\nlet s:need_cmd_window = has('win32unix') && $TERM_PROGRAM ==# 'mintty' && s:compare_versions($TERM_PROGRAM_VERSION, '3.4.5') < 0 && !executable('winpty')\n\nfunction! fzf#run(...) abort\ntry\n  let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()\n\n  let dict   = exists('a:1') ? copy(a:1) : {}\n  let temps  = { 'result': s:fzf_tempname() }\n  let optstr = s:evaluate_opts(get(dict, 'options', ''))\n  try\n    let fzf_exec = shellescape(fzf#exec())\n  catch\n    throw v:exception\n  endtry\n\n  if !s:present(dict, 'dir')\n    let dict.dir = s:fzf_getcwd()\n  endif\n  if has('win32unix') && s:present(dict, 'dir')\n    let dict.dir = fnamemodify(dict.dir, ':p')\n  endif\n\n  if has_key(dict, 'source')\n    let source = dict.source\n    let type = type(source)\n    if type == 1\n      let prefix = '('.source.')|'\n    elseif type == 3\n      let temps.input = s:fzf_tempname()\n      call s:writefile(source, temps.input)\n      let prefix = (s:is_win ? 'type ' : 'command cat ').fzf#shellescape(temps.input).'|'\n    else\n      throw 'Invalid source type'\n    endif\n  else\n    let prefix = ''\n  endif\n\n  let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')\n  let use_height = has_key(dict, 'down') && !has('gui_running') &&\n        \\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&\n        \\ executable('tput') && filereadable('/dev/tty')\n  let has_vim8_term = has('terminal') && has('patch-8.0.995')\n  let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win\n  let use_term = has_nvim_term || has_vim8_term\n    \\ && !s:need_cmd_window\n    \\ && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))\n  let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()\n  if prefer_tmux && use_tmux\n    let use_height = 0\n    let use_term = 0\n  endif\n  if use_term\n    let optstr .= ' --no-height --no-tmux'\n  elseif use_height\n    let height = s:calc_size(&lines, dict.down, dict)\n    let optstr .= ' --no-tmux --height='.height\n  endif\n\n  if exists('&winborder') && &winborder !=# '' && &winborder !=# 'none'\n    \" Add 1-column horizontal margin\n    let optstr = join(['--margin 0,1', optstr])\n  else\n    \" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'\n    let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])\n  endif\n\n  let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result\n\n  if use_term\n    return s:execute_term(dict, command, temps)\n  endif\n\n  let lines = use_tmux ? s:execute_tmux(dict, command, temps)\n                 \\ : s:execute(dict, command, use_height, temps)\n  call s:callback(dict, lines)\n  return lines\nfinally\n  let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]\nendtry\nendfunction\n\nfunction! s:present(dict, ...)\n  for key in a:000\n    if !empty(get(a:dict, key, ''))\n      return 1\n    endif\n  endfor\n  return 0\nendfunction\n\nfunction! s:fzf_tmux(dict)\n  let size = get(a:dict, 'tmux', '')\n  if empty(size)\n    for o in ['up', 'down', 'left', 'right']\n      if s:present(a:dict, o)\n        let size = o . ',' . a:dict[o]\n        break\n      endif\n    endfor\n  endif\n\n  \" Legacy fzf-tmux options\n  if size =~ '-'\n    return printf('LINES=%d COLUMNS=%d %s %s %s --',\n          \\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))\n  end\n\n  \" Using native --tmux option\n  let in = (has_key(a:dict, 'source') ? '' : ' --force-tty-in')\n  return printf('%s --tmux %s%s', fzf#shellescape(fzf#exec()), size, in)\nendfunction\n\nfunction! s:splittable(dict)\n  return s:present(a:dict, 'up', 'down') && &lines > 15 ||\n        \\ s:present(a:dict, 'left', 'right') && &columns > 40\nendfunction\n\nfunction! s:pushd(dict)\n  if s:present(a:dict, 'dir')\n    let cwd = s:fzf_getcwd()\n    let w:fzf_pushd = {\n    \\   'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),\n    \\   'origin': cwd,\n    \\   'bufname': bufname('')\n    \\ }\n    execute 'lcd' s:escape(a:dict.dir)\n    let cwd = s:fzf_getcwd()\n    let w:fzf_pushd.dir = cwd\n    let a:dict.pushd = w:fzf_pushd\n    return cwd\n  endif\n  return ''\nendfunction\n\naugroup fzf_popd\n  autocmd!\n  autocmd WinEnter * call s:dopopd()\naugroup END\n\nfunction! s:dopopd()\n  if !exists('w:fzf_pushd')\n    return\n  endif\n\n  \" FIXME: We temporarily change the working directory to 'dir' entry\n  \" of options dictionary (set to the current working directory if not given)\n  \" before running fzf.\n  \"\n  \" e.g. call fzf#run({'dir': '/tmp', 'source': 'ls', 'sink': 'e'})\n  \"\n  \" After processing the sink function, we have to restore the current working\n  \" directory. But doing so may not be desirable if the function changed the\n  \" working directory on purpose.\n  \"\n  \" So how can we tell if we should do it or not? A simple heuristic we use\n  \" here is that we change directory only if the current working directory\n  \" matches 'dir' entry. However, it is possible that the sink function did\n  \" change the directory to 'dir'. In that case, the user will have an\n  \" unexpected result.\n  if s:fzf_getcwd() ==# w:fzf_pushd.dir && (!&autochdir || w:fzf_pushd.bufname ==# bufname(''))\n    execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)\n  endif\n  unlet! w:fzf_pushd\nendfunction\n\nfunction! s:xterm_launcher()\n  let fmt = 'xterm -T \"[fzf]\" -bg \"%s\" -fg \"%s\" -geometry %dx%d+%d+%d -e bash -ic %%s'\n  if has('gui_macvim')\n    let fmt .= '&& osascript -e \"tell application \\\"MacVim\\\" to activate\"'\n  endif\n  return printf(fmt,\n    \\ escape(synIDattr(hlID(\"Normal\"), \"bg\"), '#'), escape(synIDattr(hlID(\"Normal\"), \"fg\"), '#'),\n    \\ &columns, &lines/2, getwinposx(), getwinposy())\nendfunction\nunlet! s:launcher\nif s:is_win || has('win32unix')\n  let s:launcher = '%s'\nelse\n  let s:launcher = function('s:xterm_launcher')\nendif\n\nfunction! s:exit_handler(dict, code, command, ...)\n  if has_key(a:dict, 'exit')\n    call a:dict.exit(a:code)\n  endif\n  if a:code == 2\n    call s:error('Error running ' . a:command)\n    if !empty(a:000)\n      sleep\n    endif\n  endif\n  return a:code\nendfunction\n\nfunction! s:execute(dict, command, use_height, temps) abort\n  call s:pushd(a:dict)\n  if has('unix') && !a:use_height\n    silent! !clear 2> /dev/null\n  endif\n  let escaped = (a:use_height || s:is_win) ? a:command : escape(substitute(a:command, '\\n', '\\\\n', 'g'), '%#!')\n  if has('gui_running')\n    let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher)))\n    let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher\n    if has('unix')\n      let escaped = \"'\".substitute(escaped, \"'\", \"'\\\"'\\\"'\", 'g').\"'\"\n    endif\n    let command = printf(fmt, escaped)\n  else\n    let command = escaped\n  endif\n  if s:is_win\n    let batchfile = s:fzf_tempname().'.bat'\n    call s:writefile(s:wrap_cmds(command), batchfile)\n    let command = batchfile\n    let a:temps.batchfile = batchfile\n    if has('nvim')\n      let fzf = {}\n      let fzf.dict = a:dict\n      let fzf.temps = a:temps\n      function! fzf.on_exit(job_id, exit_status, event) dict\n        call s:pushd(self.dict)\n        let lines = s:collect(self.temps)\n        call s:callback(self.dict, lines)\n      endfunction\n      let cmd = 'start /wait cmd /c '.command\n      call jobstart(cmd, fzf)\n      return []\n    endif\n  elseif s:need_cmd_window\n    let shellscript = s:fzf_tempname()\n    call s:writefile([command], shellscript)\n    let command = 'start //WAIT sh -c '.shellscript\n    let a:temps.shellscript = shellscript\n  endif\n  if a:use_height\n    let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'\n    call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))\n  else\n    execute 'silent !'.command\n  endif\n  let exit_status = v:shell_error\n  redraw!\n  let lines = s:collect(a:temps)\n  return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []\nendfunction\n\nfunction! s:execute_tmux(dict, command, temps) abort\n  let command = a:command\n  let cwd = s:pushd(a:dict)\n  if len(cwd)\n    \" -c '#{pane_current_path}' is only available on tmux 1.9 or above\n    let command = join(['cd', fzf#shellescape(cwd), '&&', command])\n  endif\n\n  call system(command)\n  let exit_status = v:shell_error\n  redraw!\n  let lines = s:collect(a:temps)\n  return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []\nendfunction\n\nfunction! s:calc_size(max, val, dict)\n  let val = substitute(a:val, '^\\~', '', '')\n  if val =~ '%$'\n    let size = a:max * str2nr(val[:-2]) / 100\n  else\n    let size = min([a:max, str2nr(val)])\n  endif\n\n  let srcsz = -1\n  if type(get(a:dict, 'source', 0)) == type([])\n    let srcsz = len(a:dict.source)\n  endif\n\n  let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))\n  if opts =~ 'preview'\n    return size\n  endif\n  let margin = match(opts, '--inline-info\\|--info[^-]\\{-}inline') > match(opts, '--no-inline-info\\|--info[^-]\\{-}\\(default\\|hidden\\)') ? 1 : 2\n  let margin += match(opts, '--border\\([^-]\\|$\\)') > match(opts, '--no-border\\([^-]\\|$\\)') ? 2 : 0\n  if stridx(opts, '--header') > stridx(opts, '--no-header')\n    let margin += len(split(opts, \"\\n\"))\n  endif\n  return srcsz >= 0 ? min([srcsz + margin, size]) : size\nendfunction\n\nfunction! s:getpos()\n  return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}\nendfunction\n\nfunction! s:border_opt(window)\n  if type(a:window) != type({})\n    return ''\n  endif\n\n  \" Border style\n  let style = tolower(get(a:window, 'border', ''))\n  if !has_key(a:window, 'border') && has_key(a:window, 'rounded')\n    let style = a:window.rounded ? 'rounded' : 'sharp'\n  endif\n  if style == 'none' || style == 'no'\n    return ''\n  endif\n\n  \" For --border styles, we need fzf 0.24.0 or above\n  call fzf#exec('0.24.0')\n  let opt = ' --border ' . style\n  if has_key(a:window, 'highlight')\n    let color = s:get_color('fg', a:window.highlight)\n    if len(color)\n      let opt .= ' --color=border:' . color\n    endif\n  endif\n  return opt\nendfunction\n\nfunction! s:split(dict)\n  let directions = {\n  \\ 'up':    ['topleft', 'resize', &lines],\n  \\ 'down':  ['botright', 'resize', &lines],\n  \\ 'left':  ['vertical topleft', 'vertical resize', &columns],\n  \\ 'right': ['vertical botright', 'vertical resize', &columns] }\n  let ppos = s:getpos()\n  let is_popup = 0\n  try\n    if s:present(a:dict, 'window')\n      if type(a:dict.window) == type({})\n        if !s:popup_support()\n          throw 'Nvim 0.4+ or Vim 8.2.191+ with popupwin feature is required for pop-up window'\n        end\n        call s:popup(a:dict.window)\n        let is_popup = 1\n      else\n        execute 'keepalt' a:dict.window\n      endif\n    elseif !s:splittable(a:dict)\n      execute (tabpagenr()-1).'tabnew'\n    else\n      for [dir, triple] in items(directions)\n        let val = get(a:dict, dir, '')\n        if !empty(val)\n          let [cmd, resz, max] = triple\n          if (dir == 'up' || dir == 'down') && val[0] == '~'\n            let sz = s:calc_size(max, val, a:dict)\n          else\n            let sz = s:calc_size(max, val, {})\n          endif\n          execute cmd sz.'new'\n          execute resz sz\n          return [ppos, {}, is_popup]\n        endif\n      endfor\n    endif\n    return [ppos, is_popup ? {} : { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }, is_popup]\n  finally\n    if !is_popup\n      setlocal winfixwidth winfixheight\n    endif\n  endtry\nendfunction\n\nnnoremap <silent> <Plug>(fzf-insert) i\nnnoremap <silent> <Plug>(fzf-normal) <Nop>\nif exists(':tnoremap')\n  tnoremap <silent> <Plug>(fzf-insert) <C-\\><C-n>i\n  tnoremap <silent> <Plug>(fzf-normal) <C-\\><C-n>\nendif\n\nlet s:warned = 0\nfunction! s:handle_ambidouble(dict)\n  if &ambiwidth == 'double'\n    let a:dict.env = { 'RUNEWIDTH_EASTASIAN': '1' }\n  elseif !s:warned && $RUNEWIDTH_EASTASIAN == '1' && &ambiwidth !=# 'double'\n    call s:warn(\"$RUNEWIDTH_EASTASIAN is '1' but &ambiwidth is not 'double'\")\n    2sleep\n    let s:warned = 1\n  endif\nendfunction\n\nfunction! s:execute_term(dict, command, temps) abort\n  let winrest = winrestcmd()\n  let pbuf = bufnr('')\n  let [ppos, winopts, is_popup] = s:split(a:dict)\n  call s:use_sh()\n  let b:fzf = a:dict\n  let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,\n            \\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,\n            \\ 'columns': &columns, 'command': a:command }\n  function! fzf.switch_back(inplace)\n    if a:inplace && bufnr('') == self.buf\n      if bufexists(self.pbuf)\n        execute 'keepalt keepjumps b' self.pbuf\n      endif\n      \" No other listed buffer\n      if bufnr('') == self.buf\n        enew\n      endif\n    endif\n  endfunction\n  function! fzf.on_exit(id, code, ...)\n    if s:getpos() == self.ppos \" {'window': 'enew'}\n      for [opt, val] in items(self.winopts)\n        execute 'let' opt '=' val\n      endfor\n      call self.switch_back(1)\n    else\n      if bufnr('') == self.buf\n        \" We use close instead of bd! since Vim does not close the split when\n        \" there's no other listed buffer (nvim +'set nobuflisted')\n        close\n      endif\n      silent! execute 'tabnext' self.ppos.tab\n      silent! execute self.ppos.win.'wincmd w'\n    endif\n\n    if bufexists(self.buf)\n      execute 'bd!' self.buf\n    endif\n\n    if &lines == self.lines && &columns == self.columns && s:getpos() == self.ppos\n      execute self.winrest\n    endif\n\n    let lines = s:collect(self.temps)\n    if s:exit_handler(self.dict, a:code, self.command, 1) >= 2\n      return\n    endif\n\n    call s:pushd(self.dict)\n    call s:callback(self.dict, lines)\n    call self.switch_back(s:getpos() == self.ppos)\n\n    if &buftype == 'terminal'\n      call feedkeys(&filetype == 'fzf' ? \"\\<Plug>(fzf-insert)\" : \"\\<Plug>(fzf-normal)\")\n    endif\n  endfunction\n\n  try\n    call s:pushd(a:dict)\n    if s:is_win\n      let fzf.temps.batchfile = s:fzf_tempname().'.bat'\n      call s:writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)\n      let command = fzf.temps.batchfile\n    else\n      let command = a:command\n    endif\n    let command .= s:term_marker\n    if has('nvim')\n      call s:handle_ambidouble(fzf)\n      call termopen(command, fzf)\n    else\n      let term_opts = {'exit_cb': function(fzf.on_exit)}\n      if v:version >= 802\n        let term_opts.term_kill = 'term'\n      endif\n      if is_popup\n        let term_opts.hidden = 1\n      else\n        let term_opts.curwin = 1\n      endif\n      call s:handle_ambidouble(term_opts)\n      keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)\n      if is_popup && exists('#TerminalWinOpen')\n        doautocmd <nomodeline> TerminalWinOpen\n      endif\n      if !has('patch-8.0.1261') && !s:is_win\n        call term_wait(fzf.buf, 20)\n      endif\n    endif\n    tnoremap <buffer> <c-z> <nop>\n    if exists('&termwinkey') && (empty(&termwinkey) || &termwinkey =~? '<c-w>')\n      tnoremap <buffer> <c-w> <c-w>.\n    endif\n  finally\n    call s:dopopd()\n  endtry\n  setlocal nospell bufhidden=wipe nobuflisted nonumber\n  setf fzf\n  startinsert\n  return []\nendfunction\n\nfunction! s:collect(temps) abort\n  try\n    return filereadable(a:temps.result) ? readfile(a:temps.result) : []\n  finally\n    for tf in values(a:temps)\n      silent! call delete(tf)\n    endfor\n  endtry\nendfunction\n\nfunction! s:callback(dict, lines) abort\n  let popd = has_key(a:dict, 'pushd')\n  if popd\n    let w:fzf_pushd = a:dict.pushd\n  endif\n\n  try\n    if has_key(a:dict, 'sink')\n      for line in a:lines\n        if type(a:dict.sink) == 2\n          call a:dict.sink(line)\n        else\n          execute a:dict.sink s:escape(line)\n        endif\n      endfor\n    endif\n    if has_key(a:dict, 'sink*')\n      call a:dict['sink*'](a:lines)\n    elseif has_key(a:dict, 'sinklist')\n      call a:dict['sinklist'](a:lines)\n    endif\n  catch\n    if stridx(v:exception, ':E325:') < 0\n      echoerr v:exception\n    endif\n  endtry\n\n  \" We may have opened a new window or tab\n  if popd\n    let w:fzf_pushd = a:dict.pushd\n    call s:dopopd()\n  endif\nendfunction\n\nif has('nvim')\n  function s:create_popup(opts) abort\n    let buf = nvim_create_buf(v:false, v:true)\n    let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)\n    let win = nvim_open_win(buf, v:true, opts)\n    call setwinvar(win, '&colorcolumn', '')\n\n    \" Colors\n    try\n      call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')\n      let rules = get(g:, 'fzf_colors', {})\n      if has_key(rules, 'bg')\n        let color = call('s:get_color', rules.bg)\n        if len(color)\n          let ns = nvim_create_namespace('fzf_popup')\n          let hl = nvim_set_hl(ns, 'Normal',\n                \\ &termguicolors ? { 'bg': color } : { 'ctermbg': str2nr(color) })\n          call nvim_win_set_hl_ns(win, ns)\n        endif\n      endif\n    catch\n    endtry\n    return buf\n  endfunction\nelse\n  function! s:create_popup(opts) abort\n    let s:popup_create = {buf -> popup_create(buf, #{\n      \\ line: a:opts.row,\n      \\ col: a:opts.col,\n      \\ minwidth: a:opts.width,\n      \\ maxwidth: a:opts.width,\n      \\ minheight: a:opts.height,\n      \\ maxheight: a:opts.height,\n      \\ zindex: 1000,\n    \\ })}\n    autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))\n  endfunction\nendif\n\nfunction! s:popup(opts) abort\n  let xoffset = get(a:opts, 'xoffset', 0.5)\n  let yoffset = get(a:opts, 'yoffset', 0.5)\n  let relative = get(a:opts, 'relative', 0)\n\n  \" Use current window size for positioning relatively positioned popups\n  let columns = relative ? winwidth(0) : &columns\n  let lines = relative ? winheight(0) : (&lines - has('nvim'))\n\n  \" Size and position\n  let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(columns * a:opts.width)]), columns])\n  let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(lines * a:opts.height)]), lines])\n  let row = float2nr(yoffset * (lines - height)) + (relative ? win_screenpos(0)[0] - 1 : 0)\n  let col = float2nr(xoffset * (columns - width)) + (relative ? win_screenpos(0)[1] - 1 : 0)\n\n  \" Managing the differences\n  let row = min([max([0, row]), &lines - has('nvim') - height])\n  let col = min([max([0, col]), &columns - width])\n  let row += !has('nvim')\n  let col += !has('nvim')\n\n  call s:create_popup({\n    \\ 'row': row, 'col': col, 'width': width, 'height': height\n  \\ })\nendfunction\n\nlet s:default_action = {\n  \\ 'ctrl-t': 'tab split',\n  \\ 'ctrl-x': 'split',\n  \\ 'ctrl-v': 'vsplit' }\n\nfunction! s:shortpath()\n  let short = fnamemodify(getcwd(), ':~:.')\n  if !has('win32unix')\n    let short = pathshorten(short)\n  endif\n  let slash = (s:is_win && !&shellslash) ? '\\' : '/'\n  return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\\').'$' ? '' : slash)\nendfunction\n\nfunction! s:cmd(bang, ...) abort\n  let args = copy(a:000)\n  let opts = { 'options': ['--multi', '--scheme', 'path'] }\n  if len(args) && isdirectory(expand(args[-1]))\n    let opts.dir = substitute(substitute(remove(args, -1), '\\\\\\([\"'']\\)', '\\1', 'g'), '[/\\\\]*$', '/', '')\n    if s:is_win && !&shellslash\n      let opts.dir = substitute(opts.dir, '/', '\\\\', 'g')\n    endif\n    let prompt = opts.dir\n  else\n    let prompt = s:shortpath()\n  endif\n  let prompt = strwidth(prompt) < &columns - 20 ? prompt : '> '\n  call extend(opts.options, ['--prompt', prompt])\n  call extend(opts.options, args)\n  call fzf#run(fzf#wrap('FZF', opts, a:bang))\nendfunction\n\ncommand! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)\n\nlet &cpo = s:cpo_save\nunlet s:cpo_save\n"
  },
  {
    "path": "shell/common.fish",
    "content": "  function __fzf_defaults\n    # $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS\n    # $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS\n    test -n \"$FZF_TMUX_HEIGHT\"; or set -l FZF_TMUX_HEIGHT 40%\n    string join ' ' -- \\\n      \"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore\" $argv[1] \\\n      (test -r \"$FZF_DEFAULT_OPTS_FILE\"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \\\n      $FZF_DEFAULT_OPTS $argv[2..-1]\n  end\n\n  function __fzfcmd\n    test -n \"$FZF_TMUX_HEIGHT\"; or set -l FZF_TMUX_HEIGHT 40%\n    if test -n \"$FZF_TMUX_OPTS\"\n      echo \"fzf-tmux $FZF_TMUX_OPTS -- \"\n    else if test \"$FZF_TMUX\" = \"1\"\n      echo \"fzf-tmux -d$FZF_TMUX_HEIGHT -- \"\n    else\n      echo \"fzf\"\n    end\n  end\n\n  function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'\n    # Get tokens - use version-appropriate flags\n    set -l tokens\n    if test (string match -r -- '^\\d+' $version) -ge 4\n      set -- tokens (commandline -xpc)\n    else\n      set -- tokens (commandline -opc)\n    end\n\n    # Filter out leading environment variable assignments\n    set -l -- var_count 0\n    for i in $tokens\n      if string match -qr -- '^[\\w]+=' $i\n        set var_count (math $var_count + 1)\n      else\n        break\n      end\n    end\n    set -e -- tokens[0..$var_count]\n\n    # Skip command prefixes so callers see the actual command name,\n    # e.g. \"builtin cd\" → \"cd\", \"env VAR=1 command cd\" → \"cd\"\n    while true\n      switch \"$tokens[1]\"\n        case builtin command\n          set -e -- tokens[1]\n          test \"$tokens[1]\" = \"--\"; and set -e -- tokens[1]\n        case env\n          set -e -- tokens[1]\n          test \"$tokens[1]\" = \"--\"; and set -e -- tokens[1]\n          while string match -qr -- '^[\\w]+=' \"$tokens[1]\"\n            set -e -- tokens[1]\n          end\n        case '*'\n          break\n      end\n    end\n\n    string escape -n -- $tokens\n  end\n\n  function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'\n    set -l fzf_query ''\n    set -l prefix ''\n    set -l dir '.'\n\n    # Set variables containing the major and minor fish version numbers, using\n    # a method compatible with all supported fish versions.\n    set -l -- fish_major (string match -r -- '^\\d+' $version)\n    set -l -- fish_minor (string match -r -- '^\\d+\\.(\\d+)' $version)[2]\n\n    # fish v3.3.0 and newer: Don't use option prefix if \" -- \" is preceded.\n    set -l -- match_regex '(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)'\n    set -l -- prefix_regex '^-[^\\s=]+=|^-(?!-)\\S'\n    if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -lt 3\n    or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))\n      set -- match_regex \"(?<prefix>$prefix_regex)?$match_regex\"\n    end\n\n    # Set $prefix and expanded $fzf_query with preserved trailing newlines.\n    if test \"$fish_major\" -ge 4\n      # fish v4.0.0 and newer\n      string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)\n    else if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n      # fish v3.2.0 - v3.7.1 (last v3)\n      string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)\n      eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\\\(?=~)|\\\\\\(?=\\$\\w)' '')\n    else\n      # fish older than v3.2.0 (v3.1b1 - v3.1.2)\n      set -l -- cl_token (commandline --current-token --tokenize | string collect -N)\n      set -- prefix (string match -r -- $prefix_regex $cl_token)\n      set -- fzf_query (string replace -- \"$prefix\" '' $cl_token | string collect -N)\n      eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\\\(?=~)|\\\\\\(?=\\$\\w)|\\\\\\n\\\\\\n$' '')\n    end\n\n    if test -n \"$fzf_query\"\n      # Normalize path in $fzf_query, set $dir to the longest existing directory.\n      if test \\( \"$fish_major\" -ge 4 \\) -o \\( \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 5 \\)\n        # fish v3.5.0 and newer\n        set -- fzf_query (path normalize -- $fzf_query)\n        set -- dir $fzf_query\n        while not path is -d $dir\n          set -- dir (path dirname $dir)\n        end\n      else\n        # fish older than v3.5.0 (v3.1b1 - v3.4.1)\n        if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n          # fish v3.2.0 - v3.4.1\n          string match -q -r -- '(?<fzf_query>^[\\s\\S]*?(?=\\n?$)$)' \\\n            (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)\n        else\n          # fish v3.1b1 - v3.1.2\n          set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)\n          eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\\\\n$' '')\n        end\n        set -- dir $fzf_query\n        while not test -d \"$dir\"\n          set -- dir (dirname -z -- \"$dir\" | string split0)\n        end\n      end\n\n      if not string match -q -- '.' $dir; or string match -q -r -- '^\\./|^\\.$' $fzf_query\n        # Strip $dir from $fzf_query - preserve trailing newlines.\n        if test \"$fish_major\" -ge 4\n          # fish v4.0.0 and newer\n          string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\\s\\S]*)' $fzf_query\n        else if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n          # fish v3.2.0 - v3.7.1 (last v3)\n          string match -q -r -- '^/?(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)' \\\n            (string replace -- \"$dir\" '' $fzf_query | string collect -N)\n        else\n          # fish older than v3.2.0 (v3.1b1 - v3.1.2)\n          set -- fzf_query (string replace -- \"$dir\" '' $fzf_query | string collect -N)\n          eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\\\\n$' '')\n        end\n      end\n    end\n\n    string escape -n -- \"$dir\" \"$fzf_query\" \"$prefix\"\n  end\n"
  },
  {
    "path": "shell/common.sh",
    "content": "__fzf_defaults() {\n  # $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS\n  # $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS\n  builtin printf '%s\\n' \"--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1\"\n  command cat \"${FZF_DEFAULT_OPTS_FILE-}\" 2> /dev/null\n  builtin printf '%s\\n' \"${FZF_DEFAULT_OPTS-} $2\"\n}\n\n__fzf_exec_awk() {\n  # This function performs `exec awk \"$@\"` safely by working around awk\n  # compatibility issues.\n  #\n  # To reduce an extra fork, this function performs \"exec\" so is expected to be\n  # run as the last command in a subshell.\n  if [[ -z ${__fzf_awk-} ]]; then\n    __fzf_awk=awk\n    if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then\n      # Note: Solaris awk at /usr/bin/awk is meant for backward compatibility\n      # with an ancient implementation of 1977 awk in the original UNIX.  It\n      # lacks many features of POSIX awk, so it is essentially useless in the\n      # modern point of view.  To use a standard-conforming version in Solaris,\n      # one needs to explicitly use /usr/xpg4/bin/awk.\n      __fzf_awk=/usr/xpg4/bin/awk\n    elif command -v mawk > /dev/null 2>&1; then\n      # choose the faster mawk if: it's installed && build date >= 20230322 &&\n      # version >= 1.3.4\n      local n x y z d\n      IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)\n      [[ $n == mawk ]] &&\n        (((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&\n        ((d >= 20230302)) 2> /dev/null &&\n        __fzf_awk=mawk\n    fi\n  fi\n  # Note: macOS awk has a quirk that it stops processing at all when it sees\n  # any data not following UTF-8 in the input stream when the current LC_CTYPE\n  # specifies the UTF-8 encoding.  To work around this quirk, one needs to\n  # specify LC_ALL=C to change the current encoding to the plain one.\n  LC_ALL=C exec \"$__fzf_awk\" \"$@\"\n}\n"
  },
  {
    "path": "shell/completion.bash",
    "content": "#     ____      ____\n#    / __/___  / __/\n#   / /_/_  / / /_\n#  / __/ / /_/ __/\n# /_/   /___/_/ completion.bash\n#\n# - $FZF_TMUX                 (default: 0)\n# - $FZF_TMUX_OPTS            (default: empty)\n# - $FZF_COMPLETION_TRIGGER   (default: '**')\n# - $FZF_COMPLETION_OPTS      (default: empty)\n# - $FZF_COMPLETION_PATH_OPTS (default: empty)\n# - $FZF_COMPLETION_DIR_OPTS  (default: empty)\n\nif [[ $- =~ i ]]; then\n\n\n# To use custom commands instead of find, override _fzf_compgen_{path,dir}\n#\n#   _fzf_compgen_path() {\n#     echo \"$1\"\n#     command find -L \"$1\" \\\n#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \\( -type d -o -type f -o -type l \\) \\\n#       -a -not -path \"$1\" -print 2> /dev/null | command sed 's@^\\./@@'\n#   }\n#\n#   _fzf_compgen_dir() {\n#     command find -L \"$1\" \\\n#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \\\n#       -a -not -path \"$1\" -print 2> /dev/null | command sed 's@^\\./@@'\n#   }\n\n###########################################################\n\n#----BEGIN shfmt\n#----BEGIN INCLUDE common.sh\n# NOTE: Do not directly edit this section, which is copied from \"common.sh\".\n# To modify it, one can edit \"common.sh\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"common.sh\" for the implementation details.\n\n__fzf_defaults() {\n  builtin printf '%s\\n' \"--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1\"\n  command cat \"${FZF_DEFAULT_OPTS_FILE-}\" 2> /dev/null\n  builtin printf '%s\\n' \"${FZF_DEFAULT_OPTS-} $2\"\n}\n\n__fzf_exec_awk() {\n  if [[ -z ${__fzf_awk-} ]]; then\n    __fzf_awk=awk\n    if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then\n      __fzf_awk=/usr/xpg4/bin/awk\n    elif command -v mawk > /dev/null 2>&1; then\n      local n x y z d\n      IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)\n      [[ $n == mawk ]] &&\n        (((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&\n        ((d >= 20230302)) 2> /dev/null &&\n        __fzf_awk=mawk\n    fi\n  fi\n  LC_ALL=C exec \"$__fzf_awk\" \"$@\"\n}\n#----END INCLUDE\n\n__fzf_comprun() {\n  if [[ \"$(type -t _fzf_comprun 2>&1)\" == function ]]; then\n    _fzf_comprun \"$@\"\n  elif [[ -n ${TMUX_PANE-} ]] && { [[ ${FZF_TMUX:-0} != 0 ]] || [[ -n ${FZF_TMUX_OPTS-} ]]; }; then\n    shift\n    fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- \"$@\"\n  else\n    shift\n    fzf \"$@\"\n  fi\n}\n\n__fzf_orig_completion() {\n  local l comp f cmd\n  while read -r l; do\n    if [[ $l =~ ^(.*\\ -F)\\ *([^ ]*).*\\ ([^ ]*)$ ]]; then\n      comp=\"${BASH_REMATCH[1]}\"\n      f=\"${BASH_REMATCH[2]}\"\n      cmd=\"${BASH_REMATCH[3]}\"\n      [[ $f == _fzf_* ]] && continue\n      builtin printf -v \"_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}\" \"%s\" \"${comp} %s ${cmd} #${f}\"\n      if [[ $l == *\" -o nospace \"* ]] && [[ ${__fzf_nospace_commands-} != *\" $cmd \"* ]]; then\n        __fzf_nospace_commands=\"${__fzf_nospace_commands-} $cmd \"\n      fi\n    fi\n  done\n}\n\n# @param $1 cmd - Command name for which the original completion is searched\n# @var[out] REPLY - Original function name is returned\n__fzf_orig_completion_get_orig_func() {\n  local cmd orig_var orig\n  cmd=$1\n  orig_var=\"_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}\"\n  orig=\"${!orig_var-}\"\n  REPLY=\"${orig##*#}\"\n  [[ $REPLY ]] && type \"$REPLY\" &> /dev/null\n}\n\n# @param $1 cmd - Command name for which the original completion is searched\n# @param $2 func - Fzf's completion function to replace the original function\n# @var[out] REPLY - Completion setting is returned as a string to \"eval\"\n__fzf_orig_completion_instantiate() {\n  local cmd func orig_var orig\n  cmd=$1\n  func=$2\n  orig_var=\"_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}\"\n  orig=\"${!orig_var-}\"\n  orig=\"${orig%#*}\"\n  [[ $orig == *' %s '* ]] || return 1\n  builtin printf -v REPLY \"$orig\" \"$func\"\n}\n\n_fzf_opts_completion() {\n  local cur prev opts\n  COMPREPLY=()\n  cur=\"${COMP_WORDS[COMP_CWORD]}\"\n  prev=\"${COMP_WORDS[COMP_CWORD - 1]}\"\n  opts=\"\n    +c --no-color\n    +i --no-ignore-case\n    +s --no-sort\n    +x --no-extended\n    --accept-nth\n    --ansi\n    --bash\n    --bind\n    --border\n    --border-label\n    --border-label-pos\n    --color\n    --cycle\n    --disabled\n    --ellipsis\n    --expect\n    --filepath-word\n    --fish\n    --footer\n    --footer-border\n    --footer-label\n    --footer-label-pos\n    --freeze-left\n    --freeze-right\n    --gap\n    --gap-line\n    --ghost\n    --gutter\n    --gutter-raw\n    --header\n    --header-border\n    --header-first\n    --header-label\n    --header-label-pos\n    --header-lines\n    --header-lines-border\n    --height\n    --highlight-line\n    --history\n    --history-size\n    --hscroll-off\n    --id-nth\n    --info\n    --info-command\n    --input-border\n    --input-label\n    --input-label-pos\n    --jump-labels\n    --keep-right\n    --layout\n    --listen\n    --listen-unsafe\n    --list-border\n    --list-label\n    --list-label-pos\n    --literal\n    --man\n    --margin\n    --marker\n    --marker-multi-line\n    --min-height\n    --no-bold\n    --no-hscroll\n    --no-input\n    --no-multi-line\n    --no-scrollbar\n    --no-separator\n    --padding\n    --pointer\n    --preview\n    --preview-border\n    --preview-label\n    --preview-label-pos\n    --preview-window\n    --print-query\n    --print0\n    --prompt\n    --raw\n    --read0\n    --scheme\n    --scroll-off\n    --scrollbar\n    --separator\n    --smart-case\n    --style\n    --sync\n    --tabstop\n    --tac\n    --tail\n    --tiebreak\n    --tmux\n    --track\n    --version\n    --walker\n    --walker-root\n    --walker-skip\n    --with-nth\n    --with-shell\n    --wrap\n    --wrap-sign\n    --preview-wrap-sign\n    --zsh\n    -0 --exit-0\n    -1 --select-1\n    -d --delimiter\n    -e --exact\n    -f --filter\n    -h --help\n    -i --ignore-case\n    -m --multi\n    -n --nth\n    -q --query\n    --\"\n\n  case \"${prev}\" in\n    --scheme)\n      COMPREPLY=($(compgen -W \"default path history\" -- \"$cur\"))\n      return 0\n      ;;\n    --tiebreak)\n      COMPREPLY=($(compgen -W \"length chunk pathname begin end index\" -- \"$cur\"))\n      return 0\n      ;;\n    --color)\n      COMPREPLY=($(compgen -W \"dark light base16 16 bw no\" -- \"$cur\"))\n      return 0\n      ;;\n    --layout)\n      COMPREPLY=($(compgen -W \"default reverse reverse-list\" -- \"$cur\"))\n      return 0\n      ;;\n    --info)\n      COMPREPLY=($(compgen -W \"default right hidden inline inline-right\" -- \"$cur\"))\n      return 0\n      ;;\n    --wrap)\n      COMPREPLY=($(compgen -W \"char word\" -- \"$cur\"))\n      return 0\n      ;;\n    --style)\n      COMPREPLY=($(compgen -W \"default minimal full\" -- \"$cur\"))\n      return 0\n      ;;\n    --preview-window)\n      COMPREPLY=($(compgen -W \"\n      default\n      hidden\n      nohidden\n      wrap\n      wrap-word\n      nowrap\n      cycle\n      nocycle\n      up top\n      down bottom\n      left\n      right\n      rounded border border-rounded\n      border-line\n      sharp border-sharp\n      border-bold\n      border-block\n      border-thinblock\n      border-double\n      noborder border-none\n      border-horizontal\n      border-vertical\n      border-up border-top\n      border-down border-bottom\n      border-left\n      border-right\n      follow\n      nofollow\n      info\n      noinfo\" -- \"$cur\"))\n      return 0\n      ;;\n    --border | --list-border | --header-border | --header-lines-border | --footer-border | --input-border | --preview-border)\n      COMPREPLY=($(compgen -W \"line rounded sharp bold block thinblock double horizontal vertical top bottom left right none\" -- \"$cur\"))\n      return 0\n      ;;\n    --border-label-pos | --preview-label-pos | --list-label-pos | --header-label-pos | --footer-label-pos | --input-label-pos)\n      COMPREPLY=($(compgen -W \"center bottom top\" -- \"$cur\"))\n      return 0\n      ;;\n  esac\n\n  if [[ $cur =~ ^-|\\+ ]]; then\n    COMPREPLY=($(compgen -W \"${opts}\" -- \"$cur\"))\n    return 0\n  fi\n\n  return 0\n}\n\n_fzf_handle_dynamic_completion() {\n  local cmd ret REPLY orig_cmd orig_complete\n  cmd=\"$1\"\n  shift\n  orig_cmd=\"$1\"\n  if __fzf_orig_completion_get_orig_func \"$cmd\"; then\n    \"$REPLY\" \"$@\"\n  elif [[ -n ${_fzf_completion_loader-} ]]; then\n    orig_complete=$(complete -p \"$orig_cmd\" 2> /dev/null)\n    $_fzf_completion_loader \"$@\"\n    ret=$?\n    # _completion_loader may not have updated completion for the command\n    if [[ \"$(complete -p \"$orig_cmd\" 2> /dev/null)\" != \"$orig_complete\" ]]; then\n      __fzf_orig_completion < <(complete -p \"$orig_cmd\" 2> /dev/null)\n      __fzf_orig_completion_get_orig_func \"$cmd\" || ret=1\n\n      # Update orig_complete by _fzf_orig_completion entry\n      [[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&\n        __fzf_orig_completion_instantiate \"$cmd\" \"${BASH_REMATCH[1]}\" &&\n        orig_complete=$REPLY\n\n      if [[ ${__fzf_nospace_commands-} == *\" $orig_cmd \"* ]]; then\n        eval \"${orig_complete/ -F / -o nospace -F }\"\n      else\n        eval \"$orig_complete\"\n      fi\n    fi\n    [[ $ret -eq 0 ]] && return 124\n    return $ret\n  fi\n}\n\n__fzf_generic_path_completion() {\n  local cur base dir leftover matches trigger cmd\n  cmd=\"${COMP_WORDS[0]}\"\n  if [[ $cmd == \\\\* ]]; then\n    cmd=\"${cmd:1}\"\n  fi\n  COMPREPLY=()\n  trigger=${FZF_COMPLETION_TRIGGER-'**'}\n  [[ $COMP_CWORD -ge 0 ]] && cur=\"${COMP_WORDS[COMP_CWORD]}\"\n  if [[ $cur == *\"$trigger\" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then\n    base=${cur:0:${#cur}-${#trigger}}\n    eval \"base=$base\" 2> /dev/null || return\n\n    dir=\n    [[ $base == *\"/\"* ]] && dir=\"$base\"\n    while true; do\n      if [[ -z $dir ]] || [[ -d $dir ]]; then\n        leftover=${base/#\"$dir\"/}\n        leftover=${leftover/#\\//}\n        [[ -z $dir ]] && dir='.'\n        [[ $dir != \"/\" ]] && dir=\"${dir/%\\//}\"\n        matches=$(\n          export FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse --scheme=path\" \"${FZF_COMPLETION_OPTS-} $2\")\n          unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE\n          if [[ $1 =~ dir ]]; then\n            eval \"rest=(${FZF_COMPLETION_DIR_OPTS-})\"\n          else\n            eval \"rest=(${FZF_COMPLETION_PATH_OPTS-})\"\n          fi\n          if declare -F \"$1\" > /dev/null; then\n            eval \"$1 $(builtin printf %q \"$dir\")\" | __fzf_comprun \"$4\" -q \"$leftover\" \"${rest[@]}\"\n          else\n            if [[ $1 =~ dir ]]; then\n              walker=dir,follow\n            else\n              walker=file,dir,follow,hidden\n            fi\n            __fzf_comprun \"$4\" -q \"$leftover\" --walker \"$walker\" --walker-root=\"$dir\" \"${rest[@]}\"\n          fi | while read -r item; do\n            builtin printf \"%q \" \"${item%$3}$3\"\n          done\n        )\n        matches=${matches% }\n        [[ -z $3 ]] && [[ ${__fzf_nospace_commands-} == *\" ${COMP_WORDS[0]} \"* ]] && matches=\"$matches \"\n        if [[ -n $matches ]]; then\n          COMPREPLY=(\"$matches\")\n        else\n          COMPREPLY=(\"$cur\")\n        fi\n        # To redraw line after fzf closes (builtin printf '\\e[5n')\n        bind '\"\\e[0n\": redraw-current-line' 2> /dev/null\n        builtin printf '\\e[5n'\n        return 0\n      fi\n      dir=$(command dirname \"$dir\")\n      [[ $dir =~ /$ ]] || dir=\"$dir\"/\n    done\n  else\n    shift\n    shift\n    shift\n    _fzf_handle_dynamic_completion \"$cmd\" \"$@\"\n  fi\n}\n\n_fzf_complete() {\n  # Split arguments around --\n  local args rest str_arg i sep\n  args=(\"$@\")\n  sep=\n  for i in \"${!args[@]}\"; do\n    if [[ ${args[$i]} == -- ]]; then\n      sep=$i\n      break\n    fi\n  done\n  if [[ -n $sep ]]; then\n    str_arg=\n    rest=(\"${args[@]:$((sep + 1)):${#args[@]}}\")\n    args=(\"${args[@]:0:sep}\")\n  else\n    str_arg=$1\n    args=()\n    shift\n    rest=(\"$@\")\n  fi\n\n  local cur selected trigger cmd post\n  post=\"$(caller 0 | __fzf_exec_awk '{print $2}')_post\"\n  type -t \"$post\" > /dev/null 2>&1 || post='command cat'\n\n  trigger=${FZF_COMPLETION_TRIGGER-'**'}\n  cmd=\"${COMP_WORDS[0]}\"\n  cur=\"${COMP_WORDS[COMP_CWORD]}\"\n  if [[ $cur == *\"$trigger\" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then\n    cur=${cur:0:${#cur}-${#trigger}}\n\n    selected=$(\n      FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse\" \"${FZF_COMPLETION_OPTS-} $str_arg\") \\\n      FZF_DEFAULT_OPTS_FILE='' \\\n        __fzf_comprun \"${rest[0]}\" \"${args[@]}\" -q \"$cur\" | eval \"$post\" | command tr '\\n' ' '\n    )\n    selected=${selected% } # Strip trailing space not to repeat \"-o nospace\"\n    if [[ -n $selected ]]; then\n      COMPREPLY=(\"$selected\")\n    else\n      COMPREPLY=(\"$cur\")\n    fi\n    bind '\"\\e[0n\": redraw-current-line' 2> /dev/null\n    builtin printf '\\e[5n'\n    return 0\n  else\n    _fzf_handle_dynamic_completion \"$cmd\" \"${rest[@]}\"\n  fi\n}\n\n_fzf_path_completion() {\n  __fzf_generic_path_completion _fzf_compgen_path \"-m\" \"\" \"$@\"\n}\n\n# Deprecated. No file only completion.\n_fzf_file_completion() {\n  _fzf_path_completion \"$@\"\n}\n\n_fzf_dir_completion() {\n  __fzf_generic_path_completion _fzf_compgen_dir \"\" \"/\" \"$@\"\n}\n\n_fzf_complete_kill() {\n  _fzf_proc_completion \"$@\"\n}\n\n_fzf_proc_completion() {\n  local transformer\n  transformer='\n    if [[ $FZF_KEY =~ ctrl|alt|shift ]] && [[ -n $FZF_NTH ]]; then\n      nths=( ${FZF_NTH//,/ } )\n      new_nths=()\n      found=0\n      for nth in ${nths[@]}; do\n        if [[ $nth = $FZF_CLICK_HEADER_NTH ]]; then\n          found=1\n        else\n          new_nths+=($nth)\n        fi\n      done\n      [[ $found = 0 ]] && new_nths+=($FZF_CLICK_HEADER_NTH)\n      new_nths=${new_nths[*]}\n      new_nths=${new_nths// /,}\n      echo \"change-nth($new_nths)+change-prompt($new_nths> )\"\n    else\n      if [[ $FZF_NTH = $FZF_CLICK_HEADER_NTH ]]; then\n        echo \"change-nth()+change-prompt(> )\"\n      else\n        echo \"change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )\"\n      fi\n    fi\n  '\n  _fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \\\n    --bind \"click-header:transform:$transformer\" -- \"$@\" < <(\n      command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||\n        command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox\n        command ps --everyone --full --windows                 # For cygwin\n    )\n}\n\n_fzf_proc_completion_post() {\n  __fzf_exec_awk '{print $2}'\n}\n\n# To use custom hostname lists, override __fzf_list_hosts.\n# The function is expected to print hostnames, one per line as well as in the\n# desired sorting and with any duplicates removed, to standard output.\n#\n# e.g.\n#   # Use bash-completions’s _known_hosts_real() for getting the list of hosts\n#   __fzf_list_hosts() {\n#     # Set the local attribute for any non-local variable that is set by _known_hosts_real()\n#     local COMPREPLY=()\n#     _known_hosts_real ''\n#     builtin printf '%s\\n' \"${COMPREPLY[@]}\" | command sort -u --version-sort\n#   }\nif ! declare -F __fzf_list_hosts > /dev/null; then\n  __fzf_list_hosts() {\n    command sort -u \\\n      <(\n        # Note: To make the pathname expansion of \"~/.ssh/config.d/*\" work\n        # properly, we need to adjust the related shell options.  We need to\n        # unset \"set -f\" and \"GLOBIGNORE\", which disable the pathname expansion\n        # totally or partially.  We need to unset \"dotglob\" and \"nocaseglob\" to\n        # avoid matching unwanted files.  We need to unset \"failglob\" to avoid\n        # outputting the error messages to the terminal when no matching is\n        # found.  We need to set \"nullglob\" to avoid attempting to read the\n        # literal filename '~/.ssh/config.d/*' when no matching is found.\n        set +f\n        GLOBIGNORE=\n        shopt -u dotglob nocaseglob failglob\n        shopt -s nullglob\n\n        __fzf_exec_awk '\n          # Note: mawk <= 1.3.3-20090705 does not support the POSIX brackets of\n          # the form [[:blank:]], and Ubuntu 18.04 LTS still uses this\n          # 16-year-old mawk unfortunately.  We need to use [ \\t] instead.\n          match(tolower($0), /^[ \\t]*host(name)?[ \\t]*[ \\t=]/) {\n            $0 = substr($0, RLENGTH + 1) # Remove \"Host(name)?=?\"\n            sub(/#.*/, \"\")\n            for (i = 1; i <= NF; i++)\n              if ($i !~ /[*?%]/)\n                print $i\n          }\n        ' ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null\n      ) \\\n      <(\n        __fzf_exec_awk -F ',' '\n          match($0, /^[][a-zA-Z0-9.,:-]+/) {\n            $0 = substr($0, 1, RLENGTH)\n            gsub(/[][]|:[^,]*/, \"\")\n            for (i = 1; i <= NF; i++)\n              print $i\n          }\n        ' ~/.ssh/known_hosts 2> /dev/null\n      ) \\\n      <(\n        __fzf_exec_awk '\n          {\n            sub(/#.*/, \"\")\n            for (i = 2; i <= NF; i++)\n              if ($i != \"0.0.0.0\")\n                print $i\n          }\n        ' /etc/hosts 2> /dev/null\n      )\n  }\nfi\n\n_fzf_host_completion() {\n  _fzf_complete +m -- \"$@\" < <(__fzf_list_hosts)\n}\n\n# Values for $1 $2 $3 are described here\n# https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html\n# > the first argument ($1) is the name of the command whose arguments are being completed,\n# > the second argument ($2) is the word being completed,\n# > and the third argument ($3) is the word preceding the word being completed on the current command line.\n_fzf_complete_ssh() {\n  case $3 in\n    -i | -F | -E)\n      _fzf_path_completion \"$@\"\n      ;;\n    *)\n      local user=\n      [[ $2 =~ '@' ]] && user=\"${2%%@*}@\"\n      _fzf_complete +m -- \"$@\" < <(__fzf_list_hosts | __fzf_exec_awk -v user=\"$user\" '{print user $0}')\n      ;;\n  esac\n}\n\n_fzf_var_completion() {\n  _fzf_complete -m -- \"$@\" < <(\n    declare -xp | command sed -En 's|^declare [^ ]+ ([^=]+).*|\\1|p'\n  )\n}\n\n_fzf_alias_completion() {\n  _fzf_complete -m -- \"$@\" < <(\n    alias | command sed -En 's|^alias ([^=]+).*|\\1|p'\n  )\n}\n\n# fzf options\ncomplete -o default -F _fzf_opts_completion fzf\n# fzf-tmux is a thin fzf wrapper that has only a few more options than fzf\n# itself. As a quick improvement we take fzf's completion. Adding the few extra\n# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.\ncomplete -o default -F _fzf_opts_completion fzf-tmux\n\n# Default path completion\n__fzf_default_completion() {\n  __fzf_generic_path_completion _fzf_compgen_path \"-m\" \"\" \"$@\"\n\n  # Dynamic completion loader has updated the completion for the command\n  if [[ $? -eq 124 ]]; then\n    # We trigger _fzf_setup_completion so that fuzzy completion for the command\n    # still works. However, loader can update the completion for multiple\n    # commands at once, and fuzzy completion will no longer work for those\n    # other commands. e.g. pytest -> py.test, pytest-2, pytest-3, etc\n    _fzf_setup_completion path \"$1\"\n    return 124\n  fi\n}\n\n# Set fuzzy path completion as the default completion for all commands.\n# We can't set up default completion,\n# 1. if it's already set up by another script\n# 2. or if the current version of bash doesn't support -D option\ncomplete | command grep -q __fzf_default_completion ||\n  complete | command grep -- '-D$' | command grep -qv _comp_complete_load ||\n  complete -D -F __fzf_default_completion -o default -o bashdefault 2> /dev/null\n\nd_cmds=\"${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}\"\n\n# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are\n# undocumented and subject to change in the future.\n#\n# NOTE: Although we have default completion, we still need to set up completion\n# for each command in case they already have completion set up by another script.\na_cmds=\"${FZF_COMPLETION_PATH_COMMANDS-\"\n  awk bat cat code diff diff3\n  emacs emacsclient ex file ftp g++ gcc gvim head hg hx java\n  javac ld less more mvim nvim patch perl python ruby\n  sed sftp sort source tail tee uniq vi view vim wc xdg-open\n  basename bunzip2 bzip2 chmod chown curl cp dirname du\n  find git grep gunzip gzip hg jar\n  ln ls mv open rm rsync scp\n  svn tar unzip zip\"}\"\nv_cmds=\"${FZF_COMPLETION_VAR_COMMANDS-export unset printenv}\"\n\n# Preserve existing completion\n__fzf_orig_completion < <(complete -p $d_cmds $a_cmds $v_cmds unalias kill ssh 2> /dev/null)\n\nif type _comp_load > /dev/null 2>&1; then\n  # _comp_load was added in bash-completion 2.12 to replace _completion_loader.\n  # We use it without -D option so that it does not use _comp_complete_minimal as the fallback.\n  _fzf_completion_loader=_comp_load\nelif type __load_completion > /dev/null 2>&1; then\n  # In bash-completion 2.11, _completion_loader internally calls __load_completion\n  # and if it returns a non-zero status, it sets the default 'minimal' completion.\n  _fzf_completion_loader=__load_completion\nelif type _completion_loader > /dev/null 2>&1; then\n  _fzf_completion_loader=_completion_loader\nfi\n\n__fzf_defc() {\n  local cmd func opts REPLY\n  cmd=\"$1\"\n  func=\"$2\"\n  opts=\"$3\"\n  if __fzf_orig_completion_instantiate \"$cmd\" \"$func\"; then\n    eval \"$REPLY\"\n  else\n    eval \"complete -F \\\"$func\\\" $opts \\\"$cmd\\\"\"\n  fi\n}\n\n# Anything\nfor cmd in $a_cmds; do\n  __fzf_defc \"$cmd\" _fzf_path_completion \"-o default -o bashdefault\"\ndone\n\n# Directory\nfor cmd in $d_cmds; do\n  __fzf_defc \"$cmd\" _fzf_dir_completion \"-o bashdefault -o nospace -o dirnames\"\ndone\n\n# Variables\nfor cmd in $v_cmds; do\n  __fzf_defc \"$cmd\" _fzf_var_completion \"-o default -o nospace -v\"\ndone\n\n# Aliases\n__fzf_defc unalias _fzf_alias_completion \"-a\"\n\n# Processes\n__fzf_defc kill _fzf_proc_completion \"-o default -o bashdefault\"\n\n# ssh\n__fzf_defc ssh _fzf_complete_ssh \"-o default -o bashdefault\"\n\nunset cmd d_cmds a_cmds v_cmds\n\n_fzf_setup_completion() {\n  local kind fn cmd\n  kind=$1\n  fn=_fzf_${1}_completion\n  if [[ $# -lt 2 ]] || ! type -t \"$fn\" > /dev/null; then\n    echo \"usage: ${FUNCNAME[0]} path|dir|var|alias|host|proc COMMANDS...\"\n    return 1\n  fi\n  shift\n  __fzf_orig_completion < <(complete -p \"$@\" 2> /dev/null)\n  for cmd in \"$@\"; do\n    case \"$kind\" in\n      dir) __fzf_defc \"$cmd\" \"$fn\" \"-o nospace -o dirnames\" ;;\n      var) __fzf_defc \"$cmd\" \"$fn\" \"-o default -o nospace -v\" ;;\n      alias) __fzf_defc \"$cmd\" \"$fn\" \"-a\" ;;\n      *) __fzf_defc \"$cmd\" \"$fn\" \"-o default -o bashdefault\" ;;\n    esac\n  done\n}\n#----END shfmt\n\nfi\n"
  },
  {
    "path": "shell/completion.fish",
    "content": "#     ____      ____\n#    / __/___  / __/\n#   / /_/_  / / /_\n#  / __/ / /_/ __/\n# /_/   /___/_/ completion.fish\n#\n# - $FZF_COMPLETION_OPTS                  (default: empty)\n\nfunction fzf_completion_setup\n\n#----BEGIN INCLUDE common.fish\n# NOTE: Do not directly edit this section, which is copied from \"common.fish\".\n# To modify it, one can edit \"common.fish\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"common.fish\" for the implementation details.\n\n  function __fzf_defaults\n    test -n \"$FZF_TMUX_HEIGHT\"; or set -l FZF_TMUX_HEIGHT 40%\n    string join ' ' -- \\\n      \"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore\" $argv[1] \\\n      (test -r \"$FZF_DEFAULT_OPTS_FILE\"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \\\n      $FZF_DEFAULT_OPTS $argv[2..-1]\n  end\n\n  function __fzfcmd\n    test -n \"$FZF_TMUX_HEIGHT\"; or set -l FZF_TMUX_HEIGHT 40%\n    if test -n \"$FZF_TMUX_OPTS\"\n      echo \"fzf-tmux $FZF_TMUX_OPTS -- \"\n    else if test \"$FZF_TMUX\" = \"1\"\n      echo \"fzf-tmux -d$FZF_TMUX_HEIGHT -- \"\n    else\n      echo \"fzf\"\n    end\n  end\n\n  function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'\n    set -l tokens\n    if test (string match -r -- '^\\d+' $version) -ge 4\n      set -- tokens (commandline -xpc)\n    else\n      set -- tokens (commandline -opc)\n    end\n\n    set -l -- var_count 0\n    for i in $tokens\n      if string match -qr -- '^[\\w]+=' $i\n        set var_count (math $var_count + 1)\n      else\n        break\n      end\n    end\n    set -e -- tokens[0..$var_count]\n\n    while true\n      switch \"$tokens[1]\"\n        case builtin command\n          set -e -- tokens[1]\n          test \"$tokens[1]\" = \"--\"; and set -e -- tokens[1]\n        case env\n          set -e -- tokens[1]\n          test \"$tokens[1]\" = \"--\"; and set -e -- tokens[1]\n          while string match -qr -- '^[\\w]+=' \"$tokens[1]\"\n            set -e -- tokens[1]\n          end\n        case '*'\n          break\n      end\n    end\n\n    string escape -n -- $tokens\n  end\n\n  function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'\n    set -l fzf_query ''\n    set -l prefix ''\n    set -l dir '.'\n\n    set -l -- fish_major (string match -r -- '^\\d+' $version)\n    set -l -- fish_minor (string match -r -- '^\\d+\\.(\\d+)' $version)[2]\n\n    set -l -- match_regex '(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)'\n    set -l -- prefix_regex '^-[^\\s=]+=|^-(?!-)\\S'\n    if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -lt 3\n    or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))\n      set -- match_regex \"(?<prefix>$prefix_regex)?$match_regex\"\n    end\n\n    if test \"$fish_major\" -ge 4\n      string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)\n    else if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n      string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)\n      eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\\\(?=~)|\\\\\\(?=\\$\\w)' '')\n    else\n      set -l -- cl_token (commandline --current-token --tokenize | string collect -N)\n      set -- prefix (string match -r -- $prefix_regex $cl_token)\n      set -- fzf_query (string replace -- \"$prefix\" '' $cl_token | string collect -N)\n      eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\\\(?=~)|\\\\\\(?=\\$\\w)|\\\\\\n\\\\\\n$' '')\n    end\n\n    if test -n \"$fzf_query\"\n      if test \\( \"$fish_major\" -ge 4 \\) -o \\( \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 5 \\)\n        set -- fzf_query (path normalize -- $fzf_query)\n        set -- dir $fzf_query\n        while not path is -d $dir\n          set -- dir (path dirname $dir)\n        end\n      else\n        if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n          string match -q -r -- '(?<fzf_query>^[\\s\\S]*?(?=\\n?$)$)' \\\n            (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)\n        else\n          set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)\n          eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\\\\n$' '')\n        end\n        set -- dir $fzf_query\n        while not test -d \"$dir\"\n          set -- dir (dirname -z -- \"$dir\" | string split0)\n        end\n      end\n\n      if not string match -q -- '.' $dir; or string match -q -r -- '^\\./|^\\.$' $fzf_query\n        if test \"$fish_major\" -ge 4\n          string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\\s\\S]*)' $fzf_query\n        else if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n          string match -q -r -- '^/?(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)' \\\n            (string replace -- \"$dir\" '' $fzf_query | string collect -N)\n        else\n          set -- fzf_query (string replace -- \"$dir\" '' $fzf_query | string collect -N)\n          eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\\\\n$' '')\n        end\n      end\n    end\n\n    string escape -n -- \"$dir\" \"$fzf_query\" \"$prefix\"\n  end\n#----END INCLUDE\n\n  # Use complete builtin for specific commands\n  function __fzf_complete_native\n    set -l -- token (commandline -t)\n    set -l -- completions (eval complete -C \\\"$argv[1]\\\")\n    test -n \"$completions\"; or begin commandline -f repaint; return; end\n\n    # Calculate tabstop based on longest completion item (sample first 500 for performance)\n    set -l -- tabstop 20\n    set -l -- sample_size (math \"min(500, \"(count $completions)\")\")\n    for c in $completions[1..$sample_size]\n      set -l -- len (string length -V -- (string split -- \\t $c))\n      test -n \"$len[2]\" -a \"$len[1]\" -gt \"$tabstop\"\n      and set -- tabstop $len[1]\n    end\n    # limit to 120 to prevent long lines\n    set -- tabstop (math \"min($tabstop + 4, 120)\")\n\n    set -l result\n    set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \\\n      \"--reverse --delimiter=\\\\t --nth=1 --tabstop=$tabstop --color=fg:dim,nth:regular\" \\\n      $FZF_COMPLETION_OPTS $argv[2..-1] --accept-nth=1 --read0 --print0)\n    set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0)\n    and begin\n      set -l -- tail ' '\n      # Append / to bare ~username results (fish omits it unlike other shells)\n      set -- result (string replace -r -- '^(~\\w+)\\s?$' '$1/' $result)\n      # Don't add trailing space if single result is a directory\n      test (count $result) -eq 1\n      and string match -q -- '*/' \"$result\"; and set -- tail ''\n\n      set -l -- result (string escape -n -- $result)\n\n      string match -q -- '~*' \"$token\"\n      and set result (string replace -r -- '^\\\\\\\\~' '~' $result)\n\n      string match -q -- '$*' \"$token\"\n      and set result (string replace -r -- '^\\\\\\\\\\$' '\\$' $result)\n\n      commandline -rt -- (string join ' ' -- $result)$tail\n    end\n    commandline -f repaint\n  end\n\n  function _fzf_complete\n    set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ')\n    set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post\n    set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1])\n    set -lx FZF_DEFAULT_OPTS_FILE\n    set -lx FZF_DEFAULT_COMMAND\n    set -l -- fzf_query (commandline -t | string escape)\n    set -l result\n    eval (__fzfcmd) --query=$fzf_query | while read -l r; set -a -- result $r; end\n    and if functions -q $post_func\n      commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' '\n    else\n      commandline -rt -- (string join -- ' ' (string escape -- $result))' '\n    end\n    commandline -f repaint\n  end\n\n  # Kill completion (process selection)\n  function _fzf_complete_kill\n    set -l -- fzf_query (commandline -t | string escape)\n    set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \\\n    --accept-nth=2 -m --header-lines=1 --no-preview --wrap)\n    set -lx FZF_DEFAULT_OPTS_FILE\n    if type -q ps\n      set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \\\n      'or command ps -eo user,pid,ppid,time,args 2>/dev/null;' \\\n      'or command ps --everyone --full --windows 2>/dev/null; end'\n      set -l -- result (eval $ps_cmd \\| (__fzfcmd) --query=$fzf_query)\n      and commandline -rt -- (string join ' ' -- $result)\" \"\n    else\n      __fzf_complete_native \"kill \" --multi --query=$fzf_query\n    end\n    commandline -f repaint\n  end\n\n  # Main completion function\n  function fzf-completion\n    set -l -- tokens (__fzf_cmd_tokens)\n    set -l -- current_token (commandline -t)\n    set -l -- cmd_name $tokens[1]\n\n    # Route to appropriate completion function\n    if test -n \"$tokens\"; and functions -q _fzf_complete_$cmd_name\n      _fzf_complete_$cmd_name $tokens\n    else\n      set -l -- fzf_opt --query=$current_token --multi\n      __fzf_complete_native \"$tokens $current_token\" $fzf_opt\n    end\n  end\n\n  # Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior)\n  if test (string match -r -- '^\\d+' $version) -ge 4\n    bind shift-tab fzf-completion\n    bind -M insert shift-tab fzf-completion\n  else\n    bind -k btab fzf-completion\n    bind -M insert -k btab fzf-completion\n  end\nend\n\n# Run setup\nfzf_completion_setup\n"
  },
  {
    "path": "shell/completion.zsh",
    "content": "#     ____      ____\n#    / __/___  / __/\n#   / /_/_  / / /_\n#  / __/ / /_/ __/\n# /_/   /___/_/ completion.zsh\n#\n# - $FZF_TMUX                 (default: 0)\n# - $FZF_TMUX_OPTS            (default: empty)\n# - $FZF_COMPLETION_TRIGGER   (default: '**')\n# - $FZF_COMPLETION_OPTS      (default: empty)\n# - $FZF_COMPLETION_PATH_OPTS (default: empty)\n# - $FZF_COMPLETION_DIR_OPTS  (default: empty)\n\n\n# Both branches of the following `if` do the same thing -- define\n# __fzf_completion_options such that `eval $__fzf_completion_options` sets\n# all options to the same values they currently have. We'll do just that at\n# the bottom of the file after changing options to what we prefer.\n#\n# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted\n# *must* be quoted in order to prevent alias expansion. In addition, code must\n# be written in a way works with any set of zsh options. This is very tricky, so\n# careful when you change it.\n#\n# Start by loading the builtin zsh/parameter module. It provides `options`\n# associative array that stores current shell options.\nif 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then\n  # This is the fast branch and it gets taken on virtually all Zsh installations.\n  #\n  # ${(kv)options[@]} expands to array of keys (option names) and values (\"on\"\n  # or \"off\"). The subsequent expansion# with (j: :) flag joins all elements\n  # together separated by spaces. __fzf_completion_options ends up with a value\n  # like this: \"options=(shwordsplit off aliases on ...)\".\n  __fzf_completion_options=\"options=(${(j: :)${(kv)options[@]}})\"\nelse\n  # This branch is much slower because it forks to get the names of all\n  # zsh options. It's possible to eliminate this fork but it's not worth the\n  # trouble because this branch gets taken only on very ancient or broken\n  # zsh installations.\n  () {\n    # That `()` above defines an anonymous function. This is essentially a scope\n    # for local parameters. We use it to avoid polluting global scope.\n    'local' '__fzf_opt'\n    __fzf_completion_options=\"setopt\"\n    # `set -o` prints one line for every zsh option. Each line contains option\n    # name, some spaces, and then either \"on\" or \"off\". We just want option names.\n    # Expansion with (@f) flag splits a string into lines. The outer expansion\n    # removes spaces and everything that follow them on every line. __fzf_opt\n    # ends up iterating over option names: shwordsplit, aliases, etc.\n    for __fzf_opt in \"${(@)${(@f)$(set -o)}%% *}\"; do\n      if [[ -o \"$__fzf_opt\" ]]; then\n        # Option $__fzf_opt is currently on, so remember to set it back on.\n        __fzf_completion_options+=\" -o $__fzf_opt\"\n      else\n        # Option $__fzf_opt is currently off, so remember to set it back off.\n        __fzf_completion_options+=\" +o $__fzf_opt\"\n      fi\n    done\n    # The value of __fzf_completion_options here looks like this:\n    # \"setopt +o shwordsplit -o aliases ...\"\n  }\nfi\n\n# Enable the default zsh options (those marked with <Z> in `man zshoptions`)\n# but without `aliases`. Aliases in functions are expanded when functions are\n# defined, so if we disable aliases here, we'll be sure to have no pesky\n# aliases in any of our functions. This way we won't need prefix every\n# command with `command` or to quote every word to defend against global\n# aliases. Note that `aliases` is not the only option that's important to\n# control. There are several others that could wreck havoc if they are set\n# to values we don't expect. With the following `emulate` command we\n# sidestep this issue entirely.\n'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'\n\n# This brace is the start of try-always block. The `always` part is like\n# `finally` in lesser languages. We use it to *always* restore user options.\n{\n# The 'emulate' command should not be placed inside the interactive if check;\n# placing it there fails to disable alias expansion. See #3731.\nif [[ -o interactive ]]; then\n\n# To use custom commands instead of find, override _fzf_compgen_{path,dir}\n#\n#   _fzf_compgen_path() {\n#     echo \"$1\"\n#     command find -L \"$1\" \\\n#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \\( -type d -o -type f -o -type l \\) \\\n#       -a -not -path \"$1\" -print 2> /dev/null | sed 's@^\\./@@'\n#   }\n#\n#   _fzf_compgen_dir() {\n#     command find -L \"$1\" \\\n#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \\\n#       -a -not -path \"$1\" -print 2> /dev/null | sed 's@^\\./@@'\n#   }\n\n###########################################################\n\n#----BEGIN INCLUDE common.sh\n# NOTE: Do not directly edit this section, which is copied from \"common.sh\".\n# To modify it, one can edit \"common.sh\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"common.sh\" for the implementation details.\n\n__fzf_defaults() {\n  builtin printf '%s\\n' \"--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1\"\n  command cat \"${FZF_DEFAULT_OPTS_FILE-}\" 2> /dev/null\n  builtin printf '%s\\n' \"${FZF_DEFAULT_OPTS-} $2\"\n}\n\n__fzf_exec_awk() {\n  if [[ -z ${__fzf_awk-} ]]; then\n    __fzf_awk=awk\n    if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then\n      __fzf_awk=/usr/xpg4/bin/awk\n    elif command -v mawk > /dev/null 2>&1; then\n      local n x y z d\n      IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)\n      [[ $n == mawk ]] &&\n        (((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&\n        ((d >= 20230302)) 2> /dev/null &&\n        __fzf_awk=mawk\n    fi\n  fi\n  LC_ALL=C exec \"$__fzf_awk\" \"$@\"\n}\n#----END INCLUDE\n\n__fzf_comprun() {\n  if [[ \"$(type _fzf_comprun 2>&1)\" =~ function ]]; then\n    _fzf_comprun \"$@\"\n  elif [ -n \"${TMUX_PANE-}\" ] && { [ \"${FZF_TMUX:-0}\" != 0 ] || [ -n \"${FZF_TMUX_OPTS-}\" ]; }; then\n    shift\n    if [ -n \"${FZF_TMUX_OPTS-}\" ]; then\n      fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- \"$@\"\n    else\n      fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- \"$@\"\n    fi\n  else\n    shift\n    fzf \"$@\"\n  fi\n}\n\n# Extract the name of the command. e.g. ls; foo=1 ssh **<tab>\n__fzf_extract_command() {\n  # Control completion with the \"compstate\" parameter, insert and list nothing\n  compstate[insert]=\n  compstate[list]=\n  cmd_word=\"${(Q)words[1]}\"\n}\n\n__fzf_generic_path_completion() {\n  local base lbuf compgen fzf_opts suffix tail dir leftover matches\n  base=$1\n  lbuf=$2\n  compgen=$3\n  fzf_opts=$4\n  suffix=$5\n  tail=$6\n\n  setopt localoptions nonomatch\n  if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then\n    return\n  fi\n  eval \"base=$base\" 2> /dev/null || return\n  [[ $base = *\"/\"* ]] && dir=\"$base\"\n  while [ 1 ]; do\n    if [[ -z \"$dir\" || -d ${dir} ]]; then\n      leftover=${base/#\"$dir\"}\n      leftover=${leftover/#\\/}\n      [ -z \"$dir\" ] && dir='.'\n      [ \"$dir\" != \"/\" ] && dir=\"${dir/%\\//}\"\n      matches=$(\n        export FZF_DEFAULT_OPTS\n        FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse --scheme=path\" \"${FZF_COMPLETION_OPTS-}\")\n        unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE\n        if [[ $compgen =~ dir ]]; then\n          rest=${FZF_COMPLETION_DIR_OPTS-}\n        else\n          rest=${FZF_COMPLETION_PATH_OPTS-}\n        fi\n        if declare -f \"$compgen\" > /dev/null; then\n          eval \"$compgen $(printf %q \"$dir\")\" | __fzf_comprun \"$cmd_word\" ${(Q)${(Z+n+)fzf_opts}} -q \"$leftover\" ${(Q)${(Z+n+)rest}}\n        else\n          if [[ $compgen =~ dir ]]; then\n            walker=dir,follow\n          else\n            walker=file,dir,follow,hidden\n          fi\n          __fzf_comprun \"$cmd_word\" ${(Q)${(Z+n+)fzf_opts}} -q \"$leftover\" --walker \"$walker\" --walker-root=\"$dir\" ${(Q)${(Z+n+)rest}} < /dev/tty\n        fi | while read -r item; do\n          item=\"${item%$suffix}$suffix\"\n          echo -n -E \"${(q)item} \"\n        done\n      )\n      matches=${matches% }\n      if [ -n \"$matches\" ]; then\n        LBUFFER=\"$lbuf$matches$tail\"\n      fi\n      zle reset-prompt\n      break\n    fi\n    dir=$(dirname \"$dir\")\n    dir=${dir%/}/\n  done\n}\n\n_fzf_path_completion() {\n  __fzf_generic_path_completion \"$1\" \"$2\" _fzf_compgen_path \\\n    \"-m\" \"\" \" \"\n}\n\n_fzf_dir_completion() {\n  __fzf_generic_path_completion \"$1\" \"$2\" _fzf_compgen_dir \\\n    \"\" \"/\" \"\"\n}\n\n_fzf_feed_fifo() {\n  command rm -f \"$1\"\n  mkfifo \"$1\"\n  cat <&0 > \"$1\" &|\n}\n\n_fzf_complete() {\n  setopt localoptions ksh_arrays\n  # Split arguments around --\n  local args rest str_arg i sep\n  args=(\"$@\")\n  sep=\n  for i in {0..${#args[@]}}; do\n    if [[ \"${args[$i]-}\" = -- ]]; then\n      sep=$i\n      break\n    fi\n  done\n  if [[ -n \"$sep\" ]]; then\n    str_arg=\n    rest=(\"${args[@]:$((sep + 1)):${#args[@]}}\")\n    args=(\"${args[@]:0:$sep}\")\n  else\n    str_arg=$1\n    args=()\n    shift\n    rest=(\"$@\")\n  fi\n\n  local fifo lbuf matches post\n  fifo=\"${TMPDIR:-/tmp}/fzf-complete-fifo-$$\"\n  lbuf=${rest[0]}\n  post=\"${funcstack[1]}_post\"\n  type $post > /dev/null 2>&1 || post=cat\n\n  _fzf_feed_fifo \"$fifo\"\n  matches=$(\n    FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse\" \"${FZF_COMPLETION_OPTS-} $str_arg\") \\\n    FZF_DEFAULT_OPTS_FILE='' \\\n      __fzf_comprun \"$cmd_word\" \"${args[@]}\" -q \"${(Q)prefix}\" < \"$fifo\" | $post | tr '\\n' ' ')\n  if [ -n \"$matches\" ]; then\n    LBUFFER=\"$lbuf$matches\"\n  fi\n  command rm -f \"$fifo\"\n}\n\n# To use custom hostname lists, override __fzf_list_hosts.\n# The function is expected to print hostnames, one per line as well as in the\n# desired sorting and with any duplicates removed, to standard output.\nif ! declare -f __fzf_list_hosts > /dev/null; then\n  __fzf_list_hosts() {\n    command sort -u \\\n      <(\n        # Note: To make the pathname expansion of \"~/.ssh/config.d/*\" work\n        # properly, we need to adjust the related shell options.  We need to\n        # unset \"NO_GLOB\" (or reset \"GLOB\"), which disable the pathname\n        # expansion totally.  We need to unset \"DOT_GLOB\" and set \"CASE_GLOB\"\n        # to avoid matching unwanted files.  We need to set \"NULL_GLOB\" to\n        # avoid attempting to read the literal filename '~/.ssh/config.d/*'\n        # when no matching is found.\n        setopt GLOB NO_DOT_GLOB CASE_GLOB NO_NOMATCH NULL_GLOB\n\n        __fzf_exec_awk '\n          # Note: mawk <= 1.3.3-20090705 does not support the POSIX brackets of\n          # the form [[:blank:]], and Ubuntu 18.04 LTS still uses this\n          # 16-year-old mawk unfortunately.  We need to use [ \\t] instead.\n          match(tolower($0), /^[ \\t]*host(name)?[ \\t]*[ \\t=]/) {\n            $0 = substr($0, RLENGTH + 1) # Remove \"Host(name)?=?\"\n            sub(/#.*/, \"\")\n            for (i = 1; i <= NF; i++)\n              if ($i !~ /[*?%]/)\n                print $i\n          }\n        ' ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null\n      ) \\\n      <(\n        __fzf_exec_awk -F ',' '\n          match($0, /^[][a-zA-Z0-9.,:-]+/) {\n            $0 = substr($0, 1, RLENGTH)\n            gsub(/[][]|:[^,]*/, \"\")\n            for (i = 1; i <= NF; i++)\n              print $i\n          }\n        ' ~/.ssh/known_hosts 2> /dev/null\n      ) \\\n      <(\n        __fzf_exec_awk '\n          {\n            sub(/#.*/, \"\")\n            for (i = 2; i <= NF; i++)\n              if ($i != \"0.0.0.0\")\n                print $i\n          }\n        ' /etc/hosts 2> /dev/null\n      )\n  }\nfi\n\n_fzf_complete_telnet() {\n  _fzf_complete +m -- \"$@\" < <(__fzf_list_hosts)\n}\n\n# The first and the only argument is the LBUFFER without the current word that contains the trigger.\n# The current word without the trigger is in the $prefix variable passed from the caller.\n_fzf_complete_ssh() {\n  local -a tokens\n  tokens=(${(z)1})\n  case ${tokens[-1]} in\n    -i|-F|-E)\n      _fzf_path_completion \"$prefix\" \"$1\"\n      ;;\n    *)\n      local user\n      [[ $prefix =~ @ ]] && user=\"${prefix%%@*}@\"\n      _fzf_complete +m -- \"$@\" < <(__fzf_list_hosts | __fzf_exec_awk -v user=\"$user\" '{print user $0}')\n      ;;\n  esac\n}\n\n_fzf_complete_export() {\n  _fzf_complete -m -- \"$@\" < <(\n    declare -xp | sed 's/=.*//' | sed 's/.* //'\n  )\n}\n\n_fzf_complete_unset() {\n  _fzf_complete -m -- \"$@\" < <(\n    declare -xp | sed 's/=.*//' | sed 's/.* //'\n  )\n}\n\n_fzf_complete_unalias() {\n  _fzf_complete +m -- \"$@\" < <(\n    alias | sed 's/=.*//'\n  )\n}\n\n_fzf_complete_kill() {\n  local transformer\n  transformer='\n    if [[ $FZF_KEY =~ ctrl|alt|shift ]] && [[ -n $FZF_NTH ]]; then\n      nths=( ${FZF_NTH//,/ } )\n      new_nths=()\n      found=0\n      for nth in ${nths[@]}; do\n        if [[ $nth = $FZF_CLICK_HEADER_NTH ]]; then\n          found=1\n        else\n          new_nths+=($nth)\n        fi\n      done\n      [[ $found = 0 ]] && new_nths+=($FZF_CLICK_HEADER_NTH)\n      new_nths=${new_nths[*]}\n      new_nths=${new_nths// /,}\n      echo \"change-nth($new_nths)+change-prompt($new_nths> )\"\n    else\n      if [[ $FZF_NTH = $FZF_CLICK_HEADER_NTH ]]; then\n        echo \"change-nth()+change-prompt(> )\"\n      else\n        echo \"change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )\"\n      fi\n    fi\n  '\n  _fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \\\n    --bind \"click-header:transform:$transformer\" -- \"$@\" < <(\n    command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||\n      command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox\n      command ps --everyone --full --windows # For cygwin\n  )\n}\n\n_fzf_complete_kill_post() {\n  __fzf_exec_awk '{print $2}'\n}\n\nfzf-completion() {\n  local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word\n  setopt localoptions noshwordsplit noksh_arrays noposixbuiltins\n\n  # http://zsh.sourceforge.net/FAQ/zshfaq03.html\n  # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags\n  tokens=(${(z)LBUFFER})\n  if [ ${#tokens} -lt 1 ]; then\n    zle ${fzf_default_completion:-expand-or-complete}\n    return\n  fi\n\n  # Explicitly allow for empty trigger.\n  trigger=${FZF_COMPLETION_TRIGGER-'**'}\n  [[ -z $trigger && ${LBUFFER[-1]} == ' ' ]] && tokens+=(\"\")\n\n  # When the trigger starts with ';', it becomes a separate token\n  if [[ ${LBUFFER} = *\"${tokens[-2]-}${tokens[-1]}\" ]]; then\n    tokens[-2]=\"${tokens[-2]-}${tokens[-1]}\"\n    tokens=(${tokens[0,-2]})\n  fi\n\n  lbuf=$LBUFFER\n  tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}\n\n  # Trigger sequence given\n  if [ ${#tokens} -gt 1 -a \"$tail\" = \"$trigger\" ]; then\n    d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})\n\n    {\n      cursor_pos=$CURSOR\n      # Move the cursor before the trigger to preserve word array elements when\n      # trigger chars like ';' or '`' would otherwise reset the 'words' array.\n      CURSOR=$((cursor_pos - ${#trigger} - 1))\n      # Check if at least one completion system (old or new) is active.\n      # If at least one user-defined completion widget is detected, nothing will\n      # be completed if neither the old nor the new completion system is enabled.\n      # In such cases, the 'zsh/compctl' module is loaded as a fallback.\n      if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then\n        zmodload -F zsh/compctl 2>/dev/null\n      fi\n      # Create a completion widget to access the 'words' array (man zshcompwid)\n      zle -C __fzf_extract_command .complete-word __fzf_extract_command\n      zle __fzf_extract_command\n    } always {\n      CURSOR=$cursor_pos\n      # Delete the completion widget\n      zle -D __fzf_extract_command  2>/dev/null\n    }\n\n    [ -z \"$trigger\"      ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}\n    if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then\n      return\n    fi\n    [ -n \"${tokens[-1]}\" ] && lbuf=${lbuf:0:-${#tokens[-1]}}\n\n    if eval \"noglob type _fzf_complete_${cmd_word} >/dev/null\"; then\n      prefix=\"$prefix\" eval _fzf_complete_${cmd_word} ${(q)lbuf}\n      zle reset-prompt\n    elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then\n      _fzf_dir_completion \"$prefix\" \"$lbuf\"\n    else\n      _fzf_path_completion \"$prefix\" \"$lbuf\"\n    fi\n  # Fall back to default completion\n  else\n    zle ${fzf_default_completion:-expand-or-complete}\n  fi\n}\n\n[ -z \"$fzf_default_completion\" ] && {\n  binding=$(bindkey '^I')\n  [[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(s: :w)2]\n  unset binding\n}\n\n# Normal widget\nzle     -N   fzf-completion\nbindkey '^I' fzf-completion\nfi\n\n} always {\n  # Restore the original options.\n  eval $__fzf_completion_options\n  'unset' '__fzf_completion_options'\n}\n"
  },
  {
    "path": "shell/key-bindings.bash",
    "content": "#     ____      ____\n#    / __/___  / __/\n#   / /_/_  / / /_\n#  / __/ / /_/ __/\n# /_/   /___/_/ key-bindings.bash\n#\n# - $FZF_TMUX_OPTS\n# - $FZF_CTRL_T_COMMAND\n# - $FZF_CTRL_T_OPTS\n# - $FZF_CTRL_R_COMMAND\n# - $FZF_CTRL_R_OPTS\n# - $FZF_ALT_C_COMMAND\n# - $FZF_ALT_C_OPTS\n\nif [[ $- =~ i ]]; then\n\n\n# Key bindings\n# ------------\n\n#----BEGIN shfmt\n#----BEGIN INCLUDE common.sh\n# NOTE: Do not directly edit this section, which is copied from \"common.sh\".\n# To modify it, one can edit \"common.sh\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"common.sh\" for the implementation details.\n\n__fzf_defaults() {\n  builtin printf '%s\\n' \"--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1\"\n  command cat \"${FZF_DEFAULT_OPTS_FILE-}\" 2> /dev/null\n  builtin printf '%s\\n' \"${FZF_DEFAULT_OPTS-} $2\"\n}\n\n__fzf_exec_awk() {\n  if [[ -z ${__fzf_awk-} ]]; then\n    __fzf_awk=awk\n    if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then\n      __fzf_awk=/usr/xpg4/bin/awk\n    elif command -v mawk > /dev/null 2>&1; then\n      local n x y z d\n      IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)\n      [[ $n == mawk ]] &&\n        (((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&\n        ((d >= 20230302)) 2> /dev/null &&\n        __fzf_awk=mawk\n    fi\n  fi\n  LC_ALL=C exec \"$__fzf_awk\" \"$@\"\n}\n#----END INCLUDE\n\n__fzf_select__() {\n  FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \\\n    FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse --walker=file,dir,follow,hidden --scheme=path\" \"${FZF_CTRL_T_OPTS-} -m\") \\\n    FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) \"$@\" |\n    while read -r item; do\n      printf '%q ' \"$item\" # escape special chars\n    done\n}\n\n__fzfcmd() {\n  [[ -n ${TMUX_PANE-} ]] && { [[ ${FZF_TMUX:-0} != 0 ]] || [[ -n ${FZF_TMUX_OPTS-} ]]; } &&\n    echo \"fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- \" || echo \"fzf\"\n}\n\nfzf-file-widget() {\n  local selected=\"$(__fzf_select__ \"$@\")\"\n  READLINE_LINE=\"${READLINE_LINE:0:READLINE_POINT}$selected${READLINE_LINE:READLINE_POINT}\"\n  READLINE_POINT=$((READLINE_POINT + ${#selected}))\n}\n\n__fzf_cd__() {\n  local dir\n  dir=$(\n    FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \\\n      FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse --walker=dir,follow,hidden --scheme=path\" \"${FZF_ALT_C_OPTS-} +m\") \\\n      FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd)\n  ) && printf 'builtin cd -- %q' \"$(builtin unset CDPATH && builtin cd -- \"$dir\" && builtin pwd)\"\n}\n\nif command -v perl > /dev/null; then\n  __fzf_history__() {\n    local output script\n    script='BEGIN { getc; $/ = \"\\n\\t\"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; s/\\n/\\n\\t/gm; print $HISTCOUNT - $. . \"\\t$_\" if !$seen{$_}++'\n    output=$(\n      set +o pipefail\n      builtin fc -lnr -2147483648 |\n        last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e \"$script\" |\n        FZF_DEFAULT_OPTS=$(__fzf_defaults \"\" \"-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\"$'\\t'\"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0\") \\\n        FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query \"$READLINE_LINE\"\n    ) || return\n    READLINE_LINE=$(command perl -pe 's/^\\d*\\t//' <<< \"$output\")\n    if [[ -z $READLINE_POINT ]]; then\n      echo \"$READLINE_LINE\"\n    else\n      READLINE_POINT=0x7fffffff\n    fi\n  }\nelse # awk - fallback for POSIX systems\n  __fzf_history__() {\n    local output script\n    [[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries\n    script='function P(b) { ++n; sub(/^[ *]/, \"\", b); if (!seen[b]++) { printf \"%d\\t%s%c\", '$((BASH_REMATCH + 1))' - n, b, 0 } }\n    NR==1 { b = substr($0, 2); next }\n    /^\\t/ { P(b); b = substr($0, 2); next }\n    { b = b RS $0 }\n    END { if (NR) P(b) }'\n    output=$(\n      set +o pipefail\n      builtin fc -lnr -2147483648 2> /dev/null | # ( $'\\t '<lines>$'\\n' )* ; <lines> ::= [^\\n]* ( $'\\n'<lines> )*\n        __fzf_exec_awk \"$script\" |               # ( <counter>$'\\t'<lines>$'\\000' )*\n        FZF_DEFAULT_OPTS=$(__fzf_defaults \"\" \"-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\"$'\\t'\"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0\") \\\n        FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query \"$READLINE_LINE\"\n    ) || return\n    READLINE_LINE=${output#*$'\\t'}\n    if [[ -z $READLINE_POINT ]]; then\n      echo \"$READLINE_LINE\"\n    else\n      READLINE_POINT=0x7fffffff\n    fi\n  }\nfi\n\n# Required to refresh the prompt after fzf\nbind -m emacs-standard '\"\\C-\\e(\": redraw-current-line'\n\nbind -m vi-command '\"\\C-z\": emacs-editing-mode'\nbind -m vi-insert '\"\\C-z\": emacs-editing-mode'\nbind -m emacs-standard '\"\\C-z\": vi-editing-mode'\n\nif ((BASH_VERSINFO[0] < 4)); then\n  # CTRL-T - Paste the selected file path into the command line\n  if [[ ${FZF_CTRL_T_COMMAND-x} != \"\" ]]; then\n    bind -m emacs-standard '\"\\C-t\": \" \\C-b\\C-k \\C-u`__fzf_select__`\\e\\C-e\\C-\\e(\\C-a\\C-y\\C-h\\C-e\\e \\C-y\\ey\\C-x\\C-x\\C-f\\C-y\\ey\\C-_\"'\n    bind -m vi-command '\"\\C-t\": \"\\C-z\\C-t\\C-z\"'\n    bind -m vi-insert '\"\\C-t\": \"\\C-z\\C-t\\C-z\"'\n  fi\n\n  # CTRL-R - Paste the selected command from history into the command line\n  if [[ ${FZF_CTRL_R_COMMAND-x} != \"\" ]]; then\n    if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then\n      echo \"warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R\" >&2\n    fi\n    bind -m emacs-standard '\"\\C-r\": \"\\C-e \\C-u\\C-y\\ey\\C-u`__fzf_history__`\\e\\C-e\\C-\\e(\"'\n    bind -m vi-command '\"\\C-r\": \"\\C-z\\C-r\\C-z\"'\n    bind -m vi-insert '\"\\C-r\": \"\\C-z\\C-r\\C-z\"'\n  fi\nelse\n  # CTRL-T - Paste the selected file path into the command line\n  if [[ ${FZF_CTRL_T_COMMAND-x} != \"\" ]]; then\n    bind -m emacs-standard -x '\"\\C-t\": fzf-file-widget'\n    bind -m vi-command -x '\"\\C-t\": fzf-file-widget'\n    bind -m vi-insert -x '\"\\C-t\": fzf-file-widget'\n  fi\n\n  # CTRL-R - Paste the selected command from history into the command line\n  if [[ ${FZF_CTRL_R_COMMAND-x} != \"\" ]]; then\n    if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then\n      echo \"warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R\" >&2\n    fi\n    bind -m emacs-standard -x '\"\\C-r\": __fzf_history__'\n    bind -m vi-command -x '\"\\C-r\": __fzf_history__'\n    bind -m vi-insert -x '\"\\C-r\": __fzf_history__'\n  fi\nfi\n\n# ALT-C - cd into the selected directory\nif [[ ${FZF_ALT_C_COMMAND-x} != \"\" ]]; then\n  bind -m emacs-standard '\"\\ec\": \" \\C-b\\C-k \\C-u`__fzf_cd__`\\e\\C-e\\C-\\e(\\C-m\\C-y\\C-h\\e \\C-y\\ey\\C-x\\C-x\\C-d\\C-y\\ey\\C-_\"'\n  bind -m vi-command '\"\\ec\": \"\\C-z\\ec\\C-z\"'\n  bind -m vi-insert '\"\\ec\": \"\\C-z\\ec\\C-z\"'\nfi\n#----END shfmt\n\nfi\n"
  },
  {
    "path": "shell/key-bindings.fish",
    "content": "#     ____      ____\n#    / __/___  / __/\n#   / /_/_  / / /_\n#  / __/ / /_/ __/\n# /_/   /___/_/ key-bindings.fish\n#\n# - $FZF_TMUX_OPTS\n# - $FZF_CTRL_T_COMMAND\n# - $FZF_CTRL_T_OPTS\n# - $FZF_CTRL_R_COMMAND\n# - $FZF_CTRL_R_OPTS\n# - $FZF_ALT_C_COMMAND\n# - $FZF_ALT_C_OPTS\n\n\n# Key bindings\n# ------------\n# The oldest supported fish version is 3.1b1. To maintain compatibility, the\n# command substitution syntax $(cmd) should never be used, even behind a version\n# check, otherwise the source command will fail on fish versions older than 3.4.0.\nfunction fzf_key_bindings\n\n  # Check fish version\n  if set -l -- fish_ver (string match -r '^(\\d+)\\.(\\d+)' $version 2>/dev/null)\n  and test \"$fish_ver[2]\" -lt 3 -o \"$fish_ver[2]\" -eq 3 -a \"$fish_ver[3]\" -lt 1\n    echo \"This script requires fish version 3.1b1 or newer.\" >&2\n    return 1\n  else if not type -q fzf\n    echo \"fzf was not found in path.\" >&2\n    return 1\n  end\n\n#----BEGIN INCLUDE common.fish\n# NOTE: Do not directly edit this section, which is copied from \"common.fish\".\n# To modify it, one can edit \"common.fish\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"common.fish\" for the implementation details.\n\n  function __fzf_defaults\n    test -n \"$FZF_TMUX_HEIGHT\"; or set -l FZF_TMUX_HEIGHT 40%\n    string join ' ' -- \\\n      \"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore\" $argv[1] \\\n      (test -r \"$FZF_DEFAULT_OPTS_FILE\"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \\\n      $FZF_DEFAULT_OPTS $argv[2..-1]\n  end\n\n  function __fzfcmd\n    test -n \"$FZF_TMUX_HEIGHT\"; or set -l FZF_TMUX_HEIGHT 40%\n    if test -n \"$FZF_TMUX_OPTS\"\n      echo \"fzf-tmux $FZF_TMUX_OPTS -- \"\n    else if test \"$FZF_TMUX\" = \"1\"\n      echo \"fzf-tmux -d$FZF_TMUX_HEIGHT -- \"\n    else\n      echo \"fzf\"\n    end\n  end\n\n  function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'\n    set -l tokens\n    if test (string match -r -- '^\\d+' $version) -ge 4\n      set -- tokens (commandline -xpc)\n    else\n      set -- tokens (commandline -opc)\n    end\n\n    set -l -- var_count 0\n    for i in $tokens\n      if string match -qr -- '^[\\w]+=' $i\n        set var_count (math $var_count + 1)\n      else\n        break\n      end\n    end\n    set -e -- tokens[0..$var_count]\n\n    while true\n      switch \"$tokens[1]\"\n        case builtin command\n          set -e -- tokens[1]\n          test \"$tokens[1]\" = \"--\"; and set -e -- tokens[1]\n        case env\n          set -e -- tokens[1]\n          test \"$tokens[1]\" = \"--\"; and set -e -- tokens[1]\n          while string match -qr -- '^[\\w]+=' \"$tokens[1]\"\n            set -e -- tokens[1]\n          end\n        case '*'\n          break\n      end\n    end\n\n    string escape -n -- $tokens\n  end\n\n  function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'\n    set -l fzf_query ''\n    set -l prefix ''\n    set -l dir '.'\n\n    set -l -- fish_major (string match -r -- '^\\d+' $version)\n    set -l -- fish_minor (string match -r -- '^\\d+\\.(\\d+)' $version)[2]\n\n    set -l -- match_regex '(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)'\n    set -l -- prefix_regex '^-[^\\s=]+=|^-(?!-)\\S'\n    if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -lt 3\n    or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))\n      set -- match_regex \"(?<prefix>$prefix_regex)?$match_regex\"\n    end\n\n    if test \"$fish_major\" -ge 4\n      string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)\n    else if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n      string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)\n      eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\\\(?=~)|\\\\\\(?=\\$\\w)' '')\n    else\n      set -l -- cl_token (commandline --current-token --tokenize | string collect -N)\n      set -- prefix (string match -r -- $prefix_regex $cl_token)\n      set -- fzf_query (string replace -- \"$prefix\" '' $cl_token | string collect -N)\n      eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\\\(?=~)|\\\\\\(?=\\$\\w)|\\\\\\n\\\\\\n$' '')\n    end\n\n    if test -n \"$fzf_query\"\n      if test \\( \"$fish_major\" -ge 4 \\) -o \\( \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 5 \\)\n        set -- fzf_query (path normalize -- $fzf_query)\n        set -- dir $fzf_query\n        while not path is -d $dir\n          set -- dir (path dirname $dir)\n        end\n      else\n        if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n          string match -q -r -- '(?<fzf_query>^[\\s\\S]*?(?=\\n?$)$)' \\\n            (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)\n        else\n          set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)\n          eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\\\\n$' '')\n        end\n        set -- dir $fzf_query\n        while not test -d \"$dir\"\n          set -- dir (dirname -z -- \"$dir\" | string split0)\n        end\n      end\n\n      if not string match -q -- '.' $dir; or string match -q -r -- '^\\./|^\\.$' $fzf_query\n        if test \"$fish_major\" -ge 4\n          string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\\s\\S]*)' $fzf_query\n        else if test \"$fish_major\" -eq 3 -a \"$fish_minor\" -ge 2\n          string match -q -r -- '^/?(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)' \\\n            (string replace -- \"$dir\" '' $fzf_query | string collect -N)\n        else\n          set -- fzf_query (string replace -- \"$dir\" '' $fzf_query | string collect -N)\n          eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\\\\n$' '')\n        end\n      end\n    end\n\n    string escape -n -- \"$dir\" \"$fzf_query\" \"$prefix\"\n  end\n#----END INCLUDE\n\n  # Store current token in $dir as root for the 'find' command\n  function fzf-file-widget -d \"List files and folders\"\n    set -l commandline (__fzf_parse_commandline)\n    set -lx dir $commandline[1]\n    set -l fzf_query $commandline[2]\n    set -l prefix $commandline[3]\n\n    set -lx FZF_DEFAULT_OPTS (__fzf_defaults \\\n      \"--reverse --walker=file,dir,follow,hidden --scheme=path\" \\\n      \"--multi $FZF_CTRL_T_OPTS --print0\")\n\n    set -lx FZF_DEFAULT_COMMAND \"$FZF_CTRL_T_COMMAND\"\n    set -lx FZF_DEFAULT_OPTS_FILE\n\n    set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)\n    and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' '\n\n    commandline -f repaint\n  end\n\n  function fzf-history-widget -d \"Show command history\"\n    set -l -- command_line (commandline)\n    set -l -- current_line (commandline -L)\n    set -l -- total_lines (count $command_line)\n    set -l -- fzf_query (string escape -- $command_line[$current_line])\n\n    set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \\\n      '--nth=2..,.. --scheme=history --multi --no-multi-line --no-wrap --wrap-sign=\"\\t\\t\\t↳ \" --preview-wrap-sign=\"↳ \"' \\\n      '--bind=\\'shift-delete:execute-silent(for i in (string split0 -- <{+f}); eval builtin history delete --exact --case-sensitive -- (string escape -n -- $i | string replace -r \"^\\d*\\\\\\\\\\\\t\" \"\"); end)+reload(eval $FZF_DEFAULT_COMMAND)\\'' \\\n      '--bind=\"alt-enter:become(string join0 -- (string collect -- {+2..} | fish_indent -i))\"' \\\n      \"--bind=ctrl-r:toggle-sort,alt-r:toggle-raw --highlight-line $FZF_CTRL_R_OPTS\" \\\n      '--accept-nth=2.. --delimiter=\"\\t\" --tabstop=4 --read0 --print0 --with-shell='(status fish-path)\\\\ -c)\n\n    # Add dynamic preview options if preview command isn't already set by user\n    if string match -qvr -- '--preview[= ]' \"$FZF_DEFAULT_OPTS\"\n      # Convert the highlighted timestamp using the date command if available\n      set -l -- date_cmd '{1}'\n      if type -q date\n        if date -d @0 '+%s' 2>/dev/null | string match -q 0\n          # GNU date\n          set -- date_cmd '(date -d @{1} \\\\\"+%F %a %T\\\\\")'\n        else if date -r 0 '+%s' 2>/dev/null | string match -q 0\n          # BSD date\n          set -- date_cmd '(date -r {1} \\\\\"+%F %a %T\\\\\")'\n        end\n      end\n\n      # Prepend the options to allow user customizations\n      set -p -- FZF_DEFAULT_OPTS \\\n        '--bind=\"focus,resize:bg-transform:if test \\\\\"$FZF_COLUMNS\\\\\" -gt 100 -a \\\\\\\\( \\\\\"$FZF_SELECT_COUNT\\\\\" -gt 0 -o \\\\\\\\( -z \\\\\"$FZF_WRAP\\\\\" -a (string length -- {}) -gt (math $FZF_COLUMNS - 4) \\\\\\\\) -o (string collect -- {2..} | fish_indent | count) -gt 1 \\\\\\\\); echo show-preview; else echo hide-preview; end\"' \\\n        '--preview=\"string collect -- (test \\\\\"$FZF_SELECT_COUNT\\\\\" -gt 0; and string collect -- {+2..}) \\\\\"\\\\n# \\\\\"'$date_cmd' {2..} | fish_indent --ansi\"' \\\n        '--preview-window=\"right,50%,wrap-word,follow,info,hidden\"'\n    end\n\n    set -lx FZF_DEFAULT_OPTS_FILE\n\n    set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time=\"%s%t\"'\n\n    # Enable syntax highlighting colors on fish v4.3.3 and newer\n    if set -l -- v (string match -r -- '^(\\d+)\\.(\\d+)(?:\\.(\\d+))?' $version)\n    and test \"$v[2]\" -gt 4 -o \"$v[2]\" -eq 4 -a \\\n      \\( \"$v[3]\" -gt 3 -o \"$v[3]\" -eq 3 -a \\\n      \\( -n \"$v[4]\" -a \"$v[4]\" -ge 3 \\) \\)\n\n      set -a -- FZF_DEFAULT_OPTS '--ansi'\n      set -a -- FZF_DEFAULT_COMMAND '--color=always'\n    end\n\n    # Merge history from other sessions before searching\n    test -z \"$fish_private_mode\"; and builtin history merge\n\n    if set -l result (eval $FZF_DEFAULT_COMMAND \\| (__fzfcmd) --query=$fzf_query | string split0)\n      if test \"$total_lines\" -eq 1\n        commandline -- $result\n      else\n        set -l a (math $current_line - 1)\n        set -l b (math $current_line + 1)\n        commandline -- $command_line[1..$a] $result\n        commandline -a -- '' $command_line[$b..-1]\n      end\n    end\n\n    commandline -f repaint\n  end\n\n  function fzf-cd-widget -d \"Change directory\"\n    set -l commandline (__fzf_parse_commandline)\n    set -lx dir $commandline[1]\n    set -l fzf_query $commandline[2]\n    set -l prefix $commandline[3]\n\n    set -lx FZF_DEFAULT_OPTS (__fzf_defaults \\\n      \"--reverse --walker=dir,follow,hidden --scheme=path\" \\\n      \"$FZF_ALT_C_OPTS --no-multi --print0\")\n\n    set -lx FZF_DEFAULT_OPTS_FILE\n    set -lx FZF_DEFAULT_COMMAND \"$FZF_ALT_C_COMMAND\"\n\n    if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)\n      cd -- $result\n      commandline -rt -- $prefix\n    end\n\n    commandline -f repaint\n  end\n\n  if not set -q FZF_CTRL_R_COMMAND; or test -n \"$FZF_CTRL_R_COMMAND\"\n    if test -n \"$FZF_CTRL_R_COMMAND\"\n      echo \"warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R\" >&2\n    end\n    bind \\cr fzf-history-widget\n    bind -M insert \\cr fzf-history-widget\n  end\n\n  if not set -q FZF_CTRL_T_COMMAND; or test -n \"$FZF_CTRL_T_COMMAND\"\n    bind \\ct fzf-file-widget\n    bind -M insert \\ct fzf-file-widget\n  end\n\n  if not set -q FZF_ALT_C_COMMAND; or test -n \"$FZF_ALT_C_COMMAND\"\n    bind \\ec fzf-cd-widget\n    bind -M insert \\ec fzf-cd-widget\n  end\n\nend\n\n# Run setup\nfzf_key_bindings\n"
  },
  {
    "path": "shell/key-bindings.zsh",
    "content": "#     ____      ____\n#    / __/___  / __/\n#   / /_/_  / / /_\n#  / __/ / /_/ __/\n# /_/   /___/_/ key-bindings.zsh\n#\n# - $FZF_TMUX_OPTS\n# - $FZF_CTRL_T_COMMAND\n# - $FZF_CTRL_T_OPTS\n# - $FZF_CTRL_R_COMMAND\n# - $FZF_CTRL_R_OPTS\n# - $FZF_ALT_C_COMMAND\n# - $FZF_ALT_C_OPTS\n\n\n# Key bindings\n# ------------\n\n# The code at the top and the bottom of this file is the same as in completion.zsh.\n# Refer to that file for explanation.\nif 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then\n  __fzf_key_bindings_options=\"options=(${(j: :)${(kv)options[@]}})\"\nelse\n  () {\n    __fzf_key_bindings_options=\"setopt\"\n    'local' '__fzf_opt'\n    for __fzf_opt in \"${(@)${(@f)$(set -o)}%% *}\"; do\n      if [[ -o \"$__fzf_opt\" ]]; then\n        __fzf_key_bindings_options+=\" -o $__fzf_opt\"\n      else\n        __fzf_key_bindings_options+=\" +o $__fzf_opt\"\n      fi\n    done\n  }\nfi\n\n'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'\n\n{\nif [[ -o interactive ]]; then\n\n#----BEGIN INCLUDE common.sh\n# NOTE: Do not directly edit this section, which is copied from \"common.sh\".\n# To modify it, one can edit \"common.sh\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"common.sh\" for the implementation details.\n\n__fzf_defaults() {\n  builtin printf '%s\\n' \"--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1\"\n  command cat \"${FZF_DEFAULT_OPTS_FILE-}\" 2> /dev/null\n  builtin printf '%s\\n' \"${FZF_DEFAULT_OPTS-} $2\"\n}\n\n__fzf_exec_awk() {\n  if [[ -z ${__fzf_awk-} ]]; then\n    __fzf_awk=awk\n    if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then\n      __fzf_awk=/usr/xpg4/bin/awk\n    elif command -v mawk > /dev/null 2>&1; then\n      local n x y z d\n      IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)\n      [[ $n == mawk ]] &&\n        (((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&\n        ((d >= 20230302)) 2> /dev/null &&\n        __fzf_awk=mawk\n    fi\n  fi\n  LC_ALL=C exec \"$__fzf_awk\" \"$@\"\n}\n#----END INCLUDE\n\n# CTRL-T - Paste the selected file path(s) into the command line\n__fzf_select() {\n  setopt localoptions pipefail no_aliases 2> /dev/null\n  local item\n  FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \\\n  FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse --walker=file,dir,follow,hidden --scheme=path\" \"${FZF_CTRL_T_OPTS-} -m\") \\\n  FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) \"$@\" < /dev/tty | while read -r item; do\n    echo -n -E \"${(q)item} \"\n  done\n  local ret=$?\n  echo\n  return $ret\n}\n\n__fzfcmd() {\n  [ -n \"${TMUX_PANE-}\" ] && { [ \"${FZF_TMUX:-0}\" != 0 ] || [ -n \"${FZF_TMUX_OPTS-}\" ]; } &&\n    echo \"fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- \" || echo \"fzf\"\n}\n\nfzf-file-widget() {\n  LBUFFER=\"${LBUFFER}$(__fzf_select)\"\n  local ret=$?\n  zle reset-prompt\n  return $ret\n}\nif [[ \"${FZF_CTRL_T_COMMAND-x}\" != \"\" ]]; then\n  zle     -N            fzf-file-widget\n  bindkey -M emacs '^T' fzf-file-widget\n  bindkey -M vicmd '^T' fzf-file-widget\n  bindkey -M viins '^T' fzf-file-widget\nfi\n\n# ALT-C - cd into the selected directory\nfzf-cd-widget() {\n  setopt localoptions pipefail no_aliases 2> /dev/null\n  local dir=\"$(\n    FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \\\n    FZF_DEFAULT_OPTS=$(__fzf_defaults \"--reverse --walker=dir,follow,hidden --scheme=path\" \"${FZF_ALT_C_OPTS-} +m\") \\\n    FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) < /dev/tty)\"\n  if [[ -z \"$dir\" ]]; then\n    zle redisplay\n    return 0\n  fi\n  zle push-line # Clear buffer. Auto-restored on next prompt.\n  BUFFER=\"builtin cd -- ${(q)dir:a}\"\n  zle accept-line\n  local ret=$?\n  unset dir # ensure this doesn't end up appearing in prompt expansion\n  zle reset-prompt\n  return $ret\n}\nif [[ \"${FZF_ALT_C_COMMAND-x}\" != \"\" ]]; then\n  zle     -N             fzf-cd-widget\n  bindkey -M emacs '\\ec' fzf-cd-widget\n  bindkey -M vicmd '\\ec' fzf-cd-widget\n  bindkey -M viins '\\ec' fzf-cd-widget\nfi\n\n# CTRL-R - Paste the selected command from history into the command line\nfzf-history-widget() {\n  local selected extracted_with_perl=0\n  setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases no_glob no_sh_glob no_ksharrays extendedglob 2> /dev/null\n  # Ensure the module is loaded if not already, and the required features, such\n  # as the associative 'history' array, which maps event numbers to full history\n  # lines, are set. Also, make sure Perl is installed for multi-line output.\n  if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then\n    selected=\"$(printf '%s\\t%s\\000' \"${(kv)history[@]}\" |\n      perl -0 -ne 'if (!$seen{(/^\\s*[0-9]+\\**\\t(.*)/s, $1)}++) { s/\\n/\\n\\t/g; print; }' |\n      FZF_DEFAULT_OPTS=$(__fzf_defaults \"\" \"-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\\t↳ ' --highlight-line --multi ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} --read0\") \\\n      FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))\"\n      extracted_with_perl=1\n  else\n    selected=\"$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \\t]*[0-9]+\\**[ \\t]+/, \"\", cmd); if (!seen[cmd]++) print $0 }' |\n      FZF_DEFAULT_OPTS=$(__fzf_defaults \"\" \"-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\\t↳ ' --highlight-line --multi ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER}\") \\\n      FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))\"\n  fi\n  local ret=$?\n  local -a cmds\n  # Avoid leaking auto assigned values when using backreferences '(#b)'\n  local -a mbegin mend match\n  if [ -n \"$selected\" ]; then\n    # Heuristic to check if the selected value is from history or a custom query\n    if ((( extracted_with_perl )) && [[ $selected == <->$'\\t'* ]]) ||\n    ((( ! extracted_with_perl )) && [[ $selected == [[:blank:]]#<->(  |\\* )* ]]); then\n      # Split at newlines\n      for line in ${(ps:\\n:)selected}; do\n        if (( extracted_with_perl )); then\n          if [[ $line == (#b)(<->)(#B)$'\\t'* ]]; then\n            (( ${+history[${match[1]}]} )) && cmds+=(\"${history[${match[1]}]}\")\n          fi\n        elif [[ $line == [[:blank:]]#(#b)(<->)(#B)(  |\\* )* ]]; then\n          # Avoid $history array: lags behind 'fc' on foreign commands (*)\n          # https://zsh.org/mla/users/2024/msg00692.html\n          # Push BUFFER onto stack; fetch and save history entry from BUFFER; restore\n          zle .push-line\n          zle vi-fetch-history -n ${match[1]}\n          (( ${#BUFFER} )) && cmds+=(\"${BUFFER}\")\n          BUFFER=\"\"\n          zle .get-line\n        fi\n      done\n      if (( ${#cmds[@]} )); then\n        # Join by newline after stripping trailing newlines from each command\n        BUFFER=\"${(pj:\\n:)${(@)cmds%%$'\\n'#}}\"\n        CURSOR=${#BUFFER}\n      fi\n    else # selected is a custom query, not from history\n      LBUFFER=\"$selected\"\n    fi\n  fi\n  zle reset-prompt\n  return $ret\n}\nif [[ ${FZF_CTRL_R_COMMAND-x} != \"\" ]]; then\n  if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then\n    echo \"warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R\" >&2\n  fi\n  zle     -N            fzf-history-widget\n  bindkey -M emacs '^R' fzf-history-widget\n  bindkey -M vicmd '^R' fzf-history-widget\n  bindkey -M viins '^R' fzf-history-widget\nfi\nfi\n\n} always {\n  eval $__fzf_key_bindings_options\n  'unset' '__fzf_key_bindings_options'\n}\n"
  },
  {
    "path": "shell/update.sh",
    "content": "#!/usr/bin/env bash\n\n# This script applies the contents of \"common.sh\" to the other files.\n\nset -e\n\ndir=${0%\"${0##*/}\"}\n\nupdate() {\n  {\n    sed -n \"1,/^#----BEGIN INCLUDE $1/p\" \"$2\"\n    cat << EOF\n# NOTE: Do not directly edit this section, which is copied from \"$1\".\n# To modify it, one can edit \"$1\" and run \"./update.sh\" to apply\n# the changes. See code comments in \"$1\" for the implementation details.\nEOF\n    echo\n    grep -v '^[[:blank:]]*#' \"$dir/$1\" # remove code comments from the common file\n    sed -n '/^#----END INCLUDE/,$p' \"$2\"\n  } > \"$2.part\"\n\n  mv -f \"$2.part\" \"$2\"\n}\n\nupdate \"common.sh\" \"$dir/completion.bash\"\nupdate \"common.sh\" \"$dir/completion.zsh\"\nupdate \"common.sh\" \"$dir/key-bindings.bash\"\nupdate \"common.sh\" \"$dir/key-bindings.zsh\"\nupdate \"common.fish\" \"$dir/completion.fish\"\nupdate \"common.fish\" \"$dir/key-bindings.fish\"\n\n# Check if --check is in ARGV\ncheck=0\nrest=()\nfor arg in \"$@\"; do\n  case $arg in\n    --check) check=1 ;;\n    *) rest+=(\"$arg\") ;;\n  esac\ndone\n\nfmt() {\n  if ! grep -q \"^#----BEGIN shfmt\" \"$1\"; then\n    if [[ $check == 1 ]]; then\n      shfmt -d \"$1\"\n      return $?\n    else\n      shfmt -w \"$1\"\n    fi\n  else\n    {\n      sed -n '1,/^#----BEGIN shfmt/p' \"$1\" | sed '$d'\n      sed -n '/^#----BEGIN shfmt/,/^#----END shfmt/p' \"$1\" | shfmt --filename \"$1\"\n      sed -n '/^#----END shfmt/,$p' \"$1\" | sed '1d'\n    } > \"$1.part\"\n\n    if [[ $check == 1 ]]; then\n      diff -q \"$1\" \"$1.part\"\n      ret=$?\n      rm -f \"$1.part\"\n      return $ret\n    fi\n\n    mv -f \"$1.part\" \"$1\"\n  fi\n}\n\nfor file in \"${rest[@]}\"; do\n  fmt \"$file\" || exit $?\ndone\n"
  },
  {
    "path": "src/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013-2026 Junegunn Choi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "src/actiontype_string.go",
    "content": "// Code generated by \"stringer -type=actionType\"; DO NOT EDIT.\n\npackage fzf\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[actIgnore-0]\n\t_ = x[actStart-1]\n\t_ = x[actClick-2]\n\t_ = x[actInvalid-3]\n\t_ = x[actBracketedPasteBegin-4]\n\t_ = x[actBracketedPasteEnd-5]\n\t_ = x[actChar-6]\n\t_ = x[actMouse-7]\n\t_ = x[actBeginningOfLine-8]\n\t_ = x[actAbort-9]\n\t_ = x[actAccept-10]\n\t_ = x[actAcceptNonEmpty-11]\n\t_ = x[actAcceptOrPrintQuery-12]\n\t_ = x[actBackwardChar-13]\n\t_ = x[actBackwardDeleteChar-14]\n\t_ = x[actBackwardDeleteCharEof-15]\n\t_ = x[actBackwardWord-16]\n\t_ = x[actBackwardSubWord-17]\n\t_ = x[actCancel-18]\n\t_ = x[actChangeBorderLabel-19]\n\t_ = x[actChangeGhost-20]\n\t_ = x[actChangeHeader-21]\n\t_ = x[actChangeHeaderLines-22]\n\t_ = x[actChangeFooter-23]\n\t_ = x[actChangeHeaderLabel-24]\n\t_ = x[actChangeFooterLabel-25]\n\t_ = x[actChangeInputLabel-26]\n\t_ = x[actChangeListLabel-27]\n\t_ = x[actChangeMulti-28]\n\t_ = x[actChangeNth-29]\n\t_ = x[actChangeWithNth-30]\n\t_ = x[actChangePointer-31]\n\t_ = x[actChangePreview-32]\n\t_ = x[actChangePreviewLabel-33]\n\t_ = x[actChangePreviewWindow-34]\n\t_ = x[actChangePrompt-35]\n\t_ = x[actChangeQuery-36]\n\t_ = x[actClearScreen-37]\n\t_ = x[actClearQuery-38]\n\t_ = x[actClearSelection-39]\n\t_ = x[actClose-40]\n\t_ = x[actDeleteChar-41]\n\t_ = x[actDeleteCharEof-42]\n\t_ = x[actEndOfLine-43]\n\t_ = x[actFatal-44]\n\t_ = x[actForwardChar-45]\n\t_ = x[actForwardWord-46]\n\t_ = x[actForwardSubWord-47]\n\t_ = x[actKillLine-48]\n\t_ = x[actKillWord-49]\n\t_ = x[actKillSubWord-50]\n\t_ = x[actUnixLineDiscard-51]\n\t_ = x[actUnixWordRubout-52]\n\t_ = x[actYank-53]\n\t_ = x[actBackwardKillWord-54]\n\t_ = x[actBackwardKillSubWord-55]\n\t_ = x[actSelectAll-56]\n\t_ = x[actDeselectAll-57]\n\t_ = x[actToggle-58]\n\t_ = x[actToggleSearch-59]\n\t_ = x[actToggleAll-60]\n\t_ = x[actToggleDown-61]\n\t_ = x[actToggleUp-62]\n\t_ = x[actToggleIn-63]\n\t_ = x[actToggleOut-64]\n\t_ = x[actToggleTrack-65]\n\t_ = x[actToggleTrackCurrent-66]\n\t_ = x[actToggleHeader-67]\n\t_ = x[actToggleWrap-68]\n\t_ = x[actToggleWrapWord-69]\n\t_ = x[actToggleMultiLine-70]\n\t_ = x[actToggleHscroll-71]\n\t_ = x[actToggleRaw-72]\n\t_ = x[actEnableRaw-73]\n\t_ = x[actDisableRaw-74]\n\t_ = x[actTrackCurrent-75]\n\t_ = x[actToggleInput-76]\n\t_ = x[actHideInput-77]\n\t_ = x[actShowInput-78]\n\t_ = x[actUntrackCurrent-79]\n\t_ = x[actDown-80]\n\t_ = x[actDownMatch-81]\n\t_ = x[actUp-82]\n\t_ = x[actUpMatch-83]\n\t_ = x[actPageUp-84]\n\t_ = x[actPageDown-85]\n\t_ = x[actPosition-86]\n\t_ = x[actHalfPageUp-87]\n\t_ = x[actHalfPageDown-88]\n\t_ = x[actOffsetUp-89]\n\t_ = x[actOffsetDown-90]\n\t_ = x[actOffsetMiddle-91]\n\t_ = x[actJump-92]\n\t_ = x[actJumpAccept-93]\n\t_ = x[actPrintQuery-94]\n\t_ = x[actRefreshPreview-95]\n\t_ = x[actReplaceQuery-96]\n\t_ = x[actToggleSort-97]\n\t_ = x[actShowPreview-98]\n\t_ = x[actHidePreview-99]\n\t_ = x[actTogglePreview-100]\n\t_ = x[actTogglePreviewWrap-101]\n\t_ = x[actTogglePreviewWrapWord-102]\n\t_ = x[actTransform-103]\n\t_ = x[actTransformBorderLabel-104]\n\t_ = x[actTransformGhost-105]\n\t_ = x[actTransformHeader-106]\n\t_ = x[actTransformHeaderLines-107]\n\t_ = x[actTransformFooter-108]\n\t_ = x[actTransformHeaderLabel-109]\n\t_ = x[actTransformFooterLabel-110]\n\t_ = x[actTransformInputLabel-111]\n\t_ = x[actTransformListLabel-112]\n\t_ = x[actTransformNth-113]\n\t_ = x[actTransformWithNth-114]\n\t_ = x[actTransformPointer-115]\n\t_ = x[actTransformPreviewLabel-116]\n\t_ = x[actTransformPrompt-117]\n\t_ = x[actTransformQuery-118]\n\t_ = x[actTransformSearch-119]\n\t_ = x[actTrigger-120]\n\t_ = x[actBgTransform-121]\n\t_ = x[actBgTransformBorderLabel-122]\n\t_ = x[actBgTransformGhost-123]\n\t_ = x[actBgTransformHeader-124]\n\t_ = x[actBgTransformHeaderLines-125]\n\t_ = x[actBgTransformFooter-126]\n\t_ = x[actBgTransformHeaderLabel-127]\n\t_ = x[actBgTransformFooterLabel-128]\n\t_ = x[actBgTransformInputLabel-129]\n\t_ = x[actBgTransformListLabel-130]\n\t_ = x[actBgTransformNth-131]\n\t_ = x[actBgTransformWithNth-132]\n\t_ = x[actBgTransformPointer-133]\n\t_ = x[actBgTransformPreviewLabel-134]\n\t_ = x[actBgTransformPrompt-135]\n\t_ = x[actBgTransformQuery-136]\n\t_ = x[actBgTransformSearch-137]\n\t_ = x[actBgCancel-138]\n\t_ = x[actSearch-139]\n\t_ = x[actPreview-140]\n\t_ = x[actPreviewTop-141]\n\t_ = x[actPreviewBottom-142]\n\t_ = x[actPreviewUp-143]\n\t_ = x[actPreviewDown-144]\n\t_ = x[actPreviewPageUp-145]\n\t_ = x[actPreviewPageDown-146]\n\t_ = x[actPreviewHalfPageUp-147]\n\t_ = x[actPreviewHalfPageDown-148]\n\t_ = x[actPrevHistory-149]\n\t_ = x[actPrevSelected-150]\n\t_ = x[actPrint-151]\n\t_ = x[actPut-152]\n\t_ = x[actNextHistory-153]\n\t_ = x[actNextSelected-154]\n\t_ = x[actExecute-155]\n\t_ = x[actExecuteSilent-156]\n\t_ = x[actExecuteMulti-157]\n\t_ = x[actSigStop-158]\n\t_ = x[actBest-159]\n\t_ = x[actFirst-160]\n\t_ = x[actLast-161]\n\t_ = x[actReload-162]\n\t_ = x[actReloadSync-163]\n\t_ = x[actDisableSearch-164]\n\t_ = x[actEnableSearch-165]\n\t_ = x[actSelect-166]\n\t_ = x[actDeselect-167]\n\t_ = x[actUnbind-168]\n\t_ = x[actRebind-169]\n\t_ = x[actToggleBind-170]\n\t_ = x[actBecome-171]\n\t_ = x[actShowHeader-172]\n\t_ = x[actHideHeader-173]\n\t_ = x[actBell-174]\n\t_ = x[actExclude-175]\n\t_ = x[actExcludeMulti-176]\n\t_ = x[actAsync-177]\n}\n\nconst _actionType_name = \"actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLinesactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangeWithNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLinesactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformWithNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformHeaderLinesactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformWithNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync\"\n\nvar _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 336, 351, 371, 391, 410, 428, 442, 454, 470, 486, 502, 523, 545, 560, 574, 588, 601, 618, 626, 639, 655, 667, 675, 689, 703, 720, 731, 742, 756, 774, 791, 798, 817, 839, 851, 865, 874, 889, 901, 914, 925, 936, 948, 962, 983, 998, 1011, 1028, 1046, 1062, 1074, 1086, 1099, 1114, 1128, 1140, 1152, 1169, 1176, 1188, 1193, 1203, 1212, 1223, 1234, 1247, 1262, 1273, 1286, 1301, 1308, 1321, 1334, 1351, 1366, 1379, 1393, 1407, 1423, 1443, 1467, 1479, 1502, 1519, 1537, 1560, 1578, 1601, 1624, 1646, 1667, 1682, 1701, 1720, 1744, 1762, 1779, 1797, 1807, 1821, 1846, 1865, 1885, 1910, 1930, 1955, 1980, 2004, 2027, 2044, 2065, 2086, 2112, 2132, 2151, 2171, 2182, 2191, 2201, 2214, 2230, 2242, 2256, 2272, 2290, 2310, 2332, 2346, 2361, 2369, 2375, 2389, 2404, 2414, 2430, 2445, 2455, 2462, 2470, 2477, 2486, 2499, 2515, 2530, 2539, 2550, 2559, 2568, 2581, 2590, 2603, 2616, 2623, 2633, 2648, 2656}\n\nfunc (i actionType) String() string {\n\tif i < 0 || i >= actionType(len(_actionType_index)-1) {\n\t\treturn \"actionType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n\treturn _actionType_name[_actionType_index[i]:_actionType_index[i+1]]\n}\n"
  },
  {
    "path": "src/algo/SIMD.md",
    "content": "# SIMD byte search: `indexByteTwo` / `lastIndexByteTwo`\n\n## What these functions do\n\n`indexByteTwo(s []byte, b1, b2 byte) int` — returns the index of the\n**first** occurrence of `b1` or `b2` in `s`, or `-1`.\n\n`lastIndexByteTwo(s []byte, b1, b2 byte) int` — returns the index of the\n**last** occurrence of `b1` or `b2` in `s`, or `-1`.\n\nThey are used by the fuzzy matching algorithm (`algo.go`) to skip ahead\nduring case-insensitive search. Instead of calling `bytes.IndexByte` twice\n(once for lowercase, once for uppercase), a single SIMD pass finds both at\nonce.\n\n## File layout\n\n| File                  | Purpose                                                           |\n| ------                | ---------                                                         |\n| `indexbyte2_arm64.go` | Go declarations (`//go:noescape`) for ARM64                       |\n| `indexbyte2_arm64.s`  | ARM64 NEON assembly (32-byte aligned blocks, syndrome extraction) |\n| `indexbyte2_amd64.go` | Go declarations + AVX2 runtime detection for AMD64                |\n| `indexbyte2_amd64.s`  | AMD64 AVX2/SSE2 assembly with CPUID dispatch                      |\n| `indexbyte2_other.go` | Pure Go fallback for all other architectures                      |\n| `indexbyte2_test.go`  | Unit tests, exhaustive tests, fuzz tests, and benchmarks          |\n\n## How the SIMD implementations work\n\n**ARM64 (NEON):**\n- Broadcasts both needle bytes into NEON registers (`VMOV`).\n- Processes 32-byte aligned chunks. For each chunk, compares all bytes\n  against both needles (`VCMEQ`), ORs the results (`VORR`), and builds a\n  64-bit syndrome with 2 bits per byte.\n- `indexByteTwo` uses `RBIT` + `CLZ` to find the lowest set bit (first match).\n- `lastIndexByteTwo` scans backward and uses `CLZ` on the raw syndrome to\n  find the highest set bit (last match).\n- Handles alignment and partial first/last blocks with bit masking.\n- Adapted from Go's `internal/bytealg/indexbyte_arm64.s`.\n\n**AMD64 (AVX2 with SSE2 fallback):**\n- At init time, `cpuHasAVX2()` checks CPUID + XGETBV for AVX2 and OS YMM\n  support. The result is cached in `_useAVX2`.\n- **AVX2 path** (inputs >= 32 bytes, when available):\n  - Broadcasts both needles via `VPBROADCASTB`.\n  - Processes 32-byte blocks: `VPCMPEQB` against both needles, `VPOR`, then\n    `VPMOVMSKB` to get a 32-bit mask.\n  - 5 instructions per loop iteration (vs 7 for SSE2) at 2x the throughput.\n  - `VZEROUPPER` before every return to avoid SSE/AVX transition penalties.\n- **SSE2 fallback** (inputs < 32 bytes, or CPUs without AVX2):\n  - Broadcasts via `PUNPCKLBW` + `PSHUFL`.\n  - Processes 16-byte blocks: `PCMPEQB`, `POR`, `PMOVMSKB`.\n  - Small inputs (<16 bytes) are handled with page-boundary-safe loads.\n- Both paths use `BSFL` (forward) / `BSRL` (reverse) for bit scanning.\n- Adapted from Go's `internal/bytealg/indexbyte_amd64.s`.\n\n**Fallback (other platforms):**\n- `indexByteTwo` uses two `bytes.IndexByte` calls with scope-limiting\n  (search `b1` first, then limit the `b2` search to `s[:i1]`).\n- `lastIndexByteTwo` uses a simple backward for loop.\n\n## Running tests\n\n```bash\n# Unit + exhaustive tests\ngo test ./src/algo/ -run 'TestIndexByteTwo|TestLastIndexByteTwo' -v\n\n# Fuzz tests (run for 10 seconds each)\ngo test ./src/algo/ -run '^$' -fuzz FuzzIndexByteTwo -fuzztime 10s\ngo test ./src/algo/ -run '^$' -fuzz FuzzLastIndexByteTwo -fuzztime 10s\n\n# Cross-architecture: test amd64 on an arm64 Mac (via Rosetta)\nGOARCH=amd64 go test ./src/algo/ -run 'TestIndexByteTwo|TestLastIndexByteTwo' -v\nGOARCH=amd64 go test ./src/algo/ -run '^$' -fuzz FuzzIndexByteTwo -fuzztime 10s\nGOARCH=amd64 go test ./src/algo/ -run '^$' -fuzz FuzzLastIndexByteTwo -fuzztime 10s\n```\n\n## Running micro-benchmarks\n\n```bash\n# All indexByteTwo / lastIndexByteTwo benchmarks\ngo test ./src/algo/ -bench 'IndexByteTwo' -benchmem\n\n# Specific size\ngo test ./src/algo/ -bench 'IndexByteTwo_1000'\n```\n\nEach benchmark compares the SIMD `asm` implementation against reference\nimplementations (`2xIndexByte` using `bytes.IndexByte`, and a simple `loop`).\n\n## Correctness verification\n\nThe assembly is verified by three layers of testing:\n\n1. **Table-driven tests** — known inputs with expected outputs.\n2. **Exhaustive tests** — all lengths 0–256, every match position, no-match\n   cases, and both-bytes-present cases, compared against a simple loop\n   reference.\n3. **Fuzz tests** — randomized inputs via `testing.F`, compared against the\n   same loop reference.\n"
  },
  {
    "path": "src/algo/algo.go",
    "content": "package algo\n\n/*\n\nAlgorithm\n---------\n\nFuzzyMatchV1 finds the first \"fuzzy\" occurrence of the pattern within the given\ntext in O(n) time where n is the length of the text. Once the position of the\nlast character is located, it traverses backwards to see if there's a shorter\nsubstring that matches the pattern.\n\n    a_____b___abc__  To find \"abc\"\n    *-----*-----*>   1. Forward scan\n             <***    2. Backward scan\n\nThe algorithm is simple and fast, but as it only sees the first occurrence,\nit is not guaranteed to find the occurrence with the highest score.\n\n    a_____b__c__abc\n    *-----*--*  ***\n\nFuzzyMatchV2 implements a modified version of Smith-Waterman algorithm to find\nthe optimal solution (highest score) according to the scoring criteria. Unlike\nthe original algorithm, omission or mismatch of a character in the pattern is\nnot allowed.\n\nPerformance\n-----------\n\nThe new V2 algorithm is slower than V1 as it examines all occurrences of the\npattern instead of stopping immediately after finding the first one. The time\ncomplexity of the algorithm is O(nm) if a match is found and O(n) otherwise\nwhere n is the length of the item and m is the length of the pattern. Thus, the\nperformance overhead may not be noticeable for a query with high selectivity.\nHowever, if the performance is more important than the quality of the result,\nyou can still choose v1 algorithm with --algo=v1.\n\nScoring criteria\n----------------\n\n- We prefer matches at special positions, such as the start of a word, or\n  uppercase character in camelCase words.\n\n- That is, we prefer an occurrence of the pattern with more characters\n  matching at special positions, even if the total match length is longer.\n    e.g. \"fuzzyfinder\" vs. \"fuzzy-finder\" on \"ff\"\n                            ````````````\n- Also, if the first character in the pattern appears at one of the special\n  positions, the bonus point for the position is multiplied by a constant\n  as it is extremely likely that the first character in the typed pattern\n  has more significance than the rest.\n    e.g. \"fo-bar\" vs. \"foob-r\" on \"br\"\n          ``````\n- But since fzf is still a fuzzy finder, not an acronym finder, we should also\n  consider the total length of the matched substring. This is why we have the\n  gap penalty. The gap penalty increases as the length of the gap (distance\n  between the matching characters) increases, so the effect of the bonus is\n  eventually cancelled at some point.\n    e.g. \"fuzzyfinder\" vs. \"fuzzy-blurry-finder\" on \"ff\"\n          ```````````\n- Consequently, it is crucial to find the right balance between the bonus\n  and the gap penalty. The parameters were chosen that the bonus is cancelled\n  when the gap size increases beyond 8 characters.\n\n- The bonus mechanism can have the undesirable side effect where consecutive\n  matches are ranked lower than the ones with gaps.\n    e.g. \"foobar\" vs. \"foo-bar\" on \"foob\"\n                       ```````\n- To correct this anomaly, we also give extra bonus point to each character\n  in a consecutive matching chunk.\n    e.g. \"foobar\" vs. \"foo-bar\" on \"foob\"\n          ``````\n- The amount of consecutive bonus is primarily determined by the bonus of the\n  first character in the chunk.\n    e.g. \"foobar\" vs. \"out-of-bound\" on \"oob\"\n                       ````````````\n*/\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nvar DEBUG bool\n\nvar delimiterChars = \"/,:;|\"\n\nconst whiteChars = \" \\t\\n\\v\\f\\r\\x85\\xA0\"\n\nfunc indexAt(index int, max int, forward bool) int {\n\tif forward {\n\t\treturn index\n\t}\n\treturn max - index - 1\n}\n\n// Result contains the results of running a match function.\ntype Result struct {\n\t// TODO int32 should suffice\n\tStart int\n\tEnd   int\n\tScore int\n}\n\nconst (\n\tscoreMatch        = 16\n\tscoreGapStart     = -3\n\tscoreGapExtension = -1\n\n\t// We prefer matches at the beginning of a word, but the bonus should not be\n\t// too great to prevent the longer acronym matches from always winning over\n\t// shorter fuzzy matches. The bonus point here was specifically chosen that\n\t// the bonus is cancelled when the gap between the acronyms grows over\n\t// 8 characters, which is approximately the average length of the words found\n\t// in web2 dictionary and my file system.\n\tbonusBoundary = scoreMatch / 2\n\n\t// Although bonus point for non-word characters is non-contextual, we need it\n\t// for computing bonus points for consecutive chunks starting with a non-word\n\t// character.\n\tbonusNonWord = scoreMatch / 2\n\n\t// Edge-triggered bonus for matches in camelCase words.\n\t// Compared to word-boundary case, they don't accompany single-character gaps\n\t// (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.\n\tbonusCamel123 = bonusBoundary + scoreGapExtension\n\n\t// Minimum bonus point given to characters in consecutive chunks.\n\t// Note that bonus points for consecutive matches shouldn't have needed if we\n\t// used fixed match score as in the original algorithm.\n\tbonusConsecutive = -(scoreGapStart + scoreGapExtension)\n\n\t// The first character in the typed pattern usually has more significance\n\t// than the rest so it's important that it appears at special positions where\n\t// bonus points are given, e.g. \"to-go\" vs. \"ongoing\" on \"og\" or on \"ogo\".\n\t// The amount of the extra bonus should be limited so that the gap penalty is\n\t// still respected.\n\tbonusFirstCharMultiplier = 2\n)\n\nvar (\n\t// Extra bonus for word boundary after whitespace character or beginning of the string\n\tbonusBoundaryWhite int16 = bonusBoundary + 2\n\n\t// Extra bonus for word boundary after slash, colon, semi-colon, and comma\n\tbonusBoundaryDelimiter int16 = bonusBoundary + 1\n\n\tinitialCharClass = charWhite\n\n\t// A minor optimization that can give 15%+ performance boost\n\tasciiCharClasses [unicode.MaxASCII + 1]charClass\n\n\t// A minor optimization that can give yet another 5% performance boost\n\tbonusMatrix [charNumber + 1][charNumber + 1]int16\n)\n\ntype charClass int\n\nconst (\n\tcharWhite charClass = iota\n\tcharNonWord\n\tcharDelimiter\n\tcharLower\n\tcharUpper\n\tcharLetter\n\tcharNumber\n)\n\nfunc Init(scheme string) bool {\n\tswitch scheme {\n\tcase \"default\":\n\t\tbonusBoundaryWhite = bonusBoundary + 2\n\t\tbonusBoundaryDelimiter = bonusBoundary + 1\n\tcase \"path\":\n\t\tbonusBoundaryWhite = bonusBoundary\n\t\tbonusBoundaryDelimiter = bonusBoundary + 1\n\t\tif os.PathSeparator == '/' {\n\t\t\tdelimiterChars = \"/\"\n\t\t} else {\n\t\t\tdelimiterChars = string([]rune{os.PathSeparator, '/'})\n\t\t}\n\t\tinitialCharClass = charDelimiter\n\tcase \"history\":\n\t\tbonusBoundaryWhite = bonusBoundary\n\t\tbonusBoundaryDelimiter = bonusBoundary\n\tdefault:\n\t\treturn false\n\t}\n\tfor i := 0; i <= unicode.MaxASCII; i++ {\n\t\tchar := rune(i)\n\t\tc := charNonWord\n\t\tif char >= 'a' && char <= 'z' {\n\t\t\tc = charLower\n\t\t} else if char >= 'A' && char <= 'Z' {\n\t\t\tc = charUpper\n\t\t} else if char >= '0' && char <= '9' {\n\t\t\tc = charNumber\n\t\t} else if strings.ContainsRune(whiteChars, char) {\n\t\t\tc = charWhite\n\t\t} else if strings.ContainsRune(delimiterChars, char) {\n\t\t\tc = charDelimiter\n\t\t}\n\t\tasciiCharClasses[i] = c\n\t}\n\tfor i := 0; i <= int(charNumber); i++ {\n\t\tfor j := 0; j <= int(charNumber); j++ {\n\t\t\tbonusMatrix[i][j] = bonusFor(charClass(i), charClass(j))\n\t\t}\n\t}\n\treturn true\n}\n\nfunc posArray(withPos bool, len int) *[]int {\n\tif withPos {\n\t\tpos := make([]int, 0, len)\n\t\treturn &pos\n\t}\n\treturn nil\n}\n\nfunc alloc16(offset int, slab *util.Slab, size int) (int, []int16) {\n\tif slab != nil && cap(slab.I16) > offset+size {\n\t\tslice := slab.I16[offset : offset+size]\n\t\treturn offset + size, slice\n\t}\n\treturn offset, make([]int16, size)\n}\n\nfunc alloc32(offset int, slab *util.Slab, size int) (int, []int32) {\n\tif slab != nil && cap(slab.I32) > offset+size {\n\t\tslice := slab.I32[offset : offset+size]\n\t\treturn offset + size, slice\n\t}\n\treturn offset, make([]int32, size)\n}\n\nfunc charClassOfNonAscii(char rune) charClass {\n\tif unicode.IsLower(char) {\n\t\treturn charLower\n\t} else if unicode.IsUpper(char) {\n\t\treturn charUpper\n\t} else if unicode.IsNumber(char) {\n\t\treturn charNumber\n\t} else if unicode.IsLetter(char) {\n\t\treturn charLetter\n\t} else if unicode.IsSpace(char) {\n\t\treturn charWhite\n\t} else if strings.ContainsRune(delimiterChars, char) {\n\t\treturn charDelimiter\n\t}\n\treturn charNonWord\n}\n\nfunc charClassOf(char rune) charClass {\n\tif char <= unicode.MaxASCII {\n\t\treturn asciiCharClasses[char]\n\t}\n\treturn charClassOfNonAscii(char)\n}\n\nfunc bonusFor(prevClass charClass, class charClass) int16 {\n\tif class > charNonWord {\n\t\tswitch prevClass {\n\t\tcase charWhite:\n\t\t\t// Word boundary after whitespace\n\t\t\treturn bonusBoundaryWhite\n\t\tcase charDelimiter:\n\t\t\t// Word boundary after a delimiter character\n\t\t\treturn bonusBoundaryDelimiter\n\t\tcase charNonWord:\n\t\t\t// Word boundary\n\t\t\treturn bonusBoundary\n\t\t}\n\t}\n\n\tif prevClass == charLower && class == charUpper ||\n\t\tprevClass != charNumber && class == charNumber {\n\t\t// camelCase letter123\n\t\treturn bonusCamel123\n\t}\n\n\tswitch class {\n\tcase charNonWord, charDelimiter:\n\t\treturn bonusNonWord\n\tcase charWhite:\n\t\treturn bonusBoundaryWhite\n\t}\n\treturn 0\n}\n\nfunc bonusAt(input *util.Chars, idx int) int16 {\n\tif idx == 0 {\n\t\treturn bonusBoundaryWhite\n\t}\n\treturn bonusMatrix[charClassOf(input.Get(idx-1))][charClassOf(input.Get(idx))]\n}\n\nfunc normalizeRune(r rune) rune {\n\tif r < 0x00C0 || r > 0xFF61 {\n\t\treturn r\n\t}\n\n\tn := normalized[r]\n\tif n > 0 {\n\t\treturn n\n\t}\n\treturn r\n}\n\n// Algo functions make two assumptions\n// 1. \"pattern\" is given in lowercase if \"caseSensitive\" is false\n// 2. \"pattern\" is already normalized if \"normalize\" is true\ntype Algo func(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)\n\nfunc trySkip(input *util.Chars, caseSensitive bool, b byte, from int) int {\n\tbyteArray := input.Bytes()[from:]\n\t// For case-insensitive search of a letter, search for both cases in one pass\n\tif !caseSensitive && b >= 'a' && b <= 'z' {\n\t\tidx := IndexByteTwo(byteArray, b, b-32)\n\t\tif idx < 0 {\n\t\t\treturn -1\n\t\t}\n\t\treturn from + idx\n\t}\n\tidx := bytes.IndexByte(byteArray, b)\n\tif idx < 0 {\n\t\treturn -1\n\t}\n\treturn from + idx\n}\n\nfunc isAscii(runes []rune) bool {\n\tfor _, r := range runes {\n\t\tif r >= utf8.RuneSelf {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) (int, int) {\n\t// Can't determine\n\tif !input.IsBytes() {\n\t\treturn 0, input.Length()\n\t}\n\n\t// Not possible\n\tif !isAscii(pattern) {\n\t\treturn -1, -1\n\t}\n\n\tfirstIdx, idx, lastIdx := 0, 0, 0\n\tvar b byte\n\tfor pidx := range pattern {\n\t\tb = byte(pattern[pidx])\n\t\tidx = trySkip(input, caseSensitive, b, idx)\n\t\tif idx < 0 {\n\t\t\treturn -1, -1\n\t\t}\n\t\tif pidx == 0 && idx > 0 {\n\t\t\t// Step back to find the right bonus point\n\t\t\tfirstIdx = idx - 1\n\t\t}\n\t\tlastIdx = idx\n\t\tidx++\n\t}\n\n\t// Find the last appearance of the last character of the pattern to limit the search scope\n\tscope := input.Bytes()[lastIdx:]\n\tif len(scope) > 1 {\n\t\ttail := scope[1:]\n\t\tvar end int\n\t\tif !caseSensitive && b >= 'a' && b <= 'z' {\n\t\t\tend = lastIndexByteTwo(tail, b, b-32)\n\t\t} else {\n\t\t\tend = bytes.LastIndexByte(tail, b)\n\t\t}\n\t\tif end >= 0 {\n\t\t\treturn firstIdx, lastIdx + 1 + end + 1\n\t\t}\n\t}\n\treturn firstIdx, lastIdx + 1\n}\n\nfunc debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {\n\twidth := lastIdx - int(F[0]) + 1\n\n\tfor i, f := range F {\n\t\tI := i * width\n\t\tif i == 0 {\n\t\t\tfmt.Print(\"  \")\n\t\t\tfor j := int(f); j <= lastIdx; j++ {\n\t\t\t\tfmt.Print(\" \" + string(T[j]) + \" \")\n\t\t\t}\n\t\t\tfmt.Println()\n\t\t}\n\t\tfmt.Print(string(pattern[i]) + \" \")\n\t\tfor idx := int(F[0]); idx < int(f); idx++ {\n\t\t\tfmt.Print(\" 0 \")\n\t\t}\n\t\tfor idx := int(f); idx <= lastIdx; idx++ {\n\t\t\tfmt.Printf(\"%2d \", H[i*width+idx-int(F[0])])\n\t\t}\n\t\tfmt.Println()\n\n\t\tfmt.Print(\"  \")\n\t\tfor idx, p := range C[I : I+width] {\n\t\t\tif idx+int(F[0]) < int(F[i]) {\n\t\t\t\tp = 0\n\t\t\t}\n\t\t\tif p > 0 {\n\t\t\t\tfmt.Printf(\"%2d \", p)\n\t\t\t} else {\n\t\t\t\tfmt.Print(\"   \")\n\t\t\t}\n\t\t}\n\t\tfmt.Println()\n\t}\n}\n\nfunc FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\t// Assume that pattern is given in lowercase if case-insensitive.\n\t// First check if there's a match and calculate bonus for each position.\n\t// If the input string is too long, consider finding the matching chars in\n\t// this phase as well (non-optimal alignment).\n\tM := len(pattern)\n\tif M == 0 {\n\t\treturn Result{0, 0, 0}, posArray(withPos, M)\n\t}\n\tN := input.Length()\n\tif M > N {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\t// Since O(nm) algorithm can be prohibitively expensive for large input,\n\t// we fall back to the greedy algorithm.\n\t// Also, we should not allow a very long pattern to avoid 16-bit integer\n\t// overflow in the score matrix. 1000 is a safe limit.\n\tif slab != nil && N*M > cap(slab.I16) || M > 1000 {\n\t\treturn FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)\n\t}\n\n\t// Phase 1. Optimized search for ASCII string\n\tminIdx, maxIdx := asciiFuzzyIndex(input, pattern, caseSensitive)\n\tif minIdx < 0 {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\t// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())\n\tN = maxIdx - minIdx\n\n\t// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages\n\toffset16 := 0\n\toffset32 := 0\n\toffset16, H0 := alloc16(offset16, slab, N)\n\toffset16, C0 := alloc16(offset16, slab, N)\n\t// Bonus point for each position\n\toffset16, B := alloc16(offset16, slab, N)\n\t// The first occurrence of each character in the pattern\n\toffset32, F := alloc32(offset32, slab, M)\n\t// Rune array\n\t_, T := alloc32(offset32, slab, N)\n\tinput.CopyRunes(T, minIdx)\n\n\t// Phase 2. Calculate bonus for each point\n\tmaxScore, maxScorePos := int16(0), 0\n\tpidx, lastIdx := 0, 0\n\tpchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false\n\tfor off, char := range T {\n\t\tvar class charClass\n\t\tif char <= unicode.MaxASCII {\n\t\t\tclass = asciiCharClasses[char]\n\t\t\tif !caseSensitive && class == charUpper {\n\t\t\t\tchar += 32\n\t\t\t\tT[off] = char\n\t\t\t}\n\t\t} else {\n\t\t\tclass = charClassOfNonAscii(char)\n\t\t\tif !caseSensitive && class == charUpper {\n\t\t\t\tchar = unicode.To(unicode.LowerCase, char)\n\t\t\t}\n\t\t\tif normalize {\n\t\t\t\tchar = normalizeRune(char)\n\t\t\t}\n\t\t\tT[off] = char\n\t\t}\n\n\t\tbonus := bonusMatrix[prevClass][class]\n\t\tB[off] = bonus\n\t\tprevClass = class\n\n\t\tif char == pchar {\n\t\t\tif pidx < M {\n\t\t\t\tF[pidx] = int32(off)\n\t\t\t\tpidx++\n\t\t\t\tpchar = pattern[min(pidx, M-1)]\n\t\t\t}\n\t\t\tlastIdx = off\n\t\t}\n\n\t\tif char == pchar0 {\n\t\t\tscore := scoreMatch + bonus*bonusFirstCharMultiplier\n\t\t\tH0[off] = score\n\t\t\tC0[off] = 1\n\t\t\tif M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {\n\t\t\t\tmaxScore, maxScorePos = score, off\n\t\t\t\tif forward && bonus >= bonusBoundary {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tinGap = false\n\t\t} else {\n\t\t\tif inGap {\n\t\t\t\tH0[off] = max(prevH0+scoreGapExtension, 0)\n\t\t\t} else {\n\t\t\t\tH0[off] = max(prevH0+scoreGapStart, 0)\n\t\t\t}\n\t\t\tC0[off] = 0\n\t\t\tinGap = true\n\t\t}\n\t\tprevH0 = H0[off]\n\t}\n\tif pidx != M {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\tif M == 1 {\n\t\tresult := Result{minIdx + maxScorePos, minIdx + maxScorePos + 1, int(maxScore)}\n\t\tif !withPos {\n\t\t\treturn result, nil\n\t\t}\n\t\tpos := []int{minIdx + maxScorePos}\n\t\treturn result, &pos\n\t}\n\n\t// Phase 3. Fill in score matrix (H)\n\t// Unlike the original algorithm, we do not allow omission.\n\tf0 := int(F[0])\n\twidth := lastIdx - f0 + 1\n\toffset16, H := alloc16(offset16, slab, width*M)\n\tcopy(H, H0[f0:lastIdx+1])\n\n\t// Possible length of consecutive chunk at each position.\n\t_, C := alloc16(offset16, slab, width*M)\n\tcopy(C, C0[f0:lastIdx+1])\n\n\tFsub := F[1:]\n\tPsub := pattern[1:][:len(Fsub)]\n\tfor off, f := range Fsub {\n\t\tf := int(f)\n\t\tpchar := Psub[off]\n\t\tpidx := off + 1\n\t\trow := pidx * width\n\t\tinGap := false\n\t\tTsub := T[f : lastIdx+1]\n\t\tBsub := B[f:][:len(Tsub)]\n\t\tCsub := C[row+f-f0:][:len(Tsub)]\n\t\tCdiag := C[row+f-f0-1-width:][:len(Tsub)]\n\t\tHsub := H[row+f-f0:][:len(Tsub)]\n\t\tHdiag := H[row+f-f0-1-width:][:len(Tsub)]\n\t\tHleft := H[row+f-f0-1:][:len(Tsub)]\n\t\tHleft[0] = 0\n\t\tfor off, char := range Tsub {\n\t\t\tcol := off + f\n\t\t\tvar s1, s2, consecutive int16\n\n\t\t\tif inGap {\n\t\t\t\ts2 = Hleft[off] + scoreGapExtension\n\t\t\t} else {\n\t\t\t\ts2 = Hleft[off] + scoreGapStart\n\t\t\t}\n\n\t\t\tif pchar == char {\n\t\t\t\ts1 = Hdiag[off] + scoreMatch\n\t\t\t\tb := Bsub[off]\n\t\t\t\tconsecutive = Cdiag[off] + 1\n\t\t\t\tif consecutive > 1 {\n\t\t\t\t\tfb := B[col-int(consecutive)+1]\n\t\t\t\t\t// Break consecutive chunk\n\t\t\t\t\tif b >= bonusBoundary && b > fb {\n\t\t\t\t\t\tconsecutive = 1\n\t\t\t\t\t} else {\n\t\t\t\t\t\tb = max(b, bonusConsecutive, fb)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif s1+b < s2 {\n\t\t\t\t\ts1 += Bsub[off]\n\t\t\t\t\tconsecutive = 0\n\t\t\t\t} else {\n\t\t\t\t\ts1 += b\n\t\t\t\t}\n\t\t\t}\n\t\t\tCsub[off] = consecutive\n\n\t\t\tinGap = s1 < s2\n\t\t\tscore := max(s1, s2, 0)\n\t\t\tif pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {\n\t\t\t\tmaxScore, maxScorePos = score, col\n\t\t\t}\n\t\t\tHsub[off] = score\n\t\t}\n\t}\n\n\tif DEBUG {\n\t\tdebugV2(T, pattern, F, lastIdx, H, C)\n\t}\n\n\t// Phase 4. (Optional) Backtrace to find character positions\n\tpos := posArray(withPos, M)\n\tj := f0\n\tif withPos {\n\t\ti := M - 1\n\t\tj = maxScorePos\n\t\tpreferMatch := true\n\t\tfor {\n\t\t\tI := i * width\n\t\t\tj0 := j - f0\n\t\t\ts := H[I+j0]\n\n\t\t\tvar s1, s2 int16\n\t\t\tif i > 0 && j >= int(F[i]) {\n\t\t\t\ts1 = H[I-width+j0-1]\n\t\t\t}\n\t\t\tif j > int(F[i]) {\n\t\t\t\ts2 = H[I+j0-1]\n\t\t\t}\n\n\t\t\tif s > s1 && (s > s2 || s == s2 && preferMatch) {\n\t\t\t\t*pos = append(*pos, j+minIdx)\n\t\t\t\tif i == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\ti--\n\t\t\t}\n\t\t\tpreferMatch = C[I+j0] > 1 || I+width+j0+1 < len(C) && C[I+width+j0+1] > 0\n\t\t\tj--\n\t\t}\n\t}\n\t// Start offset we return here is only relevant when begin tiebreak is used.\n\t// However finding the accurate offset requires backtracking, and we don't\n\t// want to pay extra cost for the option that has lost its importance.\n\treturn Result{minIdx + j, minIdx + maxScorePos + 1, int(maxScore)}, pos\n}\n\n// Implement the same sorting criteria as V2\nfunc calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {\n\tpidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)\n\tpos := posArray(withPos, len(pattern))\n\tprevClass := initialCharClass\n\tif sidx > 0 {\n\t\tprevClass = charClassOf(text.Get(sidx - 1))\n\t}\n\tfor idx := sidx; idx < eidx; idx++ {\n\t\tchar := text.Get(idx)\n\t\tclass := charClassOf(char)\n\t\tif !caseSensitive {\n\t\t\tif char >= 'A' && char <= 'Z' {\n\t\t\t\tchar += 32\n\t\t\t} else if char > unicode.MaxASCII {\n\t\t\t\tchar = unicode.To(unicode.LowerCase, char)\n\t\t\t}\n\t\t}\n\t\t// pattern is already normalized\n\t\tif normalize {\n\t\t\tchar = normalizeRune(char)\n\t\t}\n\t\tif char == pattern[pidx] {\n\t\t\tif withPos {\n\t\t\t\t*pos = append(*pos, idx)\n\t\t\t}\n\t\t\tscore += scoreMatch\n\t\t\tbonus := bonusMatrix[prevClass][class]\n\t\t\tif consecutive == 0 {\n\t\t\t\tfirstBonus = bonus\n\t\t\t} else {\n\t\t\t\t// Break consecutive chunk\n\t\t\t\tif bonus >= bonusBoundary && bonus > firstBonus {\n\t\t\t\t\tfirstBonus = bonus\n\t\t\t\t}\n\t\t\t\tbonus = max(bonus, firstBonus, bonusConsecutive)\n\t\t\t}\n\t\t\tif pidx == 0 {\n\t\t\t\tscore += int(bonus * bonusFirstCharMultiplier)\n\t\t\t} else {\n\t\t\t\tscore += int(bonus)\n\t\t\t}\n\t\t\tinGap = false\n\t\t\tconsecutive++\n\t\t\tpidx++\n\t\t} else {\n\t\t\tif inGap {\n\t\t\t\tscore += scoreGapExtension\n\t\t\t} else {\n\t\t\t\tscore += scoreGapStart\n\t\t\t}\n\t\t\tinGap = true\n\t\t\tconsecutive = 0\n\t\t\tfirstBonus = 0\n\t\t}\n\t\tprevClass = class\n\t}\n\treturn score, pos\n}\n\n// FuzzyMatchV1 performs fuzzy-match\nfunc FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\tif len(pattern) == 0 {\n\t\treturn Result{0, 0, 0}, nil\n\t}\n\tidx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)\n\tif idx < 0 {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\tpidx := 0\n\tsidx := -1\n\teidx := -1\n\n\tlenRunes := text.Length()\n\tlenPattern := len(pattern)\n\n\tfor index := range lenRunes {\n\t\tchar := text.Get(indexAt(index, lenRunes, forward))\n\t\t// This is considerably faster than blindly applying strings.ToLower to the\n\t\t// whole string\n\t\tif !caseSensitive {\n\t\t\t// Partially inlining `unicode.ToLower`. Ugly, but makes a noticeable\n\t\t\t// difference in CPU cost. (Measured on Go 1.4.1. Also note that the Go\n\t\t\t// compiler as of now does not inline non-leaf functions.)\n\t\t\tif char >= 'A' && char <= 'Z' {\n\t\t\t\tchar += 32\n\t\t\t} else if char > unicode.MaxASCII {\n\t\t\t\tchar = unicode.To(unicode.LowerCase, char)\n\t\t\t}\n\t\t}\n\t\tif normalize {\n\t\t\tchar = normalizeRune(char)\n\t\t}\n\t\tpchar := pattern[indexAt(pidx, lenPattern, forward)]\n\t\tif char == pchar {\n\t\t\tif sidx < 0 {\n\t\t\t\tsidx = index\n\t\t\t}\n\t\t\tif pidx++; pidx == lenPattern {\n\t\t\t\teidx = index + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif sidx >= 0 && eidx >= 0 {\n\t\tpidx--\n\t\tfor index := eidx - 1; index >= sidx; index-- {\n\t\t\ttidx := indexAt(index, lenRunes, forward)\n\t\t\tchar := text.Get(tidx)\n\t\t\tif !caseSensitive {\n\t\t\t\tif char >= 'A' && char <= 'Z' {\n\t\t\t\t\tchar += 32\n\t\t\t\t} else if char > unicode.MaxASCII {\n\t\t\t\t\tchar = unicode.To(unicode.LowerCase, char)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif normalize {\n\t\t\t\tchar = normalizeRune(char)\n\t\t\t}\n\n\t\t\tpidx_ := indexAt(pidx, lenPattern, forward)\n\t\t\tpchar := pattern[pidx_]\n\t\t\tif char == pchar {\n\t\t\t\tif pidx--; pidx < 0 {\n\t\t\t\t\tsidx = index\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !forward {\n\t\t\tsidx, eidx = lenRunes-eidx, lenRunes-sidx\n\t\t}\n\n\t\tscore, pos := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, withPos)\n\t\treturn Result{sidx, eidx, score}, pos\n\t}\n\treturn Result{-1, -1, 0}, nil\n}\n\n// ExactMatchNaive is a basic string searching algorithm that handles case\n// sensitivity. Although naive, it still performs better than the combination\n// of strings.ToLower + strings.Index for typical fzf use cases where input\n// strings and patterns are not very long.\n//\n// Since 0.15.0, this function searches for the match with the highest\n// bonus point, instead of stopping immediately after finding the first match.\n// The solution is much cheaper since there is only one possible alignment of\n// the pattern.\nfunc ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\treturn exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab)\n}\n\nfunc ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\treturn exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab)\n}\n\nfunc exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\tif len(pattern) == 0 {\n\t\treturn Result{0, 0, 0}, nil\n\t}\n\n\tlenRunes := text.Length()\n\tlenPattern := len(pattern)\n\n\tif lenRunes < lenPattern {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\tidx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)\n\tif idx < 0 {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\t// For simplicity, only look at the bonus at the first character position\n\tpidx := 0\n\tbestPos, bonus, bbonus, bestBonus := -1, int16(0), int16(0), int16(-1)\n\tfor index := 0; index < lenRunes; index++ {\n\t\tindex_ := indexAt(index, lenRunes, forward)\n\t\tchar := text.Get(index_)\n\t\tif !caseSensitive {\n\t\t\tif char >= 'A' && char <= 'Z' {\n\t\t\t\tchar += 32\n\t\t\t} else if char > unicode.MaxASCII {\n\t\t\t\tchar = unicode.To(unicode.LowerCase, char)\n\t\t\t}\n\t\t}\n\t\tif normalize {\n\t\t\tchar = normalizeRune(char)\n\t\t}\n\t\tpidx_ := indexAt(pidx, lenPattern, forward)\n\t\tpchar := pattern[pidx_]\n\t\tok := pchar == char\n\t\tif ok {\n\t\t\tif pidx_ == 0 {\n\t\t\t\tbonus = bonusAt(text, index_)\n\t\t\t}\n\t\t\tif boundaryCheck {\n\t\t\t\tif forward && pidx_ == 0 {\n\t\t\t\t\tbbonus = bonus\n\t\t\t\t} else if !forward && pidx_ == lenPattern-1 {\n\t\t\t\t\tif index_ < lenRunes-1 {\n\t\t\t\t\t\tbbonus = bonusAt(text, index_+1)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbbonus = bonusBoundaryWhite\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tok = bbonus >= bonusBoundary\n\t\t\t\tif ok && pidx_ == 0 {\n\t\t\t\t\tok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter\n\t\t\t\t}\n\t\t\t\tif ok && pidx_ == len(pattern)-1 {\n\t\t\t\t\tok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif ok {\n\t\t\tpidx++\n\t\t\tif pidx == lenPattern {\n\t\t\t\tif bonus > bestBonus {\n\t\t\t\t\tbestPos, bestBonus = index, bonus\n\t\t\t\t}\n\t\t\t\tif bonus >= bonusBoundary {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tindex -= pidx - 1\n\t\t\t\tpidx, bonus = 0, 0\n\t\t\t}\n\t\t} else {\n\t\t\tindex -= pidx\n\t\t\tpidx, bonus = 0, 0\n\t\t}\n\t}\n\tif bestPos >= 0 {\n\t\tvar sidx, eidx int\n\t\tif forward {\n\t\t\tsidx = bestPos - lenPattern + 1\n\t\t\teidx = bestPos + 1\n\t\t} else {\n\t\t\tsidx = lenRunes - (bestPos + 1)\n\t\t\teidx = lenRunes - (bestPos - lenPattern + 1)\n\t\t}\n\t\tvar score int\n\t\tif boundaryCheck {\n\t\t\t// Underscore boundaries should be ranked lower than the other types of boundaries\n\t\t\tscore = int(bonus)\n\t\t\tdeduct := int(bonus-bonusBoundary) + 1\n\t\t\tif sidx > 0 && text.Get(sidx-1) == '_' {\n\t\t\t\tscore -= deduct + 1\n\t\t\t\tdeduct = 1\n\t\t\t}\n\t\t\tif eidx < lenRunes && text.Get(eidx) == '_' {\n\t\t\t\tscore -= deduct\n\t\t\t}\n\t\t\t// Add base score so that this can compete with other match types e.g. 'foo' | bar\n\t\t\tscore += scoreMatch*lenPattern + int(bonusBoundaryWhite)*(lenPattern+1)\n\t\t} else {\n\t\t\tscore, _ = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)\n\t\t}\n\t\treturn Result{sidx, eidx, score}, nil\n\t}\n\treturn Result{-1, -1, 0}, nil\n}\n\n// PrefixMatch performs prefix-match\nfunc PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\tif len(pattern) == 0 {\n\t\treturn Result{0, 0, 0}, nil\n\t}\n\n\ttrimmedLen := 0\n\tif !unicode.IsSpace(pattern[0]) {\n\t\ttrimmedLen = text.LeadingWhitespaces()\n\t}\n\n\tif text.Length()-trimmedLen < len(pattern) {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\tfor index, r := range pattern {\n\t\tchar := text.Get(trimmedLen + index)\n\t\tif !caseSensitive {\n\t\t\tchar = unicode.ToLower(char)\n\t\t}\n\t\tif normalize {\n\t\t\tchar = normalizeRune(char)\n\t\t}\n\t\tif char != r {\n\t\t\treturn Result{-1, -1, 0}, nil\n\t\t}\n\t}\n\tlenPattern := len(pattern)\n\tscore, _ := calculateScore(caseSensitive, normalize, text, pattern, trimmedLen, trimmedLen+lenPattern, false)\n\treturn Result{trimmedLen, trimmedLen + lenPattern, score}, nil\n}\n\n// SuffixMatch performs suffix-match\nfunc SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\tlenRunes := text.Length()\n\ttrimmedLen := lenRunes\n\tif len(pattern) == 0 || !unicode.IsSpace(pattern[len(pattern)-1]) {\n\t\ttrimmedLen -= text.TrailingWhitespaces()\n\t}\n\tif len(pattern) == 0 {\n\t\treturn Result{trimmedLen, trimmedLen, 0}, nil\n\t}\n\tdiff := trimmedLen - len(pattern)\n\tif diff < 0 {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\tfor index, r := range pattern {\n\t\tchar := text.Get(index + diff)\n\t\tif !caseSensitive {\n\t\t\tchar = unicode.ToLower(char)\n\t\t}\n\t\tif normalize {\n\t\t\tchar = normalizeRune(char)\n\t\t}\n\t\tif char != r {\n\t\t\treturn Result{-1, -1, 0}, nil\n\t\t}\n\t}\n\tlenPattern := len(pattern)\n\tsidx := trimmedLen - lenPattern\n\teidx := trimmedLen\n\tscore, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)\n\treturn Result{sidx, eidx, score}, nil\n}\n\n// EqualMatch performs equal-match\nfunc EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {\n\tlenPattern := len(pattern)\n\tif lenPattern == 0 {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\n\t// Strip leading whitespaces\n\ttrimmedLen := 0\n\tif !unicode.IsSpace(pattern[0]) {\n\t\ttrimmedLen = text.LeadingWhitespaces()\n\t}\n\n\t// Strip trailing whitespaces\n\ttrimmedEndLen := 0\n\tif !unicode.IsSpace(pattern[lenPattern-1]) {\n\t\ttrimmedEndLen = text.TrailingWhitespaces()\n\t}\n\n\tif text.Length()-trimmedLen-trimmedEndLen != lenPattern {\n\t\treturn Result{-1, -1, 0}, nil\n\t}\n\tmatch := true\n\tif normalize {\n\t\trunes := text.ToRunes()\n\t\tfor idx, pchar := range pattern {\n\t\t\tchar := runes[trimmedLen+idx]\n\t\t\tif !caseSensitive {\n\t\t\t\tchar = unicode.To(unicode.LowerCase, char)\n\t\t\t}\n\t\t\tif normalizeRune(pchar) != normalizeRune(char) {\n\t\t\t\tmatch = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\trunes := text.ToRunes()\n\t\trunesStr := string(runes[trimmedLen : len(runes)-trimmedEndLen])\n\t\tif !caseSensitive {\n\t\t\trunesStr = strings.ToLower(runesStr)\n\t\t}\n\t\tmatch = runesStr == string(pattern)\n\t}\n\tif match {\n\t\treturn Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +\n\t\t\t(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil\n\t}\n\treturn Result{-1, -1, 0}, nil\n}\n"
  },
  {
    "path": "src/algo/algo_test.go",
    "content": "package algo\n\nimport (\n\t\"math\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc init() {\n\tInit(\"default\")\n}\n\nfunc assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {\n\tassertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)\n}\n\nfunc assertMatch2(t *testing.T, fun Algo, caseSensitive, normalize, forward bool, input, pattern string, sidx int, eidx int, score int) {\n\tif !caseSensitive {\n\t\tpattern = strings.ToLower(pattern)\n\t}\n\tchars := util.ToChars([]byte(input))\n\tres, pos := fun(caseSensitive, normalize, forward, &chars, []rune(pattern), true, nil)\n\tvar start, end int\n\tif pos == nil || len(*pos) == 0 {\n\t\tstart = res.Start\n\t\tend = res.End\n\t} else {\n\t\tsort.Ints(*pos)\n\t\tstart = (*pos)[0]\n\t\tend = (*pos)[len(*pos)-1] + 1\n\t}\n\tif start != sidx {\n\t\tt.Errorf(\"Invalid start index: %d (expected: %d, %s / %s)\", start, sidx, input, pattern)\n\t}\n\tif end != eidx {\n\t\tt.Errorf(\"Invalid end index: %d (expected: %d, %s / %s)\", end, eidx, input, pattern)\n\t}\n\tif res.Score != score {\n\t\tt.Errorf(\"Invalid score: %d (expected: %d, %s / %s)\", res.Score, score, input, pattern)\n\t}\n}\n\nfunc TestFuzzyMatch(t *testing.T) {\n\tfor _, fn := range []Algo{FuzzyMatchV1, FuzzyMatchV2} {\n\t\tfor _, forward := range []bool{true, false} {\n\t\t\tassertMatch(t, fn, false, forward, \"fooBarbaz1\", \"oBZ\", 2, 9,\n\t\t\t\tscoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)\n\t\t\tassertMatch(t, fn, false, forward, \"foo bar baz\", \"fbb\", 0, 9,\n\t\t\t\tscoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+\n\t\t\t\t\tint(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)\n\t\t\tassertMatch(t, fn, false, forward, \"/AutomatorDocument.icns\", \"rdoc\", 9, 13,\n\t\t\t\tscoreMatch*4+bonusCamel123+bonusConsecutive*2)\n\t\t\tassertMatch(t, fn, false, forward, \"/man1/zshcompctl.1\", \"zshc\", 6, 10,\n\t\t\t\tscoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3)\n\t\t\tassertMatch(t, fn, false, forward, \"/.oh-my-zsh/cache\", \"zshc\", 8, 13,\n\t\t\t\tscoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))\n\t\t\tassertMatch(t, fn, false, forward, \"ab0123 456\", \"12356\", 3, 10,\n\t\t\t\tscoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)\n\t\t\tassertMatch(t, fn, false, forward, \"abc123 456\", \"12356\", 3, 10,\n\t\t\t\tscoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)\n\t\t\tassertMatch(t, fn, false, forward, \"foo/bar/baz\", \"fbb\", 0, 9,\n\t\t\t\tscoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+\n\t\t\t\t\tint(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)\n\t\t\tassertMatch(t, fn, false, forward, \"fooBarBaz\", \"fbb\", 0, 7,\n\t\t\t\tscoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+\n\t\t\t\t\tbonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)\n\t\t\tassertMatch(t, fn, false, forward, \"foo barbaz\", \"fbb\", 0, 8,\n\t\t\t\tscoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)+\n\t\t\t\t\tscoreGapStart*2+scoreGapExtension*3)\n\t\t\tassertMatch(t, fn, false, forward, \"fooBar Baz\", \"foob\", 0, 4,\n\t\t\t\tscoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*3)\n\t\t\tassertMatch(t, fn, false, forward, \"xFoo-Bar Baz\", \"foo-b\", 1, 6,\n\t\t\t\tscoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+\n\t\t\t\t\tbonusNonWord+bonusBoundary)\n\n\t\t\tassertMatch(t, fn, true, forward, \"fooBarbaz\", \"oBz\", 2, 9,\n\t\t\t\tscoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)\n\t\t\tassertMatch(t, fn, true, forward, \"Foo/Bar/Baz\", \"FBB\", 0, 9,\n\t\t\t\tscoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*2+\n\t\t\t\t\tscoreGapStart*2+scoreGapExtension*4)\n\t\t\tassertMatch(t, fn, true, forward, \"FooBarBaz\", \"FBB\", 0, 7,\n\t\t\t\tscoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+bonusCamel123*2+\n\t\t\t\t\tscoreGapStart*2+scoreGapExtension*2)\n\t\t\tassertMatch(t, fn, true, forward, \"FooBar Baz\", \"FooB\", 0, 4,\n\t\t\t\tscoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+\n\t\t\t\t\tmax(bonusCamel123, int(bonusBoundaryWhite)))\n\n\t\t\t// Consecutive bonus updated\n\t\t\tassertMatch(t, fn, true, forward, \"foo-bar\", \"o-ba\", 2, 6,\n\t\t\t\tscoreMatch*4+bonusBoundary*3)\n\n\t\t\t// Non-match\n\t\t\tassertMatch(t, fn, true, forward, \"fooBarbaz\", \"oBZ\", -1, -1, 0)\n\t\t\tassertMatch(t, fn, true, forward, \"Foo Bar Baz\", \"fbb\", -1, -1, 0)\n\t\t\tassertMatch(t, fn, true, forward, \"fooBarbaz\", \"fooBarbazz\", -1, -1, 0)\n\t\t}\n\t}\n}\n\nfunc TestFuzzyMatchBackward(t *testing.T) {\n\tassertMatch(t, FuzzyMatchV1, false, true, \"foobar fb\", \"fb\", 0, 4,\n\t\tscoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+\n\t\t\tscoreGapStart+scoreGapExtension)\n\tassertMatch(t, FuzzyMatchV1, false, false, \"foobar fb\", \"fb\", 7, 9,\n\t\tscoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite))\n}\n\nfunc TestExactMatchNaive(t *testing.T) {\n\tfor _, dir := range []bool{true, false} {\n\t\tassertMatch(t, ExactMatchNaive, true, dir, \"fooBarbaz\", \"oBA\", -1, -1, 0)\n\t\tassertMatch(t, ExactMatchNaive, true, dir, \"fooBarbaz\", \"fooBarbazz\", -1, -1, 0)\n\n\t\tassertMatch(t, ExactMatchNaive, false, dir, \"fooBarbaz\", \"oBA\", 2, 5,\n\t\t\tscoreMatch*3+bonusCamel123+bonusConsecutive)\n\t\tassertMatch(t, ExactMatchNaive, false, dir, \"/AutomatorDocument.icns\", \"rdoc\", 9, 13,\n\t\t\tscoreMatch*4+bonusCamel123+bonusConsecutive*2)\n\t\tassertMatch(t, ExactMatchNaive, false, dir, \"/man1/zshcompctl.1\", \"zshc\", 6, 10,\n\t\t\tscoreMatch*4+int(bonusBoundaryDelimiter)*(bonusFirstCharMultiplier+3))\n\t\tassertMatch(t, ExactMatchNaive, false, dir, \"/.oh-my-zsh/cache\", \"zsh/c\", 8, 13,\n\t\t\tscoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+int(bonusBoundaryDelimiter))\n\t}\n}\n\nfunc TestExactMatchNaiveBackward(t *testing.T) {\n\tassertMatch(t, ExactMatchNaive, false, true, \"foobar foob\", \"oo\", 1, 3,\n\t\tscoreMatch*2+bonusConsecutive)\n\tassertMatch(t, ExactMatchNaive, false, false, \"foobar foob\", \"oo\", 8, 10,\n\t\tscoreMatch*2+bonusConsecutive)\n}\n\nfunc TestPrefixMatch(t *testing.T) {\n\tscore := scoreMatch*3 + int(bonusBoundaryWhite)*bonusFirstCharMultiplier + int(bonusBoundaryWhite)*2\n\n\tfor _, dir := range []bool{true, false} {\n\t\tassertMatch(t, PrefixMatch, true, dir, \"fooBarbaz\", \"Foo\", -1, -1, 0)\n\t\tassertMatch(t, PrefixMatch, false, dir, \"fooBarBaz\", \"baz\", -1, -1, 0)\n\t\tassertMatch(t, PrefixMatch, false, dir, \"fooBarbaz\", \"Foo\", 0, 3, score)\n\t\tassertMatch(t, PrefixMatch, false, dir, \"foOBarBaZ\", \"foo\", 0, 3, score)\n\t\tassertMatch(t, PrefixMatch, false, dir, \"f-oBarbaz\", \"f-o\", 0, 3, score)\n\n\t\tassertMatch(t, PrefixMatch, false, dir, \" fooBar\", \"foo\", 1, 4, score)\n\t\tassertMatch(t, PrefixMatch, false, dir, \" fooBar\", \" fo\", 0, 3, score)\n\t\tassertMatch(t, PrefixMatch, false, dir, \"     fo\", \"foo\", -1, -1, 0)\n\t}\n}\n\nfunc TestSuffixMatch(t *testing.T) {\n\tfor _, dir := range []bool{true, false} {\n\t\tassertMatch(t, SuffixMatch, true, dir, \"fooBarbaz\", \"Baz\", -1, -1, 0)\n\t\tassertMatch(t, SuffixMatch, false, dir, \"fooBarbaz\", \"Foo\", -1, -1, 0)\n\n\t\tassertMatch(t, SuffixMatch, false, dir, \"fooBarbaz\", \"baz\", 6, 9,\n\t\t\tscoreMatch*3+bonusConsecutive*2)\n\t\tassertMatch(t, SuffixMatch, false, dir, \"fooBarBaZ\", \"baz\", 6, 9,\n\t\t\t(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))\n\n\t\t// Strip trailing white space from the string\n\t\tassertMatch(t, SuffixMatch, false, dir, \"fooBarbaz \", \"baz\", 6, 9,\n\t\t\tscoreMatch*3+bonusConsecutive*2)\n\n\t\t// Only when the pattern doesn't end with a space\n\t\tassertMatch(t, SuffixMatch, false, dir, \"fooBarbaz \", \"baz \", 6, 10,\n\t\t\tscoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))\n\t}\n}\n\nfunc TestEmptyPattern(t *testing.T) {\n\tfor _, dir := range []bool{true, false} {\n\t\tassertMatch(t, FuzzyMatchV1, true, dir, \"foobar\", \"\", 0, 0, 0)\n\t\tassertMatch(t, FuzzyMatchV2, true, dir, \"foobar\", \"\", 0, 0, 0)\n\t\tassertMatch(t, ExactMatchNaive, true, dir, \"foobar\", \"\", 0, 0, 0)\n\t\tassertMatch(t, PrefixMatch, true, dir, \"foobar\", \"\", 0, 0, 0)\n\t\tassertMatch(t, SuffixMatch, true, dir, \"foobar\", \"\", 6, 6, 0)\n\t}\n}\n\nfunc TestNormalize(t *testing.T) {\n\tcaseSensitive := false\n\tnormalize := true\n\tforward := true\n\ttest := func(input, pattern string, sidx, eidx, score int, funs ...Algo) {\n\t\tfor _, fun := range funs {\n\t\t\tassertMatch2(t, fun, caseSensitive, normalize, forward,\n\t\t\t\tinput, pattern, sidx, eidx, score)\n\t\t}\n\t}\n\ttest(\"Só Danço Samba\", \"So\", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)\n\ttest(\"Só Danço Samba\", \"sodc\", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2)\n\ttest(\"Danço\", \"danco\", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)\n}\n\nfunc TestLongString(t *testing.T) {\n\tbytes := make([]byte, math.MaxUint16*2)\n\tfor i := range bytes {\n\t\tbytes[i] = 'x'\n\t}\n\tbytes[math.MaxUint16] = 'z'\n\tassertMatch(t, FuzzyMatchV2, true, true, string(bytes), \"zx\", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)\n}\n\nfunc TestLongStringWithNormalize(t *testing.T) {\n\tbytes := make([]byte, 30000)\n\tfor i := range bytes {\n\t\tbytes[i] = 'x'\n\t}\n\tunicodeString := string(bytes) + \" Minímal example\"\n\tassertMatch2(t, FuzzyMatchV1, false, true, false, unicodeString, \"minim\", 30001, 30006, 140)\n}\n"
  },
  {
    "path": "src/algo/indexbyte2_amd64.go",
    "content": "//go:build amd64\n\npackage algo\n\nvar _useAVX2 bool\n\nfunc init() {\n\t_useAVX2 = cpuHasAVX2()\n}\n\n//go:noescape\nfunc cpuHasAVX2() bool\n\n// indexByteTwo returns the index of the first occurrence of b1 or b2 in s,\n// or -1 if neither is present. Uses AVX2 when available, SSE2 otherwise.\n//\n//go:noescape\nfunc IndexByteTwo(s []byte, b1, b2 byte) int\n\n// lastIndexByteTwo returns the index of the last occurrence of b1 or b2 in s,\n// or -1 if neither is present. Uses AVX2 when available, SSE2 otherwise.\n//\n//go:noescape\nfunc lastIndexByteTwo(s []byte, b1, b2 byte) int\n"
  },
  {
    "path": "src/algo/indexbyte2_amd64.s",
    "content": "#include \"textflag.h\"\n\n// func cpuHasAVX2() bool\n//\n// Checks CPUID and XGETBV for AVX2 + OS YMM support.\nTEXT ·cpuHasAVX2(SB),NOSPLIT,$0-1\n\tMOVQ\tBX, R8          // save BX (callee-saved, clobbered by CPUID)\n\n\t// Check max CPUID leaf >= 7\n\tMOVL\t$0, AX\n\tCPUID\n\tCMPL\tAX, $7\n\tJL\tcpuid_no\n\n\t// Check OSXSAVE (CPUID.1:ECX bit 27)\n\tMOVL\t$1, AX\n\tCPUID\n\tTESTL\t$(1<<27), CX\n\tJZ\tcpuid_no\n\n\t// Check AVX2 (CPUID.7.0:EBX bit 5)\n\tMOVL\t$7, AX\n\tMOVL\t$0, CX\n\tCPUID\n\tTESTL\t$(1<<5), BX\n\tJZ\tcpuid_no\n\n\t// Check OS YMM state support via XGETBV\n\tMOVL\t$0, CX\n\tBYTE\t$0x0F; BYTE $0x01; BYTE $0xD0  // XGETBV → EDX:EAX\n\tANDL\t$6, AX          // bits 1 (XMM) and 2 (YMM)\n\tCMPL\tAX, $6\n\tJNE\tcpuid_no\n\n\tMOVQ\tR8, BX          // restore BX\n\tMOVB\t$1, ret+0(FP)\n\tRET\n\ncpuid_no:\n\tMOVQ\tR8, BX\n\tMOVB\t$0, ret+0(FP)\n\tRET\n\n// func IndexByteTwo(s []byte, b1, b2 byte) int\n//\n// Returns the index of the first occurrence of b1 or b2 in s, or -1.\n// Uses AVX2 (32 bytes/iter) when available, SSE2 (16 bytes/iter) otherwise.\nTEXT ·IndexByteTwo(SB),NOSPLIT,$0-40\n\tMOVQ\ts_base+0(FP), SI\n\tMOVQ\ts_len+8(FP), BX\n\tMOVBLZX\tb1+24(FP), AX\n\tMOVBLZX\tb2+25(FP), CX\n\tLEAQ\tret+32(FP), R8\n\n\tTESTQ\tBX, BX\n\tJEQ\tfwd_failure\n\n\t// Try AVX2 for inputs >= 32 bytes\n\tCMPQ\tBX, $32\n\tJLT\tfwd_sse2\n\tCMPB\t·_useAVX2(SB), $1\n\tJNE\tfwd_sse2\n\n\t// ====== AVX2 forward search ======\n\tMOVD\tAX, X0\n\tVPBROADCASTB\tX0, Y0       // Y0 = splat(b1)\n\tMOVD\tCX, X1\n\tVPBROADCASTB\tX1, Y1       // Y1 = splat(b2)\n\n\tMOVQ\tSI, DI\n\tLEAQ\t-32(SI)(BX*1), AX    // AX = last valid 32-byte chunk\n\tJMP\tfwd_avx2_entry\n\nfwd_avx2_loop:\n\tVMOVDQU\t(DI), Y2\n\tVPCMPEQB\tY0, Y2, Y3\n\tVPCMPEQB\tY1, Y2, Y4\n\tVPOR\tY3, Y4, Y3\n\tVPMOVMSKB\tY3, DX\n\tBSFL\tDX, DX\n\tJNZ\tfwd_avx2_success\n\tADDQ\t$32, DI\n\nfwd_avx2_entry:\n\tCMPQ\tDI, AX\n\tJB\tfwd_avx2_loop\n\n\t// Last 32-byte chunk (may overlap with previous)\n\tMOVQ\tAX, DI\n\tVMOVDQU\t(AX), Y2\n\tVPCMPEQB\tY0, Y2, Y3\n\tVPCMPEQB\tY1, Y2, Y4\n\tVPOR\tY3, Y4, Y3\n\tVPMOVMSKB\tY3, DX\n\tBSFL\tDX, DX\n\tJNZ\tfwd_avx2_success\n\n\tMOVQ\t$-1, (R8)\n\tVZEROUPPER\n\tRET\n\nfwd_avx2_success:\n\tSUBQ\tSI, DI\n\tADDQ\tDX, DI\n\tMOVQ\tDI, (R8)\n\tVZEROUPPER\n\tRET\n\n\t// ====== SSE2 forward search (< 32 bytes or no AVX2) ======\n\nfwd_sse2:\n\t// Broadcast b1 into X0\n\tMOVD\tAX, X0\n\tPUNPCKLBW\tX0, X0\n\tPUNPCKLBW\tX0, X0\n\tPSHUFL\t$0, X0, X0\n\n\t// Broadcast b2 into X4\n\tMOVD\tCX, X4\n\tPUNPCKLBW\tX4, X4\n\tPUNPCKLBW\tX4, X4\n\tPSHUFL\t$0, X4, X4\n\n\tCMPQ\tBX, $16\n\tJLT\tfwd_small\n\n\tMOVQ\tSI, DI\n\tLEAQ\t-16(SI)(BX*1), AX\n\tJMP\tfwd_sseloopentry\n\nfwd_sseloop:\n\tMOVOU\t(DI), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\tBSFL\tDX, DX\n\tJNZ\tfwd_ssesuccess\n\tADDQ\t$16, DI\n\nfwd_sseloopentry:\n\tCMPQ\tDI, AX\n\tJB\tfwd_sseloop\n\n\t// Search the last 16-byte chunk (may overlap)\n\tMOVQ\tAX, DI\n\tMOVOU\t(AX), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\tBSFL\tDX, DX\n\tJNZ\tfwd_ssesuccess\n\nfwd_failure:\n\tMOVQ\t$-1, (R8)\n\tRET\n\nfwd_ssesuccess:\n\tSUBQ\tSI, DI\n\tADDQ\tDX, DI\n\tMOVQ\tDI, (R8)\n\tRET\n\nfwd_small:\n\t// Check if loading 16 bytes from SI would cross a page boundary\n\tLEAQ\t16(SI), AX\n\tTESTW\t$0xff0, AX\n\tJEQ\tfwd_endofpage\n\n\tMOVOU\t(SI), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\tBSFL\tDX, DX\n\tJZ\tfwd_failure\n\tCMPL\tDX, BX\n\tJAE\tfwd_failure\n\tMOVQ\tDX, (R8)\n\tRET\n\nfwd_endofpage:\n\tMOVOU\t-16(SI)(BX*1), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\tMOVL\tBX, CX\n\tSHLL\tCX, DX\n\tSHRL\t$16, DX\n\tBSFL\tDX, DX\n\tJZ\tfwd_failure\n\tMOVQ\tDX, (R8)\n\tRET\n\n// func lastIndexByteTwo(s []byte, b1, b2 byte) int\n//\n// Returns the index of the last occurrence of b1 or b2 in s, or -1.\n// Uses AVX2 (32 bytes/iter) when available, SSE2 (16 bytes/iter) otherwise.\nTEXT ·lastIndexByteTwo(SB),NOSPLIT,$0-40\n\tMOVQ\ts_base+0(FP), SI\n\tMOVQ\ts_len+8(FP), BX\n\tMOVBLZX\tb1+24(FP), AX\n\tMOVBLZX\tb2+25(FP), CX\n\tLEAQ\tret+32(FP), R8\n\n\tTESTQ\tBX, BX\n\tJEQ\tback_failure\n\n\t// Try AVX2 for inputs >= 32 bytes\n\tCMPQ\tBX, $32\n\tJLT\tback_sse2\n\tCMPB\t·_useAVX2(SB), $1\n\tJNE\tback_sse2\n\n\t// ====== AVX2 backward search ======\n\tMOVD\tAX, X0\n\tVPBROADCASTB\tX0, Y0\n\tMOVD\tCX, X1\n\tVPBROADCASTB\tX1, Y1\n\n\t// DI = start of last 32-byte chunk\n\tLEAQ\t-32(SI)(BX*1), DI\n\nback_avx2_loop:\n\tCMPQ\tDI, SI\n\tJBE\tback_avx2_first\n\n\tVMOVDQU\t(DI), Y2\n\tVPCMPEQB\tY0, Y2, Y3\n\tVPCMPEQB\tY1, Y2, Y4\n\tVPOR\tY3, Y4, Y3\n\tVPMOVMSKB\tY3, DX\n\tBSRL\tDX, DX\n\tJNZ\tback_avx2_success\n\tSUBQ\t$32, DI\n\tJMP\tback_avx2_loop\n\nback_avx2_first:\n\t// First 32 bytes (DI <= SI, load from SI)\n\tVMOVDQU\t(SI), Y2\n\tVPCMPEQB\tY0, Y2, Y3\n\tVPCMPEQB\tY1, Y2, Y4\n\tVPOR\tY3, Y4, Y3\n\tVPMOVMSKB\tY3, DX\n\tBSRL\tDX, DX\n\tJNZ\tback_avx2_firstsuccess\n\n\tMOVQ\t$-1, (R8)\n\tVZEROUPPER\n\tRET\n\nback_avx2_success:\n\tSUBQ\tSI, DI\n\tADDQ\tDX, DI\n\tMOVQ\tDI, (R8)\n\tVZEROUPPER\n\tRET\n\nback_avx2_firstsuccess:\n\tMOVQ\tDX, (R8)\n\tVZEROUPPER\n\tRET\n\n\t// ====== SSE2 backward search (< 32 bytes or no AVX2) ======\n\nback_sse2:\n\t// Broadcast b1 into X0\n\tMOVD\tAX, X0\n\tPUNPCKLBW\tX0, X0\n\tPUNPCKLBW\tX0, X0\n\tPSHUFL\t$0, X0, X0\n\n\t// Broadcast b2 into X4\n\tMOVD\tCX, X4\n\tPUNPCKLBW\tX4, X4\n\tPUNPCKLBW\tX4, X4\n\tPSHUFL\t$0, X4, X4\n\n\tCMPQ\tBX, $16\n\tJLT\tback_small\n\n\t// DI = start of last 16-byte chunk\n\tLEAQ\t-16(SI)(BX*1), DI\n\nback_sseloop:\n\tCMPQ\tDI, SI\n\tJBE\tback_ssefirst\n\n\tMOVOU\t(DI), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\tBSRL\tDX, DX\n\tJNZ\tback_ssesuccess\n\tSUBQ\t$16, DI\n\tJMP\tback_sseloop\n\nback_ssefirst:\n\t// First 16 bytes (DI <= SI, load from SI)\n\tMOVOU\t(SI), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\tBSRL\tDX, DX\n\tJNZ\tback_ssefirstsuccess\n\nback_failure:\n\tMOVQ\t$-1, (R8)\n\tRET\n\nback_ssesuccess:\n\tSUBQ\tSI, DI\n\tADDQ\tDX, DI\n\tMOVQ\tDI, (R8)\n\tRET\n\nback_ssefirstsuccess:\n\t// DX = byte offset from base\n\tMOVQ\tDX, (R8)\n\tRET\n\nback_small:\n\t// Check page boundary\n\tLEAQ\t16(SI), AX\n\tTESTW\t$0xff0, AX\n\tJEQ\tback_endofpage\n\n\tMOVOU\t(SI), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\t// Mask to first BX bytes: keep bits 0..BX-1\n\tMOVL\t$1, AX\n\tMOVL\tBX, CX\n\tSHLL\tCX, AX\n\tDECL\tAX\n\tANDL\tAX, DX\n\tBSRL\tDX, DX\n\tJZ\tback_failure\n\tMOVQ\tDX, (R8)\n\tRET\n\nback_endofpage:\n\t// Load 16 bytes ending at base+n\n\tMOVOU\t-16(SI)(BX*1), X1\n\tMOVOU\tX1, X2\n\tPCMPEQB\tX0, X1\n\tPCMPEQB\tX4, X2\n\tPOR\tX2, X1\n\tPMOVMSKB\tX1, DX\n\t// Bits correspond to bytes [base+n-16, base+n).\n\t// We want original bytes [0, n), which are bits [16-n, 16).\n\t// Mask: keep bits (16-n) through 15.\n\tMOVL\t$16, CX\n\tSUBL\tBX, CX\n\tSHRL\tCX, DX\n\tSHLL\tCX, DX\n\tBSRL\tDX, DX\n\tJZ\tback_failure\n\t// DX is the bit position in the loaded chunk.\n\t// Original byte index = DX - (16 - n) = DX + n - 16\n\tADDL\tBX, DX\n\tSUBL\t$16, DX\n\tMOVQ\tDX, (R8)\n\tRET\n"
  },
  {
    "path": "src/algo/indexbyte2_arm64.go",
    "content": "//go:build arm64\n\npackage algo\n\n// indexByteTwo returns the index of the first occurrence of b1 or b2 in s,\n// or -1 if neither is present. Implemented in assembly using ARM64 NEON\n// to search for both bytes in a single pass.\n//\n//go:noescape\nfunc IndexByteTwo(s []byte, b1, b2 byte) int\n\n// lastIndexByteTwo returns the index of the last occurrence of b1 or b2 in s,\n// or -1 if neither is present. Implemented in assembly using ARM64 NEON,\n// scanning backward.\n//\n//go:noescape\nfunc lastIndexByteTwo(s []byte, b1, b2 byte) int\n"
  },
  {
    "path": "src/algo/indexbyte2_arm64.s",
    "content": "#include \"textflag.h\"\n\n// func IndexByteTwo(s []byte, b1, b2 byte) int\n//\n// Returns the index of the first occurrence of b1 or b2 in s, or -1.\n// Uses ARM64 NEON to search for both bytes in a single pass over the data.\n// Adapted from Go's internal/bytealg/indexbyte_arm64.s (single-byte version).\nTEXT ·IndexByteTwo(SB),NOSPLIT,$0-40\n\tMOVD\ts_base+0(FP), R0\n\tMOVD\ts_len+8(FP), R2\n\tMOVBU\tb1+24(FP), R1\n\tMOVBU\tb2+25(FP), R7\n\tMOVD\t$ret+32(FP), R8\n\n\t// Core algorithm:\n\t// For each 32-byte chunk we calculate a 64-bit syndrome value,\n\t// with two bits per byte. We compare against both b1 and b2,\n\t// OR the results, then use the same syndrome extraction as\n\t// Go's IndexByte.\n\n\tCBZ\tR2, fail\n\tMOVD\tR0, R11\n\t// Magic constant 0x40100401 allows us to identify which lane matches.\n\t// Each byte in the group of 4 gets a distinct bit: 1, 4, 16, 64.\n\tMOVD\t$0x40100401, R5\n\tVMOV\tR1, V0.B16    // V0 = splat(b1)\n\tVMOV\tR7, V7.B16    // V7 = splat(b2)\n\t// Work with aligned 32-byte chunks\n\tBIC\t$0x1f, R0, R3\n\tVMOV\tR5, V5.S4\n\tANDS\t$0x1f, R0, R9\n\tAND\t$0x1f, R2, R10\n\tBEQ\tloop\n\n\t// Input string is not 32-byte aligned. Process the first\n\t// aligned 32-byte block and mask off bytes before our start.\n\tVLD1.P\t(R3), [V1.B16, V2.B16]\n\tSUB\t$0x20, R9, R4\n\tADDS\tR4, R2, R2\n\t// Compare against both needles\n\tVCMEQ\tV0.B16, V1.B16, V3.B16  // b1 vs first 16 bytes\n\tVCMEQ\tV7.B16, V1.B16, V8.B16  // b2 vs first 16 bytes\n\tVORR\tV8.B16, V3.B16, V3.B16  // combine\n\tVCMEQ\tV0.B16, V2.B16, V4.B16  // b1 vs second 16 bytes\n\tVCMEQ\tV7.B16, V2.B16, V9.B16  // b2 vs second 16 bytes\n\tVORR\tV9.B16, V4.B16, V4.B16  // combine\n\t// Build syndrome\n\tVAND\tV5.B16, V3.B16, V3.B16\n\tVAND\tV5.B16, V4.B16, V4.B16\n\tVADDP\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.B16, V6.B16, V6.B16\n\tVMOV\tV6.D[0], R6\n\t// Clear the irrelevant lower bits\n\tLSL\t$1, R9, R4\n\tLSR\tR4, R6, R6\n\tLSL\tR4, R6, R6\n\t// The first block can also be the last\n\tBLS\tmasklast\n\t// Have we found something already?\n\tCBNZ\tR6, tail\n\nloop:\n\tVLD1.P\t(R3), [V1.B16, V2.B16]\n\tSUBS\t$0x20, R2, R2\n\t// Compare against both needles, OR results\n\tVCMEQ\tV0.B16, V1.B16, V3.B16\n\tVCMEQ\tV7.B16, V1.B16, V8.B16\n\tVORR\tV8.B16, V3.B16, V3.B16\n\tVCMEQ\tV0.B16, V2.B16, V4.B16\n\tVCMEQ\tV7.B16, V2.B16, V9.B16\n\tVORR\tV9.B16, V4.B16, V4.B16\n\t// If we're out of data we finish regardless of the result\n\tBLS\tend\n\t// Fast check: OR both halves and check for any match\n\tVORR\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.D2, V6.D2, V6.D2\n\tVMOV\tV6.D[0], R6\n\tCBZ\tR6, loop\n\nend:\n\t// Found something or out of data — build full syndrome\n\tVAND\tV5.B16, V3.B16, V3.B16\n\tVAND\tV5.B16, V4.B16, V4.B16\n\tVADDP\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.B16, V6.B16, V6.B16\n\tVMOV\tV6.D[0], R6\n\t// Only mask for the last block\n\tBHS\ttail\n\nmasklast:\n\t// Clear irrelevant upper bits\n\tADD\tR9, R10, R4\n\tAND\t$0x1f, R4, R4\n\tSUB\t$0x20, R4, R4\n\tNEG\tR4<<1, R4\n\tLSL\tR4, R6, R6\n\tLSR\tR4, R6, R6\n\ntail:\n\tCBZ\tR6, fail\n\tRBIT\tR6, R6\n\tSUB\t$0x20, R3, R3\n\tCLZ\tR6, R6\n\tADD\tR6>>1, R3, R0\n\tSUB\tR11, R0, R0\n\tMOVD\tR0, (R8)\n\tRET\n\nfail:\n\tMOVD\t$-1, R0\n\tMOVD\tR0, (R8)\n\tRET\n\n// func lastIndexByteTwo(s []byte, b1, b2 byte) int\n//\n// Returns the index of the last occurrence of b1 or b2 in s, or -1.\n// Scans backward using ARM64 NEON.\nTEXT ·lastIndexByteTwo(SB),NOSPLIT,$0-40\n\tMOVD\ts_base+0(FP), R0\n\tMOVD\ts_len+8(FP), R2\n\tMOVBU\tb1+24(FP), R1\n\tMOVBU\tb2+25(FP), R7\n\tMOVD\t$ret+32(FP), R8\n\n\tCBZ\tR2, lfail\n\tMOVD\tR0, R11          // save base\n\tADD\tR0, R2, R12      // R12 = end = base + len\n\tMOVD\t$0x40100401, R5\n\tVMOV\tR1, V0.B16       // V0 = splat(b1)\n\tVMOV\tR7, V7.B16       // V7 = splat(b2)\n\tVMOV\tR5, V5.S4\n\n\t// Align: find the aligned block containing the last byte\n\tSUB\t$1, R12, R3\n\tBIC\t$0x1f, R3, R3    // R3 = start of aligned block containing last byte\n\n\t// --- Process tail block ---\n\tVLD1\t(R3), [V1.B16, V2.B16]\n\tVCMEQ\tV0.B16, V1.B16, V3.B16\n\tVCMEQ\tV7.B16, V1.B16, V8.B16\n\tVORR\tV8.B16, V3.B16, V3.B16\n\tVCMEQ\tV0.B16, V2.B16, V4.B16\n\tVCMEQ\tV7.B16, V2.B16, V9.B16\n\tVORR\tV9.B16, V4.B16, V4.B16\n\tVAND\tV5.B16, V3.B16, V3.B16\n\tVAND\tV5.B16, V4.B16, V4.B16\n\tVADDP\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.B16, V6.B16, V6.B16\n\tVMOV\tV6.D[0], R6\n\n\t// Mask upper bits (bytes past end of slice)\n\t// tail_bytes = end - R3 (1..32)\n\tSUB\tR3, R12, R10     // R10 = tail_bytes\n\tMOVD\t$64, R4\n\tSUB\tR10<<1, R4, R4   // R4 = 64 - 2*tail_bytes\n\tLSL\tR4, R6, R6\n\tLSR\tR4, R6, R6\n\n\t// Is this also the head block?\n\tCMP\tR11, R3          // R3 - R11\n\tBLO\tlmaskfirst       // R3 < base: head+tail in same block\n\tBEQ\tltailonly         // R3 == base: single aligned block\n\n\t// R3 > base: more blocks before this one\n\tCBNZ\tR6, llast\n\tB\tlbacksetup\n\nltailonly:\n\t// Single block, already masked upper bits\n\tCBNZ\tR6, llast\n\tB\tlfail\n\nlmaskfirst:\n\t// Mask lower bits (bytes before start of slice)\n\tSUB\tR3, R11, R4      // R4 = base - R3\n\tLSL\t$1, R4, R4\n\tLSR\tR4, R6, R6\n\tLSL\tR4, R6, R6\n\tCBNZ\tR6, llast\n\tB\tlfail\n\nlbacksetup:\n\tSUB\t$0x20, R3\n\nlbackloop:\n\tVLD1\t(R3), [V1.B16, V2.B16]\n\tVCMEQ\tV0.B16, V1.B16, V3.B16\n\tVCMEQ\tV7.B16, V1.B16, V8.B16\n\tVORR\tV8.B16, V3.B16, V3.B16\n\tVCMEQ\tV0.B16, V2.B16, V4.B16\n\tVCMEQ\tV7.B16, V2.B16, V9.B16\n\tVORR\tV9.B16, V4.B16, V4.B16\n\t// Quick check: any match in this block?\n\tVORR\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.D2, V6.D2, V6.D2\n\tVMOV\tV6.D[0], R6\n\n\t// Is this a head block? (R3 < base)\n\tCMP\tR11, R3\n\tBLO\tlheadblock\n\n\t// Full block (R3 >= base)\n\tCBNZ\tR6, lbackfound\n\t// More blocks?\n\tBEQ\tlfail            // R3 == base, no more\n\tSUB\t$0x20, R3\n\tB\tlbackloop\n\nlbackfound:\n\t// Build full syndrome\n\tVAND\tV5.B16, V3.B16, V3.B16\n\tVAND\tV5.B16, V4.B16, V4.B16\n\tVADDP\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.B16, V6.B16, V6.B16\n\tVMOV\tV6.D[0], R6\n\tB\tllast\n\nlheadblock:\n\t// R3 < base. Build full syndrome if quick check had a match.\n\tCBZ\tR6, lfail\n\tVAND\tV5.B16, V3.B16, V3.B16\n\tVAND\tV5.B16, V4.B16, V4.B16\n\tVADDP\tV4.B16, V3.B16, V6.B16\n\tVADDP\tV6.B16, V6.B16, V6.B16\n\tVMOV\tV6.D[0], R6\n\t// Mask lower bits\n\tSUB\tR3, R11, R4      // R4 = base - R3\n\tLSL\t$1, R4, R4\n\tLSR\tR4, R6, R6\n\tLSL\tR4, R6, R6\n\tCBZ\tR6, lfail\n\nllast:\n\t// Find last match: highest set bit in syndrome\n\t// Syndrome has bit 2i set for matching byte i.\n\t// CLZ gives leading zeros; byte_offset = (63 - CLZ) / 2.\n\tCLZ\tR6, R6\n\tMOVD\t$63, R4\n\tSUB\tR6, R4, R6       // R6 = 63 - CLZ = bit position\n\tLSR\t$1, R6            // R6 = byte offset within block\n\tADD\tR3, R6, R0        // R0 = absolute address\n\tSUB\tR11, R0, R0       // R0 = slice index\n\tMOVD\tR0, (R8)\n\tRET\n\nlfail:\n\tMOVD\t$-1, R0\n\tMOVD\tR0, (R8)\n\tRET\n"
  },
  {
    "path": "src/algo/indexbyte2_other.go",
    "content": "//go:build !arm64 && !amd64\n\npackage algo\n\nimport \"bytes\"\n\n// indexByteTwo returns the index of the first occurrence of b1 or b2 in s,\n// or -1 if neither is present.\nfunc IndexByteTwo(s []byte, b1, b2 byte) int {\n\ti1 := bytes.IndexByte(s, b1)\n\tif i1 == 0 {\n\t\treturn 0\n\t}\n\tscope := s\n\tif i1 > 0 {\n\t\tscope = s[:i1]\n\t}\n\tif i2 := bytes.IndexByte(scope, b2); i2 >= 0 {\n\t\treturn i2\n\t}\n\treturn i1\n}\n\n// lastIndexByteTwo returns the index of the last occurrence of b1 or b2 in s,\n// or -1 if neither is present.\nfunc lastIndexByteTwo(s []byte, b1, b2 byte) int {\n\tfor i := len(s) - 1; i >= 0; i-- {\n\t\tif s[i] == b1 || s[i] == b2 {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n"
  },
  {
    "path": "src/algo/indexbyte2_test.go",
    "content": "package algo\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestIndexByteTwo(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\ts    string\n\t\tb1   byte\n\t\tb2   byte\n\t\twant int\n\t}{\n\t\t{\"empty\", \"\", 'a', 'b', -1},\n\t\t{\"single_b1\", \"a\", 'a', 'b', 0},\n\t\t{\"single_b2\", \"b\", 'a', 'b', 0},\n\t\t{\"single_none\", \"c\", 'a', 'b', -1},\n\t\t{\"b1_first\", \"xaxb\", 'a', 'b', 1},\n\t\t{\"b2_first\", \"xbxa\", 'a', 'b', 1},\n\t\t{\"same_byte\", \"xxa\", 'a', 'a', 2},\n\t\t{\"at_end\", \"xxxxa\", 'a', 'b', 4},\n\t\t{\"not_found\", \"xxxxxxxx\", 'a', 'b', -1},\n\t\t{\"long_b1_at_3000\", string(make([]byte, 3000)) + \"a\" + string(make([]byte, 1000)), 'a', 'b', 3000},\n\t\t{\"long_b2_at_3000\", string(make([]byte, 3000)) + \"b\" + string(make([]byte, 1000)), 'a', 'b', 3000},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := IndexByteTwo([]byte(tt.s), tt.b1, tt.b2)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"IndexByteTwo(%q, %c, %c) = %d, want %d\", tt.s[:min(len(tt.s), 40)], tt.b1, tt.b2, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Exhaustive test: compare against loop reference for various lengths,\n\t// including sizes around SIMD block boundaries (16, 32, 64).\n\tfor n := 0; n <= 256; n++ {\n\t\tdata := make([]byte, n)\n\t\tfor i := range data {\n\t\t\tdata[i] = byte('c' + (i % 20))\n\t\t}\n\t\t// Test with match at every position\n\t\tfor pos := 0; pos < n; pos++ {\n\t\t\tfor _, b := range []byte{'A', 'B'} {\n\t\t\t\tdata[pos] = b\n\t\t\t\tgot := IndexByteTwo(data, 'A', 'B')\n\t\t\t\twant := loopIndexByteTwo(data, 'A', 'B')\n\t\t\t\tif got != want {\n\t\t\t\t\tt.Fatalf(\"IndexByteTwo(len=%d, match=%c@%d) = %d, want %d\", n, b, pos, got, want)\n\t\t\t\t}\n\t\t\t\tdata[pos] = byte('c' + (pos % 20))\n\t\t\t}\n\t\t}\n\t\t// Test with no match\n\t\tgot := IndexByteTwo(data, 'A', 'B')\n\t\tif got != -1 {\n\t\t\tt.Fatalf(\"IndexByteTwo(len=%d, no match) = %d, want -1\", n, got)\n\t\t}\n\t\t// Test with both bytes present\n\t\tif n >= 2 {\n\t\t\tdata[n/3] = 'A'\n\t\t\tdata[n*2/3] = 'B'\n\t\t\tgot := IndexByteTwo(data, 'A', 'B')\n\t\t\twant := loopIndexByteTwo(data, 'A', 'B')\n\t\t\tif got != want {\n\t\t\t\tt.Fatalf(\"IndexByteTwo(len=%d, both@%d,%d) = %d, want %d\", n, n/3, n*2/3, got, want)\n\t\t\t}\n\t\t\tdata[n/3] = byte('c' + ((n / 3) % 20))\n\t\t\tdata[n*2/3] = byte('c' + ((n * 2 / 3) % 20))\n\t\t}\n\t}\n}\n\nfunc TestLastIndexByteTwo(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\ts    string\n\t\tb1   byte\n\t\tb2   byte\n\t\twant int\n\t}{\n\t\t{\"empty\", \"\", 'a', 'b', -1},\n\t\t{\"single_b1\", \"a\", 'a', 'b', 0},\n\t\t{\"single_b2\", \"b\", 'a', 'b', 0},\n\t\t{\"single_none\", \"c\", 'a', 'b', -1},\n\t\t{\"b1_last\", \"xbxa\", 'a', 'b', 3},\n\t\t{\"b2_last\", \"xaxb\", 'a', 'b', 3},\n\t\t{\"same_byte\", \"axx\", 'a', 'a', 0},\n\t\t{\"at_start\", \"axxxx\", 'a', 'b', 0},\n\t\t{\"both_present\", \"axbx\", 'a', 'b', 2},\n\t\t{\"not_found\", \"xxxxxxxx\", 'a', 'b', -1},\n\t\t{\"long_b1_at_3000\", string(make([]byte, 3000)) + \"a\" + string(make([]byte, 1000)), 'a', 'b', 3000},\n\t\t{\"long_b2_at_end\", string(make([]byte, 4000)) + \"b\", 'a', 'b', 4000},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := lastIndexByteTwo([]byte(tt.s), tt.b1, tt.b2)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"lastIndexByteTwo(%q, %c, %c) = %d, want %d\", tt.s[:min(len(tt.s), 40)], tt.b1, tt.b2, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Exhaustive test against loop reference\n\tfor n := 0; n <= 256; n++ {\n\t\tdata := make([]byte, n)\n\t\tfor i := range data {\n\t\t\tdata[i] = byte('c' + (i % 20))\n\t\t}\n\t\tfor pos := 0; pos < n; pos++ {\n\t\t\tfor _, b := range []byte{'A', 'B'} {\n\t\t\t\tdata[pos] = b\n\t\t\t\tgot := lastIndexByteTwo(data, 'A', 'B')\n\t\t\t\twant := refLastIndexByteTwo(data, 'A', 'B')\n\t\t\t\tif got != want {\n\t\t\t\t\tt.Fatalf(\"lastIndexByteTwo(len=%d, match=%c@%d) = %d, want %d\", n, b, pos, got, want)\n\t\t\t\t}\n\t\t\t\tdata[pos] = byte('c' + (pos % 20))\n\t\t\t}\n\t\t}\n\t\t// No match\n\t\tgot := lastIndexByteTwo(data, 'A', 'B')\n\t\tif got != -1 {\n\t\t\tt.Fatalf(\"lastIndexByteTwo(len=%d, no match) = %d, want -1\", n, got)\n\t\t}\n\t\t// Both bytes present\n\t\tif n >= 2 {\n\t\t\tdata[n/3] = 'A'\n\t\t\tdata[n*2/3] = 'B'\n\t\t\tgot := lastIndexByteTwo(data, 'A', 'B')\n\t\t\twant := refLastIndexByteTwo(data, 'A', 'B')\n\t\t\tif got != want {\n\t\t\t\tt.Fatalf(\"lastIndexByteTwo(len=%d, both@%d,%d) = %d, want %d\", n, n/3, n*2/3, got, want)\n\t\t\t}\n\t\t\tdata[n/3] = byte('c' + ((n / 3) % 20))\n\t\t\tdata[n*2/3] = byte('c' + ((n * 2 / 3) % 20))\n\t\t}\n\t}\n}\n\nfunc FuzzIndexByteTwo(f *testing.F) {\n\tf.Add([]byte(\"hello world\"), byte('o'), byte('l'))\n\tf.Add([]byte(\"\"), byte('a'), byte('b'))\n\tf.Add([]byte(\"aaa\"), byte('a'), byte('a'))\n\tf.Fuzz(func(t *testing.T, data []byte, b1, b2 byte) {\n\t\tgot := IndexByteTwo(data, b1, b2)\n\t\twant := loopIndexByteTwo(data, b1, b2)\n\t\tif got != want {\n\t\t\tt.Errorf(\"IndexByteTwo(len=%d, b1=%d, b2=%d) = %d, want %d\", len(data), b1, b2, got, want)\n\t\t}\n\t})\n}\n\nfunc FuzzLastIndexByteTwo(f *testing.F) {\n\tf.Add([]byte(\"hello world\"), byte('o'), byte('l'))\n\tf.Add([]byte(\"\"), byte('a'), byte('b'))\n\tf.Add([]byte(\"aaa\"), byte('a'), byte('a'))\n\tf.Fuzz(func(t *testing.T, data []byte, b1, b2 byte) {\n\t\tgot := lastIndexByteTwo(data, b1, b2)\n\t\twant := refLastIndexByteTwo(data, b1, b2)\n\t\tif got != want {\n\t\t\tt.Errorf(\"lastIndexByteTwo(len=%d, b1=%d, b2=%d) = %d, want %d\", len(data), b1, b2, got, want)\n\t\t}\n\t})\n}\n\n// Reference implementations for correctness checking\nfunc refIndexByteTwo(s []byte, b1, b2 byte) int {\n\ti1 := bytes.IndexByte(s, b1)\n\tif i1 == 0 {\n\t\treturn 0\n\t}\n\tscope := s\n\tif i1 > 0 {\n\t\tscope = s[:i1]\n\t}\n\tif i2 := bytes.IndexByte(scope, b2); i2 >= 0 {\n\t\treturn i2\n\t}\n\treturn i1\n}\n\nfunc loopIndexByteTwo(s []byte, b1, b2 byte) int {\n\tfor i, b := range s {\n\t\tif b == b1 || b == b2 {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc refLastIndexByteTwo(s []byte, b1, b2 byte) int {\n\tfor i := len(s) - 1; i >= 0; i-- {\n\t\tif s[i] == b1 || s[i] == b2 {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc benchIndexByteTwo(b *testing.B, size int, pos int) {\n\tdata := make([]byte, size)\n\tfor i := range data {\n\t\tdata[i] = byte('a' + (i % 20))\n\t}\n\tdata[pos] = 'Z'\n\n\ttype impl struct {\n\t\tname string\n\t\tfn   func([]byte, byte, byte) int\n\t}\n\timpls := []impl{\n\t\t{\"asm\", IndexByteTwo},\n\t\t{\"2xIndexByte\", refIndexByteTwo},\n\t\t{\"loop\", loopIndexByteTwo},\n\t}\n\tfor _, im := range impls {\n\t\tb.Run(im.name, func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tim.fn(data, 'Z', 'z')\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc benchLastIndexByteTwo(b *testing.B, size int, pos int) {\n\tdata := make([]byte, size)\n\tfor i := range data {\n\t\tdata[i] = byte('a' + (i % 20))\n\t}\n\tdata[pos] = 'Z'\n\n\ttype impl struct {\n\t\tname string\n\t\tfn   func([]byte, byte, byte) int\n\t}\n\timpls := []impl{\n\t\t{\"asm\", lastIndexByteTwo},\n\t\t{\"loop\", refLastIndexByteTwo},\n\t}\n\tfor _, im := range impls {\n\t\tb.Run(im.name, func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tim.fn(data, 'Z', 'z')\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkIndexByteTwo_10(b *testing.B)       { benchIndexByteTwo(b, 10, 8) }\nfunc BenchmarkIndexByteTwo_100(b *testing.B)      { benchIndexByteTwo(b, 100, 80) }\nfunc BenchmarkIndexByteTwo_1000(b *testing.B)     { benchIndexByteTwo(b, 1000, 800) }\nfunc BenchmarkLastIndexByteTwo_10(b *testing.B)   { benchLastIndexByteTwo(b, 10, 2) }\nfunc BenchmarkLastIndexByteTwo_100(b *testing.B)  { benchLastIndexByteTwo(b, 100, 20) }\nfunc BenchmarkLastIndexByteTwo_1000(b *testing.B) { benchLastIndexByteTwo(b, 1000, 200) }\n"
  },
  {
    "path": "src/algo/normalize.go",
    "content": "// Normalization of latin script letters\n// Reference: http://www.unicode.org/Public/UCD/latest/ucd/Index.txt\n\npackage algo\n\nvar normalized = map[rune]rune{\n\t0x00E1: 'a', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0103: 'a', //  WITH BREVE, LATIN SMALL LETTER\n\t0x01CE: 'a', //  WITH CARON, LATIN SMALL LETTER\n\t0x00E2: 'a', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x00E4: 'a', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x0227: 'a', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1EA1: 'a', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0201: 'a', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER\n\t0x00E0: 'a', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1EA3: 'a', //  WITH HOOK ABOVE, LATIN SMALL LETTER\n\t0x0203: 'a', //  WITH INVERTED BREVE, LATIN SMALL LETTER\n\t0x0101: 'a', //  WITH MACRON, LATIN SMALL LETTER\n\t0x0105: 'a', //  WITH OGONEK, LATIN SMALL LETTER\n\t0x1E9A: 'a', //  WITH RIGHT HALF RING, LATIN SMALL LETTER\n\t0x00E5: 'a', //  WITH RING ABOVE, LATIN SMALL LETTER\n\t0x1E01: 'a', //  WITH RING BELOW, LATIN SMALL LETTER\n\t0x00E3: 'a', //  WITH TILDE, LATIN SMALL LETTER\n\t0x0363: 'a', // , COMBINING LATIN SMALL LETTER\n\t0x0250: 'a', // , LATIN SMALL LETTER TURNED\n\t0x1E03: 'b', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E05: 'b', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0253: 'b', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E07: 'b', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x0180: 'b', //  WITH STROKE, LATIN SMALL LETTER\n\t0x0183: 'b', //  WITH TOPBAR, LATIN SMALL LETTER\n\t0x0107: 'c', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x010D: 'c', //  WITH CARON, LATIN SMALL LETTER\n\t0x00E7: 'c', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x0109: 'c', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x0255: 'c', //  WITH CURL, LATIN SMALL LETTER\n\t0x010B: 'c', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x0188: 'c', //  WITH HOOK, LATIN SMALL LETTER\n\t0x023C: 'c', //  WITH STROKE, LATIN SMALL LETTER\n\t0x0368: 'c', // , COMBINING LATIN SMALL LETTER\n\t0x0297: 'c', // , LATIN LETTER STRETCHED\n\t0x2184: 'c', // , LATIN SMALL LETTER REVERSED\n\t0x010F: 'd', //  WITH CARON, LATIN SMALL LETTER\n\t0x1E11: 'd', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E13: 'd', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER\n\t0x0221: 'd', //  WITH CURL, LATIN SMALL LETTER\n\t0x1E0B: 'd', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E0D: 'd', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0257: 'd', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E0F: 'd', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x0111: 'd', //  WITH STROKE, LATIN SMALL LETTER\n\t0x0256: 'd', //  WITH TAIL, LATIN SMALL LETTER\n\t0x018C: 'd', //  WITH TOPBAR, LATIN SMALL LETTER\n\t0x0369: 'd', // , COMBINING LATIN SMALL LETTER\n\t0x00E9: 'e', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0115: 'e', //  WITH BREVE, LATIN SMALL LETTER\n\t0x011B: 'e', //  WITH CARON, LATIN SMALL LETTER\n\t0x0229: 'e', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E19: 'e', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER\n\t0x00EA: 'e', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x00EB: 'e', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x0117: 'e', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1EB9: 'e', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0205: 'e', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER\n\t0x00E8: 'e', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1EBB: 'e', //  WITH HOOK ABOVE, LATIN SMALL LETTER\n\t0x025D: 'e', //  WITH HOOK, LATIN SMALL LETTER REVERSED OPEN\n\t0x0207: 'e', //  WITH INVERTED BREVE, LATIN SMALL LETTER\n\t0x0113: 'e', //  WITH MACRON, LATIN SMALL LETTER\n\t0x0119: 'e', //  WITH OGONEK, LATIN SMALL LETTER\n\t0x0247: 'e', //  WITH STROKE, LATIN SMALL LETTER\n\t0x1E1B: 'e', //  WITH TILDE BELOW, LATIN SMALL LETTER\n\t0x1EBD: 'e', //  WITH TILDE, LATIN SMALL LETTER\n\t0x0364: 'e', // , COMBINING LATIN SMALL LETTER\n\t0x029A: 'e', // , LATIN SMALL LETTER CLOSED OPEN\n\t0x025E: 'e', // , LATIN SMALL LETTER CLOSED REVERSED OPEN\n\t0x025B: 'e', // , LATIN SMALL LETTER OPEN\n\t0x0258: 'e', // , LATIN SMALL LETTER REVERSED\n\t0x025C: 'e', // , LATIN SMALL LETTER REVERSED OPEN\n\t0x01DD: 'e', // , LATIN SMALL LETTER TURNED\n\t0x1D08: 'e', // , LATIN SMALL LETTER TURNED OPEN\n\t0x1E1F: 'f', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x0192: 'f', //  WITH HOOK, LATIN SMALL LETTER\n\t0x01F5: 'g', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x011F: 'g', //  WITH BREVE, LATIN SMALL LETTER\n\t0x01E7: 'g', //  WITH CARON, LATIN SMALL LETTER\n\t0x0123: 'g', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x011D: 'g', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x0121: 'g', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x0260: 'g', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E21: 'g', //  WITH MACRON, LATIN SMALL LETTER\n\t0x01E5: 'g', //  WITH STROKE, LATIN SMALL LETTER\n\t0x0261: 'g', // , LATIN SMALL LETTER SCRIPT\n\t0x1E2B: 'h', //  WITH BREVE BELOW, LATIN SMALL LETTER\n\t0x021F: 'h', //  WITH CARON, LATIN SMALL LETTER\n\t0x1E29: 'h', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x0125: 'h', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x1E27: 'h', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1E23: 'h', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E25: 'h', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x02AE: 'h', //  WITH FISHHOOK, LATIN SMALL LETTER TURNED\n\t0x0266: 'h', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E96: 'h', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x0127: 'h', //  WITH STROKE, LATIN SMALL LETTER\n\t0x036A: 'h', // , COMBINING LATIN SMALL LETTER\n\t0x0265: 'h', // , LATIN SMALL LETTER TURNED\n\t0x2095: 'h', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x00ED: 'i', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x012D: 'i', //  WITH BREVE, LATIN SMALL LETTER\n\t0x01D0: 'i', //  WITH CARON, LATIN SMALL LETTER\n\t0x00EE: 'i', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x00EF: 'i', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1ECB: 'i', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0209: 'i', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER\n\t0x00EC: 'i', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1EC9: 'i', //  WITH HOOK ABOVE, LATIN SMALL LETTER\n\t0x020B: 'i', //  WITH INVERTED BREVE, LATIN SMALL LETTER\n\t0x012B: 'i', //  WITH MACRON, LATIN SMALL LETTER\n\t0x012F: 'i', //  WITH OGONEK, LATIN SMALL LETTER\n\t0x0268: 'i', //  WITH STROKE, LATIN SMALL LETTER\n\t0x1E2D: 'i', //  WITH TILDE BELOW, LATIN SMALL LETTER\n\t0x0129: 'i', //  WITH TILDE, LATIN SMALL LETTER\n\t0x0365: 'i', // , COMBINING LATIN SMALL LETTER\n\t0x0131: 'i', // , LATIN SMALL LETTER DOTLESS\n\t0x1D09: 'i', // , LATIN SMALL LETTER TURNED\n\t0x1D62: 'i', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x2071: 'i', // , SUPERSCRIPT LATIN SMALL LETTER\n\t0x01F0: 'j', //  WITH CARON, LATIN SMALL LETTER\n\t0x0135: 'j', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x029D: 'j', //  WITH CROSSED-TAIL, LATIN SMALL LETTER\n\t0x0249: 'j', //  WITH STROKE, LATIN SMALL LETTER\n\t0x025F: 'j', //  WITH STROKE, LATIN SMALL LETTER DOTLESS\n\t0x0237: 'j', // , LATIN SMALL LETTER DOTLESS\n\t0x1E31: 'k', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x01E9: 'k', //  WITH CARON, LATIN SMALL LETTER\n\t0x0137: 'k', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E33: 'k', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0199: 'k', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E35: 'k', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x029E: 'k', // , LATIN SMALL LETTER TURNED\n\t0x2096: 'k', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x013A: 'l', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x019A: 'l', //  WITH BAR, LATIN SMALL LETTER\n\t0x026C: 'l', //  WITH BELT, LATIN SMALL LETTER\n\t0x013E: 'l', //  WITH CARON, LATIN SMALL LETTER\n\t0x013C: 'l', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E3D: 'l', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER\n\t0x0234: 'l', //  WITH CURL, LATIN SMALL LETTER\n\t0x1E37: 'l', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x1E3B: 'l', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x0140: 'l', //  WITH MIDDLE DOT, LATIN SMALL LETTER\n\t0x026B: 'l', //  WITH MIDDLE TILDE, LATIN SMALL LETTER\n\t0x026D: 'l', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER\n\t0x0142: 'l', //  WITH STROKE, LATIN SMALL LETTER\n\t0x2097: 'l', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x1E3F: 'm', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x1E41: 'm', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E43: 'm', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0271: 'm', //  WITH HOOK, LATIN SMALL LETTER\n\t0x0270: 'm', //  WITH LONG LEG, LATIN SMALL LETTER TURNED\n\t0x036B: 'm', // , COMBINING LATIN SMALL LETTER\n\t0x1D1F: 'm', // , LATIN SMALL LETTER SIDEWAYS TURNED\n\t0x026F: 'm', // , LATIN SMALL LETTER TURNED\n\t0x2098: 'm', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x0144: 'n', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0148: 'n', //  WITH CARON, LATIN SMALL LETTER\n\t0x0146: 'n', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E4B: 'n', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER\n\t0x0235: 'n', //  WITH CURL, LATIN SMALL LETTER\n\t0x1E45: 'n', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E47: 'n', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x01F9: 'n', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x0272: 'n', //  WITH LEFT HOOK, LATIN SMALL LETTER\n\t0x1E49: 'n', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x019E: 'n', //  WITH LONG RIGHT LEG, LATIN SMALL LETTER\n\t0x0273: 'n', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER\n\t0x00F1: 'n', //  WITH TILDE, LATIN SMALL LETTER\n\t0x2099: 'n', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x00F3: 'o', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x014F: 'o', //  WITH BREVE, LATIN SMALL LETTER\n\t0x01D2: 'o', //  WITH CARON, LATIN SMALL LETTER\n\t0x00F4: 'o', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x00F6: 'o', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x022F: 'o', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1ECD: 'o', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0151: 'o', //  WITH DOUBLE ACUTE, LATIN SMALL LETTER\n\t0x020D: 'o', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER\n\t0x00F2: 'o', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1ECF: 'o', //  WITH HOOK ABOVE, LATIN SMALL LETTER\n\t0x01A1: 'o', //  WITH HORN, LATIN SMALL LETTER\n\t0x020F: 'o', //  WITH INVERTED BREVE, LATIN SMALL LETTER\n\t0x014D: 'o', //  WITH MACRON, LATIN SMALL LETTER\n\t0x01EB: 'o', //  WITH OGONEK, LATIN SMALL LETTER\n\t0x00F8: 'o', //  WITH STROKE, LATIN SMALL LETTER\n\t0x1D13: 'o', //  WITH STROKE, LATIN SMALL LETTER SIDEWAYS\n\t0x00F5: 'o', //  WITH TILDE, LATIN SMALL LETTER\n\t0x0366: 'o', // , COMBINING LATIN SMALL LETTER\n\t0x0275: 'o', // , LATIN SMALL LETTER BARRED\n\t0x1D17: 'o', // , LATIN SMALL LETTER BOTTOM HALF\n\t0x0254: 'o', // , LATIN SMALL LETTER OPEN\n\t0x1D11: 'o', // , LATIN SMALL LETTER SIDEWAYS\n\t0x1D12: 'o', // , LATIN SMALL LETTER SIDEWAYS OPEN\n\t0x1D16: 'o', // , LATIN SMALL LETTER TOP HALF\n\t0x1E55: 'p', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x1E57: 'p', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x01A5: 'p', //  WITH HOOK, LATIN SMALL LETTER\n\t0x209A: 'p', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x024B: 'q', //  WITH HOOK TAIL, LATIN SMALL LETTER\n\t0x02A0: 'q', //  WITH HOOK, LATIN SMALL LETTER\n\t0x0155: 'r', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0159: 'r', //  WITH CARON, LATIN SMALL LETTER\n\t0x0157: 'r', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E59: 'r', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E5B: 'r', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0211: 'r', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER\n\t0x027E: 'r', //  WITH FISHHOOK, LATIN SMALL LETTER\n\t0x027F: 'r', //  WITH FISHHOOK, LATIN SMALL LETTER REVERSED\n\t0x027B: 'r', //  WITH HOOK, LATIN SMALL LETTER TURNED\n\t0x0213: 'r', //  WITH INVERTED BREVE, LATIN SMALL LETTER\n\t0x1E5F: 'r', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x027C: 'r', //  WITH LONG LEG, LATIN SMALL LETTER\n\t0x027A: 'r', //  WITH LONG LEG, LATIN SMALL LETTER TURNED\n\t0x024D: 'r', //  WITH STROKE, LATIN SMALL LETTER\n\t0x027D: 'r', //  WITH TAIL, LATIN SMALL LETTER\n\t0x036C: 'r', // , COMBINING LATIN SMALL LETTER\n\t0x0279: 'r', // , LATIN SMALL LETTER TURNED\n\t0x1D63: 'r', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x015B: 's', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0161: 's', //  WITH CARON, LATIN SMALL LETTER\n\t0x015F: 's', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x015D: 's', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x0219: 's', //  WITH COMMA BELOW, LATIN SMALL LETTER\n\t0x1E61: 's', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E9B: 's', //  WITH DOT ABOVE, LATIN SMALL LETTER LONG\n\t0x1E63: 's', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0282: 's', //  WITH HOOK, LATIN SMALL LETTER\n\t0x023F: 's', //  WITH SWASH TAIL, LATIN SMALL LETTER\n\t0x017F: 's', // , LATIN SMALL LETTER LONG\n\t0x00DF: 's', // , LATIN SMALL LETTER SHARP\n\t0x209B: 's', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x0165: 't', //  WITH CARON, LATIN SMALL LETTER\n\t0x0163: 't', //  WITH CEDILLA, LATIN SMALL LETTER\n\t0x1E71: 't', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER\n\t0x021B: 't', //  WITH COMMA BELOW, LATIN SMALL LETTER\n\t0x0236: 't', //  WITH CURL, LATIN SMALL LETTER\n\t0x1E97: 't', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1E6B: 't', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E6D: 't', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x01AD: 't', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E6F: 't', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x01AB: 't', //  WITH PALATAL HOOK, LATIN SMALL LETTER\n\t0x0288: 't', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER\n\t0x0167: 't', //  WITH STROKE, LATIN SMALL LETTER\n\t0x036D: 't', // , COMBINING LATIN SMALL LETTER\n\t0x0287: 't', // , LATIN SMALL LETTER TURNED\n\t0x209C: 't', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x0289: 'u', //  BAR, LATIN SMALL LETTER\n\t0x00FA: 'u', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x016D: 'u', //  WITH BREVE, LATIN SMALL LETTER\n\t0x01D4: 'u', //  WITH CARON, LATIN SMALL LETTER\n\t0x1E77: 'u', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER\n\t0x00FB: 'u', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x1E73: 'u', //  WITH DIAERESIS BELOW, LATIN SMALL LETTER\n\t0x00FC: 'u', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1EE5: 'u', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0171: 'u', //  WITH DOUBLE ACUTE, LATIN SMALL LETTER\n\t0x0215: 'u', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER\n\t0x00F9: 'u', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1EE7: 'u', //  WITH HOOK ABOVE, LATIN SMALL LETTER\n\t0x01B0: 'u', //  WITH HORN, LATIN SMALL LETTER\n\t0x0217: 'u', //  WITH INVERTED BREVE, LATIN SMALL LETTER\n\t0x016B: 'u', //  WITH MACRON, LATIN SMALL LETTER\n\t0x0173: 'u', //  WITH OGONEK, LATIN SMALL LETTER\n\t0x016F: 'u', //  WITH RING ABOVE, LATIN SMALL LETTER\n\t0x1E75: 'u', //  WITH TILDE BELOW, LATIN SMALL LETTER\n\t0x0169: 'u', //  WITH TILDE, LATIN SMALL LETTER\n\t0x0367: 'u', // , COMBINING LATIN SMALL LETTER\n\t0x1D1D: 'u', // , LATIN SMALL LETTER SIDEWAYS\n\t0x1D1E: 'u', // , LATIN SMALL LETTER SIDEWAYS DIAERESIZED\n\t0x1D64: 'u', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x1E7F: 'v', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x028B: 'v', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E7D: 'v', //  WITH TILDE, LATIN SMALL LETTER\n\t0x036E: 'v', // , COMBINING LATIN SMALL LETTER\n\t0x028C: 'v', // , LATIN SMALL LETTER TURNED\n\t0x1D65: 'v', // , LATIN SUBSCRIPT SMALL LETTER\n\t0x1E83: 'w', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0175: 'w', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x1E85: 'w', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1E87: 'w', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E89: 'w', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x1E81: 'w', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1E98: 'w', //  WITH RING ABOVE, LATIN SMALL LETTER\n\t0x028D: 'w', // , LATIN SMALL LETTER TURNED\n\t0x1E8D: 'x', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1E8B: 'x', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x036F: 'x', // , COMBINING LATIN SMALL LETTER\n\t0x00FD: 'y', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x0177: 'y', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x00FF: 'y', //  WITH DIAERESIS, LATIN SMALL LETTER\n\t0x1E8F: 'y', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1EF5: 'y', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x1EF3: 'y', //  WITH GRAVE, LATIN SMALL LETTER\n\t0x1EF7: 'y', //  WITH HOOK ABOVE, LATIN SMALL LETTER\n\t0x01B4: 'y', //  WITH HOOK, LATIN SMALL LETTER\n\t0x0233: 'y', //  WITH MACRON, LATIN SMALL LETTER\n\t0x1E99: 'y', //  WITH RING ABOVE, LATIN SMALL LETTER\n\t0x024F: 'y', //  WITH STROKE, LATIN SMALL LETTER\n\t0x1EF9: 'y', //  WITH TILDE, LATIN SMALL LETTER\n\t0x028E: 'y', // , LATIN SMALL LETTER TURNED\n\t0x017A: 'z', //  WITH ACUTE, LATIN SMALL LETTER\n\t0x017E: 'z', //  WITH CARON, LATIN SMALL LETTER\n\t0x1E91: 'z', //  WITH CIRCUMFLEX, LATIN SMALL LETTER\n\t0x0291: 'z', //  WITH CURL, LATIN SMALL LETTER\n\t0x017C: 'z', //  WITH DOT ABOVE, LATIN SMALL LETTER\n\t0x1E93: 'z', //  WITH DOT BELOW, LATIN SMALL LETTER\n\t0x0225: 'z', //  WITH HOOK, LATIN SMALL LETTER\n\t0x1E95: 'z', //  WITH LINE BELOW, LATIN SMALL LETTER\n\t0x0290: 'z', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER\n\t0x01B6: 'z', //  WITH STROKE, LATIN SMALL LETTER\n\t0x0240: 'z', //  WITH SWASH TAIL, LATIN SMALL LETTER\n\t0x0251: 'a', // , latin small letter script\n\t0x00C1: 'A', //  WITH ACUTE, LATIN CAPITAL LETTER\n\t0x00C2: 'A', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER\n\t0x00C4: 'A', //  WITH DIAERESIS, LATIN CAPITAL LETTER\n\t0x00C0: 'A', //  WITH GRAVE, LATIN CAPITAL LETTER\n\t0x00C5: 'A', //  WITH RING ABOVE, LATIN CAPITAL LETTER\n\t0x023A: 'A', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x00C3: 'A', //  WITH TILDE, LATIN CAPITAL LETTER\n\t0x1D00: 'A', // , LATIN LETTER SMALL CAPITAL\n\t0x0181: 'B', //  WITH HOOK, LATIN CAPITAL LETTER\n\t0x0243: 'B', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x0299: 'B', // , LATIN LETTER SMALL CAPITAL\n\t0x1D03: 'B', // , LATIN LETTER SMALL CAPITAL BARRED\n\t0x00C7: 'C', //  WITH CEDILLA, LATIN CAPITAL LETTER\n\t0x023B: 'C', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x1D04: 'C', // , LATIN LETTER SMALL CAPITAL\n\t0x018A: 'D', //  WITH HOOK, LATIN CAPITAL LETTER\n\t0x0189: 'D', // , LATIN CAPITAL LETTER AFRICAN\n\t0x1D05: 'D', // , LATIN LETTER SMALL CAPITAL\n\t0x00C9: 'E', //  WITH ACUTE, LATIN CAPITAL LETTER\n\t0x00CA: 'E', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER\n\t0x00CB: 'E', //  WITH DIAERESIS, LATIN CAPITAL LETTER\n\t0x00C8: 'E', //  WITH GRAVE, LATIN CAPITAL LETTER\n\t0x0246: 'E', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x0190: 'E', // , LATIN CAPITAL LETTER OPEN\n\t0x018E: 'E', // , LATIN CAPITAL LETTER REVERSED\n\t0x1D07: 'E', // , LATIN LETTER SMALL CAPITAL\n\t0x0193: 'G', //  WITH HOOK, LATIN CAPITAL LETTER\n\t0x029B: 'G', //  WITH HOOK, LATIN LETTER SMALL CAPITAL\n\t0x0262: 'G', // , LATIN LETTER SMALL CAPITAL\n\t0x029C: 'H', // , LATIN LETTER SMALL CAPITAL\n\t0x00CD: 'I', //  WITH ACUTE, LATIN CAPITAL LETTER\n\t0x00CE: 'I', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER\n\t0x00CF: 'I', //  WITH DIAERESIS, LATIN CAPITAL LETTER\n\t0x0130: 'I', //  WITH DOT ABOVE, LATIN CAPITAL LETTER\n\t0x00CC: 'I', //  WITH GRAVE, LATIN CAPITAL LETTER\n\t0x0197: 'I', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x026A: 'I', // , LATIN LETTER SMALL CAPITAL\n\t0x0248: 'J', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x1D0A: 'J', // , LATIN LETTER SMALL CAPITAL\n\t0x1D0B: 'K', // , LATIN LETTER SMALL CAPITAL\n\t0x023D: 'L', //  WITH BAR, LATIN CAPITAL LETTER\n\t0x1D0C: 'L', //  WITH STROKE, LATIN LETTER SMALL CAPITAL\n\t0x029F: 'L', // , LATIN LETTER SMALL CAPITAL\n\t0x019C: 'M', // , LATIN CAPITAL LETTER TURNED\n\t0x1D0D: 'M', // , LATIN LETTER SMALL CAPITAL\n\t0x019D: 'N', //  WITH LEFT HOOK, LATIN CAPITAL LETTER\n\t0x0220: 'N', //  WITH LONG RIGHT LEG, LATIN CAPITAL LETTER\n\t0x00D1: 'N', //  WITH TILDE, LATIN CAPITAL LETTER\n\t0x0274: 'N', // , LATIN LETTER SMALL CAPITAL\n\t0x1D0E: 'N', // , LATIN LETTER SMALL CAPITAL REVERSED\n\t0x00D3: 'O', //  WITH ACUTE, LATIN CAPITAL LETTER\n\t0x00D4: 'O', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER\n\t0x00D6: 'O', //  WITH DIAERESIS, LATIN CAPITAL LETTER\n\t0x00D2: 'O', //  WITH GRAVE, LATIN CAPITAL LETTER\n\t0x019F: 'O', //  WITH MIDDLE TILDE, LATIN CAPITAL LETTER\n\t0x00D8: 'O', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x00D5: 'O', //  WITH TILDE, LATIN CAPITAL LETTER\n\t0x0186: 'O', // , LATIN CAPITAL LETTER OPEN\n\t0x1D0F: 'O', // , LATIN LETTER SMALL CAPITAL\n\t0x1D10: 'O', // , LATIN LETTER SMALL CAPITAL OPEN\n\t0x1D18: 'P', // , LATIN LETTER SMALL CAPITAL\n\t0x024A: 'Q', //  WITH HOOK TAIL, LATIN CAPITAL LETTER SMALL\n\t0x024C: 'R', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x0280: 'R', // , LATIN LETTER SMALL CAPITAL\n\t0x0281: 'R', // , LATIN LETTER SMALL CAPITAL INVERTED\n\t0x1D19: 'R', // , LATIN LETTER SMALL CAPITAL REVERSED\n\t0x1D1A: 'R', // , LATIN LETTER SMALL CAPITAL TURNED\n\t0x023E: 'T', //  WITH DIAGONAL STROKE, LATIN CAPITAL LETTER\n\t0x01AE: 'T', //  WITH RETROFLEX HOOK, LATIN CAPITAL LETTER\n\t0x1D1B: 'T', // , LATIN LETTER SMALL CAPITAL\n\t0x0244: 'U', //  BAR, LATIN CAPITAL LETTER\n\t0x00DA: 'U', //  WITH ACUTE, LATIN CAPITAL LETTER\n\t0x00DB: 'U', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER\n\t0x00DC: 'U', //  WITH DIAERESIS, LATIN CAPITAL LETTER\n\t0x00D9: 'U', //  WITH GRAVE, LATIN CAPITAL LETTER\n\t0x1D1C: 'U', // , LATIN LETTER SMALL CAPITAL\n\t0x01B2: 'V', //  WITH HOOK, LATIN CAPITAL LETTER\n\t0x0245: 'V', // , LATIN CAPITAL LETTER TURNED\n\t0x1D20: 'V', // , LATIN LETTER SMALL CAPITAL\n\t0x1D21: 'W', // , LATIN LETTER SMALL CAPITAL\n\t0x00DD: 'Y', //  WITH ACUTE, LATIN CAPITAL LETTER\n\t0x0178: 'Y', //  WITH DIAERESIS, LATIN CAPITAL LETTER\n\t0x024E: 'Y', //  WITH STROKE, LATIN CAPITAL LETTER\n\t0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL\n\t0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL\n\n\t'Ắ': 'A',\n\t'Ấ': 'A',\n\t'Ằ': 'A',\n\t'Ầ': 'A',\n\t'Ẳ': 'A',\n\t'Ẩ': 'A',\n\t'Ẵ': 'A',\n\t'Ẫ': 'A',\n\t'Ặ': 'A',\n\t'Ậ': 'A',\n\n\t'ắ': 'a',\n\t'ấ': 'a',\n\t'ằ': 'a',\n\t'ầ': 'a',\n\t'ẳ': 'a',\n\t'ẩ': 'a',\n\t'ẵ': 'a',\n\t'ẫ': 'a',\n\t'ặ': 'a',\n\t'ậ': 'a',\n\n\t'Ế': 'E',\n\t'Ề': 'E',\n\t'Ể': 'E',\n\t'Ễ': 'E',\n\t'Ệ': 'E',\n\n\t'ế': 'e',\n\t'ề': 'e',\n\t'ể': 'e',\n\t'ễ': 'e',\n\t'ệ': 'e',\n\n\t'Ố': 'O',\n\t'Ớ': 'O',\n\t'Ồ': 'O',\n\t'Ờ': 'O',\n\t'Ổ': 'O',\n\t'Ở': 'O',\n\t'Ỗ': 'O',\n\t'Ỡ': 'O',\n\t'Ộ': 'O',\n\t'Ợ': 'O',\n\n\t'ố': 'o',\n\t'ớ': 'o',\n\t'ồ': 'o',\n\t'ờ': 'o',\n\t'ổ': 'o',\n\t'ở': 'o',\n\t'ỗ': 'o',\n\t'ỡ': 'o',\n\t'ộ': 'o',\n\t'ợ': 'o',\n\n\t'Ứ': 'U',\n\t'Ừ': 'U',\n\t'Ử': 'U',\n\t'Ữ': 'U',\n\t'Ự': 'U',\n\n\t'ứ': 'u',\n\t'ừ': 'u',\n\t'ử': 'u',\n\t'ữ': 'u',\n\t'ự': 'u',\n\n\t// https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)\n\t0xFF01: '!',  // Fullwidth exclamation\n\t0xFF02: '\"',  // Fullwidth quotation mark\n\t0xFF03: '#',  // Fullwidth number sign\n\t0xFF04: '$',  // Fullwidth dollar sign\n\t0xFF05: '%',  // Fullwidth percent\n\t0xFF06: '&',  // Fullwidth ampersand\n\t0xFF07: '\\'', // Fullwidth apostrophe\n\t0xFF08: '(',  // Fullwidth left parenthesis\n\t0xFF09: ')',  // Fullwidth right parenthesis\n\t0xFF0A: '*',  // Fullwidth asterisk\n\t0xFF0B: '+',  // Fullwidth plus\n\t0xFF0C: ',',  // Fullwidth comma\n\t0xFF0D: '-',  // Fullwidth hyphen-minus\n\t0xFF0E: '.',  // Fullwidth period\n\t0xFF0F: '/',  // Fullwidth slash\n\t0xFF10: '0',\n\t0xFF11: '1',\n\t0xFF12: '2',\n\t0xFF13: '3',\n\t0xFF14: '4',\n\t0xFF15: '5',\n\t0xFF16: '6',\n\t0xFF17: '7',\n\t0xFF18: '8',\n\t0xFF19: '9',\n\t0xFF1A: ':', // Fullwidth colon\n\t0xFF1B: ';', // Fullwidth semicolon\n\t0xFF1C: '<', // Fullwidth less-than\n\t0xFF1D: '=', // Fullwidth equal\n\t0xFF1E: '>', // Fullwidth greater-than\n\t0xFF1F: '?', // Fullwidth question mark\n\t0xFF20: '@', // Fullwidth at sign\n\t0xFF21: 'A',\n\t0xFF22: 'B',\n\t0xFF23: 'C',\n\t0xFF24: 'D',\n\t0xFF25: 'E',\n\t0xFF26: 'F',\n\t0xFF27: 'G',\n\t0xFF28: 'H',\n\t0xFF29: 'I',\n\t0xFF2A: 'J',\n\t0xFF2B: 'K',\n\t0xFF2C: 'L',\n\t0xFF2D: 'M',\n\t0xFF2E: 'N',\n\t0xFF2F: 'O',\n\t0xFF30: 'P',\n\t0xFF31: 'Q',\n\t0xFF32: 'R',\n\t0xFF33: 'S',\n\t0xFF34: 'T',\n\t0xFF35: 'U',\n\t0xFF36: 'V',\n\t0xFF37: 'W',\n\t0xFF38: 'X',\n\t0xFF39: 'Y',\n\t0xFF3A: 'Z',\n\t0xFF3B: '[',  // Fullwidth left bracket\n\t0xFF3C: '\\\\', // Fullwidth backslash\n\t0xFF3D: ']',  // Fullwidth right bracket\n\t0xFF3E: '^',  // Fullwidth circumflex\n\t0xFF3F: '_',  // Fullwidth underscore\n\t0xFF40: '`',  // Fullwidth grave accent\n\t0xFF41: 'a',\n\t0xFF42: 'b',\n\t0xFF43: 'c',\n\t0xFF44: 'd',\n\t0xFF45: 'e',\n\t0xFF46: 'f',\n\t0xFF47: 'g',\n\t0xFF48: 'h',\n\t0xFF49: 'i',\n\t0xFF4A: 'j',\n\t0xFF4B: 'k',\n\t0xFF4C: 'l',\n\t0xFF4D: 'm',\n\t0xFF4E: 'n',\n\t0xFF4F: 'o',\n\t0xFF50: 'p',\n\t0xFF51: 'q',\n\t0xFF52: 'r',\n\t0xFF53: 's',\n\t0xFF54: 't',\n\t0xFF55: 'u',\n\t0xFF56: 'v',\n\t0xFF57: 'w',\n\t0xFF58: 'x',\n\t0xFF59: 'y',\n\t0xFF5A: 'z',\n\t0xFF5B: '{', // Fullwidth left brace\n\t0xFF5C: '|', // Fullwidth vertical bar\n\t0xFF5D: '}', // Fullwidth right brace\n\t0xFF5E: '~', // Fullwidth tilde\n\t0xFF61: '.', // Halfwidth ideographic full stop\n}\n\n// NormalizeRunes normalizes latin script letters\nfunc NormalizeRunes(runes []rune) []rune {\n\tret := make([]rune, len(runes))\n\tcopy(ret, runes)\n\tfor idx, r := range runes {\n\t\tif r < 0x00C0 || r > 0xFF61 {\n\t\t\tcontinue\n\t\t}\n\t\tn := normalized[r]\n\t\tif n > 0 {\n\t\t\tret[idx] = normalized[r]\n\t\t}\n\t}\n\treturn ret\n}\n"
  },
  {
    "path": "src/ansi.go",
    "content": "package fzf\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/junegunn/fzf/src/algo\"\n\t\"github.com/junegunn/fzf/src/tui\"\n)\n\ntype ansiOffset struct {\n\toffset [2]int32\n\tcolor  ansiState\n}\n\ntype url struct {\n\turi    string\n\tparams string\n}\n\ntype ansiState struct {\n\tfg   tui.Color\n\tbg   tui.Color\n\tul   tui.Color\n\tattr tui.Attr\n\tlbg  tui.Color\n\turl  *url\n}\n\nfunc (s *ansiState) colored() bool {\n\treturn s.fg != -1 || s.bg != -1 || s.ul != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil\n}\n\nfunc (s *ansiState) equals(t *ansiState) bool {\n\tif t == nil {\n\t\treturn !s.colored()\n\t}\n\treturn s.fg == t.fg && s.bg == t.bg && s.ul == t.ul && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url\n}\n\nfunc (s *ansiState) ToString() string {\n\tif !s.colored() {\n\t\treturn \"\"\n\t}\n\n\tret := \"\"\n\tif s.attr&tui.Bold > 0 || s.attr&tui.BoldForce > 0 {\n\t\tret += \"1;\"\n\t}\n\tif s.attr&tui.Dim > 0 {\n\t\tret += \"2;\"\n\t}\n\tif s.attr&tui.Italic > 0 {\n\t\tret += \"3;\"\n\t}\n\tif s.attr&tui.Underline > 0 {\n\t\tswitch s.attr.UnderlineStyle() {\n\t\tcase tui.UlStyleDouble:\n\t\t\tret += \"4:2;\"\n\t\tcase tui.UlStyleCurly:\n\t\t\tret += \"4:3;\"\n\t\tcase tui.UlStyleDotted:\n\t\t\tret += \"4:4;\"\n\t\tcase tui.UlStyleDashed:\n\t\t\tret += \"4:5;\"\n\t\tdefault:\n\t\t\tret += \"4;\"\n\t\t}\n\t}\n\tif s.attr&tui.Blink > 0 {\n\t\tret += \"5;\"\n\t}\n\tif s.attr&tui.Reverse > 0 {\n\t\tret += \"7;\"\n\t}\n\tif s.attr&tui.StrikeThrough > 0 {\n\t\tret += \"9;\"\n\t}\n\tret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)\n\tif s.ul != -1 {\n\t\tret += toAnsiStringUl(s.ul)\n\t}\n\n\tret = \"\\x1b[\" + strings.TrimSuffix(ret, \";\") + \"m\"\n\tif s.url != nil {\n\t\tret = fmt.Sprintf(\"\\x1b]8;%s;%s\\x1b\\\\%s\\x1b]8;;\\x1b\", s.url.params, s.url.uri, ret)\n\t}\n\treturn ret\n}\n\nfunc toAnsiStringUl(color tui.Color) string {\n\tcol := int(color)\n\tif col < 0 {\n\t\treturn \"\"\n\t}\n\tif col >= (1 << 24) {\n\t\tr := strconv.Itoa((col >> 16) & 0xff)\n\t\tg := strconv.Itoa((col >> 8) & 0xff)\n\t\tb := strconv.Itoa(col & 0xff)\n\t\treturn \"58;2;\" + r + \";\" + g + \";\" + b + \";\"\n\t}\n\treturn \"58;5;\" + strconv.Itoa(col) + \";\"\n}\n\nfunc toAnsiString(color tui.Color, offset int) string {\n\tcol := int(color)\n\tret := \"\"\n\tif col == -1 {\n\t\tret += strconv.Itoa(offset + 9)\n\t} else if col < 8 {\n\t\tret += strconv.Itoa(offset + col)\n\t} else if col < 16 {\n\t\tret += strconv.Itoa(offset - 30 + 90 + col - 8)\n\t} else if col < 256 {\n\t\tret += strconv.Itoa(offset+8) + \";5;\" + strconv.Itoa(col)\n\t} else if col >= (1 << 24) {\n\t\tr := strconv.Itoa((col >> 16) & 0xff)\n\t\tg := strconv.Itoa((col >> 8) & 0xff)\n\t\tb := strconv.Itoa(col & 0xff)\n\t\tret += strconv.Itoa(offset+8) + \";2;\" + r + \";\" + g + \";\" + b\n\t}\n\treturn ret + \";\"\n}\n\nfunc matchOperatingSystemCommand(s string, start int) int {\n\t// `\\x1b][0-9][;:][[:print:]]+(?:\\x1b\\\\\\\\|\\x07)`\n\t//                 ^ match starting here after the first printable character\n\t//\n\ti := start // prefix matched in nextAnsiEscapeSequence()\n\n\t// Find the terminator: BEL (\\x07) or ESC (\\x1b) for ST (\\x1b\\\\)\n\tidx := algo.IndexByteTwo(stringBytes(s[i:]), '\\x07', '\\x1b')\n\tif idx < 0 {\n\t\treturn -1\n\t}\n\ti += idx\n\n\tif s[i] == '\\x07' {\n\t\treturn i + 1\n\t}\n\t// `\\x1b]8;PARAMS;URI\\x1b\\\\TITLE\\x1b]8;;\\x1b`\n\t//                   ------\n\tif i < len(s)-1 && s[i+1] == '\\\\' {\n\t\treturn i + 2\n\t}\n\n\t// `\\x1b]8;PARAMS;URI\\x1b\\\\TITLE\\x1b]8;;\\x1b`\n\t//                              ------------\n\tif s[:i+1] == \"\\x1b]8;;\\x1b\" {\n\t\treturn i + 1\n\t}\n\n\treturn -1\n}\n\nfunc matchControlSequence(s string) int {\n\t// `\\x1b[\\\\[()][0-9;:?]*[a-zA-Z@]`\n\t//                     ^ match starting here\n\t//\n\ti := 2 // prefix matched in nextAnsiEscapeSequence()\n\tfor ; i < len(s); i++ {\n\t\tc := s[i]\n\t\tswitch c {\n\t\tcase '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', ':', '?':\n\t\t\t// ok\n\t\tdefault:\n\t\t\tif 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {\n\t\t\t\treturn i + 1\n\t\t\t}\n\t\t\treturn -1\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc isCtrlSeqStart(c uint8) bool {\n\tswitch c {\n\tcase '\\\\', '[', '(', ')':\n\t\treturn true\n\t}\n\treturn false\n}\n\n// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to\n// calling FindStringIndex() on the below regex (which was originally used):\n//\n// \"(?:\\x1b[\\\\[()][0-9;:?]*[a-zA-Z@]|\\x1b][0-9]+[;:][[:print:]]+(?:\\x1b\\\\\\\\|\\x07)|\\x1b.|[\\x0e\\x0f]|.\\x08|\\n)\"\nfunc nextAnsiEscapeSequence(s string) (int, int) {\n\t// fast check for ANSI escape sequences\n\ti := 0\n\tfor ; i < len(s); i++ {\n\t\tswitch s[i] {\n\t\tcase '\\x0e', '\\x0f', '\\x1b', '\\x08', '\\n':\n\t\t\t// We ignore the fact that '\\x08' cannot be the first char\n\t\t\t// in the string and be an escape sequence for the sake of\n\t\t\t// speed and simplicity.\n\t\t\tgoto Loop\n\t\t}\n\t}\n\treturn -1, -1\n\nLoop:\n\tfor ; i < len(s); i++ {\n\t\tswitch s[i] {\n\t\tcase '\\n':\n\t\t\t// match: `\\n`\n\t\t\treturn i, i + 1\n\t\tcase '\\x08':\n\t\t\t// backtrack to match: `.\\x08`\n\t\t\tif i > 0 && s[i-1] != '\\n' {\n\t\t\t\tif s[i-1] < utf8.RuneSelf {\n\t\t\t\t\treturn i - 1, i + 1\n\t\t\t\t}\n\t\t\t\t_, n := utf8.DecodeLastRuneInString(s[:i])\n\t\t\t\treturn i - n, i + 1\n\t\t\t}\n\t\tcase '\\x1b':\n\t\t\t// match: `\\x1b[\\\\[()][0-9;:?]*[a-zA-Z@]`\n\t\t\tif i+2 < len(s) && isCtrlSeqStart(s[i+1]) {\n\t\t\t\tif j := matchControlSequence(s[i:]); j != -1 {\n\t\t\t\t\treturn i, i + j\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// match: `\\x1b][0-9]+[;:][[:print:]]+(?:\\x1b\\\\\\\\|\\x07)`\n\t\t\tif i+5 < len(s) && s[i+1] == ']' {\n\t\t\t\tj := 2\n\t\t\t\t// \\x1b][0-9]+[;:][[:print:]]+(?:\\x1b\\\\\\\\|\\x07)\n\t\t\t\t//      ------\n\t\t\t\tfor ; i+j < len(s) && isNumeric(s[i+j]); j++ {\n\t\t\t\t}\n\n\t\t\t\t// \\x1b][0-9]+[;:][[:print:]]+(?:\\x1b\\\\\\\\|\\x07)\n\t\t\t\t//            ---------------\n\t\t\t\tif j > 2 && i+j+1 < len(s) && (s[i+j] == ';' || s[i+j] == ':') && s[i+j+1] >= '\\x20' {\n\t\t\t\t\tif k := matchOperatingSystemCommand(s[i:], j+2); k != -1 {\n\t\t\t\t\t\treturn i, i + k\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// match: `\\x1b.`\n\t\t\tif i+1 < len(s) && s[i+1] != '\\n' {\n\t\t\t\tif s[i+1] < utf8.RuneSelf {\n\t\t\t\t\treturn i, i + 2\n\t\t\t\t}\n\t\t\t\t_, n := utf8.DecodeRuneInString(s[i+1:])\n\t\t\t\treturn i, i + n + 1\n\t\t\t}\n\t\tcase '\\x0e', '\\x0f':\n\t\t\t// match: `[\\x0e\\x0f]`\n\t\t\treturn i, i + 1\n\t\t}\n\t}\n\treturn -1, -1\n}\n\nfunc extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {\n\t// We append to a stack allocated variable that we'll\n\t// later copy and return, to save on allocations.\n\toffsets := make([]ansiOffset, 0, 32)\n\n\tif state != nil {\n\t\toffsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})\n\t}\n\n\tvar (\n\t\tpstate    *ansiState // lazily allocated\n\t\toutput    strings.Builder\n\t\tprevIdx   int\n\t\truneCount int\n\t)\n\tfor idx := 0; idx < len(str); {\n\t\t// Make sure that we found an ANSI code\n\t\tstart, end := nextAnsiEscapeSequence(str[idx:])\n\t\tif start == -1 {\n\t\t\tbreak\n\t\t}\n\t\tstart += idx\n\t\tidx += end\n\n\t\t// Check if we should continue\n\t\tprev := str[prevIdx:start]\n\t\tif proc != nil && !proc(prev, state) {\n\t\t\treturn \"\", nil, nil\n\t\t}\n\t\tprevIdx = idx\n\n\t\tif len(prev) != 0 {\n\t\t\truneCount += utf8.RuneCountInString(prev)\n\t\t\t// Grow the buffer size to the maximum possible length (string length\n\t\t\t// containing ansi codes) to avoid repetitive allocation\n\t\t\tif output.Cap() == 0 {\n\t\t\t\toutput.Grow(len(str))\n\t\t\t}\n\t\t\toutput.WriteString(prev)\n\t\t}\n\n\t\tcode := str[start:idx]\n\t\tnewState := interpretCode(code, state)\n\t\tif code == \"\\n\" || !newState.equals(state) {\n\t\t\tif state != nil {\n\t\t\t\t// Update last offset\n\t\t\t\t(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)\n\t\t\t}\n\n\t\t\tif code == \"\\n\" {\n\t\t\t\toutput.WriteRune('\\n')\n\t\t\t\truneCount++\n\t\t\t\t// Full-background marker\n\t\t\t\tif newState.lbg >= 0 {\n\t\t\t\t\tmarker := newState\n\t\t\t\t\tmarker.attr |= tui.FullBg\n\t\t\t\t\toffsets = append(offsets, ansiOffset{\n\t\t\t\t\t\t[2]int32{int32(runeCount), int32(runeCount)},\n\t\t\t\t\t\tmarker,\n\t\t\t\t\t})\n\t\t\t\t\t// Reset the full-line background color\n\t\t\t\t\tnewState.lbg = -1\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif newState.colored() {\n\t\t\t\t// Append new offset\n\t\t\t\tif pstate == nil {\n\t\t\t\t\tpstate = &ansiState{}\n\t\t\t\t}\n\t\t\t\t*pstate = newState\n\t\t\t\tstate = pstate\n\t\t\t\toffsets = append(offsets, ansiOffset{\n\t\t\t\t\t[2]int32{int32(runeCount), int32(runeCount)},\n\t\t\t\t\tnewState,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t// Discard state\n\t\t\t\tstate = nil\n\t\t\t}\n\t\t}\n\t}\n\n\tvar rest string\n\tvar trimmed string\n\tif prevIdx == 0 {\n\t\t// No ANSI code found\n\t\trest = str\n\t\ttrimmed = str\n\t} else {\n\t\trest = str[prevIdx:]\n\t\toutput.WriteString(rest)\n\t\ttrimmed = output.String()\n\t}\n\tif proc != nil {\n\t\tproc(rest, state)\n\t}\n\tif len(offsets) > 0 {\n\t\tif len(rest) > 0 && state != nil {\n\t\t\t// Update last offset\n\t\t\truneCount += utf8.RuneCountInString(rest)\n\t\t\t(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)\n\t\t}\n\t\t// Return a copy of the offsets slice\n\t\ta := make([]ansiOffset, len(offsets))\n\t\tcopy(a, offsets)\n\t\treturn trimmed, &a, state\n\t}\n\treturn trimmed, nil, state\n}\n\nfunc parseAnsiCode(s string) (int, byte, string) {\n\tvar remaining string\n\tvar sep byte\n\t// Find the first separator (either ; or :)\n\ti := -1\n\tfor j := 0; j < len(s); j++ {\n\t\tif s[j] == ';' || s[j] == ':' {\n\t\t\ti = j\n\t\t\tbreak\n\t\t}\n\t}\n\tif i >= 0 {\n\t\tsep = s[i]\n\t\tremaining = s[i+1:]\n\t\ts = s[:i]\n\t}\n\n\tif len(s) > 0 {\n\t\t// Inlined version of strconv.Atoi() that only handles positive\n\t\t// integers and does not allocate on error.\n\t\tcode := 0\n\t\tfor _, ch := range stringBytes(s) {\n\t\t\tch -= '0'\n\t\t\tif ch > 9 {\n\t\t\t\treturn -1, sep, remaining\n\t\t\t}\n\t\t\tcode = code*10 + int(ch)\n\t\t}\n\t\treturn code, sep, remaining\n\t}\n\n\treturn -1, sep, remaining\n}\n\nfunc interpretCode(ansiCode string, prevState *ansiState) ansiState {\n\tif ansiCode == \"\\n\" {\n\t\tif prevState != nil {\n\t\t\treturn *prevState\n\t\t}\n\t\treturn ansiState{-1, -1, -1, 0, -1, nil}\n\t}\n\n\tvar state ansiState\n\tif prevState == nil {\n\t\tstate = ansiState{-1, -1, -1, 0, -1, nil}\n\t} else {\n\t\tstate = ansiState{prevState.fg, prevState.bg, prevState.ul, prevState.attr, prevState.lbg, prevState.url}\n\t}\n\tif ansiCode[0] != '\\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {\n\t\tif prevState != nil && (strings.HasSuffix(ansiCode, \"0K\") || strings.HasSuffix(ansiCode, \"[K\")) {\n\t\t\tstate.lbg = prevState.bg\n\t\t} else if strings.HasPrefix(ansiCode, \"\\x1b]8;\") && (strings.HasSuffix(ansiCode, \"\\x1b\\\\\") || strings.HasSuffix(ansiCode, \"\\a\")) {\n\t\t\tstLen := 2\n\t\t\tif strings.HasSuffix(ansiCode, \"\\a\") {\n\t\t\t\tstLen = 1\n\t\t\t}\n\t\t\t// \"\\x1b]8;;\\x1b\\\\\" or \"\\x1b]8;;\\a\"\n\t\t\tif len(ansiCode) == 5+stLen && ansiCode[4] == ';' {\n\t\t\t\tstate.url = nil\n\t\t\t} else if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {\n\t\t\t\tparams := ansiCode[4 : 4+paramsEnd]\n\t\t\t\turi := ansiCode[5+paramsEnd : len(ansiCode)-stLen]\n\t\t\t\tstate.url = &url{uri: uri, params: params}\n\t\t\t}\n\t\t}\n\t\treturn state\n\t}\n\n\treset := func() {\n\t\tstate.fg = -1\n\t\tstate.bg = -1\n\t\tstate.ul = -1\n\t\tstate.attr = 0\n\t}\n\n\tif len(ansiCode) <= 3 {\n\t\treset()\n\t\treturn state\n\t}\n\tansiCode = ansiCode[2 : len(ansiCode)-1]\n\n\tstate256 := 0\n\tptr := &state.fg\n\n\tcount := 0\n\tfor len(ansiCode) != 0 {\n\t\tvar num int\n\t\tvar sep byte\n\t\tif num, sep, ansiCode = parseAnsiCode(ansiCode); num != -1 {\n\t\t\tcount++\n\t\t\tswitch state256 {\n\t\t\tcase 0:\n\t\t\t\tswitch num {\n\t\t\t\tcase 38:\n\t\t\t\t\tptr = &state.fg\n\t\t\t\t\tstate256++\n\t\t\t\tcase 48:\n\t\t\t\t\tptr = &state.bg\n\t\t\t\t\tstate256++\n\t\t\t\tcase 58:\n\t\t\t\t\tptr = &state.ul\n\t\t\t\t\tstate256++\n\t\t\t\tcase 39:\n\t\t\t\t\tstate.fg = -1\n\t\t\t\tcase 49:\n\t\t\t\t\tstate.bg = -1\n\t\t\t\tcase 59:\n\t\t\t\t\tstate.ul = -1\n\t\t\t\tcase 1:\n\t\t\t\t\tstate.attr = state.attr | tui.Bold\n\t\t\t\tcase 2:\n\t\t\t\t\tstate.attr = state.attr | tui.Dim\n\t\t\t\tcase 3:\n\t\t\t\t\tstate.attr = state.attr | tui.Italic\n\t\t\t\tcase 4:\n\t\t\t\t\tif sep == ':' {\n\t\t\t\t\t\t// SGR 4:N — underline style sub-parameter\n\t\t\t\t\t\tvar subNum int\n\t\t\t\t\t\tsubNum, _, ansiCode = parseAnsiCode(ansiCode)\n\t\t\t\t\t\tstate.attr = state.attr &^ tui.UnderlineStyleMask\n\t\t\t\t\t\tswitch subNum {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tstate.attr = state.attr &^ tui.Underline\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tstate.attr = state.attr | tui.Underline\n\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\tstate.attr = state.attr | tui.Underline | tui.UlStyleDouble\n\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\tstate.attr = state.attr | tui.Underline | tui.UlStyleCurly\n\t\t\t\t\t\tcase 4:\n\t\t\t\t\t\t\tstate.attr = state.attr | tui.Underline | tui.UlStyleDotted\n\t\t\t\t\t\tcase 5:\n\t\t\t\t\t\t\tstate.attr = state.attr | tui.Underline | tui.UlStyleDashed\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tstate.attr = state.attr | tui.Underline\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstate.attr = state.attr | tui.Underline\n\t\t\t\t\t}\n\t\t\t\tcase 5:\n\t\t\t\t\tstate.attr = state.attr | tui.Blink\n\t\t\t\tcase 7:\n\t\t\t\t\tstate.attr = state.attr | tui.Reverse\n\t\t\t\tcase 9:\n\t\t\t\t\tstate.attr = state.attr | tui.StrikeThrough\n\t\t\t\tcase 22:\n\t\t\t\t\tstate.attr = state.attr &^ tui.Bold\n\t\t\t\t\tstate.attr = state.attr &^ tui.Dim\n\t\t\t\tcase 23: // tput rmso\n\t\t\t\t\tstate.attr = state.attr &^ tui.Italic\n\t\t\t\tcase 24: // tput rmul\n\t\t\t\t\tstate.attr = state.attr &^ tui.Underline\n\t\t\t\t\tstate.attr = state.attr &^ tui.UnderlineStyleMask\n\t\t\t\tcase 25:\n\t\t\t\t\tstate.attr = state.attr &^ tui.Blink\n\t\t\t\tcase 27:\n\t\t\t\t\tstate.attr = state.attr &^ tui.Reverse\n\t\t\t\tcase 29:\n\t\t\t\t\tstate.attr = state.attr &^ tui.StrikeThrough\n\t\t\t\tcase 0:\n\t\t\t\t\treset()\n\t\t\t\t\tstate256 = 0\n\t\t\t\tdefault:\n\t\t\t\t\tif num >= 30 && num <= 37 {\n\t\t\t\t\t\tstate.fg = tui.Color(num - 30)\n\t\t\t\t\t} else if num >= 40 && num <= 47 {\n\t\t\t\t\t\tstate.bg = tui.Color(num - 40)\n\t\t\t\t\t} else if num >= 90 && num <= 97 {\n\t\t\t\t\t\tstate.fg = tui.Color(num - 90 + 8)\n\t\t\t\t\t} else if num >= 100 && num <= 107 {\n\t\t\t\t\t\tstate.bg = tui.Color(num - 100 + 8)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase 1:\n\t\t\t\tswitch num {\n\t\t\t\tcase 2:\n\t\t\t\t\tstate256 = 10 // MAGIC\n\t\t\t\tcase 5:\n\t\t\t\t\tstate256++\n\t\t\t\tdefault:\n\t\t\t\t\tstate256 = 0\n\t\t\t\t}\n\t\t\tcase 2:\n\t\t\t\t*ptr = tui.Color(num)\n\t\t\t\tstate256 = 0\n\t\t\tcase 10:\n\t\t\t\t*ptr = tui.Color(1<<24) | tui.Color(num<<16)\n\t\t\t\tstate256++\n\t\t\tcase 11:\n\t\t\t\t*ptr = *ptr | tui.Color(num<<8)\n\t\t\t\tstate256++\n\t\t\tcase 12:\n\t\t\t\t*ptr = *ptr | tui.Color(num)\n\t\t\t\tstate256 = 0\n\t\t\t}\n\t\t}\n\t}\n\n\t// Empty sequence: reset\n\tif count == 0 {\n\t\treset()\n\t}\n\n\tif state256 > 0 {\n\t\t*ptr = -1\n\t}\n\treturn state\n}\n"
  },
  {
    "path": "src/ansi_test.go",
    "content": "package fzf\n\nimport (\n\t\"math/rand\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"unicode/utf8\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n)\n\n// The following regular expression will include not all but most of the\n// frequently used ANSI sequences. This regex is used as a reference for\n// testing nextAnsiEscapeSequence().\n//\n// References:\n//   - https://github.com/gnachman/iTerm2\n//   - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php\n//     (archived from http://ascii-table.com/ansi-escape-sequences.php)\n//   - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php\n//     (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)\n//   - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html\n//   - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html\nvar ansiRegexReference = regexp.MustCompile(\"(?:\\x1b[\\\\[()][0-9;:]*[a-zA-Z@]|\\x1b][0-9][;:][[:print:]]+(?:\\x1b\\\\\\\\|\\x07)|\\x1b.|[\\x0e\\x0f]|.\\x08|\\n)\")\n\nfunc testParserReference(t testing.TB, str string) {\n\tt.Helper()\n\n\ttoSlice := func(start, end int) []int {\n\t\tif start == -1 {\n\t\t\treturn nil\n\t\t}\n\t\treturn []int{start, end}\n\t}\n\n\ts := str\n\tfor i := 0; ; i++ {\n\t\tgot := toSlice(nextAnsiEscapeSequence(s))\n\t\texp := ansiRegexReference.FindStringIndex(s)\n\n\t\tequal := len(got) == len(exp)\n\t\tif equal {\n\t\t\tfor i := range got {\n\t\t\t\tif got[i] != exp[i] {\n\t\t\t\t\tequal = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !equal {\n\t\t\tvar exps, gots []rune\n\t\t\tif len(got) == 2 {\n\t\t\t\tgots = []rune(s[got[0]:got[1]])\n\t\t\t}\n\t\t\tif len(exp) == 2 {\n\t\t\t\texps = []rune(s[exp[0]:exp[1]])\n\t\t\t}\n\t\t\tt.Errorf(\"%d: %q: got: %v (%q) want: %v (%q)\", i, s, got, gots, exp, exps)\n\t\t\treturn\n\t\t}\n\t\tif len(exp) == 0 {\n\t\t\treturn\n\t\t}\n\t\ts = s[exp[1]:]\n\t}\n}\n\nfunc TestNextAnsiEscapeSequence(t *testing.T) {\n\ttestStrs := []string{\n\t\t\"\\x1b[0mhello world\",\n\t\t\"\\x1b[1mhello world\",\n\t\t\"椙\\x1b[1m椙\",\n\t\t\"椙\\x1b[1椙m椙\",\n\t\t\"\\x1b[1mhello \\x1b[mw\\x1b7o\\x1b8r\\x1b(Bl\\x1b[2@d\",\n\t\t\"\\x1b[1mhello \\x1b[Kworld\",\n\t\t\"hello \\x1b[34;45;1mworld\",\n\t\t\"hello \\x1b[34;45;1mwor\\x1b[34;45;1mld\",\n\t\t\"hello \\x1b[34;45;1mwor\\x1b[0mld\",\n\t\t\"hello \\x1b[34;48;5;233;1mwo\\x1b[38;5;161mr\\x1b[0ml\\x1b[38;5;161md\",\n\t\t\"hello \\x1b[38;5;38;48;5;48;1mwor\\x1b[38;5;48;48;5;38ml\\x1b[0md\",\n\t\t\"hello \\x1b[32;1mworld\",\n\t\t\"hello world\",\n\t\t\"hello \\x1b[0;38;5;200;48;5;100mworld\",\n\t\t\"\\x1b椙\",\n\t\t\"椙\\x08\",\n\t\t\"\\n\\x08\",\n\t\t\"X\\x08\",\n\t\t\"\",\n\t\t\"\\x1b]4;3;rgb:aa/bb/cc\\x07 \",\n\t\t\"\\x1b]4;3;rgb:aa/bb/cc\\x1b\\\\ \",\n\t\tansiBenchmarkString,\n\t}\n\n\tfor _, s := range testStrs {\n\t\ttestParserReference(t, s)\n\t}\n}\n\nfunc TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {\n\tt.Parallel()\n\tif testing.Short() {\n\t\tt.Skip(\"short test\")\n\t}\n\n\ttestStrs := []string{\n\t\t\"\\x1b[0mhello world\",\n\t\t\"\\x1b[1mhello world\",\n\t\t\"椙\\x1b[1m椙\",\n\t\t\"椙\\x1b[1椙m椙\",\n\t\t\"\\x1b[1mhello \\x1b[mw\\x1b7o\\x1b8r\\x1b(Bl\\x1b[2@d\",\n\t\t\"\\x1b[1mhello \\x1b[Kworld\",\n\t\t\"hello \\x1b[34;45;1mworld\",\n\t\t\"hello \\x1b[34;45;1mwor\\x1b[34;45;1mld\",\n\t\t\"hello \\x1b[34;45;1mwor\\x1b[0mld\",\n\t\t\"hello \\x1b[34;48;5;233;1mwo\\x1b[38;5;161mr\\x1b[0ml\\x1b[38;5;161md\",\n\t\t\"hello \\x1b[38;5;38;48;5;48;1mwor\\x1b[38;5;48;48;5;38ml\\x1b[0md\",\n\t\t\"hello \\x1b[32;1mworld\",\n\t\t\"hello world\",\n\t\t\"hello \\x1b[0;38;5;200;48;5;100mworld\",\n\t\tansiBenchmarkString,\n\t}\n\n\treplacementBytes := [...]rune{'\\x0e', '\\x0f', '\\x1b', '\\x08'}\n\n\tmodifyString := func(s string, rr *rand.Rand) string {\n\t\tn := rr.Intn(len(s))\n\t\tb := []rune(s)\n\t\tfor ; n >= 0 && len(b) != 0; n-- {\n\t\t\ti := rr.Intn(len(b))\n\t\t\tswitch x := rr.Intn(4); x {\n\t\t\tcase 0:\n\t\t\t\tb = append(b[:i], b[i+1:]...)\n\t\t\tcase 1:\n\t\t\t\tj := rr.Intn(len(replacementBytes) - 1)\n\t\t\t\tb[i] = replacementBytes[j]\n\t\t\tcase 2:\n\t\t\t\tx := rune(rr.Intn(utf8.MaxRune))\n\t\t\t\tfor !utf8.ValidRune(x) {\n\t\t\t\t\tx = rune(rr.Intn(utf8.MaxRune))\n\t\t\t\t}\n\t\t\t\tb[i] = x\n\t\t\tcase 3:\n\t\t\t\tb[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid\n\t\t\tdefault:\n\t\t\t\tt.Fatalf(\"unsupported value: %d\", x)\n\t\t\t}\n\t\t}\n\t\treturn string(b)\n\t}\n\n\trr := rand.New(rand.NewSource(1))\n\tfor _, s := range testStrs {\n\t\tfor i := 1_000; i >= 0; i-- {\n\t\t\ttestParserReference(t, modifyString(s, rr))\n\t\t}\n\t}\n}\n\nfunc TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {\n\tt.Parallel()\n\n\tif testing.Short() {\n\t\tt.Skip(\"short test\")\n\t}\n\n\trandomString := func(rr *rand.Rand) string {\n\t\tnumChars := rand.Intn(50)\n\t\tcodePoints := make([]rune, numChars)\n\t\tfor i := range codePoints {\n\t\t\tvar r rune\n\t\t\tfor range 1000 {\n\t\t\t\tr = rune(rr.Intn(utf8.MaxRune))\n\t\t\t\t// Allow 10% of runes to be invalid\n\t\t\t\tif utf8.ValidRune(r) || rr.Float64() < 0.10 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tcodePoints[i] = r\n\t\t}\n\t\treturn string(codePoints)\n\t}\n\n\trr := rand.New(rand.NewSource(1))\n\tfor range 100_000 {\n\t\ttestParserReference(t, randomString(rr))\n\t}\n}\n\nfunc TestExtractColor(t *testing.T) {\n\tassert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {\n\t\tvar attr tui.Attr\n\t\tif bold {\n\t\t\tattr = tui.Bold\n\t\t}\n\t\tif offset.offset[0] != b || offset.offset[1] != e ||\n\t\t\toffset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {\n\t\t\tt.Error(offset, b, e, fg, bg, attr)\n\t\t}\n\t}\n\n\tsrc := \"hello world\"\n\tvar state *ansiState\n\tclean := \"\\x1b[0m\"\n\tcheck := func(assertion func(ansiOffsets *[]ansiOffset, state *ansiState)) {\n\t\toutput, ansiOffsets, newState := extractColor(src, state, nil)\n\t\tstate = newState\n\t\tif output != \"hello world\" {\n\t\t\tt.Errorf(\"Invalid output: %s %v\", output, []rune(output))\n\t\t}\n\t\tt.Log(src, ansiOffsets, clean)\n\t\tassertion(ansiOffsets, state)\n\t}\n\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif offsets != nil {\n\t\t\tt.Fail()\n\t\t}\n\t})\n\n\tstate = nil\n\tsrc = \"\\x1b[0mhello world\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif offsets != nil {\n\t\t\tt.Fail()\n\t\t}\n\t})\n\n\tstate = nil\n\tsrc = \"\\x1b[1mhello world\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 11, -1, -1, true)\n\t})\n\n\tstate = nil\n\tsrc = \"\\x1b[1mhello \\x1b[mw\\x1b7o\\x1b8r\\x1b(Bl\\x1b[2@d\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 6, -1, -1, true)\n\t})\n\n\tstate = nil\n\tsrc = \"\\x1b[1mhello \\x1b[Kworld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 11, -1, -1, true)\n\t})\n\n\tstate = nil\n\tsrc = \"hello \\x1b[34;45;1mworld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 6, 11, 4, 5, true)\n\t})\n\n\tstate = nil\n\tsrc = \"hello \\x1b[34;45;1mwor\\x1b[34;45;1mld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 6, 11, 4, 5, true)\n\t})\n\n\tstate = nil\n\tsrc = \"hello \\x1b[34;45;1mwor\\x1b[0mld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 6, 9, 4, 5, true)\n\t})\n\n\tstate = nil\n\tsrc = \"hello \\x1b[34;48;5;233;1mwo\\x1b[38;5;161mr\\x1b[0ml\\x1b[38;5;161md\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 3 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 6, 8, 4, 233, true)\n\t\tassert((*offsets)[1], 8, 9, 161, 233, true)\n\t\tassert((*offsets)[2], 10, 11, 161, -1, false)\n\t})\n\n\t// {38,48};5;{38,48}\n\tstate = nil\n\tsrc = \"hello \\x1b[38;5;38;48;5;48;1mwor\\x1b[38;5;48;48;5;38ml\\x1b[0md\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 2 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 6, 9, 38, 48, true)\n\t\tassert((*offsets)[1], 9, 10, 48, 38, true)\n\t})\n\n\tsrc = \"hello \\x1b[32;1mworld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tif state.fg != 2 || state.bg != -1 || state.attr == 0 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 6, 11, 2, -1, true)\n\t})\n\n\tsrc = \"hello world\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tif state.fg != 2 || state.bg != -1 || state.attr == 0 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 11, 2, -1, true)\n\t})\n\n\tsrc = \"hello \\x1b[0;38;5;200;48;5;100mworld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 2 {\n\t\t\tt.Fail()\n\t\t}\n\t\tif state.fg != 200 || state.bg != 100 || state.attr > 0 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 6, 2, -1, true)\n\t\tassert((*offsets)[1], 6, 11, 200, 100, false)\n\t})\n\n\tstate = nil\n\tvar color24 tui.Color = (1 << 24) + (180 << 16) + (190 << 8) + 254\n\tsrc = \"\\x1b[1mhello \\x1b[22;1;38:2:180:190:254mworld\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 2 {\n\t\t\tt.Fail()\n\t\t}\n\t\tif state.fg != color24 || state.attr != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 6, -1, -1, true)\n\t\tassert((*offsets)[1], 6, 11, color24, -1, true)\n\t})\n\n\tsrc = \"\\x1b]133;A\\x1b\\\\hello \\x1b]133;C\\x1b\\\\world\"\n\tcheck(func(offsets *[]ansiOffset, state *ansiState) {\n\t\tif len(*offsets) != 1 {\n\t\t\tt.Fail()\n\t\t}\n\t\tassert((*offsets)[0], 0, 11, color24, -1, true)\n\t})\n}\n\nfunc TestAnsiCodeStringConversion(t *testing.T) {\n\tassert := func(code string, prevState *ansiState, expected string) {\n\t\tstate := interpretCode(code, prevState)\n\t\tif expected != state.ToString() {\n\t\t\tt.Errorf(\"expected: %s, actual: %s\",\n\t\t\t\tstrings.ReplaceAll(expected, \"\\x1b[\", \"\\\\x1b[\"),\n\t\t\t\tstrings.ReplaceAll(state.ToString(), \"\\x1b[\", \"\\\\x1b[\"))\n\t\t}\n\t}\n\tassert(\"\\x1b[m\", nil, \"\")\n\tassert(\"\\x1b[m\", &ansiState{attr: tui.Blink, ul: -1, lbg: -1}, \"\")\n\tassert(\"\\x1b[0m\", &ansiState{fg: 4, bg: 4, ul: -1, lbg: -1}, \"\")\n\tassert(\"\\x1b[;m\", &ansiState{fg: 4, bg: 4, ul: -1, lbg: -1}, \"\")\n\tassert(\"\\x1b[;;m\", &ansiState{fg: 4, bg: 4, ul: -1, lbg: -1}, \"\")\n\n\tassert(\"\\x1b[31m\", nil, \"\\x1b[31;49m\")\n\tassert(\"\\x1b[41m\", nil, \"\\x1b[39;41m\")\n\n\tassert(\"\\x1b[92m\", nil, \"\\x1b[92;49m\")\n\tassert(\"\\x1b[102m\", nil, \"\\x1b[39;102m\")\n\n\tassert(\"\\x1b[31m\", &ansiState{fg: 4, bg: 4, ul: -1, lbg: -1}, \"\\x1b[31;44m\")\n\tassert(\"\\x1b[1;2;31m\", &ansiState{fg: 2, bg: -1, ul: -1, attr: tui.Reverse, lbg: -1}, \"\\x1b[1;2;7;31;49m\")\n\tassert(\"\\x1b[38;5;100;48;5;200m\", nil, \"\\x1b[38;5;100;48;5;200m\")\n\tassert(\"\\x1b[38:5:100:48:5:200m\", nil, \"\\x1b[38;5;100;48;5;200m\")\n\tassert(\"\\x1b[48;5;100;38;5;200m\", nil, \"\\x1b[38;5;200;48;5;100m\")\n\tassert(\"\\x1b[48;5;100;38;2;10;20;30;1m\", nil, \"\\x1b[1;38;2;10;20;30;48;5;100m\")\n\tassert(\"\\x1b[48;5;100;38;2;10;20;30;7m\",\n\t\t&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1, ul: -1},\n\t\t\"\\x1b[2;3;7;38;2;10;20;30;48;5;100m\")\n\n\t// Underline styles\n\tassert(\"\\x1b[4:3m\", nil, \"\\x1b[4:3;39;49m\")\n\tassert(\"\\x1b[4:2m\", nil, \"\\x1b[4:2;39;49m\")\n\tassert(\"\\x1b[4:4m\", nil, \"\\x1b[4:4;39;49m\")\n\tassert(\"\\x1b[4:5m\", nil, \"\\x1b[4:5;39;49m\")\n\tassert(\"\\x1b[4:1m\", nil, \"\\x1b[4;39;49m\")\n\n\t// Underline color (256-color)\n\tassert(\"\\x1b[4;58;5;100m\", nil, \"\\x1b[4;39;49;58;5;100m\")\n\t// Underline color (24-bit)\n\tassert(\"\\x1b[4;58;2;255;0;128m\", nil, \"\\x1b[4;39;49;58;2;255;0;128m\")\n\t// Curly underline + underline color\n\tassert(\"\\x1b[4:3;58;2;255;0;0m\", nil, \"\\x1b[4:3;39;49;58;2;255;0;0m\")\n\t// SGR 59 resets underline color\n\tassert(\"\\x1b[59m\", &ansiState{fg: 1, bg: -1, ul: 100, lbg: -1}, \"\\x1b[31;49m\")\n}\n\nfunc TestParseAnsiCode(t *testing.T) {\n\ttests := []struct {\n\t\tIn  string\n\t\tExp string\n\t\tN   int\n\t\tSep byte\n\t}{\n\t\t{\"123\", \"\", 123, 0},\n\t\t{\"1a\", \"\", -1, 0},\n\t\t{\"1a;12\", \"12\", -1, ';'},\n\t\t{\"12;a\", \"a\", 12, ';'},\n\t\t{\"-2\", \"\", -1, 0},\n\t\t// Colon sub-parameters: earliest separator wins (@shtse8)\n\t\t{\"4:3\", \"3\", 4, ':'},\n\t\t{\"4:3;31\", \"3;31\", 4, ':'},\n\t\t{\"38:2:255:0:0\", \"2:255:0:0\", 38, ':'},\n\t\t{\"58:5:200\", \"5:200\", 58, ':'},\n\t\t// Semicolon before colon\n\t\t{\"4;38:2:0:0:0\", \"38:2:0:0:0\", 4, ';'},\n\t}\n\tfor _, x := range tests {\n\t\tn, sep, s := parseAnsiCode(x.In)\n\t\tif n != x.N || s != x.Exp || sep != x.Sep {\n\t\t\tt.Fatalf(\"%q: got: (%d %q %q) want: (%d %q %q)\", x.In, n, s, string(sep), x.N, x.Exp, string(x.Sep))\n\t\t}\n\t}\n}\n\n// Test cases adapted from @shtse8 (PR #4678)\nfunc TestInterpretCodeUnderlineStyles(t *testing.T) {\n\t// 4:0 = no underline\n\tstate := interpretCode(\"\\x1b[4:0m\", nil)\n\tif state.attr&tui.Underline != 0 {\n\t\tt.Error(\"4:0 should not set underline\")\n\t}\n\n\t// 4:1 = single underline\n\tstate = interpretCode(\"\\x1b[4:1m\", nil)\n\tif state.attr&tui.Underline == 0 {\n\t\tt.Error(\"4:1 should set underline\")\n\t}\n\n\t// 4:3 = curly underline\n\tstate = interpretCode(\"\\x1b[4:3m\", nil)\n\tif state.attr&tui.Underline == 0 {\n\t\tt.Error(\"4:3 should set underline\")\n\t}\n\tif state.attr.UnderlineStyle() != tui.UlStyleCurly {\n\t\tt.Error(\"4:3 should set curly underline style\")\n\t}\n\n\t// 4:3 should NOT set italic (3 is a sub-param, not SGR 3)\n\tif state.attr&tui.Italic != 0 {\n\t\tt.Error(\"4:3 should not set italic\")\n\t}\n\n\t// 4:2;31 = double underline + red fg\n\tstate = interpretCode(\"\\x1b[4:2;31m\", nil)\n\tif state.attr&tui.Underline == 0 {\n\t\tt.Error(\"4:2;31 should set underline\")\n\t}\n\tif state.fg != 1 {\n\t\tt.Errorf(\"4:2;31 should set fg to red (1), got %d\", state.fg)\n\t}\n\tif state.attr&tui.Dim != 0 {\n\t\tt.Error(\"4:2;31 should not set dim\")\n\t}\n\n\t// Plain 4 still works\n\tstate = interpretCode(\"\\x1b[4m\", nil)\n\tif state.attr&tui.Underline == 0 {\n\t\tt.Error(\"4 should set underline\")\n\t}\n\n\t// 4;2 (semicolon) = underline + dim\n\tstate = interpretCode(\"\\x1b[4;2m\", nil)\n\tif state.attr&tui.Underline == 0 {\n\t\tt.Error(\"4;2 should set underline\")\n\t}\n\tif state.attr&tui.Dim == 0 {\n\t\tt.Error(\"4;2 should set dim\")\n\t}\n}\n\n// Test cases adapted from @shtse8 (PR #4678)\nfunc TestInterpretCodeUnderlineColor(t *testing.T) {\n\t// 58:2:R:G:B should not affect fg or bg\n\tstate := interpretCode(\"\\x1b[58:2:255:0:0m\", nil)\n\tif state.fg != -1 || state.bg != -1 {\n\t\tt.Errorf(\"58:2:R:G:B should not affect fg/bg, got fg=%d bg=%d\", state.fg, state.bg)\n\t}\n\n\t// 58:5:200 should not affect fg or bg\n\tstate = interpretCode(\"\\x1b[58:5:200m\", nil)\n\tif state.fg != -1 || state.bg != -1 {\n\t\tt.Errorf(\"58:5:N should not affect fg/bg, got fg=%d bg=%d\", state.fg, state.bg)\n\t}\n\n\t// 58:2:R:G:B combined with 38:2:R:G:B should only set fg\n\tstate = interpretCode(\"\\x1b[58:2:255:0:0;38:2:0:255:0m\", nil)\n\texpectedFg := tui.Color(1<<24 | 0<<16 | 255<<8 | 0)\n\tif state.fg != expectedFg {\n\t\tt.Errorf(\"expected fg=%d, got %d\", expectedFg, state.fg)\n\t}\n\tif state.bg != -1 {\n\t\tt.Errorf(\"bg should be -1, got %d\", state.bg)\n\t}\n}\n\n// kernel/bpf/preload/iterators/README\nconst ansiBenchmarkString = \"\\x1b[38;5;81m\\x1b[01;31m\\x1b[Kkernel/\\x1b[0m\\x1b[38:5:81mbpf/\" +\n\t\"\\x1b[0m\\x1b[38:5:81mpreload/\\x1b[0m\\x1b[38;5;81miterators/\" +\n\t\"\\x1b[0m\\x1b[38:5:149mMakefile\\x1b[m\\x1b[K\\x1b[0m\"\n\nfunc BenchmarkNextAnsiEscapeSequence(b *testing.B) {\n\tb.SetBytes(int64(len(ansiBenchmarkString)))\n\tfor i := 0; i < b.N; i++ {\n\t\ts := ansiBenchmarkString\n\t\tfor {\n\t\t\t_, o := nextAnsiEscapeSequence(s)\n\t\t\tif o == -1 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ts = s[o:]\n\t\t}\n\t}\n}\n\n// Baseline test to compare the speed of nextAnsiEscapeSequence() to the\n// previously used regex based implementation.\nfunc BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {\n\tb.SetBytes(int64(len(ansiBenchmarkString)))\n\tfor i := 0; i < b.N; i++ {\n\t\ts := ansiBenchmarkString\n\t\tfor {\n\t\t\ta := ansiRegexReference.FindStringIndex(s)\n\t\t\tif len(a) == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ts = s[a[1]:]\n\t\t}\n\t}\n}\n\nfunc BenchmarkExtractColor(b *testing.B) {\n\tb.SetBytes(int64(len(ansiBenchmarkString)))\n\tfor i := 0; i < b.N; i++ {\n\t\textractColor(ansiBenchmarkString, nil, nil)\n\t}\n}\n"
  },
  {
    "path": "src/cache.go",
    "content": "package fzf\n\nimport \"sync\"\n\n// ChunkBitmap is a bitmap with one bit per item in a chunk.\ntype ChunkBitmap [chunkBitWords]uint64\n\n// queryCache associates query strings to bitmaps of matching items\ntype queryCache map[string]ChunkBitmap\n\n// ChunkCache associates Chunk and query string to bitmaps\ntype ChunkCache struct {\n\tmutex sync.Mutex\n\tcache map[*Chunk]*queryCache\n}\n\n// NewChunkCache returns a new ChunkCache\nfunc NewChunkCache() *ChunkCache {\n\treturn &ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}\n}\n\nfunc (cc *ChunkCache) Clear() {\n\tcc.mutex.Lock()\n\tcc.cache = make(map[*Chunk]*queryCache)\n\tcc.mutex.Unlock()\n}\n\nfunc (cc *ChunkCache) retire(chunk ...*Chunk) {\n\tcc.mutex.Lock()\n\tfor _, c := range chunk {\n\t\tdelete(cc.cache, c)\n\t}\n\tcc.mutex.Unlock()\n}\n\n// Add stores the bitmap for the given chunk and key\nfunc (cc *ChunkCache) Add(chunk *Chunk, key string, bitmap ChunkBitmap, matchCount int) {\n\tif len(key) == 0 || !chunk.IsFull() || matchCount > queryCacheMax {\n\t\treturn\n\t}\n\n\tcc.mutex.Lock()\n\tdefer cc.mutex.Unlock()\n\n\tqc, ok := cc.cache[chunk]\n\tif !ok {\n\t\tcc.cache[chunk] = &queryCache{}\n\t\tqc = cc.cache[chunk]\n\t}\n\t(*qc)[key] = bitmap\n}\n\n// Lookup returns the bitmap for the exact key\nfunc (cc *ChunkCache) Lookup(chunk *Chunk, key string) *ChunkBitmap {\n\tif len(key) == 0 || !chunk.IsFull() {\n\t\treturn nil\n\t}\n\n\tcc.mutex.Lock()\n\tdefer cc.mutex.Unlock()\n\n\tqc, ok := cc.cache[chunk]\n\tif ok {\n\t\tif bm, ok := (*qc)[key]; ok {\n\t\t\treturn &bm\n\t\t}\n\t}\n\treturn nil\n}\n\n// Search finds the bitmap for the longest prefix or suffix of the key\nfunc (cc *ChunkCache) Search(chunk *Chunk, key string) *ChunkBitmap {\n\tif len(key) == 0 || !chunk.IsFull() {\n\t\treturn nil\n\t}\n\n\tcc.mutex.Lock()\n\tdefer cc.mutex.Unlock()\n\n\tqc, ok := cc.cache[chunk]\n\tif !ok {\n\t\treturn nil\n\t}\n\n\tfor idx := 1; idx < len(key); idx++ {\n\t\t// [---------| ] | [ |---------]\n\t\t// [--------|  ] | [  |--------]\n\t\t// [-------|   ] | [   |-------]\n\t\tprefix := key[:len(key)-idx]\n\t\tsuffix := key[idx:]\n\t\tfor _, substr := range [2]string{prefix, suffix} {\n\t\t\tif bm, found := (*qc)[substr]; found {\n\t\t\t\treturn &bm\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/cache_test.go",
    "content": "package fzf\n\nimport \"testing\"\n\nfunc TestChunkCache(t *testing.T) {\n\tcache := NewChunkCache()\n\tchunk1p := &Chunk{}\n\tchunk2p := &Chunk{count: chunkSize}\n\tbm1 := ChunkBitmap{1}\n\tbm2 := ChunkBitmap{1, 2}\n\tcache.Add(chunk1p, \"foo\", bm1, 1)\n\tcache.Add(chunk2p, \"foo\", bm1, 1)\n\tcache.Add(chunk2p, \"bar\", bm2, 2)\n\n\t{ // chunk1 is not full\n\t\tcached := cache.Lookup(chunk1p, \"foo\")\n\t\tif cached != nil {\n\t\t\tt.Error(\"Cached disabled for non-full chunks\", cached)\n\t\t}\n\t}\n\t{\n\t\tcached := cache.Lookup(chunk2p, \"foo\")\n\t\tif cached == nil || cached[0] != 1 {\n\t\t\tt.Error(\"Expected bitmap cached\", cached)\n\t\t}\n\t}\n\t{\n\t\tcached := cache.Lookup(chunk2p, \"bar\")\n\t\tif cached == nil || cached[1] != 2 {\n\t\t\tt.Error(\"Expected bitmap cached\", cached)\n\t\t}\n\t}\n\t{\n\t\tcached := cache.Lookup(chunk1p, \"foobar\")\n\t\tif cached != nil {\n\t\t\tt.Error(\"Expected nil cached\", cached)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/chunklist.go",
    "content": "package fzf\n\nimport \"sync\"\n\n// Chunk is a list of Items whose size has the upper limit of chunkSize\ntype Chunk struct {\n\titems [chunkSize]Item\n\tcount int\n}\n\n// ItemBuilder is a closure type that builds Item object from byte array\ntype ItemBuilder func(*Item, []byte) bool\n\n// ChunkList is a list of Chunks\ntype ChunkList struct {\n\tchunks []*Chunk\n\tmutex  sync.Mutex\n\ttrans  ItemBuilder\n\tcache  *ChunkCache\n}\n\n// NewChunkList returns a new ChunkList\nfunc NewChunkList(cache *ChunkCache, trans ItemBuilder) *ChunkList {\n\treturn &ChunkList{\n\t\tchunks: []*Chunk{},\n\t\tmutex:  sync.Mutex{},\n\t\ttrans:  trans,\n\t\tcache:  cache}\n}\n\nfunc (c *Chunk) push(trans ItemBuilder, data []byte) bool {\n\tif trans(&c.items[c.count], data) {\n\t\tc.count++\n\t\treturn true\n\t}\n\treturn false\n}\n\n// IsFull returns true if the Chunk is full\nfunc (c *Chunk) IsFull() bool {\n\treturn c.count == chunkSize\n}\n\nfunc (c *Chunk) lastIndex(minValue int32) int32 {\n\tif c.count == 0 {\n\t\treturn minValue\n\t}\n\treturn c.items[c.count-1].Index() + 1 // Exclusive\n}\n\nfunc (cl *ChunkList) lastChunk() *Chunk {\n\treturn cl.chunks[len(cl.chunks)-1]\n}\n\n// GetItems returns the first n items from the given chunks\nfunc GetItems(chunks []*Chunk, n int) []Item {\n\titems := make([]Item, 0, n)\n\tfor _, chunk := range chunks {\n\t\tfor i := 0; i < chunk.count && len(items) < n; i++ {\n\t\t\titems = append(items, chunk.items[i])\n\t\t}\n\t\tif len(items) >= n {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn items\n}\n\n// CountItems returns the total number of Items\nfunc CountItems(cs []*Chunk) int {\n\tif len(cs) == 0 {\n\t\treturn 0\n\t}\n\tif len(cs) == 1 {\n\t\treturn cs[0].count\n\t}\n\n\t// First chunk might not be full due to --tail=N\n\treturn cs[0].count + chunkSize*(len(cs)-2) + cs[len(cs)-1].count\n}\n\n// Push adds the item to the list\nfunc (cl *ChunkList) Push(data []byte) bool {\n\tcl.mutex.Lock()\n\n\tif len(cl.chunks) == 0 || cl.lastChunk().IsFull() {\n\t\tcl.chunks = append(cl.chunks, &Chunk{})\n\t}\n\n\tret := cl.lastChunk().push(cl.trans, data)\n\tcl.mutex.Unlock()\n\treturn ret\n}\n\n// Clear clears the data\nfunc (cl *ChunkList) Clear() {\n\tcl.mutex.Lock()\n\tcl.chunks = nil\n\tcl.mutex.Unlock()\n}\n\n// ForEachItem iterates all items and applies fn to each one.\n// The done callback runs under the lock to safely update shared state.\nfunc (cl *ChunkList) ForEachItem(fn func(*Item), done func()) {\n\tcl.mutex.Lock()\n\tfor _, chunk := range cl.chunks {\n\t\tfor i := 0; i < chunk.count; i++ {\n\t\t\tfn(&chunk.items[i])\n\t\t}\n\t}\n\tif done != nil {\n\t\tdone()\n\t}\n\tcl.mutex.Unlock()\n}\n\n// Snapshot returns immutable snapshot of the ChunkList\nfunc (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {\n\tcl.mutex.Lock()\n\n\tchanged := false\n\tif tail > 0 && CountItems(cl.chunks) > tail {\n\t\tchanged = true\n\t\t// Find the number of chunks to keep\n\t\tnumChunks := 0\n\t\tfor left, i := tail, len(cl.chunks)-1; left > 0 && i >= 0; i-- {\n\t\t\tnumChunks++\n\t\t\tleft -= cl.chunks[i].count\n\t\t}\n\n\t\t// Copy the chunks to keep\n\t\tret := make([]*Chunk, numChunks)\n\t\tminIndex := len(cl.chunks) - numChunks\n\t\tcl.cache.retire(cl.chunks[:minIndex]...)\n\t\tcopy(ret, cl.chunks[minIndex:])\n\n\t\tfor left, i := tail, len(ret)-1; i >= 0; i-- {\n\t\t\tchunk := ret[i]\n\t\t\tif chunk.count > left {\n\t\t\t\tnewChunk := *chunk\n\t\t\t\tnewChunk.count = left\n\t\t\t\toldCount := chunk.count\n\t\t\t\tfor i := 0; i < left; i++ {\n\t\t\t\t\tnewChunk.items[i] = chunk.items[oldCount-left+i]\n\t\t\t\t}\n\t\t\t\tret[i] = &newChunk\n\t\t\t\tcl.cache.retire(chunk)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tleft -= chunk.count\n\t\t}\n\t\tcl.chunks = ret\n\t}\n\n\tret := make([]*Chunk, len(cl.chunks))\n\tcopy(ret, cl.chunks)\n\n\t// Duplicate the first and the last chunk\n\tif cnt := len(ret); cnt > 0 {\n\t\tif tail > 0 && cnt > 1 {\n\t\t\tnewChunk := *ret[0]\n\t\t\tret[0] = &newChunk\n\t\t}\n\t\tnewChunk := *ret[cnt-1]\n\t\tret[cnt-1] = &newChunk\n\t}\n\n\tcl.mutex.Unlock()\n\treturn ret, CountItems(ret), changed\n}\n"
  },
  {
    "path": "src/chunklist_test.go",
    "content": "package fzf\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc TestChunkList(t *testing.T) {\n\t// FIXME global\n\tsortCriteria = []criterion{byScore, byLength}\n\n\tcl := NewChunkList(NewChunkCache(), func(item *Item, s []byte) bool {\n\t\titem.text = util.ToChars(s)\n\t\treturn true\n\t})\n\n\t// Snapshot\n\tsnapshot, count, _ := cl.Snapshot(0)\n\tif len(snapshot) > 0 || count > 0 {\n\t\tt.Error(\"Snapshot should be empty now\")\n\t}\n\n\t// Add some data\n\tcl.Push([]byte(\"hello\"))\n\tcl.Push([]byte(\"world\"))\n\n\t// Previously created snapshot should remain the same\n\tif len(snapshot) > 0 {\n\t\tt.Error(\"Snapshot should not have changed\")\n\t}\n\n\t// But the new snapshot should contain the added items\n\tsnapshot, count, _ = cl.Snapshot(0)\n\tif len(snapshot) != 1 && count != 2 {\n\t\tt.Error(\"Snapshot should not be empty now\")\n\t}\n\n\t// Check the content of the ChunkList\n\tchunk1 := snapshot[0]\n\tif chunk1.count != 2 {\n\t\tt.Error(\"Snapshot should contain only two items\")\n\t}\n\tif chunk1.items[0].text.ToString() != \"hello\" ||\n\t\tchunk1.items[1].text.ToString() != \"world\" {\n\t\tt.Error(\"Invalid data\")\n\t}\n\tif chunk1.IsFull() {\n\t\tt.Error(\"Chunk should not have been marked full yet\")\n\t}\n\n\t// Add more data\n\tfor i := range chunkSize * 2 {\n\t\tcl.Push(fmt.Appendf(nil, \"item %d\", i))\n\t}\n\n\t// Previous snapshot should remain the same\n\tif len(snapshot) != 1 {\n\t\tt.Error(\"Snapshot should stay the same\")\n\t}\n\n\t// New snapshot\n\tsnapshot, count, _ = cl.Snapshot(0)\n\tif len(snapshot) != 3 || !snapshot[0].IsFull() ||\n\t\t!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {\n\t\tt.Error(\"Expected two full chunks and one more chunk\")\n\t}\n\tif snapshot[2].count != 2 {\n\t\tt.Error(\"Unexpected number of items\")\n\t}\n\n\tcl.Push([]byte(\"hello\"))\n\tcl.Push([]byte(\"world\"))\n\n\tlastChunkCount := snapshot[len(snapshot)-1].count\n\tif lastChunkCount != 2 {\n\t\tt.Error(\"Unexpected number of items:\", lastChunkCount)\n\t}\n}\n\nfunc TestChunkListTail(t *testing.T) {\n\tcl := NewChunkList(NewChunkCache(), func(item *Item, s []byte) bool {\n\t\titem.text = util.ToChars(s)\n\t\treturn true\n\t})\n\ttotal := chunkSize*2 + chunkSize/2\n\tfor i := range total {\n\t\tcl.Push(fmt.Appendf(nil, \"item %d\", i))\n\t}\n\n\tsnapshot, count, changed := cl.Snapshot(0)\n\tassertCount := func(expected int, shouldChange bool) {\n\t\tif count != expected || CountItems(snapshot) != expected {\n\t\t\tt.Errorf(\"Unexpected count: %d (expected: %d)\", count, expected)\n\t\t}\n\t\tif changed != shouldChange {\n\t\t\tt.Error(\"Unexpected change status\")\n\t\t}\n\t}\n\tassertCount(total, false)\n\n\ttail := chunkSize + chunkSize/2\n\tsnapshot, count, changed = cl.Snapshot(tail)\n\tassertCount(tail, true)\n\n\tsnapshot, count, changed = cl.Snapshot(tail)\n\tassertCount(tail, false)\n\n\tsnapshot, count, changed = cl.Snapshot(0)\n\tassertCount(tail, false)\n\n\ttail = chunkSize / 2\n\tsnapshot, count, changed = cl.Snapshot(tail)\n\tassertCount(tail, true)\n}\n"
  },
  {
    "path": "src/constants.go",
    "content": "package fzf\n\nimport (\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nconst (\n\t// Core\n\tcoordinatorDelayMax  time.Duration = 100 * time.Millisecond\n\tcoordinatorDelayStep time.Duration = 10 * time.Millisecond\n\n\t// Reader\n\treaderBufferSize       = 64 * 1024\n\treaderSlabSize         = 128 * 1024\n\treaderPollIntervalMin  = 10 * time.Millisecond\n\treaderPollIntervalStep = 5 * time.Millisecond\n\treaderPollIntervalMax  = 50 * time.Millisecond\n\n\t// Terminal\n\tinitialDelay      = 20 * time.Millisecond\n\tinitialDelayTac   = 100 * time.Millisecond\n\tspinnerDuration   = 100 * time.Millisecond\n\tpreviewCancelWait = 500 * time.Millisecond\n\tpreviewChunkDelay = 100 * time.Millisecond\n\tpreviewDelayed    = 500 * time.Millisecond\n\tmaxPatternLength  = 1000\n\tmaxMulti          = math.MaxInt32\n\n\t// Background processes\n\tmaxBgProcesses          = 30\n\tmaxBgProcessesPerAction = 3\n\n\t// Matcher\n\tprogressMinDuration = 200 * time.Millisecond\n\n\t// Capacity of each chunk\n\tchunkSize     int = 1024\n\tchunkBitWords     = (chunkSize + 63) / 64\n\n\t// Pre-allocated memory slices to minimize GC\n\tslab16Size int = 100 * 1024 // 200KB * 32 = 12.8MB\n\tslab32Size int = 2048       // 8KB * 32 = 256KB\n\n\t// Do not cache results of low selectivity queries\n\tqueryCacheMax int = chunkSize / 2\n\n\t// Not to cache mergers with large lists\n\tmergerCacheMax int = 100000\n\n\t// History\n\tdefaultHistoryMax int = 1000\n\n\t// Jump labels\n\tdefaultJumpLabels string = \"asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\\\"!@#$%^&*()[{]}-_=+\"\n)\n\n// fzf events\nconst (\n\tEvtReadNew util.EventType = iota\n\tEvtReadFin\n\tEvtSearchNew\n\tEvtSearchProgress\n\tEvtSearchFin\n\tEvtReady\n\tEvtQuit\n)\n\nconst (\n\tExitOk        = 0\n\tExitNoMatch   = 1\n\tExitError     = 2\n\tExitBecome    = 126\n\tExitInterrupt = 130\n)\n"
  },
  {
    "path": "src/core.go",
    "content": "// Package fzf implements fzf, a command-line fuzzy finder.\npackage fzf\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n/*\nReader   -> EvtReadFin\nReader   -> EvtReadNew        -> Matcher  (restart)\nTerminal -> EvtSearchNew:bool -> Matcher  (restart)\nMatcher  -> EvtSearchProgress -> Terminal (update info)\nMatcher  -> EvtSearchFin      -> Terminal (update list)\n*/\n\ntype revision struct {\n\tmajor int\n\tminor int\n}\n\nfunc (r *revision) bumpMajor() {\n\tr.major++\n\tr.minor = 0\n}\n\nfunc (r *revision) bumpMinor() {\n\tr.minor++\n}\n\nfunc (r revision) compatible(other revision) bool {\n\treturn r.major == other.major\n}\n\nfunc buildItemTransformer(opts *Options) func(*Item) string {\n\tif opts.AcceptNth != nil {\n\t\tfn := opts.AcceptNth(opts.Delimiter)\n\t\treturn func(item *Item) string {\n\t\t\treturn item.acceptNth(opts.Ansi, opts.Delimiter, fn)\n\t\t}\n\t}\n\treturn func(item *Item) string {\n\t\treturn item.AsString(opts.Ansi)\n\t}\n}\n\n// Run starts fzf\nfunc Run(opts *Options) (int, error) {\n\tif opts.Filter == nil {\n\t\tif opts.useTmux() {\n\t\t\treturn runTmux(os.Args, opts)\n\t\t}\n\n\t\tif needWinpty(opts) {\n\t\t\treturn runWinpty(os.Args, opts)\n\t\t}\n\t}\n\n\tif err := postProcessOptions(opts); err != nil {\n\t\treturn ExitError, err\n\t}\n\n\tdefer util.RunAtExitFuncs()\n\n\t// Output channel given\n\tif opts.Output != nil {\n\t\topts.Printer = func(str string) {\n\t\t\topts.Output <- str\n\t\t}\n\t}\n\n\tsort := opts.Sort > 0\n\tsortCriteria = opts.Criteria\n\n\t// Event channel\n\teventBox := util.NewEventBox()\n\n\t// ANSI code processor\n\tansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {\n\t\treturn util.ToChars(data), nil\n\t}\n\n\tvar lineAnsiState, prevLineAnsiState *ansiState\n\tif opts.Ansi {\n\t\tansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {\n\t\t\tprevLineAnsiState = lineAnsiState\n\t\t\ttrimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)\n\t\t\tlineAnsiState = newState\n\n\t\t\t// Full line background is found. Add a special marker.\n\t\t\tif offsets != nil && newState != nil && len(*offsets) > 0 && newState.lbg >= 0 {\n\t\t\t\tmarker := (*offsets)[len(*offsets)-1]\n\t\t\t\tmarker.offset[0] = marker.offset[1]\n\t\t\t\tmarker.color.bg = newState.lbg\n\t\t\t\tmarker.color.attr = marker.color.attr | tui.FullBg\n\t\t\t\tnewOffsets := append(*offsets, marker)\n\t\t\t\toffsets = &newOffsets\n\n\t\t\t\t// Reset the full-line background color\n\t\t\t\tlineAnsiState.lbg = -1\n\t\t\t}\n\t\t\treturn util.ToChars(stringBytes(trimmed)), offsets\n\t\t}\n\t}\n\n\t// Chunk list\n\tcache := NewChunkCache()\n\tvar chunkList *ChunkList\n\tvar itemIndex int32\n\t// transformItem applies with-nth transformation to an item's raw data.\n\t// It handles ANSI token propagation using prevLineAnsiState for cross-line continuity.\n\ttransformItem := func(item *Item, data []byte, transformer func([]Token, int32) string, index int32) {\n\t\ttokens := Tokenize(byteString(data), opts.Delimiter)\n\t\tif opts.Ansi && len(tokens) > 1 {\n\t\t\tvar ansiState *ansiState\n\t\t\tif prevLineAnsiState != nil {\n\t\t\t\tansiStateDup := *prevLineAnsiState\n\t\t\t\tansiState = &ansiStateDup\n\t\t\t}\n\t\t\tfor _, token := range tokens {\n\t\t\t\tprevAnsiState := ansiState\n\t\t\t\t_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)\n\t\t\t\tif prevAnsiState != nil {\n\t\t\t\t\ttoken.text.Prepend(\"\\x1b[m\" + prevAnsiState.ToString())\n\t\t\t\t} else {\n\t\t\t\t\ttoken.text.Prepend(\"\\x1b[m\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ttransformed := transformer(tokens, index)\n\t\titem.text, item.colors = ansiProcessor(stringBytes(transformed))\n\n\t\t// We should not trim trailing whitespaces with background colors\n\t\tvar maxColorOffset int32\n\t\tif item.colors != nil {\n\t\t\tfor _, ansi := range *item.colors {\n\t\t\t\tif ansi.color.bg >= 0 {\n\t\t\t\t\tmaxColorOffset = max(maxColorOffset, ansi.offset[1])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\titem.text.TrimTrailingWhitespaces(int(maxColorOffset))\n\t}\n\n\tvar nthTransformer func([]Token, int32) string\n\tif opts.WithNth == nil {\n\t\tchunkList = NewChunkList(cache, func(item *Item, data []byte) bool {\n\t\t\titem.text, item.colors = ansiProcessor(data)\n\t\t\titem.text.Index = itemIndex\n\t\t\titemIndex++\n\t\t\treturn true\n\t\t})\n\t} else {\n\t\tnthTransformer = opts.WithNth(opts.Delimiter)\n\t\tchunkList = NewChunkList(cache, func(item *Item, data []byte) bool {\n\t\t\tif nthTransformer == nil {\n\t\t\t\titem.text, item.colors = ansiProcessor(data)\n\t\t\t} else {\n\t\t\t\ttransformItem(item, data, nthTransformer, itemIndex)\n\t\t\t}\n\t\t\titem.text.Index = itemIndex\n\t\t\titem.origText = &data\n\t\t\titemIndex++\n\t\t\treturn true\n\t\t})\n\t}\n\n\t// Process executor\n\texecutor := util.NewExecutor(opts.WithShell)\n\n\t// Terminal I/O\n\tvar terminal *Terminal\n\tvar err error\n\tvar initialEnv []string\n\tinitialReload := opts.extractReloadOnStart()\n\tif opts.Filter == nil {\n\t\tterminal, err = NewTerminal(opts, eventBox, executor)\n\t\tif err != nil {\n\t\t\treturn ExitError, err\n\t\t}\n\t\tif len(initialReload) > 0 {\n\t\t\tvar temps []string\n\t\t\tinitialReload, temps = terminal.replacePlaceholderInInitialCommand(initialReload)\n\t\t\tinitialEnv = terminal.environ()\n\t\t\tdefer removeFiles(temps)\n\t\t}\n\t}\n\n\t// Reader\n\tstreamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync && opts.Bench == 0\n\tvar reader *Reader\n\tvar ingestionStart time.Time\n\tif !streamingFilter {\n\t\treader = NewReader(func(data []byte) bool {\n\t\t\treturn chunkList.Push(data)\n\t\t}, eventBox, executor, opts.ReadZero, opts.Filter == nil)\n\n\t\tingestionStart = time.Now()\n\t\treadyChan := make(chan bool)\n\t\tgo reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)\n\t\t<-readyChan\n\t}\n\n\t// Matcher\n\tforward := true\n\twithPos := false\n\tfor idx := len(opts.Criteria) - 1; idx > 0; idx-- {\n\t\tswitch opts.Criteria[idx] {\n\t\tcase byChunk:\n\t\t\twithPos = true\n\t\tcase byEnd:\n\t\t\tforward = false\n\t\tcase byBegin:\n\t\t\tforward = true\n\t\tcase byPathname:\n\t\t\twithPos = true\n\t\t\tforward = false\n\t\t}\n\t}\n\n\tnth := opts.Nth\n\tinputRevision := revision{}\n\tsnapshotRevision := revision{}\n\tpatternCache := make(map[string]*Pattern)\n\tdenyMutex := sync.Mutex{}\n\tdenylist := make(map[int32]struct{})\n\tclearDenylist := func() {\n\t\tdenyMutex.Lock()\n\t\tif len(denylist) > 0 {\n\t\t\tpatternCache = make(map[string]*Pattern)\n\t\t}\n\t\tdenylist = make(map[int32]struct{})\n\t\tdenyMutex.Unlock()\n\t}\n\theaderLines := int32(opts.HeaderLines)\n\theaderUpdated := false\n\tpatternBuilder := func(runes []rune) *Pattern {\n\t\tdenyMutex.Lock()\n\t\tdenylistCopy := maps.Clone(denylist)\n\t\tdenyMutex.Unlock()\n\t\treturn BuildPattern(cache, patternCache,\n\t\t\topts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,\n\t\t\topts.Filter == nil, nth, opts.Delimiter, inputRevision, runes, denylistCopy, headerLines)\n\t}\n\tmatcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision, opts.Threads)\n\n\t// Filtering mode\n\tif opts.Filter != nil {\n\t\tif opts.PrintQuery {\n\t\t\topts.Printer(*opts.Filter)\n\t\t}\n\n\t\tpattern := patternBuilder([]rune(*opts.Filter))\n\t\tmatcher.sort = pattern.sortable\n\n\t\ttransformer := buildItemTransformer(opts)\n\n\t\tfound := false\n\t\tif streamingFilter {\n\t\t\tslab := util.MakeSlab(slab16Size, slab32Size)\n\t\t\tmutex := sync.Mutex{}\n\t\t\treader := NewReader(\n\t\t\t\tfunc(runes []byte) bool {\n\t\t\t\t\titem := Item{}\n\t\t\t\t\tif chunkList.trans(&item, runes) {\n\t\t\t\t\t\tif item.Index() < headerLines {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmutex.Lock()\n\t\t\t\t\t\tif result, _, _ := pattern.MatchItem(&item, false, slab); result.item != nil {\n\t\t\t\t\t\t\topts.Printer(transformer(&item))\n\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmutex.Unlock()\n\t\t\t\t\t}\n\t\t\t\t\treturn false\n\t\t\t\t}, eventBox, executor, opts.ReadZero, false)\n\t\t\treader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, nil)\n\t\t} else {\n\t\t\teventBox.Unwatch(EvtReadNew)\n\t\t\teventBox.WaitFor(EvtReadFin)\n\t\t\tingestionTime := time.Since(ingestionStart)\n\n\t\t\t// NOTE: Streaming filter is inherently not compatible with --tail\n\t\t\tsnapshot, _, _ := chunkList.Snapshot(opts.Tail)\n\n\t\t\tif opts.Bench > 0 {\n\t\t\t\t// Benchmark mode: repeat scan for the given duration\n\t\t\t\ttotalItems := CountItems(snapshot)\n\t\t\t\tvar matchCount int\n\t\t\t\tvar times []time.Duration\n\t\t\t\tdeadline := time.Now().Add(opts.Bench)\n\t\t\t\tfor time.Now().Before(deadline) {\n\t\t\t\t\tcache.Clear()\n\t\t\t\t\tstart := time.Now()\n\t\t\t\t\tresult := matcher.scan(MatchRequest{\n\t\t\t\t\t\tchunks:  snapshot,\n\t\t\t\t\t\tpattern: pattern})\n\t\t\t\t\ttimes = append(times, time.Since(start))\n\t\t\t\t\tmatchCount = result.merger.Length()\n\t\t\t\t}\n\t\t\t\t// Print stats\n\t\t\t\tvar total time.Duration\n\t\t\t\tminD, maxD := times[0], times[0]\n\t\t\t\tfor _, d := range times {\n\t\t\t\t\ttotal += d\n\t\t\t\t\tif d < minD {\n\t\t\t\t\t\tminD = d\n\t\t\t\t\t}\n\t\t\t\t\tif d > maxD {\n\t\t\t\t\t\tmaxD = d\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tavg := total / time.Duration(len(times))\n\t\t\t\tselectivity := float64(matchCount) / float64(totalItems) * 100\n\t\t\t\tfmt.Printf(\"  %d iterations  avg: %.2fms  min: %.2fms  max: %.2fms  total: %.2fs  items: %d  matches: %d (%.2f%%)  ingestion: %.2fms\\n\",\n\t\t\t\t\tlen(times),\n\t\t\t\t\tfloat64(avg.Microseconds())/1000,\n\t\t\t\t\tfloat64(minD.Microseconds())/1000,\n\t\t\t\t\tfloat64(maxD.Microseconds())/1000,\n\t\t\t\t\ttotal.Seconds(),\n\t\t\t\t\ttotalItems, matchCount, selectivity,\n\t\t\t\t\tfloat64(ingestionTime.Microseconds())/1000)\n\t\t\t\treturn ExitOk, nil\n\t\t\t}\n\n\t\t\tresult := matcher.scan(MatchRequest{\n\t\t\t\tchunks:  snapshot,\n\t\t\t\tpattern: pattern})\n\t\t\tfor i := 0; i < result.merger.Length(); i++ {\n\t\t\t\topts.Printer(transformer(result.merger.Get(i).item))\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif found {\n\t\t\treturn ExitOk, nil\n\t\t}\n\t\treturn ExitNoMatch, nil\n\t}\n\n\t// Synchronous search\n\tif opts.Sync {\n\t\teventBox.Unwatch(EvtReadNew)\n\t\teventBox.WaitFor(EvtReadFin)\n\t}\n\n\t// Go interactive\n\tgo matcher.Loop()\n\tdefer matcher.Stop()\n\n\t// Handling adaptive height\n\tmaxFit := 0 // Maximum number of items that can fit on screen\n\tpadHeight := 0\n\theightUnknown := opts.Height.auto\n\tif heightUnknown {\n\t\tmaxFit, padHeight = terminal.MaxFitAndPad()\n\t}\n\tdeferred := opts.Select1 || opts.Exit0 || opts.Sync\n\tgo terminal.Loop()\n\tif !deferred && !heightUnknown {\n\t\t// Start right away\n\t\tterminal.startChan <- fitpad{-1, -1}\n\t}\n\n\t// Event coordination\n\treading := true\n\tticks := 0\n\tstartTick := 0\n\tvar nextCommand *commandSpec\n\tvar nextEnviron []string\n\teventBox.Watch(EvtReadNew)\n\ttotal := 0\n\tquery := []rune{}\n\tdetermine := func(final bool) {\n\t\tif heightUnknown {\n\t\t\titems := max(0, total-int(headerLines))\n\t\t\tif items >= maxFit || final {\n\t\t\t\tdeferred = false\n\t\t\t\theightUnknown = false\n\t\t\t\tterminal.startChan <- fitpad{min(items, maxFit), padHeight}\n\t\t\t}\n\t\t} else if deferred {\n\t\t\tdeferred = false\n\t\t\tterminal.startChan <- fitpad{-1, -1}\n\t\t}\n\t}\n\n\tuseSnapshot := false\n\tvar snapshot []*Chunk\n\tvar count int\n\trestart := func(command commandSpec, environ []string) {\n\t\tif !useSnapshot {\n\t\t\tclearDenylist()\n\t\t}\n\t\treading = true\n\t\theaderUpdated = false\n\t\tstartTick = ticks\n\t\tchunkList.Clear()\n\t\titemIndex = 0\n\t\tinputRevision.bumpMajor()\n\t\treadyChan := make(chan bool)\n\t\tgo reader.restart(command, environ, readyChan)\n\t\t<-readyChan\n\t}\n\n\texitCode := ExitOk\n\tstop := false\n\tfor {\n\t\tdelay := true\n\t\tticks++\n\t\tinput := func() []rune {\n\t\t\tpaused, input := terminal.Input()\n\t\t\tif !paused {\n\t\t\t\tquery = input\n\t\t\t}\n\t\t\treturn query\n\t\t}\n\t\teventBox.Wait(func(events *util.Events) {\n\t\t\tif _, fin := (*events)[EvtReadFin]; fin {\n\t\t\t\tdelete(*events, EvtReadNew)\n\t\t\t}\n\t\t\tfor evt, value := range *events {\n\t\t\t\tswitch evt {\n\t\t\t\tcase EvtQuit:\n\t\t\t\t\tif reading {\n\t\t\t\t\t\treader.terminate()\n\t\t\t\t\t}\n\t\t\t\t\tquitSignal := value.(quitSignal)\n\t\t\t\t\texitCode = quitSignal.code\n\t\t\t\t\terr = quitSignal.err\n\t\t\t\t\tstop = true\n\t\t\t\t\treturn\n\t\t\t\tcase EvtReadNew, EvtReadFin:\n\t\t\t\t\tif evt == EvtReadFin && nextCommand != nil {\n\t\t\t\t\t\trestart(*nextCommand, nextEnviron)\n\t\t\t\t\t\tnextCommand = nil\n\t\t\t\t\t\tnextEnviron = nil\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} else {\n\t\t\t\t\t\treading = reading && evt == EvtReadNew\n\t\t\t\t\t}\n\t\t\t\t\tif useSnapshot && evt == EvtReadFin { // reload-sync\n\t\t\t\t\t\tclearDenylist()\n\t\t\t\t\t\tuseSnapshot = false\n\t\t\t\t\t}\n\t\t\t\t\tif !useSnapshot {\n\t\t\t\t\t\tif !snapshotRevision.compatible(inputRevision) {\n\t\t\t\t\t\t\tquery = []rune{}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar changed bool\n\t\t\t\t\t\tsnapshot, count, changed = chunkList.Snapshot(opts.Tail)\n\t\t\t\t\t\tif changed {\n\t\t\t\t\t\t\tinputRevision.bumpMinor()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsnapshotRevision = inputRevision\n\t\t\t\t\t}\n\t\t\t\t\ttotal = count\n\t\t\t\t\tterminal.UpdateCount(max(0, total-int(headerLines)), !reading, value.(*string))\n\t\t\t\t\tif headerLines > 0 && !headerUpdated {\n\t\t\t\t\t\tterminal.UpdateHeader(GetItems(snapshot, int(headerLines)))\n\t\t\t\t\t\theaderUpdated = int32(total) >= headerLines\n\t\t\t\t\t}\n\t\t\t\t\tif heightUnknown && !deferred {\n\t\t\t\t\t\tdetermine(!reading)\n\t\t\t\t\t}\n\t\t\t\t\tif !useSnapshot || evt == EvtReadFin {\n\t\t\t\t\t\tmatcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)\n\t\t\t\t\t}\n\n\t\t\t\tcase EvtSearchNew:\n\t\t\t\t\tvar command *commandSpec\n\t\t\t\t\tvar environ []string\n\t\t\t\t\tvar changed bool\n\t\t\t\t\theaderLinesChanged := false\n\t\t\t\t\twithNthChanged := false\n\t\t\t\t\tswitch val := value.(type) {\n\t\t\t\t\tcase searchRequest:\n\t\t\t\t\t\tsort = val.sort\n\t\t\t\t\t\tcommand = val.command\n\t\t\t\t\t\tenviron = val.environ\n\t\t\t\t\t\tchanged = val.changed\n\t\t\t\t\t\tbump := false\n\t\t\t\t\t\tif len(val.denylist) > 0 && val.revision.compatible(inputRevision) {\n\t\t\t\t\t\t\tdenyMutex.Lock()\n\t\t\t\t\t\t\tfor _, itemIndex := range val.denylist {\n\t\t\t\t\t\t\t\tdenylist[itemIndex] = struct{}{}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdenyMutex.Unlock()\n\t\t\t\t\t\t\tbump = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif val.nth != nil {\n\t\t\t\t\t\t\t// Change nth and clear caches\n\t\t\t\t\t\t\tnth = *val.nth\n\t\t\t\t\t\t\tbump = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif val.headerLines != nil {\n\t\t\t\t\t\t\theaderLines = int32(*val.headerLines)\n\t\t\t\t\t\t\theaderUpdated = false\n\t\t\t\t\t\t\theaderLinesChanged = true\n\t\t\t\t\t\t\tbump = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif val.withNth != nil {\n\t\t\t\t\t\t\tnewTransformer := val.withNth.fn\n\t\t\t\t\t\t\t// Cancel any in-flight scan and block the terminal from reading\n\t\t\t\t\t\t\t// items before mutating them in-place. Snapshot shares middle\n\t\t\t\t\t\t\t// chunk pointers, so the matcher and terminal can race with us.\n\t\t\t\t\t\t\tmatcher.CancelScan()\n\t\t\t\t\t\t\tterminal.PauseRendering()\n\t\t\t\t\t\t\t// Reset cross-line ANSI state before re-processing all items\n\t\t\t\t\t\t\tlineAnsiState = nil\n\t\t\t\t\t\t\tprevLineAnsiState = nil\n\t\t\t\t\t\t\tchunkList.ForEachItem(func(item *Item) {\n\t\t\t\t\t\t\t\torigBytes := *item.origText\n\t\t\t\t\t\t\t\tsavedIndex := item.Index()\n\t\t\t\t\t\t\t\tif newTransformer != nil {\n\t\t\t\t\t\t\t\t\ttransformItem(item, origBytes, newTransformer, savedIndex)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\titem.text, item.colors = ansiProcessor(origBytes)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\titem.text.Index = savedIndex\n\t\t\t\t\t\t\t\titem.transformed = nil\n\t\t\t\t\t\t\t}, func() {\n\t\t\t\t\t\t\t\tnthTransformer = newTransformer\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tterminal.ResumeRendering()\n\t\t\t\t\t\t\tmatcher.ResumeScan()\n\t\t\t\t\t\t\twithNthChanged = true\n\t\t\t\t\t\t\tbump = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif bump {\n\t\t\t\t\t\t\tpatternCache = make(map[string]*Pattern)\n\t\t\t\t\t\t\tcache.Clear()\n\t\t\t\t\t\t\tinputRevision.bumpMinor()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif command != nil {\n\t\t\t\t\t\t\tuseSnapshot = val.sync\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif command != nil {\n\t\t\t\t\t\tif reading {\n\t\t\t\t\t\t\treader.terminate()\n\t\t\t\t\t\t\tnextCommand = command\n\t\t\t\t\t\t\tnextEnviron = environ\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trestart(*command, environ)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !changed {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif !useSnapshot {\n\t\t\t\t\t\tnewSnapshot, newCount, changed := chunkList.Snapshot(opts.Tail)\n\t\t\t\t\t\tif changed {\n\t\t\t\t\t\t\tinputRevision.bumpMinor()\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// We want to avoid showing empty list when reload is triggered\n\t\t\t\t\t\t// and the query string is changed at the same time i.e. command != nil && changed\n\t\t\t\t\t\tif command == nil || newCount > 0 {\n\t\t\t\t\t\t\tif snapshotRevision != inputRevision {\n\t\t\t\t\t\t\t\tquery = []rune{}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsnapshot = newSnapshot\n\t\t\t\t\t\t\tsnapshotRevision = inputRevision\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif headerLinesChanged {\n\t\t\t\t\t\tterminal.UpdateCount(max(0, total-int(headerLines)), !reading, nil)\n\t\t\t\t\t\tif headerLines > 0 {\n\t\t\t\t\t\t\tterminal.UpdateHeader(GetItems(snapshot, int(headerLines)))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tterminal.UpdateHeader(nil)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if withNthChanged && headerLines > 0 {\n\t\t\t\t\t\tterminal.UpdateHeader(GetItems(snapshot, int(headerLines)))\n\t\t\t\t\t}\n\t\t\t\t\tmatcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)\n\t\t\t\t\tdelay = false\n\n\t\t\t\tcase EvtSearchProgress:\n\t\t\t\t\tswitch val := value.(type) {\n\t\t\t\t\tcase float32:\n\t\t\t\t\t\tterminal.UpdateProgress(val)\n\t\t\t\t\t}\n\n\t\t\t\tcase EvtSearchFin:\n\t\t\t\t\tswitch val := value.(type) {\n\t\t\t\t\tcase MatchResult:\n\t\t\t\t\t\tmerger := val.merger\n\t\t\t\t\t\tif deferred {\n\t\t\t\t\t\t\tcount := merger.Length()\n\t\t\t\t\t\t\tif opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {\n\t\t\t\t\t\t\t\tdetermine(merger.final)\n\t\t\t\t\t\t\t} else if merger.final {\n\t\t\t\t\t\t\t\tif opts.Exit0 && count == 0 || opts.Select1 && count == 1 {\n\t\t\t\t\t\t\t\t\tif opts.PrintQuery {\n\t\t\t\t\t\t\t\t\t\topts.Printer(opts.Query)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif len(opts.Expect) > 0 {\n\t\t\t\t\t\t\t\t\t\topts.Printer(\"\")\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttransformer := buildItemTransformer(opts)\n\t\t\t\t\t\t\t\t\tfor i := range count {\n\t\t\t\t\t\t\t\t\t\topts.Printer(transformer(merger.Get(i).item))\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif count == 0 {\n\t\t\t\t\t\t\t\t\t\texitCode = ExitNoMatch\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tstop = true\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdetermine(merger.final)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tterminal.UpdateList(val)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tevents.Clear()\n\t\t})\n\t\tif stop {\n\t\t\tbreak\n\t\t}\n\t\tif delay && reading {\n\t\t\tdur := util.Constrain(\n\t\t\t\ttime.Duration(ticks-startTick)*coordinatorDelayStep,\n\t\t\t\t0, coordinatorDelayMax)\n\t\t\ttime.Sleep(dur)\n\t\t}\n\t}\n\treturn exitCode, err\n}\n"
  },
  {
    "path": "src/functions.go",
    "content": "package fzf\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"unsafe\"\n)\n\nfunc WriteTemporaryFile(data []string, printSep string) string {\n\tf, err := os.CreateTemp(\"\", \"fzf-temp-*\")\n\tif err != nil {\n\t\t// Unable to create temporary file\n\t\t// FIXME: Should we terminate the program?\n\t\treturn \"\"\n\t}\n\tdefer f.Close()\n\n\tf.WriteString(strings.Join(data, printSep))\n\tf.WriteString(printSep)\n\treturn f.Name()\n}\n\nfunc removeFiles(files []string) {\n\tfor _, filename := range files {\n\t\tos.Remove(filename)\n\t}\n}\n\nfunc stringBytes(data string) []byte {\n\treturn unsafe.Slice(unsafe.StringData(data), len(data))\n}\n\nfunc byteString(data []byte) string {\n\treturn unsafe.String(unsafe.SliceData(data), len(data))\n}\n"
  },
  {
    "path": "src/history.go",
    "content": "package fzf\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"strings\"\n)\n\n// History struct represents input history\ntype History struct {\n\tpath     string\n\tlines    []string\n\tmodified map[int]string\n\tmaxSize  int\n\tcursor   int\n}\n\n// NewHistory returns the pointer to a new History struct\nfunc NewHistory(path string, maxSize int) (*History, error) {\n\tfmtError := func(e error) error {\n\t\tif os.IsPermission(e) {\n\t\t\treturn errors.New(\"permission denied: \" + path)\n\t\t}\n\t\treturn errors.New(\"invalid history file: \" + e.Error())\n\t}\n\n\t// Read history file\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\t// If it doesn't exist, check if we can create a file with the name\n\t\tif os.IsNotExist(err) {\n\t\t\tdata = []byte{}\n\t\t\tif err := os.WriteFile(path, data, 0600); err != nil {\n\t\t\t\treturn nil, fmtError(err)\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, fmtError(err)\n\t\t}\n\t}\n\t// Split lines and limit the maximum number of lines\n\tlines := strings.Split(strings.Trim(string(data), \"\\n\"), \"\\n\")\n\tif len(lines[len(lines)-1]) > 0 {\n\t\tlines = append(lines, \"\")\n\t}\n\treturn &History{\n\t\tpath:     path,\n\t\tmaxSize:  maxSize,\n\t\tlines:    lines,\n\t\tmodified: make(map[int]string),\n\t\tcursor:   len(lines) - 1}, nil\n}\n\nfunc (h *History) append(line string) error {\n\t// We don't append empty lines\n\tif len(line) == 0 {\n\t\treturn nil\n\t}\n\n\tlines := append(h.lines[:len(h.lines)-1], line)\n\tif len(lines) > h.maxSize {\n\t\tlines = lines[len(lines)-h.maxSize:]\n\t}\n\th.lines = append(lines, \"\")\n\treturn os.WriteFile(h.path, []byte(strings.Join(h.lines, \"\\n\")), 0600)\n}\n\nfunc (h *History) override(str string) {\n\t// You can update the history, but they're not written to the file\n\tif h.cursor == len(h.lines)-1 {\n\t\th.lines[h.cursor] = str\n\t} else if h.cursor < len(h.lines)-1 {\n\t\th.modified[h.cursor] = str\n\t}\n}\n\nfunc (h *History) current() string {\n\tif str, prs := h.modified[h.cursor]; prs {\n\t\treturn str\n\t}\n\treturn h.lines[h.cursor]\n}\n\nfunc (h *History) previous() string {\n\tif h.cursor > 0 {\n\t\th.cursor--\n\t}\n\treturn h.current()\n}\n\nfunc (h *History) next() string {\n\tif h.cursor < len(h.lines)-1 {\n\t\th.cursor++\n\t}\n\treturn h.current()\n}\n"
  },
  {
    "path": "src/history_test.go",
    "content": "package fzf\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"testing\"\n)\n\nfunc TestHistory(t *testing.T) {\n\tmaxHistory := 50\n\n\t// Invalid arguments\n\tvar paths []string\n\tif runtime.GOOS == \"windows\" {\n\t\t// GOPATH should exist, so we shouldn't be able to override it\n\t\tpaths = []string{os.Getenv(\"GOPATH\")}\n\t} else {\n\t\tpaths = []string{\"/etc\", \"/proc\"}\n\t}\n\n\tfor _, path := range paths {\n\t\tif _, e := NewHistory(path, maxHistory); e == nil {\n\t\t\tt.Error(\"Error expected for: \" + path)\n\t\t}\n\t}\n\n\tf, _ := os.CreateTemp(\"\", \"fzf-history\")\n\tf.Close()\n\n\t{ // Append lines\n\t\th, _ := NewHistory(f.Name(), maxHistory)\n\t\tfor i := 0; i < maxHistory+10; i++ {\n\t\t\th.append(\"foobar\")\n\t\t}\n\t}\n\t{ // Read lines\n\t\th, _ := NewHistory(f.Name(), maxHistory)\n\t\tif len(h.lines) != maxHistory+1 {\n\t\t\tt.Errorf(\"Expected: %d, actual: %d\\n\", maxHistory+1, len(h.lines))\n\t\t}\n\t\tfor i := range maxHistory {\n\t\t\tif h.lines[i] != \"foobar\" {\n\t\t\t\tt.Error(\"Expected: foobar, actual: \" + h.lines[i])\n\t\t\t}\n\t\t}\n\t}\n\t{ // Append lines\n\t\th, _ := NewHistory(f.Name(), maxHistory)\n\t\th.append(\"barfoo\")\n\t\th.append(\"\")\n\t\th.append(\"foobarbaz\")\n\t}\n\t{ // Read lines again\n\t\th, _ := NewHistory(f.Name(), maxHistory)\n\t\tif len(h.lines) != maxHistory+1 {\n\t\t\tt.Errorf(\"Expected: %d, actual: %d\\n\", maxHistory+1, len(h.lines))\n\t\t}\n\t\tcompare := func(idx int, exp string) {\n\t\t\tif h.lines[idx] != exp {\n\t\t\t\tt.Errorf(\"Expected: %s, actual: %s\\n\", exp, h.lines[idx])\n\t\t\t}\n\t\t}\n\t\tcompare(maxHistory-3, \"foobar\")\n\t\tcompare(maxHistory-2, \"barfoo\")\n\t\tcompare(maxHistory-1, \"foobarbaz\")\n\t}\n}\n"
  },
  {
    "path": "src/item.go",
    "content": "package fzf\n\nimport (\n\t\"math\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\ntype transformed struct {\n\t// Because nth can be changed dynamically by change-nth action, we need to\n\t// keep the revision number at the time of transformation.\n\trevision revision\n\ttokens   []Token\n}\n\n// Item represents each input line. 56 bytes.\ntype Item struct {\n\ttext        util.Chars    // 32 = 24 + 1 + 1 + 2 + 4\n\ttransformed *transformed  // 8\n\torigText    *[]byte       // 8\n\tcolors      *[]ansiOffset // 8\n}\n\n// Index returns ordinal index of the Item\nfunc (item *Item) Index() int32 {\n\treturn item.text.Index\n}\n\nvar minItem = Item{text: util.Chars{Index: math.MinInt32}}\n\nfunc (item *Item) TrimLength() uint16 {\n\treturn item.text.TrimLength()\n}\n\n// Colors returns ansiOffsets of the Item\nfunc (item *Item) Colors() []ansiOffset {\n\tif item.colors == nil {\n\t\treturn []ansiOffset{}\n\t}\n\treturn *item.colors\n}\n\n// AsString returns the original string\nfunc (item *Item) AsString(stripAnsi bool) string {\n\tif item.origText != nil {\n\t\tif stripAnsi {\n\t\t\ttrimmed, _, _ := extractColor(string(*item.origText), nil, nil)\n\t\t\treturn trimmed\n\t\t}\n\t\treturn string(*item.origText)\n\t}\n\treturn item.text.ToString()\n}\n\nfunc (item *Item) acceptNth(stripAnsi bool, delimiter Delimiter, transformer func([]Token, int32) string) string {\n\ttokens := Tokenize(item.AsString(stripAnsi), delimiter)\n\ttransformed := transformer(tokens, item.Index())\n\treturn StripLastDelimiter(transformed, delimiter)\n}\n"
  },
  {
    "path": "src/item_test.go",
    "content": "package fzf\n\nimport (\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc TestStringPtr(t *testing.T) {\n\torig := []byte(\"\\x1b[34mfoo\")\n\ttext := []byte(\"\\x1b[34mbar\")\n\titem := Item{origText: &orig, text: util.ToChars(text)}\n\tif item.AsString(true) != \"foo\" || item.AsString(false) != string(orig) {\n\t\tt.Fail()\n\t}\n\tif item.AsString(true) != \"foo\" {\n\t\tt.Fail()\n\t}\n\titem.origText = nil\n\tif item.AsString(true) != string(text) || item.AsString(false) != string(text) {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "src/matcher.go",
    "content": "package fzf\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n// MatchRequest represents a search request\ntype MatchRequest struct {\n\tchunks   []*Chunk\n\tpattern  *Pattern\n\tfinal    bool\n\tsort     bool\n\trevision revision\n}\n\ntype MatchResult struct {\n\tmerger     *Merger\n\tpassMerger *Merger\n\tcancelled  bool\n}\n\nfunc (mr MatchResult) cacheable() bool {\n\treturn mr.merger != nil && mr.merger.cacheable()\n}\n\nfunc (mr MatchResult) final() bool {\n\treturn mr.merger != nil && mr.merger.final\n}\n\n// Matcher is responsible for performing search\ntype Matcher struct {\n\tcache          *ChunkCache\n\tpatternBuilder func([]rune) *Pattern\n\tsort           bool\n\ttac            bool\n\teventBox       *util.EventBox\n\treqBox         *util.EventBox\n\tpartitions     int\n\tslab           []*util.Slab\n\tsortBuf        [][]Result\n\tmergerCache    map[string]MatchResult\n\trevision       revision\n\tscanMutex      sync.Mutex\n\tcancelScan     *util.AtomicBool\n}\n\nconst (\n\treqRetry util.EventType = iota\n\treqReset\n)\n\n// NewMatcher returns a new Matcher\nfunc NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,\n\tsort bool, tac bool, eventBox *util.EventBox, revision revision, threads int) *Matcher {\n\tpartitions := runtime.NumCPU()\n\tif threads > 0 {\n\t\tpartitions = threads\n\t}\n\treturn &Matcher{\n\t\tcache:          cache,\n\t\tpatternBuilder: patternBuilder,\n\t\tsort:           sort,\n\t\ttac:            tac,\n\t\teventBox:       eventBox,\n\t\treqBox:         util.NewEventBox(),\n\t\tpartitions:     partitions,\n\t\tslab:           make([]*util.Slab, partitions),\n\t\tsortBuf:        make([][]Result, partitions),\n\t\tmergerCache:    make(map[string]MatchResult),\n\t\trevision:       revision,\n\t\tcancelScan:     util.NewAtomicBool(false)}\n}\n\n// Loop puts Matcher in action\nfunc (m *Matcher) Loop() {\n\tprevCount := 0\n\n\tfor {\n\t\tvar request MatchRequest\n\n\t\tstop := false\n\t\tm.reqBox.Wait(func(events *util.Events) {\n\t\t\tfor t, val := range *events {\n\t\t\t\tif t == reqQuit {\n\t\t\t\t\tstop = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tswitch val := val.(type) {\n\t\t\t\tcase MatchRequest:\n\t\t\t\t\trequest = val\n\t\t\t\tdefault:\n\t\t\t\t\tpanic(fmt.Sprintf(\"Unexpected type: %T\", val))\n\t\t\t\t}\n\t\t\t}\n\t\t\tevents.Clear()\n\t\t})\n\t\tif stop {\n\t\t\tbreak\n\t\t}\n\n\t\tcacheCleared := false\n\t\tif request.sort != m.sort || request.revision != m.revision {\n\t\t\tm.sort = request.sort\n\t\t\tm.mergerCache = make(map[string]MatchResult)\n\t\t\tif !request.revision.compatible(m.revision) {\n\t\t\t\tm.cache.Clear()\n\t\t\t}\n\t\t\tm.revision = request.revision\n\t\t\tcacheCleared = true\n\t\t}\n\n\t\t// Restart search\n\t\tpatternString := request.pattern.AsString()\n\t\tvar result MatchResult\n\t\tcount := CountItems(request.chunks)\n\n\t\tif !cacheCleared {\n\t\t\tif count == prevCount {\n\t\t\t\t// Look up mergerCache\n\t\t\t\tif cached, found := m.mergerCache[patternString]; found && cached.final() == request.final {\n\t\t\t\t\tresult = cached\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Invalidate mergerCache\n\t\t\t\tprevCount = count\n\t\t\t\tm.mergerCache = make(map[string]MatchResult)\n\t\t\t}\n\t\t}\n\n\t\tif result.merger == nil {\n\t\t\tm.scanMutex.Lock()\n\t\t\tresult = m.scan(request)\n\t\t\tm.scanMutex.Unlock()\n\t\t}\n\n\t\tif !result.cancelled {\n\t\t\tif result.cacheable() {\n\t\t\t\tm.mergerCache[patternString] = result\n\t\t\t}\n\t\t\tresult.merger.final = request.final\n\t\t\tm.eventBox.Set(EvtSearchFin, result)\n\t\t}\n\t}\n}\n\ntype partialResult struct {\n\tindex   int\n\tmatches []Result\n}\n\nfunc (m *Matcher) scan(request MatchRequest) MatchResult {\n\tstartedAt := time.Now()\n\n\tnumChunks := len(request.chunks)\n\tif numChunks == 0 {\n\t\tm := EmptyMerger(request.revision)\n\t\treturn MatchResult{m, m, false}\n\t}\n\tpattern := request.pattern\n\tpassMerger := PassMerger(&request.chunks, m.tac, request.revision, pattern.startIndex)\n\tif pattern.IsEmpty() {\n\t\treturn MatchResult{passMerger, passMerger, false}\n\t}\n\n\tminIndex := request.chunks[0].items[0].Index()\n\tmaxIndex := request.chunks[numChunks-1].lastIndex(minIndex)\n\tcancelled := util.NewAtomicBool(false)\n\n\tnumWorkers := min(m.partitions, numChunks)\n\tvar nextChunk atomic.Int32\n\tresultChan := make(chan partialResult, numWorkers)\n\tcountChan := make(chan int, numChunks)\n\twaitGroup := sync.WaitGroup{}\n\n\tfor idx := range numWorkers {\n\t\twaitGroup.Add(1)\n\t\tif m.slab[idx] == nil {\n\t\t\tm.slab[idx] = util.MakeSlab(slab16Size, slab32Size)\n\t\t}\n\t\tgo func(idx int, slab *util.Slab) {\n\t\t\tdefer waitGroup.Done()\n\t\t\tvar matches []Result\n\t\t\tfor {\n\t\t\t\tci := int(nextChunk.Add(1)) - 1\n\t\t\t\tif ci >= numChunks {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tchunkMatches := request.pattern.Match(request.chunks[ci], slab)\n\t\t\t\tmatches = append(matches, chunkMatches...)\n\t\t\t\tif cancelled.Get() {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcountChan <- len(chunkMatches)\n\t\t\t}\n\t\t\tif m.sort && request.pattern.sortable {\n\t\t\t\tm.sortBuf[idx] = radixSortResults(matches, m.tac, m.sortBuf[idx])\n\t\t\t}\n\t\t\tresultChan <- partialResult{idx, matches}\n\t\t}(idx, m.slab[idx])\n\t}\n\n\twait := func() bool {\n\t\tcancelled.Set(true)\n\t\twaitGroup.Wait()\n\t\treturn true\n\t}\n\n\tcount := 0\n\tmatchCount := 0\n\tfor matchesInChunk := range countChan {\n\t\tcount++\n\t\tmatchCount += matchesInChunk\n\n\t\tif count == numChunks {\n\t\t\tbreak\n\t\t}\n\n\t\tif m.cancelScan.Get() || m.reqBox.Peek(reqReset) {\n\t\t\treturn MatchResult{nil, nil, wait()}\n\t\t}\n\n\t\tif time.Since(startedAt) > progressMinDuration {\n\t\t\tm.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))\n\t\t}\n\t}\n\n\tpartialResults := make([][]Result, numWorkers)\n\tfor range numWorkers {\n\t\tpartialResult := <-resultChan\n\t\tpartialResults[partialResult.index] = partialResult.matches\n\t}\n\tmerger := NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex)\n\treturn MatchResult{merger, passMerger, false}\n}\n\n// Reset is called to interrupt/signal the ongoing search\nfunc (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision revision) {\n\tpattern := m.patternBuilder(patternRunes)\n\n\tvar event util.EventType\n\tif cancel {\n\t\tevent = reqReset\n\t} else {\n\t\tevent = reqRetry\n\t}\n\tm.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})\n}\n\n// CancelScan cancels any in-flight scan, waits for it to finish,\n// and prevents new scans from starting until ResumeScan is called.\n// This is used to safely mutate shared items (e.g., during with-nth changes).\nfunc (m *Matcher) CancelScan() {\n\tm.cancelScan.Set(true)\n\tm.scanMutex.Lock()\n\tm.cancelScan.Set(false)\n}\n\n// ResumeScan allows scans to proceed again after CancelScan.\nfunc (m *Matcher) ResumeScan() {\n\tm.scanMutex.Unlock()\n}\n\nfunc (m *Matcher) Stop() {\n\tm.reqBox.Set(reqQuit, nil)\n}\n"
  },
  {
    "path": "src/merger.go",
    "content": "package fzf\n\nimport \"fmt\"\n\n// EmptyMerger is a Merger with no data\nfunc EmptyMerger(revision revision) *Merger {\n\treturn NewMerger(nil, [][]Result{}, false, false, revision, 0, 0)\n}\n\n// Merger holds a set of locally sorted lists of items and provides the view of\n// a single, globally-sorted list\ntype Merger struct {\n\tpattern    *Pattern\n\tlists      [][]Result\n\tmerged     []Result\n\tchunks     *[]*Chunk\n\tcursors    []int\n\tsorted     bool\n\ttac        bool\n\tfinal      bool\n\tcount      int\n\tpass       bool\n\tstartIndex int\n\trevision   revision\n\tminIndex   int32\n\tmaxIndex   int32\n}\n\n// PassMerger returns a new Merger that simply returns the items in the\n// original order. startIndex items are skipped from the beginning.\nfunc PassMerger(chunks *[]*Chunk, tac bool, revision revision, startIndex int32) *Merger {\n\tvar minIndex, maxIndex int32\n\tif len(*chunks) > 0 {\n\t\tminIndex = (*chunks)[0].items[0].Index()\n\t\tmaxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)\n\t}\n\tsi := int(startIndex)\n\tmg := Merger{\n\t\tpattern:    nil,\n\t\tchunks:     chunks,\n\t\ttac:        tac,\n\t\tcount:      0,\n\t\tpass:       true,\n\t\tstartIndex: si,\n\t\trevision:   revision,\n\t\tminIndex:   minIndex + startIndex,\n\t\tmaxIndex:   maxIndex}\n\n\tfor _, chunk := range *mg.chunks {\n\t\tmg.count += chunk.count\n\t}\n\tmg.count = max(0, mg.count-si)\n\treturn &mg\n}\n\n// NewMerger returns a new Merger\nfunc NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32, maxIndex int32) *Merger {\n\tmg := Merger{\n\t\tpattern:  pattern,\n\t\tlists:    lists,\n\t\tmerged:   []Result{},\n\t\tchunks:   nil,\n\t\tcursors:  make([]int, len(lists)),\n\t\tsorted:   sorted,\n\t\ttac:      tac,\n\t\tfinal:    false,\n\t\tcount:    0,\n\t\trevision: revision,\n\t\tminIndex: minIndex,\n\t\tmaxIndex: maxIndex}\n\n\tfor _, list := range mg.lists {\n\t\tmg.count += len(list)\n\t}\n\treturn &mg\n}\n\n// Revision returns revision number\nfunc (mg *Merger) Revision() revision {\n\treturn mg.revision\n}\n\n// Length returns the number of items\nfunc (mg *Merger) Length() int {\n\treturn mg.count\n}\n\nfunc (mg *Merger) First() Result {\n\tif mg.tac && !mg.sorted {\n\t\treturn mg.Get(mg.count - 1)\n\t}\n\treturn mg.Get(0)\n}\n\n// FindIndex returns the index of the item with the given item index\nfunc (mg *Merger) FindIndex(itemIndex int32) int {\n\tindex := -1\n\tif mg.pass {\n\t\tindex = int(itemIndex - mg.minIndex)\n\t\tif mg.tac {\n\t\t\tindex = mg.count - index - 1\n\t\t}\n\t} else {\n\t\tfor i := 0; i < mg.count; i++ {\n\t\t\tif mg.Get(i).item.Index() == itemIndex {\n\t\t\t\tindex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn index\n}\n\n// Get returns the pointer to the Result object indexed by the given integer\nfunc (mg *Merger) Get(idx int) Result {\n\tif mg.chunks != nil {\n\t\tif mg.tac {\n\t\t\tidx = mg.count - idx - 1\n\t\t}\n\t\tidx += mg.startIndex\n\t\tfirstChunk := (*mg.chunks)[0]\n\t\tif firstChunk.count < chunkSize && idx >= firstChunk.count {\n\t\t\tidx -= firstChunk.count\n\n\t\t\tchunk := (*mg.chunks)[idx/chunkSize+1]\n\t\t\treturn Result{item: &chunk.items[idx%chunkSize]}\n\t\t}\n\t\tchunk := (*mg.chunks)[idx/chunkSize]\n\t\treturn Result{item: &chunk.items[idx%chunkSize]}\n\t}\n\n\tif mg.sorted {\n\t\treturn mg.mergedGet(idx)\n\t}\n\n\tif mg.tac {\n\t\tidx = mg.count - idx - 1\n\t}\n\treturn mg.mergedGet(idx)\n}\n\nfunc (mg *Merger) ToMap() map[int32]Result {\n\tret := make(map[int32]Result, mg.count)\n\tfor i := 0; i < mg.count; i++ {\n\t\tresult := mg.Get(i)\n\t\tret[result.Index()] = result\n\t}\n\treturn ret\n}\n\nfunc (mg *Merger) cacheable() bool {\n\treturn mg.count < mergerCacheMax\n}\n\nfunc (mg *Merger) mergedGet(idx int) Result {\n\tfor i := len(mg.merged); i <= idx; i++ {\n\t\tminRank := minRank()\n\t\tminIdx := -1\n\t\tfor listIdx, list := range mg.lists {\n\t\t\tcursor := mg.cursors[listIdx]\n\t\t\tif cursor < 0 || cursor == len(list) {\n\t\t\t\tmg.cursors[listIdx] = -1\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif cursor >= 0 {\n\t\t\t\trank := list[cursor]\n\t\t\t\tif minIdx < 0 || mg.sorted && compareRanks(rank, minRank, mg.tac) || !mg.sorted && rank.item.Index() < minRank.item.Index() {\n\t\t\t\t\tminRank = rank\n\t\t\t\t\tminIdx = listIdx\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif minIdx >= 0 {\n\t\t\tchosen := mg.lists[minIdx]\n\t\t\tmg.merged = append(mg.merged, chosen[mg.cursors[minIdx]])\n\t\t\tmg.cursors[minIdx]++\n\t\t} else {\n\t\t\tpanic(fmt.Sprintf(\"Index out of bounds (sorted, %d/%d)\", i, mg.count))\n\t\t}\n\t}\n\treturn mg.merged[idx]\n}\n"
  },
  {
    "path": "src/merger_test.go",
    "content": "package fzf\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc assert(t *testing.T, cond bool, msg ...string) {\n\tif !cond {\n\t\tt.Error(msg)\n\t}\n}\n\nfunc randResult() Result {\n\tstr := fmt.Sprintf(\"%d\", rand.Uint32())\n\tchars := util.ToChars([]byte(str))\n\tchars.Index = rand.Int31()\n\treturn Result{item: &Item{text: chars}}\n}\n\nfunc TestEmptyMerger(t *testing.T) {\n\tr := revision{}\n\tassert(t, EmptyMerger(r).Length() == 0, \"Not empty\")\n\tassert(t, EmptyMerger(r).count == 0, \"Invalid count\")\n\tassert(t, len(EmptyMerger(r).lists) == 0, \"Invalid lists\")\n\tassert(t, len(EmptyMerger(r).merged) == 0, \"Invalid merged list\")\n}\n\nfunc buildLists(partiallySorted bool) ([][]Result, []Result) {\n\tnumLists := 4\n\tlists := make([][]Result, numLists)\n\tcnt := 0\n\tfor i := range numLists {\n\t\tnumResults := rand.Int() % 20\n\t\tcnt += numResults\n\t\tlists[i] = make([]Result, numResults)\n\t\tfor j := range numResults {\n\t\t\titem := randResult()\n\t\t\tlists[i][j] = item\n\t\t}\n\t\tif partiallySorted {\n\t\t\tsort.Sort(ByRelevance(lists[i]))\n\t\t}\n\t}\n\titems := []Result{}\n\tfor _, list := range lists {\n\t\titems = append(items, list...)\n\t}\n\treturn lists, items\n}\n\nfunc TestMergerUnsorted(t *testing.T) {\n\tlists, _ := buildLists(false)\n\n\t// Sort each list by index to simulate real worker behavior\n\t// (workers process chunks in ascending order via nextChunk.Add(1))\n\tfor _, list := range lists {\n\t\tsort.Slice(list, func(i, j int) bool {\n\t\t\treturn list[i].item.Index() < list[j].item.Index()\n\t\t})\n\t}\n\titems := []Result{}\n\tfor _, list := range lists {\n\t\titems = append(items, list...)\n\t}\n\tsort.Slice(items, func(i, j int) bool {\n\t\treturn items[i].item.Index() < items[j].item.Index()\n\t})\n\tcnt := len(items)\n\n\t// Not sorted: items in ascending index order\n\tmg := NewMerger(nil, lists, false, false, revision{}, 0, 0)\n\tassert(t, cnt == mg.Length(), \"Invalid Length\")\n\tfor i := range cnt {\n\t\tassert(t, items[i] == mg.Get(i), \"Invalid Get\")\n\t}\n}\n\nfunc TestMergerSorted(t *testing.T) {\n\tlists, items := buildLists(true)\n\tcnt := len(items)\n\n\t// Sorted sorted order\n\tmg := NewMerger(nil, lists, true, false, revision{}, 0, 0)\n\tassert(t, cnt == mg.Length(), \"Invalid Length\")\n\tsort.Sort(ByRelevance(items))\n\tfor i := range cnt {\n\t\tif items[i] != mg.Get(i) {\n\t\t\tt.Error(\"Not sorted\", items[i], mg.Get(i))\n\t\t}\n\t}\n\n\t// Inverse order\n\tmg2 := NewMerger(nil, lists, true, false, revision{}, 0, 0)\n\tfor i := cnt - 1; i >= 0; i-- {\n\t\tif items[i] != mg2.Get(i) {\n\t\t\tt.Error(\"Not sorted\", items[i], mg2.Get(i))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/options.go",
    "content": "package fzf\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"maps\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/junegunn/fzf/src/algo\"\n\t\"github.com/junegunn/fzf/src/tui\"\n\t\"github.com/junegunn/fzf/src/util\"\n\n\t\"github.com/junegunn/go-shellwords\"\n\t\"github.com/rivo/uniseg\"\n)\n\nconst Usage = `fzf is an interactive filter program for any kind of list.\n\nIt implements a \"fuzzy\" matching algorithm, so you can quickly type in patterns\nwith omitted characters and still get the results you want.\n\nProject URL: https://github.com/junegunn/fzf\nAuthor: Junegunn Choi <junegunn.c@gmail.com>\n\n* See man page for more information: fzf --man\n\nUsage: fzf [options]\n\n  SEARCH\n    -e, --exact              Enable exact-match\n    +x, --no-extended        Disable extended-search mode\n    -i, --ignore-case        Case-insensitive match\n    +i, --no-ignore-case     Case-sensitive match\n        --smart-case         Smart-case match (default)\n    --scheme=SCHEME          Scoring scheme [default|path|history]\n    -n, --nth=N[,..]         Comma-separated list of field index expressions\n                             for limiting search scope. Each can be a non-zero\n                             integer or a range expression ([BEGIN]..[END]).\n    --with-nth=N[,..]        Transform the presentation of each line using\n                             field index expressions\n    --accept-nth=N[,..]      Define which fields to print on accept\n    -d, --delimiter=STR      Field delimiter regex (default: AWK-style)\n    +s, --no-sort            Do not sort the result\n    --literal                Do not normalize latin script letters\n    --tail=NUM               Maximum number of items to keep in memory\n    --disabled               Do not perform search\n    --tiebreak=CRI[,..]      Comma-separated list of sort criteria to apply\n                             when the scores are tied\n                             [length|chunk|pathname|begin|end|index] (default: length)\n\n  INPUT/OUTPUT\n    --read0                  Read input delimited by ASCII NUL characters\n    --print0                 Print output delimited by ASCII NUL characters\n    --ansi                   Enable processing of ANSI color codes\n    --sync                   Synchronous search for multi-staged filtering\n\n  GLOBAL STYLE\n    --style=PRESET           Apply a style preset [default|minimal|full[:BORDER_STYLE]\n    --color=COLSPEC          Base scheme (dark|light|base16|bw) and/or custom colors\n    --no-color               Disable colors\n    --no-bold                Do not use bold text\n\n  DISPLAY MODE\n    --height=[~]HEIGHT[%]    Display fzf window below the cursor with the given\n                             height instead of using fullscreen.\n                             A negative value is calculated as the terminal height\n                             minus the given value.\n                             If prefixed with '~', fzf will determine the height\n                             according to the input size.\n    --min-height=HEIGHT[+]   Minimum height when --height is given as a percentage.\n                             Add '+' to automatically increase the value\n                             according to the other layout options (default: 10+).\n    --tmux[=OPTS]            Start fzf in a tmux popup (requires tmux 3.3+)\n                             [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]\n                             [,border-native] (default: center,50%)\n\n  LAYOUT\n    --layout=LAYOUT          Choose layout: [default|reverse|reverse-list]\n    --margin=MARGIN          Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)\n    --padding=PADDING        Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)\n    --border[=STYLE]         Draw border around the finder\n                             [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|\n                              top|bottom|left|right|line|none] (default: rounded)\n    --border-label=LABEL     Label to print on the border\n    --border-label-pos=COL   Position of the border label\n                             [POSITIVE_INTEGER: columns from left|\n                              NEGATIVE_INTEGER: columns from right][:bottom]\n                             (default: 0 or center)\n\n  LIST SECTION\n    -m, --multi[=MAX]        Enable multi-select with tab/shift-tab\n    --highlight-line         Highlight the whole current line\n    --cycle                  Enable cyclic scroll\n    --wrap[=MODE]            Enable line wrap (char|word, default: char)\n    --wrap-sign=STR          Indicator for wrapped lines\n    --no-multi-line          Disable multi-line display of items when using --read0\n    --raw                    Enable raw mode (show non-matching items)\n    --track                  Track the current selection when the result is updated\n    --id-nth=N[,..]          Define item identity fields for cross-reload operations\n    --tac                    Reverse the order of the input\n    --gap[=N]                Render empty lines between each item\n    --gap-line[=STR]         Draw horizontal line on each gap using the string\n                             (default: '┈' or '-')\n    --freeze-left=N          Number of fields to freeze on the left\n    --freeze-right=N         Number of fields to freeze on the right\n    --keep-right             Keep the right end of the line visible on overflow\n    --scroll-off=LINES       Number of screen lines to keep above or below when\n                             scrolling to the top or to the bottom (default: 0)\n    --no-hscroll             Disable horizontal scroll\n    --hscroll-off=COLS       Number of screen columns to keep to the right of the\n                             highlighted substring (default: 10)\n    --jump-labels=CHARS      Label characters for jump mode\n    --gutter=CHAR            Character used for the gutter column (default: '▌')\n    --gutter-raw=CHAR        Character used for the gutter column in raw mode (default: '▖')\n    --pointer=STR            Pointer to the current line (default: '▌' or '>')\n    --marker=STR             Multi-select marker (default: '┃' or '>')\n    --marker-multi-line=STR  Multi-select marker for multi-line entries;\n                             3 elements for top, middle, and bottom (default: '╻┃╹')\n    --ellipsis=STR           Ellipsis to show when line is truncated (default: '··')\n    --tabstop=SPACES         Number of spaces for a tab character (default: 8)\n    --scrollbar[=C1[C2]]     Scrollbar character(s)\n                             (each for list section and preview window)\n    --no-scrollbar           Hide scrollbar\n    --list-border[=STYLE]    Draw border around the list section\n                             [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|\n                              top|bottom|left|right|none] (default: rounded)\n    --list-label=LABEL       Label to print on the list border\n    --list-label-pos=COL     Position of the list label\n                             [POSITIVE_INTEGER: columns from left|\n                              NEGATIVE_INTEGER: columns from right][:bottom]\n                             (default: 0 or center)\n\n  INPUT SECTION\n    --no-input               Disable and hide the input section\n    --prompt=STR             Input prompt (default: '> ')\n    --info=STYLE             Finder info style\n                             [default|right|hidden|inline[-right][:PREFIX]]\n    --info-command=COMMAND   Command to generate info line\n    --separator=STR          Draw horizontal separator on info line using the string\n                             (default: '─' or '-')\n    --no-separator           Hide info line separator\n    --ghost=TEXT             Ghost text to display when the input is empty\n    --filepath-word          Make word-wise movements respect path separators\n    --input-border[=STYLE]   Draw border around the input section\n                             [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|\n                              top|bottom|left|right|line|none] (default: rounded)\n    --input-label=LABEL      Label to print on the input border\n    --input-label-pos=COL    Position of the input label\n                             [POSITIVE_INTEGER: columns from left|\n                              NEGATIVE_INTEGER: columns from right][:bottom]\n                             (default: 0 or center)\n\n  PREVIEW WINDOW\n    --preview=COMMAND        Command to preview highlighted line ({})\n    --preview-window=OPT     Preview window layout (default: right:50%)\n                             [up|down|left|right][,SIZE[%]]\n                             [,[no]wrap[-word]][,[no]cycle][,[no]follow][,[no]info]\n                             [,[no]hidden][,border-STYLE]\n                             [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]\n                             [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]\n    --preview-border[=STYLE] Short for --preview-window=border-STYLE\n                             [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|\n                              top|bottom|left|right|line|none] (default: rounded)\n    --preview-label=LABEL\n    --preview-label-pos=N    Same as --border-label and --border-label-pos,\n                             but for preview window\n    --preview-wrap-sign=STR  Indicator for wrapped lines in the preview window\n\n  HEADER\n    --header=STR             String to print as header\n    --header-lines=N         The first N lines of the input are treated as header\n    --header-first           Print header before the prompt line\n    --header-border[=STYLE]  Draw border around the header section\n                             [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|\n                              top|bottom|left|right|line|none] (default: rounded)\n    --header-lines-border[=STYLE]\n                             Display header from --header-lines with a separate border.\n                             Pass 'none' to still separate it but without a border.\n    --header-label=LABEL     Label to print on the header border\n    --header-label-pos=COL   Position of the header label\n                             [POSITIVE_INTEGER: columns from left|\n                              NEGATIVE_INTEGER: columns from right][:bottom]\n                             (default: 0 or center)\n\n  FOOTER\n    --footer=STR             String to print as footer\n    --footer-border[=STYLE]  Draw border around the footer section\n                             [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|\n                              top|bottom|left|right|line|none] (default: line)\n    --footer-label=LABEL     Label to print on the footer border\n    --footer-label-pos=COL   Position of the footer label\n                             [POSITIVE_INTEGER: columns from left|\n                              NEGATIVE_INTEGER: columns from right][:bottom]\n                             (default: 0 or center)\n\n  SCRIPTING\n    -q, --query=STR          Start the finder with the given query\n    -1, --select-1           Automatically select the only match\n    -0, --exit-0             Exit immediately when there's no match\n    -f, --filter=STR         Print matches for the initial query and exit\n    --print-query            Print query as the first line\n    --expect=KEYS            Comma-separated list of keys to complete fzf\n\n  KEY/EVENT BINDING\n    --bind=BINDINGS          Custom key/event bindings\n\n  ADVANCED\n    --with-shell=STR         Shell command and flags to start child processes with\n    --listen[=[ADDR:]PORT]   Start HTTP server to receive actions via TCP\n                             (To allow remote process execution, use --listen-unsafe)\n    --listen=SOCKET_PATH     Start HTTP server to receive actions via Unix domain socket\n                             (Path should end with .sock)\n\n  DIRECTORY TRAVERSAL        (Only used when $FZF_DEFAULT_COMMAND is not set)\n    --walker=OPTS            [file][,dir][,follow][,hidden] (default: file,follow,hidden)\n    --walker-root=DIR [...]  List of directories to walk (default: .)\n    --walker-skip=DIRS       Comma-separated list of directory names to skip\n                             (default: .git,node_modules)\n\n  HISTORY\n    --history=FILE           File to store fzf search history (*not* shell command history)\n    --history-size=N         Maximum number of entries to keep in the file (default: 1000)\n\n  SHELL INTEGRATION\n    --bash                   Print script to set up Bash shell integration\n    --zsh                    Print script to set up Zsh shell integration\n    --fish                   Print script to set up Fish shell integration\n\n  HELP\n    --version                Display version information and exit\n    --help                   Show this message\n    --man                    Show man page\n\n  ENVIRONMENT VARIABLES\n    FZF_DEFAULT_COMMAND      Default command to use when input is tty\n    FZF_DEFAULT_OPTS         Default options (e.g. '--layout=reverse --info=inline')\n    FZF_DEFAULT_OPTS_FILE    Location of the file to read default options from\n    FZF_API_KEY              X-API-Key header for HTTP server (--listen)\n\n`\n\n// Can be changed by --style\nvar defaultBorderShape tui.BorderShape = tui.DefaultBorderShape\n\nconst defaultInfoPrefix = \" < \"\n\n// Case denotes case-sensitivity of search\ntype Case int\n\n// Case-sensitivities\nconst (\n\tCaseSmart Case = iota\n\tCaseIgnore\n\tCaseRespect\n)\n\n// Sort criteria\ntype criterion int\n\nconst (\n\tbyScore criterion = iota\n\tbyChunk\n\tbyLength\n\tbyBegin\n\tbyEnd\n\tbyPathname\n)\n\ntype heightSpec struct {\n\tsize    float64\n\tpercent bool\n\tauto    bool\n\tinverse bool\n\tindex   int\n}\n\ntype sizeSpec struct {\n\tsize    float64\n\tpercent bool\n}\n\nfunc (s sizeSpec) String() string {\n\tif s.percent {\n\t\treturn fmt.Sprintf(\"%d%%\", int(s.size))\n\t}\n\treturn fmt.Sprintf(\"%d\", int(s.size))\n}\n\nfunc defaultMargin() [4]sizeSpec {\n\treturn [4]sizeSpec{}\n}\n\ntype trackOption struct {\n\tenabled bool\n\tindex   int32\n}\n\nvar (\n\ttrackDisabled = trackOption{false, minItem.Index()}\n\ttrackEnabled  = trackOption{true, minItem.Index()}\n)\n\nfunc (t trackOption) Disabled() bool {\n\treturn !t.enabled\n}\n\nfunc (t trackOption) Global() bool {\n\treturn t.enabled && t.index == minItem.Index()\n}\n\nfunc (t trackOption) Current() bool {\n\treturn t.enabled && t.index != minItem.Index()\n}\n\nfunc trackCurrent(index int32) trackOption {\n\treturn trackOption{true, index}\n}\n\ntype windowPosition int\n\nconst (\n\tposUp windowPosition = iota\n\tposDown\n\tposLeft\n\tposRight\n\tposCenter\n)\n\ntype tmuxOptions struct {\n\twidth    sizeSpec\n\theight   sizeSpec\n\tposition windowPosition\n\tindex    int\n\tborder   bool\n}\n\ntype layoutType int\n\nconst (\n\tlayoutDefault layoutType = iota\n\tlayoutReverse\n\tlayoutReverseList\n)\n\ntype infoStyle int\n\nconst (\n\tinfoDefault infoStyle = iota\n\tinfoRight\n\tinfoInline\n\tinfoInlineRight\n\tinfoHidden\n)\n\ntype labelOpts struct {\n\tlabel  string\n\tcolumn int\n\tbottom bool\n}\n\ntype previewOpts struct {\n\tcommand     string\n\tposition    windowPosition\n\tsize        sizeSpec\n\tscroll      string\n\thidden      bool\n\twrap        bool\n\twrapWord    bool\n\tcycle       bool\n\tfollow      bool\n\tinfo        bool\n\tborder      tui.BorderShape\n\theaderLines int\n\tthreshold   int\n\talternative *previewOpts\n}\n\nfunc (o *previewOpts) Visible() bool {\n\treturn o.size.size > 0 || o.alternative != nil && o.alternative.size.size > 0\n}\n\nfunc (o *previewOpts) Toggle() {\n\to.hidden = !o.hidden\n}\n\nfunc (o *previewOpts) Border() tui.BorderShape {\n\tshape := o.border\n\tif shape == tui.BorderLine {\n\t\tswitch o.position {\n\t\tcase posUp:\n\t\t\tshape = tui.BorderBottom\n\t\tcase posDown:\n\t\t\tshape = tui.BorderTop\n\t\tcase posLeft:\n\t\t\tshape = tui.BorderRight\n\t\tcase posRight:\n\t\t\tshape = tui.BorderLeft\n\t\t}\n\t}\n\treturn shape\n}\n\nfunc defaultTmuxOptions(index int) *tmuxOptions {\n\treturn &tmuxOptions{\n\t\tposition: posCenter,\n\t\twidth:    sizeSpec{50, true},\n\t\theight:   sizeSpec{50, true},\n\t\tindex:    index}\n}\n\nfunc parseTmuxOptions(arg string, index int) (*tmuxOptions, error) {\n\tvar err error\n\topts := defaultTmuxOptions(index)\n\ttokens := splitRegexp.Split(arg, -1)\n\terrorToReturn := errors.New(\"invalid tmux option: \" + arg + \" (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]])\")\n\tif len(tokens) == 0 || len(tokens) > 4 {\n\t\treturn nil, errorToReturn\n\t}\n\n\tfor i, token := range tokens {\n\t\tif token == \"border-native\" {\n\t\t\ttokens = append(tokens[:i], tokens[i+1:]...) // cut the 'border-native' option\n\t\t\topts.border = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Defaults to 'center'\n\tfirst := \"center\"\n\tif len(tokens) > 0 {\n\t\tfirst = tokens[0]\n\t}\n\n\tswitch first {\n\tcase \"top\", \"up\":\n\t\topts.position = posUp\n\t\topts.width = sizeSpec{100, true}\n\tcase \"bottom\", \"down\":\n\t\topts.position = posDown\n\t\topts.width = sizeSpec{100, true}\n\tcase \"left\":\n\t\topts.position = posLeft\n\t\topts.height = sizeSpec{100, true}\n\tcase \"right\":\n\t\topts.position = posRight\n\t\topts.height = sizeSpec{100, true}\n\tcase \"center\":\n\tdefault:\n\t\ttokens = append([]string{\"center\"}, tokens...)\n\t}\n\n\t// One size given\n\tvar size1 sizeSpec\n\tif len(tokens) > 1 {\n\t\tif size1, err = parseSize(tokens[1], 100, \"size\"); err != nil {\n\t\t\treturn nil, errorToReturn\n\t\t}\n\t}\n\n\t// Two sizes given\n\tvar size2 sizeSpec\n\tif len(tokens) == 3 {\n\t\tif size2, err = parseSize(tokens[2], 100, \"size\"); err != nil {\n\t\t\treturn nil, errorToReturn\n\t\t}\n\t\topts.width = size1\n\t\topts.height = size2\n\t} else if len(tokens) == 2 {\n\t\tswitch tokens[0] {\n\t\tcase \"top\", \"up\":\n\t\t\topts.height = size1\n\t\tcase \"bottom\", \"down\":\n\t\t\topts.height = size1\n\t\tcase \"left\":\n\t\t\topts.width = size1\n\t\tcase \"right\":\n\t\t\topts.width = size1\n\t\tcase \"center\":\n\t\t\topts.width = size1\n\t\t\topts.height = size1\n\t\t}\n\t}\n\n\treturn opts, nil\n}\n\nfunc parseLabelPosition(opts *labelOpts, arg string) error {\n\topts.column = 0\n\topts.bottom = false\n\tvar err error\n\tfor _, token := range splitRegexp.Split(strings.ToLower(arg), -1) {\n\t\tswitch token {\n\t\tcase \"center\":\n\t\t\topts.column = 0\n\t\tcase \"bottom\":\n\t\t\topts.bottom = true\n\t\tcase \"top\":\n\t\t\topts.bottom = false\n\t\tdefault:\n\t\t\topts.column, err = atoi(token)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (a previewOpts) aboveOrBelow() bool {\n\treturn a.size.size > 0 && (a.position == posUp || a.position == posDown)\n}\n\ntype previewOptsCompare int\n\nconst (\n\tpreviewOptsSame previewOptsCompare = iota\n\tpreviewOptsDifferentContentLayout\n\tpreviewOptsDifferentLayout\n)\n\nfunc (o *previewOpts) compare(active *previewOpts, b *previewOpts) previewOptsCompare {\n\ta := o\n\n\tsameThreshold := o.position == b.position && o.threshold == b.threshold\n\t// Alternative layout is being used\n\tif o.alternative == active {\n\t\ta = active\n\n\t\t// If the other also has an alternative layout,\n\t\tif b.alternative != nil {\n\t\t\t// and if the same condition is the same, compare alt vs. alt.\n\t\t\tif sameThreshold {\n\t\t\t\tb = b.alternative\n\t\t\t} else {\n\t\t\t\t// If not, we pessimistically decide that the layouts may not be the same\n\t\t\t\treturn previewOptsDifferentLayout\n\t\t\t}\n\t\t}\n\t} else if b.alternative != nil && !sameThreshold {\n\t\t// We may choose the other's alternative layout, so let's be conservative.\n\t\treturn previewOptsDifferentLayout\n\t}\n\n\tif !(a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden) {\n\t\treturn previewOptsDifferentLayout\n\t}\n\n\tif a.wrap == b.wrap && a.wrapWord == b.wrapWord && a.headerLines == b.headerLines && a.info == b.info && a.scroll == b.scroll {\n\t\treturn previewOptsSame\n\t}\n\n\treturn previewOptsDifferentContentLayout\n}\n\nfunc firstLine(s string) string {\n\treturn strings.SplitN(s, \"\\n\", 2)[0]\n}\n\ntype walkerOpts struct {\n\tfile   bool\n\tdir    bool\n\thidden bool\n\tfollow bool\n}\n\n// Options stores the values of command-line options\ntype Options struct {\n\tInput             chan string\n\tOutput            chan string\n\tNoWinpty          bool\n\tTmux              *tmuxOptions\n\tForceTtyIn        bool\n\tProxyScript       string\n\tBash              bool\n\tZsh               bool\n\tFish              bool\n\tMan               bool\n\tFuzzy             bool\n\tFuzzyAlgo         algo.Algo\n\tScheme            string\n\tExtended          bool\n\tPhony             bool\n\tInputless         bool\n\tCase              Case\n\tNormalize         bool\n\tNth               []Range\n\tFreezeLeft        int\n\tFreezeRight       int\n\tWithNth           func(Delimiter) func([]Token, int32) string\n\tWithNthExpr       string\n\tAcceptNth         func(Delimiter) func([]Token, int32) string\n\tDelimiter         Delimiter\n\tSort              int\n\tRaw               bool\n\tTrack             trackOption\n\tIdNth             []Range\n\tTac               bool\n\tTail              int\n\tCriteria          []criterion\n\tMulti             int\n\tAnsi              bool\n\tMouse             bool\n\tBaseTheme         *tui.ColorTheme\n\tTheme             *tui.ColorTheme\n\tBlack             bool\n\tBold              bool\n\tHeight            heightSpec\n\tMinHeight         int\n\tLayout            layoutType\n\tCycle             bool\n\tWrap              bool\n\tWrapWord          bool\n\tWrapSign          *string\n\tPreviewWrapSign   *string\n\tMultiLine         bool\n\tCursorLine        bool\n\tKeepRight         bool\n\tHscroll           bool\n\tHscrollOff        int\n\tScrollOff         int\n\tFileWord          bool\n\tInfoStyle         infoStyle\n\tInfoPrefix        string\n\tInfoCommand       string\n\tGhost             string\n\tSeparator         *string\n\tJumpLabels        string\n\tPrompt            string\n\tGutter            *string\n\tGutterRaw         *string\n\tPointer           *string\n\tMarker            *string\n\tMarkerMulti       *[3]string\n\tQuery             string\n\tSelect1           bool\n\tExit0             bool\n\tFilter            *string\n\tToggleSort        bool\n\tExpect            map[tui.Event]string\n\tKeymap            map[tui.Event][]*action\n\tPreview           previewOpts\n\tPrintQuery        bool\n\tReadZero          bool\n\tPrinter           func(string)\n\tPrintSep          string\n\tSync              bool\n\tHistory           *History\n\tHeader            []string\n\tHeaderLines       int\n\tHeaderFirst       bool\n\tFooter            []string\n\tGap               int\n\tGapLine           *string\n\tEllipsis          *string\n\tScrollbar         *string\n\tMargin            [4]sizeSpec\n\tPadding           [4]sizeSpec\n\tBorderShape       tui.BorderShape\n\tListBorderShape   tui.BorderShape\n\tInputBorderShape  tui.BorderShape\n\tHeaderBorderShape tui.BorderShape\n\tHeaderLinesShape  tui.BorderShape\n\tFooterBorderShape tui.BorderShape\n\tInputLabel        labelOpts\n\tHeaderLabel       labelOpts\n\tFooterLabel       labelOpts\n\tBorderLabel       labelOpts\n\tListLabel         labelOpts\n\tPreviewLabel      labelOpts\n\tUnicode           bool\n\tAmbidouble        bool\n\tTabstop           int\n\tWithShell         string\n\tListenAddr        *listenAddress\n\tUnsafe            bool\n\tClearOnExit       bool\n\tWalkerOpts        walkerOpts\n\tWalkerRoot        []string\n\tWalkerSkip        []string\n\tVersion           bool\n\tHelp              bool\n\tThreads           int\n\tBench             time.Duration\n\tCPUProfile        string\n\tMEMProfile        string\n\tBlockProfile      string\n\tMutexProfile      string\n\tTtyDefault        string\n}\n\nfunc filterNonEmpty(input []string) []string {\n\toutput := make([]string, 0, len(input))\n\tfor _, str := range input {\n\t\tif len(str) > 0 {\n\t\t\toutput = append(output, str)\n\t\t}\n\t}\n\treturn output\n}\n\nfunc defaultPreviewOpts(command string) previewOpts {\n\treturn previewOpts{\n\t\tcommand:  command,\n\t\tposition: posRight,\n\t\tsize:     sizeSpec{50, true},\n\t\tinfo:     true,\n\t\tborder:   defaultBorderShape,\n\t}\n}\n\nfunc defaultOptions() *Options {\n\tvar theme, baseTheme *tui.ColorTheme\n\tif os.Getenv(\"NO_COLOR\") != \"\" {\n\t\ttheme = tui.NoColorTheme\n\t\tbaseTheme = tui.NoColorTheme\n\t} else {\n\t\ttheme = tui.EmptyTheme\n\t}\n\n\treturn &Options{\n\t\tBash:         false,\n\t\tZsh:          false,\n\t\tFish:         false,\n\t\tMan:          false,\n\t\tFuzzy:        true,\n\t\tFuzzyAlgo:    algo.FuzzyMatchV2,\n\t\tScheme:       \"\", // Unknown\n\t\tExtended:     true,\n\t\tPhony:        false,\n\t\tInputless:    false,\n\t\tCase:         CaseSmart,\n\t\tNormalize:    true,\n\t\tNth:          make([]Range, 0),\n\t\tDelimiter:    Delimiter{},\n\t\tSort:         1000,\n\t\tTrack:        trackDisabled,\n\t\tTac:          false,\n\t\tCriteria:     []criterion{}, // Unknown\n\t\tMulti:        0,\n\t\tAnsi:         false,\n\t\tMouse:        true,\n\t\tTheme:        theme,\n\t\tBaseTheme:    baseTheme,\n\t\tBlack:        false,\n\t\tBold:         true,\n\t\tMinHeight:    -10,\n\t\tLayout:       layoutDefault,\n\t\tCycle:        false,\n\t\tWrap:         false,\n\t\tWrapWord:     false,\n\t\tMultiLine:    true,\n\t\tKeepRight:    false,\n\t\tHscroll:      true,\n\t\tHscrollOff:   10,\n\t\tScrollOff:    3,\n\t\tFileWord:     false,\n\t\tInfoStyle:    infoDefault,\n\t\tGhost:        \"\",\n\t\tSeparator:    nil,\n\t\tJumpLabels:   defaultJumpLabels,\n\t\tPrompt:       \"> \",\n\t\tGutter:       nil,\n\t\tGutterRaw:    nil,\n\t\tPointer:      nil,\n\t\tMarker:       nil,\n\t\tMarkerMulti:  nil,\n\t\tQuery:        \"\",\n\t\tSelect1:      false,\n\t\tExit0:        false,\n\t\tFilter:       nil,\n\t\tToggleSort:   false,\n\t\tExpect:       make(map[tui.Event]string),\n\t\tKeymap:       make(map[tui.Event][]*action),\n\t\tPreview:      defaultPreviewOpts(\"\"),\n\t\tPrintQuery:   false,\n\t\tReadZero:     false,\n\t\tPrinter:      func(str string) { fmt.Println(str) },\n\t\tPrintSep:     \"\\n\",\n\t\tSync:         false,\n\t\tHistory:      nil,\n\t\tHeader:       make([]string, 0),\n\t\tHeaderLines:  0,\n\t\tHeaderFirst:  false,\n\t\tFooter:       make([]string, 0),\n\t\tGap:          0,\n\t\tEllipsis:     nil,\n\t\tScrollbar:    nil,\n\t\tMargin:       defaultMargin(),\n\t\tPadding:      defaultMargin(),\n\t\tUnicode:      true,\n\t\tAmbidouble:   os.Getenv(\"RUNEWIDTH_EASTASIAN\") == \"1\",\n\t\tTabstop:      8,\n\t\tBorderLabel:  labelOpts{},\n\t\tPreviewLabel: labelOpts{},\n\t\tUnsafe:       false,\n\t\tClearOnExit:  true,\n\t\tWalkerOpts:   walkerOpts{file: true, hidden: true, follow: true},\n\t\tWalkerRoot:   []string{\".\"},\n\t\tWalkerSkip:   []string{\".git\", \"node_modules\"},\n\t\tTtyDefault:   tui.DefaultTtyDevice,\n\t\tHelp:         false,\n\t\tVersion:      false}\n}\n\nfunc isDir(path string) bool {\n\tstat, err := os.Stat(path)\n\treturn err == nil && stat.IsDir()\n}\n\nfunc atoi(str string) (int, error) {\n\tnum, err := strconv.Atoi(str)\n\tif err != nil {\n\t\treturn 0, errors.New(\"not a valid integer: \" + str)\n\t}\n\treturn num, nil\n}\n\nfunc atof(str string) (float64, error) {\n\tnum, err := strconv.ParseFloat(str, 64)\n\tif err != nil {\n\t\treturn 0, errors.New(\"not a valid number: \" + str)\n\t}\n\treturn num, nil\n}\n\nfunc splitNth(str string) ([]Range, error) {\n\tif match, _ := regexp.MatchString(\"^[0-9,-.]+$\", str); !match {\n\t\treturn nil, errors.New(\"invalid format: \" + str)\n\t}\n\n\ttokens := strings.Split(str, \",\")\n\tranges := make([]Range, len(tokens))\n\tfor idx, s := range tokens {\n\t\tr, ok := ParseRange(&s)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid format: \" + str)\n\t\t}\n\t\tranges[idx] = r\n\t}\n\treturn ranges, nil\n}\n\nfunc nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, error) {\n\t// ^[0-9,-.]+$\"\n\tif match, _ := regexp.MatchString(\"^[0-9,-.]+$\", str); match {\n\t\tnth, err := splitNth(str)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn func(Delimiter) func([]Token, int32) string {\n\t\t\treturn func(tokens []Token, index int32) string {\n\t\t\t\treturn JoinTokens(Transform(tokens, nth))\n\t\t\t}\n\t\t}, nil\n\t}\n\n\t// {...} {...} ...\n\tplaceholder := regexp.MustCompile(\"{[0-9,-.]+}|{n}\")\n\tindexes := placeholder.FindAllStringIndex(str, -1)\n\tif indexes == nil {\n\t\treturn nil, errors.New(\"template should include at least 1 placeholder: \" + str)\n\t}\n\n\ttype NthParts struct {\n\t\tstr   string\n\t\tindex bool\n\t\tnth   []Range\n\t}\n\n\tparts := make([]NthParts, len(indexes))\n\tidx := 0\n\tfor _, index := range indexes {\n\t\tif idx < index[0] {\n\t\t\tparts = append(parts, NthParts{str: str[idx:index[0]]})\n\t\t}\n\t\texpr := str[index[0]+1 : index[1]-1]\n\t\tif expr == \"n\" {\n\t\t\tparts = append(parts, NthParts{index: true})\n\t\t} else if nth, err := splitNth(expr); err == nil {\n\t\t\tparts = append(parts, NthParts{nth: nth})\n\t\t}\n\t\tidx = index[1]\n\t}\n\tif idx < len(str) {\n\t\tparts = append(parts, NthParts{str: str[idx:]})\n\t}\n\n\treturn func(delimiter Delimiter) func([]Token, int32) string {\n\t\treturn func(tokens []Token, index int32) string {\n\t\t\tstr := \"\"\n\t\t\tfor _, holder := range parts {\n\t\t\t\tif holder.nth != nil {\n\t\t\t\t\tstr += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)\n\t\t\t\t} else if holder.index {\n\t\t\t\t\tif index >= 0 {\n\t\t\t\t\t\tstr += strconv.Itoa(int(index))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tstr += holder.str\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn str\n\t\t}\n\t}, nil\n}\n\nfunc delimiterRegexp(str string) Delimiter {\n\t// Special handling of \\t\n\tstr = strings.ReplaceAll(str, \"\\\\t\", \"\\t\")\n\n\t// 1. Pattern is a single character\n\tif len([]rune(str)) == 1 {\n\t\treturn Delimiter{str: &str}\n\t}\n\n\t// 2. Pattern does not contain any special character\n\tif regexp.QuoteMeta(str) == str {\n\t\treturn Delimiter{str: &str}\n\t}\n\n\trx, e := regexp.Compile(str)\n\t// 3. Pattern is not a valid regular expression\n\tif e != nil {\n\t\treturn Delimiter{str: &str}\n\t}\n\n\t// 4. Pattern as regular expression. Slow.\n\treturn Delimiter{regex: rx}\n}\n\nfunc isAlphabet(char uint8) bool {\n\treturn char >= 'a' && char <= 'z'\n}\n\nfunc isNumeric(char uint8) bool {\n\treturn char >= '0' && char <= '9'\n}\n\nfunc parseAlgo(str string) (algo.Algo, error) {\n\tswitch str {\n\tcase \"v1\":\n\t\treturn algo.FuzzyMatchV1, nil\n\tcase \"v2\":\n\t\treturn algo.FuzzyMatchV2, nil\n\t}\n\treturn nil, errors.New(\"invalid algorithm (expected: v1 or v2)\")\n}\n\nfunc parseBorder(str string, optional bool) (tui.BorderShape, error) {\n\tswitch str {\n\tcase \"line\":\n\t\treturn tui.BorderLine, nil\n\tcase \"rounded\":\n\t\treturn tui.BorderRounded, nil\n\tcase \"sharp\":\n\t\treturn tui.BorderSharp, nil\n\tcase \"bold\":\n\t\treturn tui.BorderBold, nil\n\tcase \"block\":\n\t\treturn tui.BorderBlock, nil\n\tcase \"thinblock\":\n\t\treturn tui.BorderThinBlock, nil\n\tcase \"double\":\n\t\treturn tui.BorderDouble, nil\n\tcase \"horizontal\":\n\t\treturn tui.BorderHorizontal, nil\n\tcase \"vertical\":\n\t\treturn tui.BorderVertical, nil\n\tcase \"top\":\n\t\treturn tui.BorderTop, nil\n\tcase \"bottom\":\n\t\treturn tui.BorderBottom, nil\n\tcase \"left\":\n\t\treturn tui.BorderLeft, nil\n\tcase \"right\":\n\t\treturn tui.BorderRight, nil\n\tcase \"none\":\n\t\treturn tui.BorderNone, nil\n\t}\n\tif optional && str == \"\" {\n\t\treturn defaultBorderShape, nil\n\t}\n\treturn tui.BorderNone, errors.New(\"invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)\")\n}\n\nfunc parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Event, error) {\n\tif len(str) == 0 {\n\t\treturn nil, nil, errors.New(message)\n\t}\n\n\tlist := []tui.Event{}\n\tstr = regexp.MustCompile(\"(?i)(alt-),\").ReplaceAllString(str, \"$1\"+string([]rune{escapedComma}))\n\ttokens := strings.Split(str, \",\")\n\tif str == \",\" || strings.HasPrefix(str, \",,\") || strings.HasSuffix(str, \",,\") || strings.Contains(str, \",,,\") {\n\t\ttokens = append(tokens, \",\")\n\t}\n\n\tchords := make(map[tui.Event]string)\n\tfor _, key := range tokens {\n\t\tif len(key) == 0 {\n\t\t\tcontinue // ignore\n\t\t}\n\t\tkey = strings.ReplaceAll(key, string([]rune{escapedComma}), \",\")\n\t\tlkey := strings.ToLower(key)\n\t\tadd := func(e tui.EventType) {\n\t\t\tchords[e.AsEvent()] = key\n\t\t\tlist = append(list, e.AsEvent())\n\t\t}\n\t\tswitch lkey {\n\t\tcase \"up\":\n\t\t\tadd(tui.Up)\n\t\tcase \"down\":\n\t\t\tadd(tui.Down)\n\t\tcase \"left\":\n\t\t\tadd(tui.Left)\n\t\tcase \"right\":\n\t\t\tadd(tui.Right)\n\t\tcase \"enter\", \"return\":\n\t\t\tadd(tui.Enter)\n\t\tcase \"space\":\n\t\t\tevt := tui.Key(' ')\n\t\t\tchords[evt] = key\n\t\t\tlist = append(list, evt)\n\t\tcase \"backspace\", \"bspace\", \"bs\":\n\t\t\tadd(tui.Backspace)\n\t\tcase \"ctrl-space\":\n\t\t\tadd(tui.CtrlSpace)\n\t\tcase \"ctrl-^\", \"ctrl-6\":\n\t\t\tadd(tui.CtrlCaret)\n\t\tcase \"ctrl-/\", \"ctrl-_\":\n\t\t\tadd(tui.CtrlSlash)\n\t\tcase \"ctrl-\\\\\":\n\t\t\tadd(tui.CtrlBackSlash)\n\t\tcase \"ctrl-]\":\n\t\t\tadd(tui.CtrlRightBracket)\n\t\tcase \"change\":\n\t\t\tadd(tui.Change)\n\t\tcase \"backward-eof\":\n\t\t\tadd(tui.BackwardEOF)\n\t\tcase \"start\":\n\t\t\tadd(tui.Start)\n\t\tcase \"load\":\n\t\t\tadd(tui.Load)\n\t\tcase \"focus\":\n\t\t\tadd(tui.Focus)\n\t\tcase \"result\":\n\t\t\tadd(tui.Result)\n\t\tcase \"resize\":\n\t\t\tadd(tui.Resize)\n\t\tcase \"one\":\n\t\t\tadd(tui.One)\n\t\tcase \"zero\":\n\t\t\tadd(tui.Zero)\n\t\tcase \"jump\":\n\t\t\tadd(tui.Jump)\n\t\tcase \"jump-cancel\":\n\t\t\tadd(tui.JumpCancel)\n\t\tcase \"click-header\":\n\t\t\tadd(tui.ClickHeader)\n\t\tcase \"click-footer\":\n\t\t\tadd(tui.ClickFooter)\n\t\tcase \"multi\":\n\t\t\tadd(tui.Multi)\n\t\tcase \"alt-enter\", \"alt-return\":\n\t\t\tevt := tui.CtrlAltKey('m')\n\t\t\tchords[evt] = key\n\t\t\tlist = append(list, evt)\n\t\tcase \"alt-space\":\n\t\t\tevt := tui.AltKey(' ')\n\t\t\tchords[evt] = key\n\t\t\tlist = append(list, evt)\n\t\tcase \"alt-bs\", \"alt-bspace\", \"alt-backspace\":\n\t\t\tadd(tui.AltBackspace)\n\t\tcase \"ctrl-bs\", \"ctrl-bspace\", \"ctrl-backspace\":\n\t\t\tadd(tui.CtrlBackspace)\n\t\tcase \"ctrl-alt-bs\", \"ctrl-alt-bspace\", \"ctrl-alt-backspace\":\n\t\t\tadd(tui.CtrlAltBackspace)\n\t\tcase \"alt-up\":\n\t\t\tadd(tui.AltUp)\n\t\tcase \"alt-down\":\n\t\t\tadd(tui.AltDown)\n\t\tcase \"alt-left\":\n\t\t\tadd(tui.AltLeft)\n\t\tcase \"alt-right\":\n\t\t\tadd(tui.AltRight)\n\t\tcase \"alt-home\":\n\t\t\tadd(tui.AltHome)\n\t\tcase \"alt-end\":\n\t\t\tadd(tui.AltEnd)\n\t\tcase \"alt-delete\":\n\t\t\tadd(tui.AltDelete)\n\t\tcase \"alt-page-up\":\n\t\t\tadd(tui.AltPageUp)\n\t\tcase \"alt-page-down\":\n\t\t\tadd(tui.AltPageDown)\n\t\tcase \"tab\":\n\t\t\tadd(tui.Tab)\n\t\tcase \"btab\", \"shift-tab\":\n\t\t\tadd(tui.ShiftTab)\n\t\tcase \"esc\":\n\t\t\tadd(tui.Esc)\n\t\tcase \"delete\", \"del\":\n\t\t\tadd(tui.Delete)\n\t\tcase \"home\":\n\t\t\tadd(tui.Home)\n\t\tcase \"end\":\n\t\t\tadd(tui.End)\n\t\tcase \"insert\":\n\t\t\tadd(tui.Insert)\n\t\tcase \"pgup\", \"page-up\":\n\t\t\tadd(tui.PageUp)\n\t\tcase \"pgdn\", \"page-down\":\n\t\t\tadd(tui.PageDown)\n\t\tcase \"alt-shift-up\", \"shift-alt-up\":\n\t\t\tadd(tui.AltShiftUp)\n\t\tcase \"alt-shift-down\", \"shift-alt-down\":\n\t\t\tadd(tui.AltShiftDown)\n\t\tcase \"alt-shift-left\", \"shift-alt-left\":\n\t\t\tadd(tui.AltShiftLeft)\n\t\tcase \"alt-shift-right\", \"shift-alt-right\":\n\t\t\tadd(tui.AltShiftRight)\n\t\tcase \"alt-shift-home\", \"shift-alt-home\":\n\t\t\tadd(tui.AltShiftHome)\n\t\tcase \"alt-shift-end\", \"shift-alt-end\":\n\t\t\tadd(tui.AltShiftEnd)\n\t\tcase \"alt-shift-delete\", \"shift-alt-delete\":\n\t\t\tadd(tui.AltShiftDelete)\n\t\tcase \"alt-shift-page-up\", \"shift-alt-page-up\":\n\t\t\tadd(tui.AltShiftPageUp)\n\t\tcase \"alt-shift-page-down\", \"shift-alt-page-down\":\n\t\t\tadd(tui.AltShiftPageDown)\n\t\tcase \"ctrl-up\":\n\t\t\tadd(tui.CtrlUp)\n\t\tcase \"ctrl-down\":\n\t\t\tadd(tui.CtrlDown)\n\t\tcase \"ctrl-right\":\n\t\t\tadd(tui.CtrlRight)\n\t\tcase \"ctrl-left\":\n\t\t\tadd(tui.CtrlLeft)\n\t\tcase \"ctrl-home\":\n\t\t\tadd(tui.CtrlHome)\n\t\tcase \"ctrl-end\":\n\t\t\tadd(tui.CtrlEnd)\n\t\tcase \"ctrl-delete\":\n\t\t\tadd(tui.CtrlDelete)\n\t\tcase \"ctrl-page-up\":\n\t\t\tadd(tui.CtrlPageUp)\n\t\tcase \"ctrl-page-down\":\n\t\t\tadd(tui.CtrlPageDown)\n\t\tcase \"ctrl-alt-up\", \"alt-ctrl-up\":\n\t\t\tadd(tui.CtrlAltUp)\n\t\tcase \"ctrl-alt-down\", \"alt-ctrl-down\":\n\t\t\tadd(tui.CtrlAltDown)\n\t\tcase \"ctrl-alt-right\", \"alt-ctrl-right\":\n\t\t\tadd(tui.CtrlAltRight)\n\t\tcase \"ctrl-alt-left\", \"alt-ctrl-left\":\n\t\t\tadd(tui.CtrlAltLeft)\n\t\tcase \"ctrl-alt-home\", \"alt-ctrl-home\":\n\t\t\tadd(tui.CtrlAltHome)\n\t\tcase \"ctrl-alt-end\", \"alt-ctrl-end\":\n\t\t\tadd(tui.CtrlAltEnd)\n\t\tcase \"ctrl-alt-delete\", \"alt-ctrl-delete\":\n\t\t\tadd(tui.CtrlAltDelete)\n\t\tcase \"ctrl-alt-page-up\", \"alt-ctrl-page-up\":\n\t\t\tadd(tui.CtrlAltPageUp)\n\t\tcase \"ctrl-alt-page-down\", \"alt-ctrl-page-down\":\n\t\t\tadd(tui.CtrlAltPageDown)\n\t\tcase \"ctrl-shift-up\", \"shift-ctrl-up\":\n\t\t\tadd(tui.CtrlShiftUp)\n\t\tcase \"ctrl-shift-down\", \"shift-ctrl-down\":\n\t\t\tadd(tui.CtrlShiftDown)\n\t\tcase \"ctrl-shift-right\", \"shift-ctrl-right\":\n\t\t\tadd(tui.CtrlShiftRight)\n\t\tcase \"ctrl-shift-left\", \"shift-ctrl-left\":\n\t\t\tadd(tui.CtrlShiftLeft)\n\t\tcase \"ctrl-shift-home\", \"shift-ctrl-home\":\n\t\t\tadd(tui.CtrlShiftHome)\n\t\tcase \"ctrl-shift-end\", \"shift-ctrl-end\":\n\t\t\tadd(tui.CtrlShiftEnd)\n\t\tcase \"ctrl-shift-delete\", \"shift-ctrl-delete\":\n\t\t\tadd(tui.CtrlShiftDelete)\n\t\tcase \"ctrl-shift-page-up\", \"shift-ctrl-page-up\":\n\t\t\tadd(tui.CtrlShiftPageUp)\n\t\tcase \"ctrl-shift-page-down\", \"shift-ctrl-page-down\":\n\t\t\tadd(tui.CtrlShiftPageDown)\n\t\tcase \"ctrl-alt-shift-up\":\n\t\t\tadd(tui.CtrlAltShiftUp)\n\t\tcase \"ctrl-alt-shift-down\":\n\t\t\tadd(tui.CtrlAltShiftDown)\n\t\tcase \"ctrl-alt-shift-right\":\n\t\t\tadd(tui.CtrlAltShiftRight)\n\t\tcase \"ctrl-alt-shift-left\":\n\t\t\tadd(tui.CtrlAltShiftLeft)\n\t\tcase \"ctrl-alt-shift-home\":\n\t\t\tadd(tui.CtrlAltShiftHome)\n\t\tcase \"ctrl-alt-shift-end\":\n\t\t\tadd(tui.CtrlAltShiftEnd)\n\t\tcase \"ctrl-alt-shift-delete\":\n\t\t\tadd(tui.CtrlAltShiftDelete)\n\t\tcase \"ctrl-alt-shift-page-up\":\n\t\t\tadd(tui.CtrlAltShiftPageUp)\n\t\tcase \"ctrl-alt-shift-page-down\":\n\t\t\tadd(tui.CtrlAltShiftPageDown)\n\t\tcase \"shift-up\":\n\t\t\tadd(tui.ShiftUp)\n\t\tcase \"shift-down\":\n\t\t\tadd(tui.ShiftDown)\n\t\tcase \"shift-left\":\n\t\t\tadd(tui.ShiftLeft)\n\t\tcase \"shift-right\":\n\t\t\tadd(tui.ShiftRight)\n\t\tcase \"shift-home\":\n\t\t\tadd(tui.ShiftHome)\n\t\tcase \"shift-end\":\n\t\t\tadd(tui.ShiftEnd)\n\t\tcase \"shift-delete\":\n\t\t\tadd(tui.ShiftDelete)\n\t\tcase \"shift-page-up\":\n\t\t\tadd(tui.ShiftPageUp)\n\t\tcase \"shift-page-down\":\n\t\t\tadd(tui.ShiftPageDown)\n\t\tcase \"left-click\":\n\t\t\tadd(tui.LeftClick)\n\t\tcase \"right-click\":\n\t\t\tadd(tui.RightClick)\n\t\tcase \"shift-left-click\":\n\t\t\tadd(tui.SLeftClick)\n\t\tcase \"shift-right-click\":\n\t\t\tadd(tui.SRightClick)\n\t\tcase \"double-click\":\n\t\t\tadd(tui.DoubleClick)\n\t\tcase \"scroll-up\":\n\t\t\tadd(tui.ScrollUp)\n\t\tcase \"scroll-down\":\n\t\t\tadd(tui.ScrollDown)\n\t\tcase \"shift-scroll-up\":\n\t\t\tadd(tui.SScrollUp)\n\t\tcase \"shift-scroll-down\":\n\t\t\tadd(tui.SScrollDown)\n\t\tcase \"preview-scroll-up\":\n\t\t\tadd(tui.PreviewScrollUp)\n\t\tcase \"preview-scroll-down\":\n\t\t\tadd(tui.PreviewScrollDown)\n\t\tcase \"f10\":\n\t\t\tadd(tui.F10)\n\t\tcase \"f11\":\n\t\t\tadd(tui.F11)\n\t\tcase \"f12\":\n\t\t\tadd(tui.F12)\n\t\tdefault:\n\t\t\trunes := []rune(key)\n\t\t\tif len(key) == 10 && strings.HasPrefix(lkey, \"ctrl-alt-\") && isAlphabet(lkey[9]) {\n\t\t\t\tr := rune(lkey[9])\n\t\t\t\tevt := tui.CtrlAltKey(r)\n\t\t\t\tif r == 'h' && !util.IsWindows() {\n\t\t\t\t\tevt = tui.CtrlAltBackspace.AsEvent()\n\t\t\t\t}\n\t\t\t\tchords[evt] = key\n\t\t\t\tlist = append(list, evt)\n\t\t\t} else if len(key) == 6 && strings.HasPrefix(lkey, \"ctrl-\") && isAlphabet(lkey[5]) {\n\t\t\t\tevt := tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a')\n\t\t\t\tr := rune(lkey[5])\n\t\t\t\tif r == 'h' && !util.IsWindows() {\n\t\t\t\t\tevt = tui.CtrlBackspace\n\t\t\t\t}\n\t\t\t\tadd(evt)\n\t\t\t} else if len(runes) == 5 && strings.HasPrefix(lkey, \"alt-\") {\n\t\t\t\tr := runes[4]\n\t\t\t\tswitch r {\n\t\t\t\tcase escapedColon:\n\t\t\t\t\tr = ':'\n\t\t\t\tcase escapedComma:\n\t\t\t\t\tr = ','\n\t\t\t\tcase escapedPlus:\n\t\t\t\t\tr = '+'\n\t\t\t\t}\n\t\t\t\tevt := tui.AltKey(r)\n\t\t\t\tchords[evt] = key\n\t\t\t\tlist = append(list, evt)\n\t\t\t} else if len(key) == 2 && strings.HasPrefix(lkey, \"f\") && key[1] >= '1' && key[1] <= '9' {\n\t\t\t\tadd(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))\n\t\t\t} else if len(runes) == 1 {\n\t\t\t\tevt := tui.Key(runes[0])\n\t\t\t\tchords[evt] = key\n\t\t\t\tlist = append(list, evt)\n\t\t\t} else {\n\t\t\t\treturn nil, list, errors.New(\"unsupported key: \" + key)\n\t\t\t}\n\t\t}\n\t}\n\treturn chords, list, nil\n}\n\nfunc parseScheme(str string) (string, []criterion, error) {\n\tstr = strings.ToLower(str)\n\tswitch str {\n\tcase \"history\":\n\t\treturn str, []criterion{byScore}, nil\n\tcase \"path\":\n\t\treturn str, []criterion{byScore, byPathname, byLength}, nil\n\tcase \"default\":\n\t\treturn str, []criterion{byScore, byLength}, nil\n\t}\n\treturn str, nil, errors.New(\"invalid scoring scheme: \" + str + \" (expected: default|path|history)\")\n}\n\nfunc parseTiebreak(str string) ([]criterion, error) {\n\tcriteria := []criterion{byScore}\n\thasIndex := false\n\thasChunk := false\n\thasLength := false\n\thasBegin := false\n\thasEnd := false\n\thasPathname := false\n\tcheck := func(notExpected *bool, name string) error {\n\t\tif *notExpected {\n\t\t\treturn errors.New(\"duplicate sort criteria: \" + name)\n\t\t}\n\t\tif hasIndex {\n\t\t\treturn errors.New(\"index should be the last criterion\")\n\t\t}\n\t\t*notExpected = true\n\t\treturn nil\n\t}\n\tfor _, str := range strings.Split(strings.ToLower(str), \",\") {\n\t\tswitch str {\n\t\tcase \"index\":\n\t\t\tif err := check(&hasIndex, \"index\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase \"chunk\":\n\t\t\tif err := check(&hasChunk, \"chunk\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcriteria = append(criteria, byChunk)\n\t\tcase \"pathname\":\n\t\t\tif err := check(&hasPathname, \"pathname\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcriteria = append(criteria, byPathname)\n\t\tcase \"length\":\n\t\t\tif err := check(&hasLength, \"length\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcriteria = append(criteria, byLength)\n\t\tcase \"begin\":\n\t\t\tif err := check(&hasBegin, \"begin\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcriteria = append(criteria, byBegin)\n\t\tcase \"end\":\n\t\t\tif err := check(&hasEnd, \"end\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcriteria = append(criteria, byEnd)\n\t\tdefault:\n\t\t\treturn nil, errors.New(\"invalid sort criterion: \" + str)\n\t\t}\n\t}\n\tif len(criteria) > 4 {\n\t\treturn nil, errors.New(\"at most 3 tiebreaks are allowed: \" + str)\n\t}\n\treturn criteria, nil\n}\n\nfunc dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {\n\tdupe := *theme\n\treturn &dupe\n}\n\nfunc parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui.ColorTheme, error) {\n\tvar err error\n\tvar baseTheme *tui.ColorTheme\n\ttheme := dupeTheme(defaultTheme)\n\trrggbb := regexp.MustCompile(\"^#[0-9a-fA-F]{6}$\")\n\tcomma := regexp.MustCompile(`[\\s,]+`)\n\tfor _, str := range comma.Split(strings.ToLower(str), -1) {\n\t\tstr = strings.TrimSpace(str)\n\t\tif len(str) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch str {\n\t\tcase \"dark\":\n\t\t\tbaseTheme = tui.Dark256\n\t\t\ttheme = dupeTheme(tui.Dark256)\n\t\tcase \"light\":\n\t\t\tbaseTheme = tui.Light256\n\t\t\ttheme = dupeTheme(tui.Light256)\n\t\tcase \"base16\", \"16\":\n\t\t\tbaseTheme = tui.Default16\n\t\t\ttheme = dupeTheme(tui.Default16)\n\t\tcase \"bw\", \"no\":\n\t\t\tbaseTheme = tui.NoColorTheme\n\t\t\ttheme = dupeTheme(tui.NoColorTheme)\n\t\tdefault:\n\t\t\tfail := func() {\n\t\t\t\t// Let the code proceed to simplify the error handling\n\t\t\t\terr = errors.New(\"invalid color specification: \" + str)\n\t\t\t}\n\t\t\t// Color is disabled\n\t\t\tif theme == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcomponents := strings.Split(str, \":\")\n\t\t\tif len(components) < 2 {\n\t\t\t\tfail()\n\t\t\t}\n\n\t\t\tmergeAttr := func(cattr *tui.ColorAttr) {\n\t\t\t\tfor _, component := range components[1:] {\n\t\t\t\t\tswitch component {\n\t\t\t\t\tcase \"regular\":\n\t\t\t\t\t\tcattr.Attr = tui.AttrRegular\n\t\t\t\t\tcase \"bold\", \"strong\":\n\t\t\t\t\t\tcattr.Attr |= tui.Bold\n\t\t\t\t\tcase \"dim\":\n\t\t\t\t\t\tcattr.Attr |= tui.Dim\n\t\t\t\t\tcase \"strip\":\n\t\t\t\t\t\tcattr.Attr |= tui.Strip\n\t\t\t\t\tcase \"italic\":\n\t\t\t\t\t\tcattr.Attr |= tui.Italic\n\t\t\t\t\tcase \"underline\":\n\t\t\t\t\t\tcattr.Attr |= tui.Underline\n\t\t\t\t\tcase \"underline-double\":\n\t\t\t\t\t\tcattr.Attr |= tui.Underline | tui.UlStyleDouble\n\t\t\t\t\tcase \"underline-curly\":\n\t\t\t\t\t\tcattr.Attr |= tui.Underline | tui.UlStyleCurly\n\t\t\t\t\tcase \"underline-dotted\":\n\t\t\t\t\t\tcattr.Attr |= tui.Underline | tui.UlStyleDotted\n\t\t\t\t\tcase \"underline-dashed\":\n\t\t\t\t\t\tcattr.Attr |= tui.Underline | tui.UlStyleDashed\n\t\t\t\t\tcase \"blink\":\n\t\t\t\t\t\tcattr.Attr |= tui.Blink\n\t\t\t\t\tcase \"reverse\":\n\t\t\t\t\t\tcattr.Attr |= tui.Reverse\n\t\t\t\t\tcase \"strikethrough\":\n\t\t\t\t\t\tcattr.Attr |= tui.StrikeThrough\n\t\t\t\t\tcase \"black\":\n\t\t\t\t\t\tcattr.Color = tui.Color(0)\n\t\t\t\t\tcase \"red\":\n\t\t\t\t\t\tcattr.Color = tui.Color(1)\n\t\t\t\t\tcase \"green\":\n\t\t\t\t\t\tcattr.Color = tui.Color(2)\n\t\t\t\t\tcase \"yellow\":\n\t\t\t\t\t\tcattr.Color = tui.Color(3)\n\t\t\t\t\tcase \"blue\":\n\t\t\t\t\t\tcattr.Color = tui.Color(4)\n\t\t\t\t\tcase \"magenta\":\n\t\t\t\t\t\tcattr.Color = tui.Color(5)\n\t\t\t\t\tcase \"cyan\":\n\t\t\t\t\t\tcattr.Color = tui.Color(6)\n\t\t\t\t\tcase \"white\":\n\t\t\t\t\t\tcattr.Color = tui.Color(7)\n\t\t\t\t\tcase \"bright-black\", \"gray\", \"grey\":\n\t\t\t\t\t\tcattr.Color = tui.Color(8)\n\t\t\t\t\tcase \"bright-red\":\n\t\t\t\t\t\tcattr.Color = tui.Color(9)\n\t\t\t\t\tcase \"bright-green\":\n\t\t\t\t\t\tcattr.Color = tui.Color(10)\n\t\t\t\t\tcase \"bright-yellow\":\n\t\t\t\t\t\tcattr.Color = tui.Color(11)\n\t\t\t\t\tcase \"bright-blue\":\n\t\t\t\t\t\tcattr.Color = tui.Color(12)\n\t\t\t\t\tcase \"bright-magenta\":\n\t\t\t\t\t\tcattr.Color = tui.Color(13)\n\t\t\t\t\tcase \"bright-cyan\":\n\t\t\t\t\t\tcattr.Color = tui.Color(14)\n\t\t\t\t\tcase \"bright-white\":\n\t\t\t\t\t\tcattr.Color = tui.Color(15)\n\t\t\t\t\tcase \"\":\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tif rrggbb.MatchString(component) {\n\t\t\t\t\t\t\tcattr.Color = tui.HexToColor(component)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tansi32, err := strconv.Atoi(component)\n\t\t\t\t\t\t\tif err != nil || ansi32 < -1 || ansi32 > 255 {\n\t\t\t\t\t\t\t\tfail()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcattr.Color = tui.Color(ansi32)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tswitch components[0] {\n\t\t\tcase \"query\", \"input\", \"input-fg\":\n\t\t\t\tmergeAttr(&theme.Input)\n\t\t\tcase \"ghost\":\n\t\t\t\tmergeAttr(&theme.Ghost)\n\t\t\tcase \"disabled\":\n\t\t\t\tmergeAttr(&theme.Disabled)\n\t\t\tcase \"fg\":\n\t\t\t\tmergeAttr(&theme.Fg)\n\t\t\tcase \"bg\":\n\t\t\t\tmergeAttr(&theme.Bg)\n\t\t\tcase \"list-fg\":\n\t\t\t\tmergeAttr(&theme.ListFg)\n\t\t\tcase \"list-bg\":\n\t\t\t\tmergeAttr(&theme.ListBg)\n\t\t\tcase \"preview-fg\":\n\t\t\t\tmergeAttr(&theme.PreviewFg)\n\t\t\tcase \"preview-bg\":\n\t\t\t\tmergeAttr(&theme.PreviewBg)\n\t\t\tcase \"current-fg\", \"fg+\":\n\t\t\t\tmergeAttr(&theme.Current)\n\t\t\tcase \"current-bg\", \"bg+\":\n\t\t\t\tmergeAttr(&theme.DarkBg)\n\t\t\tcase \"alt-bg\":\n\t\t\t\tmergeAttr(&theme.AltBg)\n\t\t\tcase \"selected-fg\":\n\t\t\t\tmergeAttr(&theme.SelectedFg)\n\t\t\tcase \"selected-bg\":\n\t\t\t\tmergeAttr(&theme.SelectedBg)\n\t\t\tcase \"nth\":\n\t\t\t\tmergeAttr(&theme.Nth)\n\t\t\tcase \"nomatch\":\n\t\t\t\tmergeAttr(&theme.Nomatch)\n\t\t\tcase \"gutter\":\n\t\t\t\tmergeAttr(&theme.Gutter)\n\t\t\tcase \"alt-gutter\":\n\t\t\t\tmergeAttr(&theme.AltGutter)\n\t\t\tcase \"hl\":\n\t\t\t\tmergeAttr(&theme.Match)\n\t\t\tcase \"current-hl\", \"hl+\":\n\t\t\t\tmergeAttr(&theme.CurrentMatch)\n\t\t\tcase \"selected-hl\":\n\t\t\t\tmergeAttr(&theme.SelectedMatch)\n\t\t\tcase \"border\":\n\t\t\t\tmergeAttr(&theme.Border)\n\t\t\tcase \"preview-border\":\n\t\t\t\tmergeAttr(&theme.PreviewBorder)\n\t\t\tcase \"separator\":\n\t\t\t\tmergeAttr(&theme.Separator)\n\t\t\tcase \"scrollbar\":\n\t\t\t\tmergeAttr(&theme.Scrollbar)\n\t\t\tcase \"preview-scrollbar\":\n\t\t\t\tmergeAttr(&theme.PreviewScrollbar)\n\t\t\tcase \"label\":\n\t\t\t\tmergeAttr(&theme.BorderLabel)\n\t\t\tcase \"list-label\":\n\t\t\t\tmergeAttr(&theme.ListLabel)\n\t\t\tcase \"list-border\":\n\t\t\t\tmergeAttr(&theme.ListBorder)\n\t\t\tcase \"preview-label\":\n\t\t\t\tmergeAttr(&theme.PreviewLabel)\n\t\t\tcase \"prompt\":\n\t\t\t\tmergeAttr(&theme.Prompt)\n\t\t\tcase \"input-bg\":\n\t\t\t\tmergeAttr(&theme.InputBg)\n\t\t\tcase \"input-border\":\n\t\t\t\tmergeAttr(&theme.InputBorder)\n\t\t\tcase \"input-label\":\n\t\t\t\tmergeAttr(&theme.InputLabel)\n\t\t\tcase \"header-border\":\n\t\t\t\tmergeAttr(&theme.HeaderBorder)\n\t\t\tcase \"header-label\":\n\t\t\t\tmergeAttr(&theme.HeaderLabel)\n\t\t\tcase \"footer-border\":\n\t\t\t\tmergeAttr(&theme.FooterBorder)\n\t\t\tcase \"footer-label\":\n\t\t\t\tmergeAttr(&theme.FooterLabel)\n\t\t\tcase \"spinner\":\n\t\t\t\tmergeAttr(&theme.Spinner)\n\t\t\tcase \"info\":\n\t\t\t\tmergeAttr(&theme.Info)\n\t\t\tcase \"pointer\":\n\t\t\t\tmergeAttr(&theme.Cursor)\n\t\t\tcase \"marker\":\n\t\t\t\tmergeAttr(&theme.Marker)\n\t\t\tcase \"header\", \"header-fg\":\n\t\t\t\tmergeAttr(&theme.Header)\n\t\t\tcase \"header-bg\":\n\t\t\t\tmergeAttr(&theme.HeaderBg)\n\t\t\tcase \"footer\", \"footer-fg\":\n\t\t\t\tmergeAttr(&theme.Footer)\n\t\t\tcase \"footer-bg\":\n\t\t\t\tmergeAttr(&theme.FooterBg)\n\t\t\tcase \"gap-line\":\n\t\t\t\tmergeAttr(&theme.GapLine)\n\t\t\tdefault:\n\t\t\t\tfail()\n\t\t\t}\n\t\t}\n\t}\n\treturn baseTheme, theme, err\n}\n\nfunc parseWalkerOpts(str string) (walkerOpts, error) {\n\topts := walkerOpts{}\n\tfor _, str := range strings.Split(strings.ToLower(str), \",\") {\n\t\tswitch str {\n\t\tcase \"file\":\n\t\t\topts.file = true\n\t\tcase \"dir\":\n\t\t\topts.dir = true\n\t\tcase \"hidden\":\n\t\t\topts.hidden = true\n\t\tcase \"follow\":\n\t\t\topts.follow = true\n\t\tcase \"\":\n\t\t\t// Ignored\n\t\tdefault:\n\t\t\treturn opts, errors.New(\"invalid walker option: \" + str)\n\t\t}\n\t}\n\tif !opts.file && !opts.dir {\n\t\treturn opts, errors.New(\"at least one of 'file' or 'dir' should be specified\")\n\t}\n\treturn opts, nil\n}\n\nvar (\n\targActionRegexp  *regexp.Regexp\n\tsplitRegexp      *regexp.Regexp\n\tactionNameRegexp *regexp.Regexp\n)\n\nfunc firstKey(keymap map[tui.Event]string) tui.Event {\n\tfor k := range keymap {\n\t\treturn k\n\t}\n\treturn tui.EventType(0).AsEvent()\n}\n\nconst (\n\tescapedColon = 0\n\tescapedComma = 1\n\tescapedPlus  = 2\n)\n\nfunc init() {\n\targActionRegexp = regexp.MustCompile(\n\t\t`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)\n\tsplitRegexp = regexp.MustCompile(\"[,:]+\")\n\tactionNameRegexp = regexp.MustCompile(\"(?i)^[a-z-]+\")\n}\n\nfunc maskActionContents(action string) string {\n\tmasked := \"\"\nLoop:\n\tfor len(action) > 0 {\n\t\tloc := argActionRegexp.FindStringIndex(action)\n\t\tif loc == nil {\n\t\t\tmasked += action\n\t\t\tbreak\n\t\t}\n\t\tmasked += action[:loc[1]]\n\t\taction = action[loc[1]:]\n\t\tif len(action) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tcs := string(action[0])\n\t\tvar ce string\n\t\tswitch action[0] {\n\t\tcase ':':\n\t\t\tmasked += strings.Repeat(\" \", len(action))\n\t\t\tbreak Loop\n\t\tcase '(':\n\t\t\tce = \")\"\n\t\tcase '{':\n\t\t\tce = \"}\"\n\t\tcase '[':\n\t\t\tce = \"]\"\n\t\tcase '<':\n\t\t\tce = \">\"\n\t\tcase '~', '!', '@', '#', '$', '%', '^', '&', '*', ';', '/', '|':\n\t\t\tce = string(cs)\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tcs = regexp.QuoteMeta(cs)\n\t\tce = regexp.QuoteMeta(ce)\n\n\t\t// @$ or @+\n\t\tloc = regexp.MustCompile(fmt.Sprintf(`(?s)^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)\n\t\tif loc == nil {\n\t\t\tmasked += action\n\t\t\tbreak\n\t\t}\n\t\t// Keep + or , at the end\n\t\tlastChar := action[loc[1]-1]\n\t\tif lastChar == '+' || lastChar == ',' {\n\t\t\tloc[1]--\n\t\t}\n\t\tmasked += strings.Repeat(\" \", loc[1])\n\t\taction = action[loc[1]:]\n\t}\n\tmasked = strings.ReplaceAll(masked, \",,,\", string([]rune{',', escapedComma, ','}))\n\tmasked = strings.ReplaceAll(masked, \",:,\", string([]rune{',', escapedColon, ','}))\n\tmasked = strings.ReplaceAll(masked, \"::\", string([]rune{escapedColon, ':'}))\n\tmasked = strings.ReplaceAll(masked, \",:\", string([]rune{escapedComma, ':'}))\n\tmasked = strings.ReplaceAll(masked, \"+:\", string([]rune{escapedPlus, ':'}))\n\treturn masked\n}\n\nfunc parseSingleActionList(str string) ([]*action, error) {\n\t// We prepend a colon to satisfy argActionRegexp and remove it later\n\tmasked := maskActionContents(\":\" + str)[1:]\n\treturn parseActionList(masked, str, []*action{}, false)\n}\n\nfunc parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) {\n\tmaskedStrings := strings.Split(masked, \"+\")\n\toriginalStrings := make([]string, len(maskedStrings))\n\tidx := 0\n\tfor i, maskedString := range maskedStrings {\n\t\toriginalStrings[i] = original[idx : idx+len(maskedString)]\n\t\tidx += len(maskedString) + 1\n\t}\n\tactions := make([]*action, 0, len(maskedStrings))\n\tappendAction := func(types ...actionType) {\n\t\tactions = append(actions, toActions(types...)...)\n\t}\n\tprevSpec := \"\"\n\tfor specIndex, spec := range originalStrings {\n\t\tspec = prevSpec + spec\n\t\tspecLower := strings.ToLower(spec)\n\t\tswitch specLower {\n\t\tcase \"ignore\":\n\t\t\tappendAction(actIgnore)\n\t\tcase \"beginning-of-line\":\n\t\t\tappendAction(actBeginningOfLine)\n\t\tcase \"abort\":\n\t\t\tappendAction(actAbort)\n\t\tcase \"accept\":\n\t\t\tappendAction(actAccept)\n\t\tcase \"accept-non-empty\":\n\t\t\tappendAction(actAcceptNonEmpty)\n\t\tcase \"accept-or-print-query\":\n\t\t\tappendAction(actAcceptOrPrintQuery)\n\t\tcase \"print-query\":\n\t\t\tappendAction(actPrintQuery)\n\t\tcase \"refresh-preview\":\n\t\t\tappendAction(actRefreshPreview)\n\t\tcase \"replace-query\":\n\t\t\tappendAction(actReplaceQuery)\n\t\tcase \"backward-char\":\n\t\t\tappendAction(actBackwardChar)\n\t\tcase \"backward-delete-char\":\n\t\t\tappendAction(actBackwardDeleteChar)\n\t\tcase \"backward-delete-char/eof\":\n\t\t\tappendAction(actBackwardDeleteCharEof)\n\t\tcase \"backward-word\":\n\t\t\tappendAction(actBackwardWord)\n\t\tcase \"backward-subword\":\n\t\t\tappendAction(actBackwardSubWord)\n\t\tcase \"clear-screen\":\n\t\t\tappendAction(actClearScreen)\n\t\tcase \"delete-char\":\n\t\t\tappendAction(actDeleteChar)\n\t\tcase \"delete-char/eof\":\n\t\t\tappendAction(actDeleteCharEof)\n\t\tcase \"deselect\":\n\t\t\tappendAction(actDeselect)\n\t\tcase \"end-of-line\":\n\t\t\tappendAction(actEndOfLine)\n\t\tcase \"cancel\":\n\t\t\tappendAction(actCancel)\n\t\tcase \"clear-query\":\n\t\t\tappendAction(actClearQuery)\n\t\tcase \"clear-multi\", \"clear-selection\":\n\t\t\tappendAction(actClearSelection)\n\t\tcase \"forward-char\":\n\t\t\tappendAction(actForwardChar)\n\t\tcase \"forward-word\":\n\t\t\tappendAction(actForwardWord)\n\t\tcase \"forward-subword\":\n\t\t\tappendAction(actForwardSubWord)\n\t\tcase \"jump\":\n\t\t\tappendAction(actJump)\n\t\tcase \"jump-accept\":\n\t\t\tappendAction(actJumpAccept)\n\t\tcase \"kill-line\":\n\t\t\tappendAction(actKillLine)\n\t\tcase \"kill-word\":\n\t\t\tappendAction(actKillWord)\n\t\tcase \"kill-subword\":\n\t\t\tappendAction(actKillSubWord)\n\t\tcase \"unix-line-discard\", \"line-discard\":\n\t\t\tappendAction(actUnixLineDiscard)\n\t\tcase \"unix-word-rubout\", \"word-rubout\":\n\t\t\tappendAction(actUnixWordRubout)\n\t\tcase \"yank\":\n\t\t\tappendAction(actYank)\n\t\tcase \"backward-kill-word\":\n\t\t\tappendAction(actBackwardKillWord)\n\t\tcase \"backward-kill-subword\":\n\t\t\tappendAction(actBackwardKillSubWord)\n\t\tcase \"toggle-down\":\n\t\t\tappendAction(actToggle, actDown)\n\t\tcase \"toggle-up\":\n\t\t\tappendAction(actToggle, actUp)\n\t\tcase \"toggle-in\":\n\t\t\tappendAction(actToggleIn)\n\t\tcase \"toggle-out\":\n\t\t\tappendAction(actToggleOut)\n\t\tcase \"toggle-all\":\n\t\t\tappendAction(actToggleAll)\n\t\tcase \"toggle-search\":\n\t\t\tappendAction(actToggleSearch)\n\t\tcase \"toggle-track\":\n\t\t\tappendAction(actToggleTrack)\n\t\tcase \"toggle-track-current\":\n\t\t\tappendAction(actToggleTrackCurrent)\n\t\tcase \"toggle-input\":\n\t\t\tappendAction(actToggleInput)\n\t\tcase \"hide-input\":\n\t\t\tappendAction(actHideInput)\n\t\tcase \"show-input\":\n\t\t\tappendAction(actShowInput)\n\t\tcase \"toggle-header\":\n\t\t\tappendAction(actToggleHeader)\n\t\tcase \"toggle-wrap\":\n\t\t\tappendAction(actToggleWrap)\n\t\tcase \"toggle-wrap-word\":\n\t\t\tappendAction(actToggleWrapWord)\n\t\tcase \"toggle-multi-line\":\n\t\t\tappendAction(actToggleMultiLine)\n\t\tcase \"toggle-hscroll\":\n\t\t\tappendAction(actToggleHscroll)\n\t\tcase \"toggle-raw\":\n\t\t\tappendAction(actToggleRaw)\n\t\tcase \"enable-raw\":\n\t\t\tappendAction(actEnableRaw)\n\t\tcase \"disable-raw\":\n\t\t\tappendAction(actDisableRaw)\n\t\tcase \"show-header\":\n\t\t\tappendAction(actShowHeader)\n\t\tcase \"hide-header\":\n\t\t\tappendAction(actHideHeader)\n\t\tcase \"track\", \"track-current\":\n\t\t\tappendAction(actTrackCurrent)\n\t\tcase \"untrack-current\":\n\t\t\tappendAction(actUntrackCurrent)\n\t\tcase \"select\":\n\t\t\tappendAction(actSelect)\n\t\tcase \"select-all\":\n\t\t\tappendAction(actSelectAll)\n\t\tcase \"deselect-all\":\n\t\t\tappendAction(actDeselectAll)\n\t\tcase \"close\":\n\t\t\tappendAction(actClose)\n\t\tcase \"toggle\":\n\t\t\tappendAction(actToggle)\n\t\tcase \"down\":\n\t\t\tappendAction(actDown)\n\t\tcase \"down-match\":\n\t\t\tappendAction(actDownMatch)\n\t\tcase \"up\":\n\t\t\tappendAction(actUp)\n\t\tcase \"up-match\":\n\t\t\tappendAction(actUpMatch)\n\t\tcase \"first\", \"top\":\n\t\t\tappendAction(actFirst)\n\t\tcase \"last\":\n\t\t\tappendAction(actLast)\n\t\tcase \"best\":\n\t\t\tappendAction(actBest)\n\t\tcase \"page-up\":\n\t\t\tappendAction(actPageUp)\n\t\tcase \"page-down\":\n\t\t\tappendAction(actPageDown)\n\t\tcase \"half-page-up\":\n\t\t\tappendAction(actHalfPageUp)\n\t\tcase \"half-page-down\":\n\t\t\tappendAction(actHalfPageDown)\n\t\tcase \"prev-history\", \"previous-history\":\n\t\t\tappendAction(actPrevHistory)\n\t\tcase \"next-history\":\n\t\t\tappendAction(actNextHistory)\n\t\tcase \"up-selected\", \"prev-selected\":\n\t\t\tappendAction(actPrevSelected)\n\t\tcase \"down-selected\", \"next-selected\":\n\t\t\tappendAction(actNextSelected)\n\t\tcase \"show-preview\":\n\t\t\tappendAction(actShowPreview)\n\t\tcase \"hide-preview\":\n\t\t\tappendAction(actHidePreview)\n\t\tcase \"toggle-preview\":\n\t\t\tappendAction(actTogglePreview)\n\t\tcase \"toggle-preview-wrap\":\n\t\t\tappendAction(actTogglePreviewWrap)\n\t\tcase \"toggle-preview-wrap-word\":\n\t\t\tappendAction(actTogglePreviewWrapWord)\n\t\tcase \"toggle-sort\":\n\t\t\tappendAction(actToggleSort)\n\t\tcase \"offset-up\":\n\t\t\tappendAction(actOffsetUp)\n\t\tcase \"offset-down\":\n\t\t\tappendAction(actOffsetDown)\n\t\tcase \"offset-middle\":\n\t\t\tappendAction(actOffsetMiddle)\n\t\tcase \"preview-top\":\n\t\t\tappendAction(actPreviewTop)\n\t\tcase \"preview-bottom\":\n\t\t\tappendAction(actPreviewBottom)\n\t\tcase \"preview-up\":\n\t\t\tappendAction(actPreviewUp)\n\t\tcase \"preview-down\":\n\t\t\tappendAction(actPreviewDown)\n\t\tcase \"preview-page-up\":\n\t\t\tappendAction(actPreviewPageUp)\n\t\tcase \"preview-page-down\":\n\t\t\tappendAction(actPreviewPageDown)\n\t\tcase \"preview-half-page-up\":\n\t\t\tappendAction(actPreviewHalfPageUp)\n\t\tcase \"preview-half-page-down\":\n\t\t\tappendAction(actPreviewHalfPageDown)\n\t\tcase \"enable-search\":\n\t\t\tappendAction(actEnableSearch)\n\t\tcase \"disable-search\":\n\t\t\tappendAction(actDisableSearch)\n\t\tcase \"put\":\n\t\t\tif putAllowed {\n\t\t\t\tappendAction(actChar)\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"unable to put non-printable character\")\n\t\t\t}\n\t\tcase \"bell\":\n\t\t\tappendAction(actBell)\n\t\tcase \"exclude\":\n\t\t\tappendAction(actExclude)\n\t\tcase \"exclude-multi\":\n\t\t\tappendAction(actExcludeMulti)\n\t\tcase \"bg-cancel\":\n\t\t\tappendAction(actBgCancel)\n\t\tdefault:\n\t\t\tt := isExecuteAction(specLower)\n\t\t\tif t == actIgnore {\n\t\t\t\tif specIndex == 0 && specLower == \"\" {\n\t\t\t\t\tactions = append(prevActions, actions...)\n\t\t\t\t} else if specLower == \"change-multi\" {\n\t\t\t\t\tappendAction(actChangeMulti)\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, errors.New(\"unknown action: \" + spec)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\toffset := len(actionNameRegexp.FindString(spec))\n\t\t\t\tvar actionArg string\n\t\t\t\tif spec[offset] == ':' {\n\t\t\t\t\tif specIndex == len(originalStrings)-1 {\n\t\t\t\t\t\tactionArg = spec[offset+1:]\n\t\t\t\t\t\tactions = append(actions, &action{t: t, a: actionArg})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprevSpec = spec + \"+\"\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tactionArg = spec[offset+1 : len(spec)-1]\n\t\t\t\t\tactions = append(actions, &action{t: t, a: actionArg})\n\t\t\t\t}\n\t\t\t\tswitch t {\n\t\t\t\tcase actUnbind, actRebind, actToggleBind:\n\t\t\t\t\tif _, _, err := parseKeyChords(actionArg, spec[0:offset]+\" target required\"); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\tcase actChangePreviewWindow:\n\t\t\t\t\topts := previewOpts{}\n\t\t\t\t\tfor _, arg := range strings.Split(actionArg, \"|\") {\n\t\t\t\t\t\t// Make sure that each expression is valid\n\t\t\t\t\t\tif err := parsePreviewWindowImpl(&opts, arg); err != nil {\n\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tprevSpec = \"\"\n\t}\n\treturn actions, nil\n}\n\nfunc parseKeymap(keymap map[tui.Event][]*action, str string) error {\n\tvar err error\n\tmasked := maskActionContents(str)\n\tidx := 0\n\tkeys := []string{}\n\tfor _, pairStr := range strings.Split(masked, \",\") {\n\t\torigPairStr := str[idx : idx+len(pairStr)]\n\t\tidx += len(pairStr) + 1\n\n\t\tpair := strings.SplitN(pairStr, \":\", 2)\n\t\tif len(pair[0]) == 0 {\n\t\t\treturn errors.New(\"key name required\")\n\t\t}\n\t\tkeys = append(keys, pair[0])\n\t\tif len(pair) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, keyName := range keys {\n\t\t\tvar key tui.Event\n\t\t\tif len(keyName) == 1 && keyName[0] == escapedColon {\n\t\t\t\tkey = tui.Key(':')\n\t\t\t} else if len(keyName) == 1 && keyName[0] == escapedComma {\n\t\t\t\tkey = tui.Key(',')\n\t\t\t} else if len(keyName) == 1 && keyName[0] == escapedPlus {\n\t\t\t\tkey = tui.Key('+')\n\t\t\t} else {\n\t\t\t\tkeys, _, err := parseKeyChords(keyName, \"key name required\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tkey = firstKey(keys)\n\t\t\t}\n\t\t\tputAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char)\n\t\t\tkeymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tkeys = keys[:0]\n\t}\n\tif len(keys) > 0 {\n\t\treturn errors.New(\"bind action not specified: \" + strings.Join(keys, \", \"))\n\t}\n\treturn nil\n}\n\nfunc isExecuteAction(str string) actionType {\n\tmasked := maskActionContents(\":\" + str)[1:]\n\tif masked == str {\n\t\t// Not masked\n\t\treturn actIgnore\n\t}\n\n\tprefix := actionNameRegexp.FindString(str)\n\tswitch prefix {\n\tcase \"become\":\n\t\treturn actBecome\n\tcase \"reload\":\n\t\treturn actReload\n\tcase \"reload-sync\":\n\t\treturn actReloadSync\n\tcase \"unbind\":\n\t\treturn actUnbind\n\tcase \"rebind\":\n\t\treturn actRebind\n\tcase \"toggle-bind\":\n\t\treturn actToggleBind\n\tcase \"preview\":\n\t\treturn actPreview\n\tcase \"change-header\":\n\t\treturn actChangeHeader\n\tcase \"change-header-lines\":\n\t\treturn actChangeHeaderLines\n\tcase \"change-footer\":\n\t\treturn actChangeFooter\n\tcase \"change-list-label\":\n\t\treturn actChangeListLabel\n\tcase \"change-border-label\":\n\t\treturn actChangeBorderLabel\n\tcase \"change-preview-label\":\n\t\treturn actChangePreviewLabel\n\tcase \"change-input-label\":\n\t\treturn actChangeInputLabel\n\tcase \"change-header-label\":\n\t\treturn actChangeHeaderLabel\n\tcase \"change-footer-label\":\n\t\treturn actChangeFooterLabel\n\tcase \"change-ghost\":\n\t\treturn actChangeGhost\n\tcase \"change-pointer\":\n\t\treturn actChangePointer\n\tcase \"change-preview-window\":\n\t\treturn actChangePreviewWindow\n\tcase \"change-preview\":\n\t\treturn actChangePreview\n\tcase \"change-prompt\":\n\t\treturn actChangePrompt\n\tcase \"change-query\":\n\t\treturn actChangeQuery\n\tcase \"change-multi\":\n\t\treturn actChangeMulti\n\tcase \"change-nth\":\n\t\treturn actChangeNth\n\tcase \"change-with-nth\":\n\t\treturn actChangeWithNth\n\tcase \"pos\":\n\t\treturn actPosition\n\tcase \"execute\":\n\t\treturn actExecute\n\tcase \"execute-silent\":\n\t\treturn actExecuteSilent\n\tcase \"execute-multi\":\n\t\treturn actExecuteMulti\n\tcase \"print\":\n\t\treturn actPrint\n\tcase \"put\":\n\t\treturn actPut\n\tcase \"transform\":\n\t\treturn actTransform\n\tcase \"transform-list-label\":\n\t\treturn actTransformListLabel\n\tcase \"transform-border-label\":\n\t\treturn actTransformBorderLabel\n\tcase \"transform-preview-label\":\n\t\treturn actTransformPreviewLabel\n\tcase \"transform-input-label\":\n\t\treturn actTransformInputLabel\n\tcase \"transform-header-label\":\n\t\treturn actTransformHeaderLabel\n\tcase \"transform-footer-label\":\n\t\treturn actTransformFooterLabel\n\tcase \"transform-footer\":\n\t\treturn actTransformFooter\n\tcase \"transform-header\":\n\t\treturn actTransformHeader\n\tcase \"transform-header-lines\":\n\t\treturn actTransformHeaderLines\n\tcase \"transform-ghost\":\n\t\treturn actTransformGhost\n\tcase \"transform-nth\":\n\t\treturn actTransformNth\n\tcase \"transform-with-nth\":\n\t\treturn actTransformWithNth\n\tcase \"transform-pointer\":\n\t\treturn actTransformPointer\n\tcase \"transform-prompt\":\n\t\treturn actTransformPrompt\n\tcase \"transform-query\":\n\t\treturn actTransformQuery\n\tcase \"transform-search\":\n\t\treturn actTransformSearch\n\tcase \"bg-transform\":\n\t\treturn actBgTransform\n\tcase \"bg-transform-list-label\":\n\t\treturn actBgTransformListLabel\n\tcase \"bg-transform-border-label\":\n\t\treturn actBgTransformBorderLabel\n\tcase \"bg-transform-preview-label\":\n\t\treturn actBgTransformPreviewLabel\n\tcase \"bg-transform-input-label\":\n\t\treturn actBgTransformInputLabel\n\tcase \"bg-transform-header-label\":\n\t\treturn actBgTransformHeaderLabel\n\tcase \"bg-transform-footer-label\":\n\t\treturn actBgTransformFooterLabel\n\tcase \"bg-transform-footer\":\n\t\treturn actBgTransformFooter\n\tcase \"bg-transform-header\":\n\t\treturn actBgTransformHeader\n\tcase \"bg-transform-header-lines\":\n\t\treturn actBgTransformHeaderLines\n\tcase \"bg-transform-ghost\":\n\t\treturn actBgTransformGhost\n\tcase \"bg-transform-nth\":\n\t\treturn actBgTransformNth\n\tcase \"bg-transform-with-nth\":\n\t\treturn actBgTransformWithNth\n\tcase \"bg-transform-pointer\":\n\t\treturn actBgTransformPointer\n\tcase \"bg-transform-prompt\":\n\t\treturn actBgTransformPrompt\n\tcase \"bg-transform-query\":\n\t\treturn actBgTransformQuery\n\tcase \"bg-transform-search\":\n\t\treturn actBgTransformSearch\n\tcase \"trigger\":\n\t\treturn actTrigger\n\tcase \"search\":\n\t\treturn actSearch\n\t}\n\treturn actIgnore\n}\n\nfunc parseToggleSort(keymap map[tui.Event][]*action, str string) error {\n\tkeys, _, err := parseKeyChords(str, \"key name required\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(keys) != 1 {\n\t\treturn errors.New(\"multiple keys specified\")\n\t}\n\tkeymap[firstKey(keys)] = toActions(actToggleSort)\n\treturn nil\n}\n\nfunc strLines(str string) []string {\n\treturn strings.Split(strings.TrimSuffix(str, \"\\n\"), \"\\n\")\n}\n\nfunc parseSize(str string, maxPercent float64, label string) (sizeSpec, error) {\n\tvar spec = sizeSpec{}\n\tvar val float64\n\tvar err error\n\tpercent := strings.HasSuffix(str, \"%\")\n\tif percent {\n\t\tif val, err = atof(str[:len(str)-1]); err != nil {\n\t\t\treturn spec, err\n\t\t}\n\n\t\tif val < 0 {\n\t\t\treturn spec, errors.New(label + \" must be non-negative\")\n\t\t}\n\t\tif val > maxPercent {\n\t\t\treturn spec, fmt.Errorf(\"%s too large (max: %d%%)\", label, int(maxPercent))\n\t\t}\n\t} else {\n\t\tif strings.Contains(str, \".\") {\n\t\t\treturn spec, errors.New(label + \" (without %) must be a non-negative integer\")\n\t\t}\n\n\t\ti, err := atoi(str)\n\t\tif err != nil {\n\t\t\treturn spec, err\n\t\t}\n\t\tval = float64(i)\n\t\tif val < 0 {\n\t\t\treturn spec, errors.New(label + \" must be non-negative\")\n\t\t}\n\t}\n\treturn sizeSpec{val, percent}, nil\n}\n\nfunc parseHeight(str string, index int) (heightSpec, error) {\n\theightSpec := heightSpec{index: index}\n\tif strings.HasPrefix(str, \"~\") {\n\t\theightSpec.auto = true\n\t\tstr = str[1:]\n\t}\n\tif strings.HasPrefix(str, \"-\") {\n\t\tif heightSpec.auto {\n\t\t\treturn heightSpec, errors.New(\"negative(-) height is not compatible with adaptive(~) height\")\n\t\t}\n\t\theightSpec.inverse = true\n\t\tstr = str[1:]\n\t}\n\n\tsize, err := parseSize(str, 100, \"height\")\n\tif err != nil {\n\t\treturn heightSpec, err\n\t}\n\theightSpec.size = size.size\n\theightSpec.percent = size.percent\n\treturn heightSpec, nil\n}\n\nfunc parseLayout(str string) (layoutType, error) {\n\tswitch str {\n\tcase \"default\":\n\t\treturn layoutDefault, nil\n\tcase \"reverse\":\n\t\treturn layoutReverse, nil\n\tcase \"reverse-list\":\n\t\treturn layoutReverseList, nil\n\t}\n\treturn layoutDefault, errors.New(\"invalid layout (expected: default / reverse / reverse-list)\")\n}\n\nfunc parseInfoStyle(str string) (infoStyle, string, error) {\n\tswitch str {\n\tcase \"default\":\n\t\treturn infoDefault, \"\", nil\n\tcase \"right\":\n\t\treturn infoRight, \"\", nil\n\tcase \"inline\":\n\t\treturn infoInline, defaultInfoPrefix, nil\n\tcase \"inline-right\":\n\t\treturn infoInlineRight, \"\", nil\n\tcase \"hidden\":\n\t\treturn infoHidden, \"\", nil\n\t}\n\ttype infoSpec struct {\n\t\tname  string\n\t\tstyle infoStyle\n\t}\n\tfor _, spec := range []infoSpec{\n\t\t{\"inline\", infoInline},\n\t\t{\"inline-right\", infoInlineRight}} {\n\t\tif strings.HasPrefix(str, spec.name+\":\") {\n\t\t\treturn spec.style, strings.ReplaceAll(str[len(spec.name)+1:], \"\\n\", \" \"), nil\n\t\t}\n\t}\n\treturn infoDefault, \"\", errors.New(\"invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])\")\n}\n\nfunc parsePreviewWindow(opts *previewOpts, input string) error {\n\treturn parsePreviewWindowImpl(opts, input)\n}\n\nfunc parsePreviewWindowImpl(opts *previewOpts, input string) error {\n\tvar err error\n\ttokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\\(([^)<]+)\\)|[^,:]+)`)\n\tsizeRegex := regexp.MustCompile(\"^[0-9]+%?$\")\n\toffsetRegex := regexp.MustCompile(`^(\\+{(-?[0-9]+|n)})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)\n\theaderRegex := regexp.MustCompile(\"^~(0|[1-9][0-9]*)$\")\n\ttokens := tokenRegex.FindAllStringSubmatch(input, -1)\n\tvar alternative string\n\tfor _, match := range tokens {\n\t\tif len(match[2]) > 0 {\n\t\t\tif opts.threshold, err = atoi(match[2]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\talternative = match[3]\n\t\t\tcontinue\n\t\t}\n\t\ttoken := match[1]\n\t\tswitch token {\n\t\tcase \"\":\n\t\tcase \"default\":\n\t\t\t*opts = defaultPreviewOpts(opts.command)\n\t\tcase \"hidden\":\n\t\t\topts.hidden = true\n\t\tcase \"nohidden\":\n\t\t\topts.hidden = false\n\t\tcase \"wrap\":\n\t\t\topts.wrap = true\n\t\t\topts.wrapWord = false\n\t\tcase \"wrap-word\":\n\t\t\topts.wrap = true\n\t\t\topts.wrapWord = true\n\t\tcase \"nowrap\":\n\t\t\topts.wrap = false\n\t\t\topts.wrapWord = false\n\t\tcase \"cycle\":\n\t\t\topts.cycle = true\n\t\tcase \"nocycle\":\n\t\t\topts.cycle = false\n\t\tcase \"up\", \"top\":\n\t\t\topts.position = posUp\n\t\tcase \"down\", \"bottom\":\n\t\t\topts.position = posDown\n\t\tcase \"left\":\n\t\t\topts.position = posLeft\n\t\tcase \"right\":\n\t\t\topts.position = posRight\n\t\tcase \"rounded\", \"border\", \"border-rounded\":\n\t\t\topts.border = tui.BorderRounded\n\t\tcase \"border-line\":\n\t\t\topts.border = tui.BorderLine\n\t\tcase \"sharp\", \"border-sharp\":\n\t\t\topts.border = tui.BorderSharp\n\t\tcase \"border-bold\":\n\t\t\topts.border = tui.BorderBold\n\t\tcase \"border-block\":\n\t\t\topts.border = tui.BorderBlock\n\t\tcase \"border-thinblock\":\n\t\t\topts.border = tui.BorderThinBlock\n\t\tcase \"border-double\":\n\t\t\topts.border = tui.BorderDouble\n\t\tcase \"noborder\", \"border-none\":\n\t\t\topts.border = tui.BorderNone\n\t\tcase \"border-horizontal\":\n\t\t\topts.border = tui.BorderHorizontal\n\t\tcase \"border-vertical\":\n\t\t\topts.border = tui.BorderVertical\n\t\tcase \"border-up\", \"border-top\":\n\t\t\topts.border = tui.BorderTop\n\t\tcase \"border-down\", \"border-bottom\":\n\t\t\topts.border = tui.BorderBottom\n\t\tcase \"border-left\":\n\t\t\topts.border = tui.BorderLeft\n\t\tcase \"border-right\":\n\t\t\topts.border = tui.BorderRight\n\t\tcase \"follow\":\n\t\t\topts.follow = true\n\t\tcase \"nofollow\":\n\t\t\topts.follow = false\n\t\tcase \"info\":\n\t\t\topts.info = true\n\t\tcase \"noinfo\":\n\t\t\topts.info = false\n\t\tdefault:\n\t\t\tif headerRegex.MatchString(token) {\n\t\t\t\tif opts.headerLines, err = atoi(token[1:]); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if sizeRegex.MatchString(token) {\n\t\t\t\tif opts.size, err = parseSize(token, 99, \"window size\"); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if offsetRegex.MatchString(token) {\n\t\t\t\topts.scroll = token\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"invalid preview window option: \" + token)\n\t\t\t}\n\t\t}\n\t}\n\tif len(alternative) > 0 {\n\t\talternativeOpts := *opts\n\t\topts.alternative = &alternativeOpts\n\t\topts.alternative.hidden = false\n\t\topts.alternative.alternative = nil\n\t\terr = parsePreviewWindowImpl(opts.alternative, alternative)\n\t}\n\treturn err\n}\n\nfunc parseMargin(opt string, margin string) ([4]sizeSpec, error) {\n\tmargins := strings.Split(margin, \",\")\n\tchecked := func(str string) (sizeSpec, error) {\n\t\treturn parseSize(str, 49, opt)\n\t}\n\tswitch len(margins) {\n\tcase 1:\n\t\tm, e := checked(margins[0])\n\t\treturn [4]sizeSpec{m, m, m, m}, e\n\tcase 2:\n\t\ttb, e := checked(margins[0])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\trl, e := checked(margins[1])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\treturn [4]sizeSpec{tb, rl, tb, rl}, nil\n\tcase 3:\n\t\tt, e := checked(margins[0])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\trl, e := checked(margins[1])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\tb, e := checked(margins[2])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\treturn [4]sizeSpec{t, rl, b, rl}, nil\n\tcase 4:\n\t\tt, e := checked(margins[0])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\tr, e := checked(margins[1])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\tb, e := checked(margins[2])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\tl, e := checked(margins[3])\n\t\tif e != nil {\n\t\t\treturn defaultMargin(), e\n\t\t}\n\t\treturn [4]sizeSpec{t, r, b, l}, nil\n\t}\n\treturn [4]sizeSpec{}, errors.New(\"invalid \" + opt + \": \" + margin)\n}\n\nfunc parseMarkerMultiLine(str string) (*[3]string, error) {\n\tif str == \"\" {\n\t\treturn &[3]string{}, nil\n\t}\n\tgr := uniseg.NewGraphemes(str)\n\tparts := []string{}\n\ttotalWidth := 0\n\tfor gr.Next() {\n\t\ts := string(gr.Runes())\n\t\ttotalWidth += uniseg.StringWidth(s)\n\t\tparts = append(parts, s)\n\t}\n\n\tresult := [3]string{}\n\tif totalWidth != 3 && totalWidth != 6 {\n\t\treturn &result, fmt.Errorf(\"invalid total marker width: %d (expected: 0, 3 or 6)\", totalWidth)\n\t}\n\n\texpected := totalWidth / 3\n\tidx := 0\n\tfor _, part := range parts {\n\t\texpected -= uniseg.StringWidth(part)\n\t\tresult[idx] += part\n\t\tif expected <= 0 {\n\t\t\tidx++\n\t\t\texpected = totalWidth / 3\n\t\t}\n\t\tif idx == 3 {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn &result, nil\n}\n\nfunc optString(arg string, prefix string) (bool, string) {\n\tif strings.HasPrefix(arg, prefix) {\n\t\treturn true, arg[len(prefix):]\n\t}\n\treturn false, \"\"\n}\n\nfunc parseOptions(index *int, opts *Options, allArgs []string) error {\n\tvar err error\n\tvar historyMax int\n\tif opts.History == nil {\n\t\thistoryMax = defaultHistoryMax\n\t} else {\n\t\thistoryMax = opts.History.maxSize\n\t}\n\tsetHistory := func(path string) error {\n\t\th, e := NewHistory(path, historyMax)\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\topts.History = h\n\t\treturn nil\n\t}\n\tsetHistoryMax := func(max int) error {\n\t\thistoryMax = max\n\t\tif historyMax < 1 {\n\t\t\treturn errors.New(\"history max must be a positive integer\")\n\t\t}\n\t\tif opts.History != nil {\n\t\t\topts.History.maxSize = historyMax\n\t\t}\n\t\treturn nil\n\t}\n\tvalidateJumpLabels := false\n\tclearExitingOpts := func() {\n\t\t// Last-one-wins strategy\n\t\topts.Bash = false\n\t\topts.Zsh = false\n\t\topts.Fish = false\n\t\topts.Help = false\n\t\topts.Version = false\n\t\topts.Man = false\n\t}\n\n\tstartIndex := *index\n\n\tvar i int\n\tvar val *string = nil\n\tnextString := func(message string) (string, error) {\n\t\tdefer func() { val = nil }()\n\t\tif val != nil {\n\t\t\treturn *val, nil\n\t\t}\n\t\tif len(allArgs) > i+1 {\n\t\t\ti++\n\t\t} else {\n\t\t\treturn \"\", errors.New(message)\n\t\t}\n\t\treturn allArgs[i], nil\n\t}\n\n\toptionalNextString := func() (bool, string) {\n\t\tdefer func() { val = nil }()\n\t\tif val != nil {\n\t\t\treturn true, *val\n\t\t}\n\t\tif len(allArgs) > i+1 && !strings.HasPrefix(allArgs[i+1], \"-\") && !strings.HasPrefix(allArgs[i+1], \"+\") {\n\t\t\ti++\n\t\t\treturn true, allArgs[i]\n\t\t}\n\t\treturn false, \"\"\n\t}\n\n\tnextDirs := func() ([]string, error) {\n\t\tdefer func() { val = nil }()\n\t\tdirs := []string{}\n\t\tif val != nil {\n\t\t\tdirs = append(dirs, *val)\n\t\t}\n\t\tfor i < len(allArgs)-1 {\n\t\t\targ := allArgs[i+1]\n\t\t\tif isDir(arg) {\n\t\t\t\tdirs = append(dirs, arg)\n\t\t\t\ti++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif len(dirs) == 0 {\n\t\t\treturn nil, errors.New(\"no directory specified\")\n\t\t}\n\t\treturn dirs, nil\n\t}\n\n\tnextInt := func(message string) (int, error) {\n\t\tdefer func() { val = nil }()\n\t\tvar str string\n\t\tif val != nil {\n\t\t\tstr = *val\n\t\t} else if len(allArgs) > i+1 {\n\t\t\ti++\n\t\t\tstr = allArgs[i]\n\t\t} else {\n\t\t\treturn 0, errors.New(message)\n\t\t}\n\t\tn, err := atoi(str)\n\t\tif err != nil {\n\t\t\treturn 0, errors.New(message)\n\t\t}\n\t\treturn n, nil\n\t}\n\n\toptionalNumeric := func(defaultValue int) (int, error) {\n\t\tdefer func() { val = nil }()\n\t\tvar str string\n\t\tif val != nil {\n\t\t\tstr = *val\n\t\t} else if len(allArgs) > i+1 && strings.IndexAny(allArgs[i+1], \"0123456789\") == 0 {\n\t\t\ti++\n\t\t\tstr = allArgs[i]\n\t\t} else {\n\t\t\treturn defaultValue, nil\n\t\t}\n\t\tn, err := atoi(str)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\treturn n, nil\n\t}\n\n\tfor ; i < len(allArgs); i++ {\n\t\targ := allArgs[i]\n\t\tindex := i + startIndex\n\t\tif strings.HasPrefix(arg, \"--\") && strings.IndexRune(arg, '=') > 0 {\n\t\t\ttokens := strings.SplitN(arg, \"=\", 2)\n\t\t\targ = tokens[0]\n\t\t\tval = &tokens[1]\n\t\t}\n\t\tswitch arg {\n\t\tcase \"--man\":\n\t\t\tclearExitingOpts()\n\t\t\topts.Man = true\n\t\tcase \"--bash\":\n\t\t\tclearExitingOpts()\n\t\t\topts.Bash = true\n\t\tcase \"--zsh\":\n\t\t\tclearExitingOpts()\n\t\t\topts.Zsh = true\n\t\tcase \"--fish\":\n\t\t\tclearExitingOpts()\n\t\t\topts.Fish = true\n\t\tcase \"-h\", \"--help\":\n\t\t\tclearExitingOpts()\n\t\t\topts.Help = true\n\t\tcase \"--version\":\n\t\t\tclearExitingOpts()\n\t\t\topts.Version = true\n\t\tcase \"--no-winpty\":\n\t\t\topts.NoWinpty = true\n\t\tcase \"--tmux\":\n\t\t\tgiven, str := optionalNextString()\n\t\t\tif given {\n\t\t\t\tif opts.Tmux, err = parseTmuxOptions(str, index); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\topts.Tmux = defaultTmuxOptions(index)\n\t\t\t}\n\t\tcase \"--no-tmux\":\n\t\t\topts.Tmux = nil\n\t\tcase \"--tty-default\":\n\t\t\tif opts.TtyDefault, err = nextString(\"tty device name required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-tty-default\":\n\t\t\topts.TtyDefault = \"\"\n\t\tcase \"--force-tty-in\":\n\t\t\t// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't\n\t\t\t// work on Neovim. Same as '-' option of fzf-tmux.\n\t\t\topts.ForceTtyIn = true\n\t\tcase \"--no-force-tty-in\":\n\t\t\topts.ForceTtyIn = false\n\t\tcase \"--proxy-script\":\n\t\t\tif opts.ProxyScript, err = nextString(\"\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"-x\", \"--extended\":\n\t\t\topts.Extended = true\n\t\tcase \"-e\", \"--exact\":\n\t\t\topts.Fuzzy = false\n\t\tcase \"--extended-exact\":\n\t\t\t// Note that we now don't have --no-extended-exact\n\t\t\topts.Fuzzy = false\n\t\t\topts.Extended = true\n\t\tcase \"+x\", \"--no-extended\":\n\t\t\topts.Extended = false\n\t\tcase \"+e\", \"--no-exact\":\n\t\t\topts.Fuzzy = true\n\t\tcase \"-q\", \"--query\":\n\t\t\tif opts.Query, err = nextString(\"query string required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"-f\", \"--filter\":\n\t\t\tfilter, err := nextString(\"query string required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.Filter = &filter\n\t\tcase \"--literal\":\n\t\t\topts.Normalize = false\n\t\tcase \"--no-literal\":\n\t\t\topts.Normalize = true\n\t\tcase \"--algo\":\n\t\t\tstr, err := nextString(\"algorithm required (v1|v2)\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.FuzzyAlgo, err = parseAlgo(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--scheme\":\n\t\t\tstr, err := nextString(\"scoring scheme required (default|path|history)\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Scheme, opts.Criteria, err = parseScheme(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--expect\":\n\t\t\tstr, err := nextString(\"key names required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tchords, _, err := parseKeyChords(str, \"key names required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmaps.Copy(opts.Expect, chords)\n\t\tcase \"--no-expect\":\n\t\t\topts.Expect = make(map[tui.Event]string)\n\t\tcase \"--enabled\", \"--no-phony\":\n\t\t\topts.Phony = false\n\t\tcase \"--disabled\", \"--phony\":\n\t\t\topts.Phony = true\n\t\tcase \"--no-input\":\n\t\t\topts.Inputless = true\n\t\tcase \"--tiebreak\":\n\t\t\tstr, err := nextString(\"sort criterion required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Criteria, err = parseTiebreak(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--bind\":\n\t\t\tstr, err := nextString(\"bind expression required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseKeymap(opts.Keymap, str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--color\":\n\t\t\t_, spec := optionalNextString()\n\t\t\tif len(spec) == 0 {\n\t\t\t\topts.Theme = tui.EmptyTheme\n\t\t\t} else {\n\t\t\t\tvar baseTheme *tui.ColorTheme\n\t\t\t\tif baseTheme, opts.Theme, err = parseTheme(opts.Theme, spec); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif baseTheme != nil {\n\t\t\t\t\topts.BaseTheme = baseTheme\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"--toggle-sort\":\n\t\t\tstr, err := nextString(\"key name required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseToggleSort(opts.Keymap, str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"-d\", \"--delimiter\":\n\t\t\tstr, err := nextString(\"delimiter required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.Delimiter = delimiterRegexp(str)\n\t\tcase \"-n\", \"--nth\":\n\t\t\tstr, err := nextString(\"nth expression required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Nth, err = splitNth(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--freeze-left\":\n\t\t\tif opts.FreezeLeft, err = nextInt(\"number of fields required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--freeze-right\":\n\t\t\tif opts.FreezeRight, err = nextInt(\"number of fields required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--with-nth\":\n\t\t\tstr, err := nextString(\"nth expression required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.WithNth, err = nthTransformer(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.WithNthExpr = str\n\t\tcase \"--accept-nth\":\n\t\t\tstr, err := nextString(\"nth expression required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.AcceptNth, err = nthTransformer(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"-s\", \"--sort\":\n\t\t\tif opts.Sort, err = optionalNumeric(1); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"+s\", \"--no-sort\":\n\t\t\topts.Sort = 0\n\t\tcase \"--raw\":\n\t\t\topts.Raw = true\n\t\tcase \"--no-raw\":\n\t\t\topts.Raw = false\n\t\tcase \"--track\":\n\t\t\topts.Track = trackEnabled\n\t\tcase \"--no-track\":\n\t\t\topts.Track = trackDisabled\n\t\tcase \"--id-nth\":\n\t\t\tstr, err := nextString(\"nth expression required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.IdNth, err = splitNth(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-id-nth\":\n\t\t\topts.IdNth = nil\n\t\tcase \"--tac\":\n\t\t\topts.Tac = true\n\t\tcase \"--no-tac\":\n\t\t\topts.Tac = false\n\t\tcase \"--tail\":\n\t\t\tif opts.Tail, err = nextInt(\"number of items to keep required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Tail <= 0 {\n\t\t\t\treturn errors.New(\"number of items to keep must be a positive integer\")\n\t\t\t}\n\t\tcase \"--no-tail\":\n\t\t\topts.Tail = 0\n\t\tcase \"--smart-case\":\n\t\t\topts.Case = CaseSmart\n\t\tcase \"-i\", \"--ignore-case\":\n\t\t\topts.Case = CaseIgnore\n\t\tcase \"+i\", \"--no-ignore-case\":\n\t\t\topts.Case = CaseRespect\n\t\tcase \"-m\", \"--multi\":\n\t\t\tif opts.Multi, err = optionalNumeric(maxMulti); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"+m\", \"--no-multi\":\n\t\t\topts.Multi = 0\n\t\tcase \"--ansi\":\n\t\t\topts.Ansi = true\n\t\tcase \"--no-ansi\":\n\t\t\topts.Ansi = false\n\t\tcase \"--no-mouse\":\n\t\t\topts.Mouse = false\n\t\tcase \"+c\", \"--no-color\":\n\t\t\topts.BaseTheme = tui.NoColorTheme\n\t\t\topts.Theme = tui.NoColorTheme\n\t\tcase \"+2\", \"--no-256\":\n\t\t\topts.Theme = tui.Default16\n\t\tcase \"--black\":\n\t\t\topts.Black = true\n\t\tcase \"--no-black\":\n\t\t\topts.Black = false\n\t\tcase \"--bold\":\n\t\t\topts.Bold = true\n\t\tcase \"--no-bold\":\n\t\t\topts.Bold = false\n\t\tcase \"--layout\":\n\t\t\tstr, err := nextString(\"layout required (default / reverse / reverse-list)\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Layout, err = parseLayout(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--reverse\":\n\t\t\topts.Layout = layoutReverse\n\t\tcase \"--no-reverse\":\n\t\t\topts.Layout = layoutDefault\n\t\tcase \"--cycle\":\n\t\t\topts.Cycle = true\n\t\tcase \"--highlight-line\":\n\t\t\topts.CursorLine = true\n\t\tcase \"--no-highlight-line\":\n\t\t\topts.CursorLine = false\n\t\tcase \"--no-cycle\":\n\t\t\topts.Cycle = false\n\t\tcase \"--wrap\":\n\t\t\tgiven, str := optionalNextString()\n\t\t\tif given {\n\t\t\t\tswitch str {\n\t\t\t\tcase \"char\":\n\t\t\t\t\topts.Wrap = true\n\t\t\t\t\topts.WrapWord = false\n\t\t\t\tcase \"word\":\n\t\t\t\t\topts.Wrap = true\n\t\t\t\t\topts.WrapWord = true\n\t\t\t\tdefault:\n\t\t\t\t\treturn errors.New(\"invalid wrap mode: \" + str + \" (expected: char or word)\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\topts.Wrap = true\n\t\t\t}\n\t\tcase \"--no-wrap\":\n\t\t\topts.Wrap = false\n\t\t\topts.WrapWord = false\n\t\tcase \"--wrap-word\":\n\t\t\topts.Wrap = true\n\t\t\topts.WrapWord = true\n\t\tcase \"--no-wrap-word\":\n\t\t\topts.WrapWord = false\n\t\tcase \"--wrap-sign\":\n\t\t\tstr, err := nextString(\"wrap sign required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.WrapSign = &str\n\t\tcase \"--multi-line\":\n\t\t\topts.MultiLine = true\n\t\tcase \"--no-multi-line\":\n\t\t\topts.MultiLine = false\n\t\tcase \"--keep-right\":\n\t\t\topts.KeepRight = true\n\t\tcase \"--no-keep-right\":\n\t\t\topts.KeepRight = false\n\t\tcase \"--hscroll\":\n\t\t\topts.Hscroll = true\n\t\tcase \"--no-hscroll\":\n\t\t\topts.Hscroll = false\n\t\tcase \"--hscroll-off\":\n\t\t\tif opts.HscrollOff, err = nextInt(\"hscroll offset required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--scroll-off\":\n\t\t\tif opts.ScrollOff, err = nextInt(\"scroll offset required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--filepath-word\":\n\t\t\topts.FileWord = true\n\t\tcase \"--no-filepath-word\":\n\t\t\topts.FileWord = false\n\t\tcase \"--info\":\n\t\t\tstr, err := nextString(\"info style required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--info-command\":\n\t\t\tif opts.InfoCommand, err = nextString(\"info command required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-info-command\":\n\t\t\topts.InfoCommand = \"\"\n\t\tcase \"--no-info\":\n\t\t\topts.InfoStyle = infoHidden\n\t\tcase \"--inline-info\":\n\t\t\topts.InfoStyle = infoInline\n\t\t\topts.InfoPrefix = defaultInfoPrefix\n\t\tcase \"--no-inline-info\":\n\t\t\topts.InfoStyle = infoDefault\n\t\tcase \"--separator\":\n\t\t\tseparator, err := nextString(\"separator character required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.Separator = &separator\n\t\tcase \"--no-separator\":\n\t\t\tnosep := \"\"\n\t\t\topts.Separator = &nosep\n\t\tcase \"--ghost\":\n\t\t\tif opts.Ghost, err = nextString(\"ghost text required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--scrollbar\":\n\t\t\tgiven, bar := optionalNextString()\n\t\t\tif given {\n\t\t\t\topts.Scrollbar = &bar\n\t\t\t} else {\n\t\t\t\topts.Scrollbar = nil\n\t\t\t}\n\t\tcase \"--no-scrollbar\":\n\t\t\tnoBar := \"\"\n\t\t\topts.Scrollbar = &noBar\n\t\tcase \"--jump-labels\":\n\t\t\tif opts.JumpLabels, err = nextString(\"label characters required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvalidateJumpLabels = true\n\t\tcase \"-1\", \"--select-1\":\n\t\t\topts.Select1 = true\n\t\tcase \"+1\", \"--no-select-1\":\n\t\t\topts.Select1 = false\n\t\tcase \"-0\", \"--exit-0\":\n\t\t\topts.Exit0 = true\n\t\tcase \"+0\", \"--no-exit-0\":\n\t\t\topts.Exit0 = false\n\t\tcase \"--read0\":\n\t\t\topts.ReadZero = true\n\t\tcase \"--no-read0\":\n\t\t\topts.ReadZero = false\n\t\tcase \"--print0\":\n\t\t\topts.Printer = func(str string) { fmt.Print(str, \"\\x00\") }\n\t\t\topts.PrintSep = \"\\x00\"\n\t\tcase \"--no-print0\":\n\t\t\topts.Printer = func(str string) { fmt.Println(str) }\n\t\t\topts.PrintSep = \"\\n\"\n\t\tcase \"--print-query\":\n\t\t\topts.PrintQuery = true\n\t\tcase \"--no-print-query\":\n\t\t\topts.PrintQuery = false\n\t\tcase \"--prompt\":\n\t\t\topts.Prompt, err = nextString(\"prompt string required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--gutter\":\n\t\t\tstr, err := nextString(\"gutter character required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tstr = firstLine(str)\n\t\t\topts.Gutter = &str\n\t\tcase \"--gutter-raw\":\n\t\t\tstr, err := nextString(\"gutter character for raw mode required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tstr = firstLine(str)\n\t\t\topts.GutterRaw = &str\n\t\tcase \"--pointer\":\n\t\t\tstr, err := nextString(\"pointer sign required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tstr = firstLine(str)\n\t\t\topts.Pointer = &str\n\t\tcase \"--marker\":\n\t\t\tstr, err := nextString(\"marker sign required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tstr = firstLine(str)\n\t\t\topts.Marker = &str\n\t\tcase \"--marker-multi-line\":\n\t\t\tstr, err := nextString(\"marker sign for multi-line entries required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.MarkerMulti, err = parseMarkerMultiLine(firstLine(str)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--sync\":\n\t\t\topts.Sync = true\n\t\tcase \"--no-sync\", \"--async\":\n\t\t\topts.Sync = false\n\t\tcase \"--no-history\":\n\t\t\topts.History = nil\n\t\tcase \"--history\":\n\t\t\tstr, err := nextString(\"history file path required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := setHistory(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--history-size\":\n\t\t\tn, err := nextInt(\"history max size required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := setHistoryMax(n); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-header\":\n\t\t\topts.Header = []string{}\n\t\tcase \"--no-header-lines\":\n\t\t\topts.HeaderLines = 0\n\t\tcase \"--header\":\n\t\t\tstr, err := nextString(\"header string required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.Header = strLines(str)\n\t\tcase \"--header-lines\":\n\t\t\tif opts.HeaderLines, err = nextInt(\"number of header lines required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-footer\":\n\t\t\topts.Footer = []string{}\n\t\tcase \"--footer\":\n\t\t\tstr, err := nextString(\"footer string required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.Footer = strLines(str)\n\t\tcase \"--header-first\":\n\t\t\topts.HeaderFirst = true\n\t\tcase \"--no-header-first\":\n\t\t\topts.HeaderFirst = false\n\t\tcase \"--gap\":\n\t\t\tif opts.Gap, err = optionalNumeric(1); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-gap\":\n\t\t\topts.Gap = 0\n\t\tcase \"--gap-line\":\n\t\t\tif given, bar := optionalNextString(); given {\n\t\t\t\topts.GapLine = &bar\n\t\t\t} else {\n\t\t\t\topts.GapLine = nil\n\t\t\t}\n\t\tcase \"--no-gap-line\":\n\t\t\tempty := \"\"\n\t\t\topts.GapLine = &empty\n\t\tcase \"--ellipsis\":\n\t\t\tstr, err := nextString(\"ellipsis string required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tstr = firstLine(str)\n\t\t\topts.Ellipsis = &str\n\t\tcase \"--preview\":\n\t\t\tif opts.Preview.command, err = nextString(\"preview command required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-preview\":\n\t\t\topts.Preview.command = \"\"\n\t\tcase \"--preview-window\":\n\t\t\tstr, err := nextString(\"preview window layout required: [up|down|left|right][,SIZE[%]][,border-STYLE][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parsePreviewWindow(&opts.Preview, str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-preview-border\":\n\t\t\topts.Preview.border = tui.BorderNone\n\t\tcase \"--preview-border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--preview-wrap-sign\":\n\t\t\tstr, err := nextString(\"preview wrap sign required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.PreviewWrapSign = &str\n\t\tcase \"--height\":\n\t\t\tstr, err := nextString(\"height required: [~]HEIGHT[%]\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Height, err = parseHeight(str, index); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--min-height\":\n\t\t\texpr, err := nextString(\"minimum height required: HEIGHT[+]\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tauto := false\n\t\t\tif strings.HasSuffix(expr, \"+\") {\n\t\t\t\texpr = expr[:len(expr)-1]\n\t\t\t\tauto = true\n\t\t\t}\n\t\t\tnum, err := atoi(expr)\n\t\t\tif err != nil || num < 0 {\n\t\t\t\treturn errors.New(\"minimum height must be a non-negative integer\")\n\t\t\t}\n\t\t\tif auto {\n\t\t\t\tnum *= -1\n\t\t\t}\n\t\t\topts.MinHeight = num\n\t\tcase \"--no-height\":\n\t\t\topts.Height = heightSpec{}\n\t\tcase \"--no-margin\":\n\t\t\topts.Margin = defaultMargin()\n\t\tcase \"--no-padding\":\n\t\t\topts.Padding = defaultMargin()\n\t\tcase \"--no-border\":\n\t\t\topts.BorderShape = tui.BorderNone\n\t\tcase \"--border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.BorderShape, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--list-border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.ListBorderShape, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.ListBorderShape == tui.BorderLine {\n\t\t\t\tif hasArg {\n\t\t\t\t\t// '--list-border line' is not allowed\n\t\t\t\t\treturn errors.New(\"list border cannot be 'line'\")\n\t\t\t\t}\n\t\t\t\t// This is when '--style full:line' is previously specified and\n\t\t\t\t// '--list-border' is specified without an argument.\n\t\t\t\topts.ListBorderShape = tui.BorderRounded\n\t\t\t}\n\t\tcase \"--no-list-border\":\n\t\t\topts.ListBorderShape = tui.BorderNone\n\t\tcase \"--no-list-label\":\n\t\t\topts.ListLabel.label = \"\"\n\t\tcase \"--list-label\":\n\t\t\topts.ListLabel.label, err = nextString(\"label required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--list-label-pos\":\n\t\t\tpos, err := nextString(\"label position required (positive or negative integer or 'center')\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseLabelPosition(&opts.ListLabel, pos); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-header-border\":\n\t\t\topts.HeaderBorderShape = tui.BorderNone\n\t\tcase \"--header-border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.HeaderBorderShape, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-header-lines-border\":\n\t\t\topts.HeaderLinesShape = tui.BorderUndefined\n\t\tcase \"--header-lines-border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.HeaderLinesShape, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-header-label\":\n\t\t\topts.HeaderLabel.label = \"\"\n\t\tcase \"--header-label\":\n\t\t\tif opts.HeaderLabel.label, err = nextString(\"header label required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--header-label-pos\":\n\t\t\tpos, err := nextString(\"header label position required (positive or negative integer or 'center')\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseLabelPosition(&opts.HeaderLabel, pos); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-footer-border\":\n\t\t\topts.FooterBorderShape = tui.BorderNone\n\t\tcase \"--footer-border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.FooterBorderShape, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-footer-label\":\n\t\t\topts.FooterLabel.label = \"\"\n\t\tcase \"--footer-label\":\n\t\t\tif opts.FooterLabel.label, err = nextString(\"footer label required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--footer-label-pos\":\n\t\t\tpos, err := nextString(\"footer label position required (positive or negative integer or 'center')\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseLabelPosition(&opts.FooterLabel, pos); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-input-border\":\n\t\t\topts.InputBorderShape = tui.BorderNone\n\t\tcase \"--input-border\":\n\t\t\thasArg, arg := optionalNextString()\n\t\t\tif opts.InputBorderShape, err = parseBorder(arg, !hasArg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-input-label\":\n\t\t\topts.InputLabel.label = \"\"\n\t\tcase \"--input-label\":\n\t\t\tif opts.InputLabel.label, err = nextString(\"input label required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--input-label-pos\":\n\t\t\tpos, err := nextString(\"input label position required (positive or negative integer or 'center')\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseLabelPosition(&opts.InputLabel, pos); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-border-label\":\n\t\t\topts.BorderLabel.label = \"\"\n\t\tcase \"--border-label\":\n\t\t\topts.BorderLabel.label, err = nextString(\"label required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--border-label-pos\":\n\t\t\tpos, err := nextString(\"label position required (positive or negative integer or 'center')\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseLabelPosition(&opts.BorderLabel, pos); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-preview-label\":\n\t\t\topts.PreviewLabel.label = \"\"\n\t\tcase \"--preview-label\":\n\t\t\tif opts.PreviewLabel.label, err = nextString(\"preview label required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--preview-label-pos\":\n\t\t\tpos, err := nextString(\"preview label position required (positive or negative integer or 'center')\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := parseLabelPosition(&opts.PreviewLabel, pos); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--style\":\n\t\t\tpreset, err := nextString(\"preset name required: [default|minimal|full[:BORDER_STYLE]]\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := applyPreset(opts, preset); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--no-unicode\":\n\t\t\topts.Unicode = false\n\t\tcase \"--unicode\":\n\t\t\topts.Unicode = true\n\t\tcase \"--ambidouble\":\n\t\t\topts.Ambidouble = true\n\t\tcase \"--no-ambidouble\":\n\t\t\topts.Ambidouble = false\n\t\tcase \"--margin\":\n\t\t\tstr, err := nextString(\"margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Margin, err = parseMargin(\"margin\", str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--padding\":\n\t\t\tstr, err := nextString(\"padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Padding, err = parseMargin(\"padding\", str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--tabstop\":\n\t\t\tif opts.Tabstop, err = nextInt(\"tab stop required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--with-shell\":\n\t\t\tif opts.WithShell, err = nextString(\"shell command and flags required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--listen\", \"--listen-unsafe\":\n\t\t\tgiven, str := optionalNextString()\n\t\t\taddr := defaultListenAddr\n\t\t\tif given {\n\t\t\t\tvar err error\n\t\t\t\taddr, err = parseListenAddress(str)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\topts.ListenAddr = &addr\n\t\t\topts.Unsafe = arg == \"--listen-unsafe\"\n\t\tcase \"--no-listen\", \"--no-listen-unsafe\":\n\t\t\topts.ListenAddr = nil\n\t\t\topts.Unsafe = false\n\t\tcase \"--clear\":\n\t\t\topts.ClearOnExit = true\n\t\tcase \"--no-clear\":\n\t\t\topts.ClearOnExit = false\n\t\tcase \"--walker\":\n\t\t\tstr, err := nextString(\"walker options required [file][,dir][,follow][,hidden]\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.WalkerOpts, err = parseWalkerOpts(str); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--walker-root\":\n\t\t\tif opts.WalkerRoot, err = nextDirs(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--walker-skip\":\n\t\t\tstr, err := nextString(\"directory names to ignore required\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\topts.WalkerSkip = filterNonEmpty(strings.Split(str, \",\"))\n\t\tcase \"--threads\":\n\t\t\tif opts.Threads, err = nextInt(\"number of threads required\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif opts.Threads < 0 {\n\t\t\t\treturn errors.New(\"--threads must be a positive integer\")\n\t\t\t}\n\t\tcase \"--bench\":\n\t\t\tstr, err := nextString(\"duration required (e.g. 3s, 500ms)\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdur, err := time.ParseDuration(str)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"invalid duration for --bench: \" + str)\n\t\t\t}\n\t\t\topts.Bench = dur\n\t\tcase \"--profile-cpu\":\n\t\t\tif opts.CPUProfile, err = nextString(\"file path required: cpu\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--profile-mem\":\n\t\t\tif opts.MEMProfile, err = nextString(\"file path required: mem\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--profile-block\":\n\t\t\tif opts.BlockProfile, err = nextString(\"file path required: block\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--profile-mutex\":\n\t\t\tif opts.MutexProfile, err = nextString(\"file path required: mutex\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase \"--\":\n\t\t\t// Ignored\n\t\tdefault:\n\t\t\tif match, value := optString(arg, \"-q\"); match {\n\t\t\t\topts.Query = value\n\t\t\t} else if match, value := optString(arg, \"-f\"); match {\n\t\t\t\topts.Filter = &value\n\t\t\t} else if match, value := optString(arg, \"-d\"); match {\n\t\t\t\topts.Delimiter = delimiterRegexp(value)\n\t\t\t} else if match, value := optString(arg, \"-n\"); match {\n\t\t\t\tif opts.Nth, err = splitNth(value); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if match, _ := optString(arg, \"-s\"); match {\n\t\t\t\topts.Sort = 1 // Don't care\n\t\t\t} else if match, value := optString(arg, \"-m\"); match {\n\t\t\t\tif opts.Multi, err = atoi(value); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"unknown option: \" + arg)\n\t\t\t}\n\t\t}\n\n\t\tif val != nil {\n\t\t\treturn errors.New(\"unexpected value for \" + arg + \": \" + *val)\n\t\t}\n\t}\n\t*index += len(allArgs)\n\n\tif opts.HeaderLines < 0 {\n\t\treturn errors.New(\"header lines must be a non-negative integer\")\n\t}\n\n\tif opts.HscrollOff < 0 {\n\t\treturn errors.New(\"hscroll offset must be a non-negative integer\")\n\t}\n\n\tif opts.ScrollOff < 0 {\n\t\treturn errors.New(\"scroll offset must be a non-negative integer\")\n\t}\n\n\tif opts.Tabstop < 1 {\n\t\treturn errors.New(\"tab stop must be a positive integer\")\n\t}\n\n\tif len(opts.JumpLabels) == 0 {\n\t\treturn errors.New(\"empty jump labels\")\n\t}\n\n\tif opts.FreezeLeft < 0 || opts.FreezeRight < 0 {\n\t\treturn errors.New(\"number of fields to freeze must be a non-negative integer\")\n\t}\n\n\tif validateJumpLabels {\n\t\tfor _, r := range opts.JumpLabels {\n\t\t\tif r < 32 || r > 126 {\n\t\t\t\treturn errors.New(\"non-ascii jump labels are not allowed\")\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n\nfunc applyPreset(opts *Options, preset string) error {\n\t// Reset to the platform default\n\tdefaultBorderShape = tui.DefaultBorderShape\n\n\tswitch strings.ToLower(preset) {\n\tcase \"default\":\n\t\topts.ListBorderShape = tui.BorderUndefined\n\t\topts.InputBorderShape = tui.BorderUndefined\n\t\topts.HeaderBorderShape = tui.BorderUndefined\n\t\topts.FooterBorderShape = tui.BorderUndefined\n\t\topts.Preview.border = defaultBorderShape\n\t\topts.Preview.info = true\n\t\topts.InfoStyle = infoDefault\n\t\topts.Theme.Gutter = tui.NewColorAttr()\n\t\topts.Separator = nil\n\t\topts.Scrollbar = nil\n\t\topts.CursorLine = false\n\tcase \"minimal\":\n\t\topts.ListBorderShape = tui.BorderUndefined\n\t\topts.InputBorderShape = tui.BorderUndefined\n\t\topts.HeaderBorderShape = tui.BorderUndefined\n\t\topts.FooterBorderShape = tui.BorderLine\n\t\topts.Preview.border = tui.BorderLine\n\t\topts.Preview.info = false\n\t\topts.InfoStyle = infoDefault\n\t\topts.Theme.Gutter = tui.ColorAttr{Color: -1, Attr: 0}\n\t\tempty := \"\"\n\t\topts.Separator = &empty\n\t\topts.Scrollbar = &empty\n\t\topts.CursorLine = false\n\tdefault:\n\t\ttokens := strings.SplitN(preset, \":\", 2)\n\t\tif tokens[0] != \"full\" {\n\t\t\treturn errors.New(\"unsupported style preset: \" + preset)\n\t\t}\n\t\tif len(tokens) == 2 && len(tokens[1]) > 0 {\n\t\t\tvar err error\n\t\t\tdefaultBorderShape, err = parseBorder(tokens[1], false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif defaultBorderShape != tui.BorderLine {\n\t\t\topts.ListBorderShape = defaultBorderShape\n\t\t}\n\t\topts.InputBorderShape = defaultBorderShape\n\t\topts.HeaderBorderShape = defaultBorderShape\n\t\topts.FooterBorderShape = defaultBorderShape\n\t\topts.Preview.border = defaultBorderShape\n\t\tif defaultBorderShape == tui.BorderLine {\n\t\t\topts.BorderShape = defaultBorderShape\n\t\t}\n\t\topts.Preview.info = true\n\t\topts.InfoStyle = infoInlineRight\n\t\topts.Theme.Gutter = tui.NewColorAttr()\n\t\topts.Separator = nil\n\t\topts.Scrollbar = nil\n\t\topts.CursorLine = true\n\t}\n\treturn nil\n}\n\nfunc validateSign(sign string, signOptName string, maxWidth int) error {\n\tif uniseg.StringWidth(sign) > maxWidth {\n\t\treturn fmt.Errorf(\"%v display width should be up to %d\", signOptName, maxWidth)\n\t}\n\treturn nil\n}\n\nfunc validateOptions(opts *Options) error {\n\tif opts.Pointer != nil {\n\t\tif err := validateSign(*opts.Pointer, \"pointer\", 2); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif opts.Marker != nil {\n\t\tif err := validateSign(*opts.Marker, \"marker\", 2); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif opts.Gutter != nil && uniseg.StringWidth(*opts.Gutter) != 1 ||\n\t\topts.GutterRaw != nil && uniseg.StringWidth(*opts.GutterRaw) != 1 {\n\t\treturn errors.New(\"gutter display width should be 1\")\n\t}\n\n\tif opts.Scrollbar != nil {\n\t\trunes := []rune(*opts.Scrollbar)\n\t\tif len(runes) > 2 {\n\t\t\treturn errors.New(\"--scrollbar should be given one or two characters\")\n\t\t}\n\t\tfor _, r := range runes {\n\t\t\tif uniseg.StringWidth(string(r)) != 1 {\n\t\t\t\treturn errors.New(\"scrollbar display width should be 1\")\n\t\t\t}\n\t\t}\n\t}\n\n\tif opts.Height.auto {\n\t\tfor _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {\n\t\t\tif s.percent {\n\t\t\t\treturn errors.New(\"adaptive height is not compatible with top/bottom percent margin\")\n\t\t\t}\n\t\t}\n\t\tfor _, s := range []sizeSpec{opts.Padding[0], opts.Padding[2]} {\n\t\t\tif s.percent {\n\t\t\t\treturn errors.New(\"adaptive height is not compatible with top/bottom percent padding\")\n\t\t\t}\n\t\t}\n\t}\n\n\tif opts.Theme.Nth.IsColorDefined() {\n\t\treturn errors.New(\"only ANSI attributes are allowed for 'nth' (regular, bold, underline, reverse, dim, italic, strikethrough)\")\n\t}\n\n\treturn nil\n}\n\nfunc noSeparatorLine(style infoStyle, separator bool) bool {\n\tswitch style {\n\tcase infoInline:\n\t\treturn true\n\tcase infoHidden, infoInlineRight:\n\t\treturn !separator\n\t}\n\treturn false\n}\n\nfunc (opts *Options) useTmux() bool {\n\treturn opts.Tmux != nil && len(os.Getenv(\"TMUX\")) > 0 && opts.Tmux.index >= opts.Height.index\n}\n\nfunc (opts *Options) noSeparatorLine() bool {\n\tif opts.Inputless {\n\t\treturn true\n\t}\n\tsep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0\n\treturn noSeparatorLine(opts.InfoStyle, sep)\n}\n\n// This function can have side-effects and alter some global states.\n// So we run it on fzf.Run and not on ParseOptions.\nfunc postProcessOptions(opts *Options) error {\n\tif opts.Ambidouble {\n\t\tuniseg.EastAsianAmbiguousWidth = 2\n\t}\n\n\tif opts.BorderShape == tui.BorderUndefined {\n\t\topts.BorderShape = tui.BorderNone\n\t}\n\n\tif opts.ListBorderShape == tui.BorderUndefined {\n\t\topts.ListBorderShape = tui.BorderNone\n\t}\n\n\tif opts.InputBorderShape == tui.BorderUndefined {\n\t\topts.InputBorderShape = tui.BorderNone\n\t}\n\n\tif opts.HeaderBorderShape == tui.BorderUndefined {\n\t\topts.HeaderBorderShape = tui.BorderNone\n\t}\n\n\tif opts.FooterBorderShape == tui.BorderUndefined {\n\t\topts.FooterBorderShape = tui.BorderLine\n\t}\n\n\tif opts.HeaderLinesShape == tui.BorderNone {\n\t\topts.HeaderLinesShape = tui.BorderPhantom\n\t}\n\n\tif opts.Pointer == nil {\n\t\tdefaultPointer := \"▌\"\n\t\tif !opts.Unicode {\n\t\t\tdefaultPointer = \">\"\n\t\t}\n\t\topts.Pointer = &defaultPointer\n\t}\n\n\tif opts.GapLine == nil {\n\t\tdefaultGapLine := \"┈\"\n\t\tif !opts.Unicode {\n\t\t\tdefaultGapLine = \"-\"\n\t\t}\n\t\topts.GapLine = &defaultGapLine\n\t}\n\n\tmarkerLen := 1\n\tif opts.Marker == nil {\n\t\tif opts.MarkerMulti != nil && opts.MarkerMulti[0] == \"\" {\n\t\t\tempty := \"\"\n\t\t\topts.Marker = &empty\n\t\t\tmarkerLen = 0\n\t\t} else {\n\t\t\t// \"▎\" looks better, but not all terminals render it correctly\n\t\t\tdefaultMarker := \"┃\"\n\t\t\tif !opts.Unicode {\n\t\t\t\tdefaultMarker = \">\"\n\t\t\t}\n\t\t\topts.Marker = &defaultMarker\n\t\t}\n\t} else {\n\t\tmarkerLen = uniseg.StringWidth(*opts.Marker)\n\t}\n\n\tmarkerMultiLen := 1\n\tif opts.MarkerMulti == nil {\n\t\tif *opts.Marker == \"\" {\n\t\t\topts.MarkerMulti = &[3]string{}\n\t\t\tmarkerMultiLen = 0\n\t\t} else if opts.Unicode {\n\t\t\topts.MarkerMulti = &[3]string{\"╻\", \"┃\", \"╹\"}\n\t\t} else {\n\t\t\topts.MarkerMulti = &[3]string{\".\", \"|\", \"'\"}\n\t\t}\n\t} else {\n\t\tmarkerMultiLen = uniseg.StringWidth(opts.MarkerMulti[0])\n\t}\n\tdiff := markerMultiLen - markerLen\n\tif diff > 0 {\n\t\tpadded := *opts.Marker + strings.Repeat(\" \", diff)\n\t\topts.Marker = &padded\n\t} else if diff < 0 {\n\t\tfor idx := range opts.MarkerMulti {\n\t\t\topts.MarkerMulti[idx] += strings.Repeat(\" \", -diff)\n\t\t}\n\t}\n\n\t// Default actions for CTRL-N / CTRL-P when --history is set\n\tif opts.History != nil {\n\t\tif _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {\n\t\t\topts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPrevHistory)\n\t\t}\n\t\tif _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {\n\t\t\topts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)\n\t\t}\n\t}\n\n\t// Extend the default key map\n\tkeymap := defaultKeymap()\n\tfor key, actions := range opts.Keymap {\n\t\treordered := []*action{}\n\t\tfor _, act := range actions {\n\t\t\tswitch act.t {\n\t\t\tcase actToggleSort:\n\t\t\t\t// To display \"+S\"/\"-S\" on info line\n\t\t\t\topts.ToggleSort = true\n\t\t\tcase actTogglePreview, actShowPreview, actHidePreview, actChangePreviewWindow:\n\t\t\t\treordered = append(reordered, act)\n\t\t\t}\n\t\t}\n\n\t\t// Re-organize actions so that we put actions that change the preview window first in the list.\n\t\t//  *  change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)\n\t\t//  -> change-preview-window(up,+10)+change-preview-window(up,+20)+preview(sleep 3; cat {})\n\t\tif len(reordered) > 0 {\n\t\t\tfor _, act := range actions {\n\t\t\t\tswitch act.t {\n\t\t\t\tcase actTogglePreview, actShowPreview, actHidePreview, actChangePreviewWindow:\n\t\t\t\tdefault:\n\t\t\t\t\treordered = append(reordered, act)\n\t\t\t\t}\n\t\t\t}\n\t\t\tactions = reordered\n\t\t}\n\t\tkeymap[key] = actions\n\t}\n\topts.Keymap = keymap\n\n\t// If 'double-click' is left unbound, bind it to the action bound to 'enter'\n\tif _, prs := opts.Keymap[tui.DoubleClick.AsEvent()]; !prs {\n\t\topts.Keymap[tui.DoubleClick.AsEvent()] = opts.Keymap[tui.Enter.AsEvent()]\n\t}\n\n\t// If we're not using extended search mode, --nth option becomes irrelevant\n\t// if it contains the whole range\n\tif !opts.Extended || len(opts.Nth) == 1 {\n\t\tfor _, r := range opts.Nth {\n\t\t\tif r.begin == rangeEllipsis && r.end == rangeEllipsis {\n\t\t\t\topts.Nth = make([]Range, 0)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// If --height option is not supported on the platform, just ignore it\n\tif !tui.IsLightRendererSupported() && opts.Height.size > 0 {\n\t\topts.Height = heightSpec{}\n\t}\n\n\t// Sets --min-height automatically\n\tif opts.Height.size > 0 && opts.Height.percent && opts.MinHeight < 0 {\n\t\topts.MinHeight = -opts.MinHeight + borderLines(opts.BorderShape) + borderLines(opts.ListBorderShape)\n\t\tif !opts.Inputless {\n\t\t\topts.MinHeight += 1 + borderLines(opts.InputBorderShape)\n\t\t\tif !opts.noSeparatorLine() {\n\t\t\t\topts.MinHeight++\n\t\t\t}\n\t\t}\n\t\tif len(opts.Header) > 0 {\n\t\t\topts.MinHeight += borderLines(opts.HeaderBorderShape) + len(opts.Header)\n\t\t}\n\t\tif opts.HeaderLines > 0 {\n\t\t\tborderShape := opts.HeaderBorderShape\n\t\t\tif opts.HeaderLinesShape.Visible() {\n\t\t\t\tborderShape = opts.HeaderLinesShape\n\t\t\t}\n\t\t\topts.MinHeight += borderLines(borderShape) + opts.HeaderLines\n\t\t}\n\t\tif len(opts.Preview.command) > 0 && (opts.Preview.position == posUp || opts.Preview.position == posDown) && opts.Preview.Visible() && opts.Preview.position == posUp {\n\t\t\tborderShape := opts.Preview.border\n\t\t\tif opts.Preview.border == tui.BorderLine {\n\t\t\t\tborderShape = tui.BorderTop\n\t\t\t}\n\t\t\topts.MinHeight += borderLines(borderShape) + 10\n\t\t}\n\t\tfor _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2], opts.Padding[0], opts.Padding[2]} {\n\t\t\tif !s.percent {\n\t\t\t\topts.MinHeight += int(s.size)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := opts.initProfiling(); err != nil {\n\t\treturn errors.New(\"failed to start pprof profiles: \" + err.Error())\n\t}\n\n\talgo.Init(opts.Scheme)\n\n\treturn nil\n}\n\nfunc parseShellWords(str string) ([]string, error) {\n\tparser := shellwords.NewParser()\n\tparser.ParseComment = true\n\treturn parser.Parse(str)\n}\n\n// ParseOptions parses command-line options\nfunc ParseOptions(useDefaults bool, args []string) (*Options, error) {\n\topts := defaultOptions()\n\tindex := 0\n\n\tif useDefaults {\n\t\t// 1. Options from $FZF_DEFAULT_OPTS_FILE\n\t\tif path := os.Getenv(\"FZF_DEFAULT_OPTS_FILE\"); path != \"\" {\n\t\t\tbytes, err := os.ReadFile(path)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.New(\"$FZF_DEFAULT_OPTS_FILE: \" + err.Error())\n\t\t\t}\n\n\t\t\twords, parseErr := parseShellWords(string(bytes))\n\t\t\tif parseErr != nil {\n\t\t\t\treturn nil, errors.New(path + \": \" + parseErr.Error())\n\t\t\t}\n\t\t\tif len(words) > 0 {\n\t\t\t\tif err := parseOptions(&index, opts, words); err != nil {\n\t\t\t\t\treturn nil, errors.New(path + \": \" + err.Error())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 2. Options from $FZF_DEFAULT_OPTS string\n\t\twords, parseErr := parseShellWords(os.Getenv(\"FZF_DEFAULT_OPTS\"))\n\t\tif parseErr != nil {\n\t\t\treturn nil, errors.New(\"$FZF_DEFAULT_OPTS: \" + parseErr.Error())\n\t\t}\n\t\tif len(words) > 0 {\n\t\t\tif err := parseOptions(&index, opts, words); err != nil {\n\t\t\t\treturn nil, errors.New(\"$FZF_DEFAULT_OPTS: \" + err.Error())\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Options from command-line arguments\n\tif err := parseOptions(&index, opts, args); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 4. Change default scheme when built-in walker is used\n\tif len(opts.Scheme) == 0 {\n\t\topts.Scheme = \"default\"\n\t\tif len(opts.Criteria) == 0 {\n\t\t\t// NOTE: Let's assume $FZF_DEFAULT_COMMAND generates a list of file paths.\n\t\t\t// But it is possible that it is set to a command that doesn't generate\n\t\t\t// file paths.\n\t\t\t//\n\t\t\t// In that case, you can either\n\t\t\t//   1. explicitly set --scheme=default,\n\t\t\t//   2. or replace $FZF_DEFAULT_COMMAND with an equivalent 'start:reload'\n\t\t\t//      binding, which is the new preferred way.\n\t\t\tif !opts.hasReloadOrTransformOnStart() && util.IsTty(os.Stdin) {\n\t\t\t\topts.Scheme = \"path\"\n\t\t\t}\n\t\t\t_, opts.Criteria, _ = parseScheme(opts.Scheme)\n\t\t}\n\t}\n\n\t// 5. Final validation of merged options\n\tif err := validateOptions(opts); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn opts, nil\n}\n\nfunc (opts *Options) hasReloadOrTransformOnStart() bool {\n\tif actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {\n\t\tfor _, action := range actions {\n\t\t\tif action.t == actReload || action.t == actReloadSync || action.t == actTransform {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (opts *Options) extractReloadOnStart() string {\n\tcmd := \"\"\n\tif actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {\n\t\tfiltered := []*action{}\n\t\tfor _, action := range actions {\n\t\t\tif action.t == actReload || action.t == actReloadSync {\n\t\t\t\tcmd = action.a\n\t\t\t} else {\n\t\t\t\tfiltered = append(filtered, action)\n\t\t\t}\n\t\t}\n\t\topts.Keymap[tui.Start.AsEvent()] = filtered\n\t}\n\treturn cmd\n}\n"
  },
  {
    "path": "src/options_no_pprof.go",
    "content": "//go:build !pprof\n// +build !pprof\n\npackage fzf\n\nimport \"errors\"\n\nfunc (o *Options) initProfiling() error {\n\tif o.CPUProfile != \"\" || o.MEMProfile != \"\" || o.BlockProfile != \"\" || o.MutexProfile != \"\" {\n\t\treturn errors.New(\"error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "src/options_pprof.go",
    "content": "//go:build pprof\n// +build pprof\n\npackage fzf\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc (o *Options) initProfiling() error {\n\tif o.CPUProfile != \"\" {\n\t\tf, err := os.Create(o.CPUProfile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not create CPU profile: %w\", err)\n\t\t}\n\n\t\tif err := pprof.StartCPUProfile(f); err != nil {\n\t\t\treturn fmt.Errorf(\"could not start CPU profile: %w\", err)\n\t\t}\n\n\t\tutil.AtExit(func() {\n\t\t\tpprof.StopCPUProfile()\n\t\t\tif err := f.Close(); err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, \"Error: closing cpu profile:\", err)\n\t\t\t}\n\t\t})\n\t}\n\n\tstopProfile := func(name string, f *os.File) {\n\t\tif err := pprof.Lookup(name).WriteTo(f, 0); err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"Error: could not write %s profile: %v\\n\", name, err)\n\t\t}\n\t\tif err := f.Close(); err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"Error: closing %s profile: %v\\n\", name, err)\n\t\t}\n\t}\n\n\tif o.MEMProfile != \"\" {\n\t\tf, err := os.Create(o.MEMProfile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not create MEM profile: %w\", err)\n\t\t}\n\t\tutil.AtExit(func() {\n\t\t\truntime.GC()\n\t\t\tstopProfile(\"allocs\", f)\n\t\t})\n\t}\n\n\tif o.BlockProfile != \"\" {\n\t\truntime.SetBlockProfileRate(1)\n\t\tf, err := os.Create(o.BlockProfile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not create BLOCK profile: %w\", err)\n\t\t}\n\t\tutil.AtExit(func() { stopProfile(\"block\", f) })\n\t}\n\n\tif o.MutexProfile != \"\" {\n\t\truntime.SetMutexProfileFraction(1)\n\t\tf, err := os.Create(o.MutexProfile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not create MUTEX profile: %w\", err)\n\t\t}\n\t\tutil.AtExit(func() { stopProfile(\"mutex\", f) })\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/options_pprof_test.go",
    "content": "//go:build pprof\n// +build pprof\n\npackage fzf\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n// runInitProfileTests is an internal flag used TestInitProfiling\nvar runInitProfileTests = flag.Bool(\"test-init-profile\", false, \"run init profile tests\")\n\nfunc TestInitProfiling(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"short test\")\n\t}\n\n\t// Run this test in a separate process since it interferes with\n\t// profiling and modifies the global atexit state. Without this\n\t// running `go test -bench . -cpuprofile cpu.out` will fail.\n\tif !*runInitProfileTests {\n\t\tt.Parallel()\n\n\t\t// Make sure we are not the child process.\n\t\tif os.Getenv(\"_FZF_CHILD_PROC\") != \"\" {\n\t\t\tt.Fatal(\"already running as child process!\")\n\t\t}\n\n\t\tcmd := exec.Command(os.Args[0],\n\t\t\t\"-test.timeout\", \"30s\",\n\t\t\t\"-test.run\", \"^\"+t.Name()+\"$\",\n\t\t\t\"-test-init-profile\",\n\t\t)\n\t\tcmd.Env = append(os.Environ(), \"_FZF_CHILD_PROC=1\")\n\n\t\tout, err := cmd.CombinedOutput()\n\t\tout = bytes.TrimSpace(out)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Child test process failed: %v:\\n%s\", err, out)\n\t\t}\n\t\t// Make sure the test actually ran\n\t\tif bytes.Contains(out, []byte(\"no tests to run\")) {\n\t\t\tt.Fatalf(\"Failed to run test %q:\\n%s\", t.Name(), out)\n\t\t}\n\t\treturn\n\t}\n\n\t// Child process\n\n\ttempdir := t.TempDir()\n\tt.Cleanup(util.RunAtExitFuncs)\n\n\to := Options{\n\t\tCPUProfile:   filepath.Join(tempdir, \"cpu.prof\"),\n\t\tMEMProfile:   filepath.Join(tempdir, \"mem.prof\"),\n\t\tBlockProfile: filepath.Join(tempdir, \"block.prof\"),\n\t\tMutexProfile: filepath.Join(tempdir, \"mutex.prof\"),\n\t}\n\tif err := o.initProfiling(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tprofiles := []string{\n\t\to.CPUProfile,\n\t\to.MEMProfile,\n\t\to.BlockProfile,\n\t\to.MutexProfile,\n\t}\n\tfor _, name := range profiles {\n\t\tif _, err := os.Stat(name); err != nil {\n\t\t\tt.Errorf(\"Failed to create profile %s: %v\", filepath.Base(name), err)\n\t\t}\n\t}\n\n\tutil.RunAtExitFuncs()\n\n\tfor _, name := range profiles {\n\t\tif _, err := os.Stat(name); err != nil {\n\t\t\tt.Errorf(\"Failed to write profile %s: %v\", filepath.Base(name), err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/options_test.go",
    "content": "package fzf\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n)\n\nfunc TestDelimiterRegex(t *testing.T) {\n\t// Valid regex, but a single character -> string\n\tdelim := delimiterRegexp(\".\")\n\tif delim.regex != nil || *delim.str != \".\" {\n\t\tt.Error(delim)\n\t}\n\tdelim = delimiterRegexp(\"|\")\n\tif delim.regex != nil || *delim.str != \"|\" {\n\t\tt.Error(delim)\n\t}\n\t// Broken regex -> string\n\tdelim = delimiterRegexp(\"[0-9\")\n\tif delim.regex != nil || *delim.str != \"[0-9\" {\n\t\tt.Error(delim)\n\t}\n\t// Valid regex\n\tdelim = delimiterRegexp(\"[0-9]\")\n\tif delim.regex.String() != \"[0-9]\" || delim.str != nil {\n\t\tt.Error(delim)\n\t}\n\t// Tab character\n\tdelim = delimiterRegexp(\"\\t\")\n\tif delim.regex != nil || *delim.str != \"\\t\" {\n\t\tt.Error(delim)\n\t}\n\t// Tab expression\n\tdelim = delimiterRegexp(\"\\\\t\")\n\tif delim.regex != nil || *delim.str != \"\\t\" {\n\t\tt.Error(delim)\n\t}\n\t// Tabs -> regex\n\tdelim = delimiterRegexp(\"\\t+\")\n\tif delim.regex == nil || delim.str != nil {\n\t\tt.Error(delim)\n\t}\n}\n\nfunc TestDelimiterRegexString(t *testing.T) {\n\tdelim := delimiterRegexp(\"*\")\n\ttokens := Tokenize(\"-*--*---**---\", delim)\n\tif delim.regex != nil ||\n\t\ttokens[0].text.ToString() != \"-*\" ||\n\t\ttokens[1].text.ToString() != \"--*\" ||\n\t\ttokens[2].text.ToString() != \"---*\" ||\n\t\ttokens[3].text.ToString() != \"*\" ||\n\t\ttokens[4].text.ToString() != \"---\" {\n\t\tt.Errorf(\"%s %v %d\", delim, tokens, len(tokens))\n\t}\n}\n\nfunc TestDelimiterRegexRegex(t *testing.T) {\n\tdelim := delimiterRegexp(\"--\\\\*\")\n\ttokens := Tokenize(\"-*--*---**---\", delim)\n\tif delim.str != nil ||\n\t\ttokens[0].text.ToString() != \"-*--*\" ||\n\t\ttokens[1].text.ToString() != \"---*\" ||\n\t\ttokens[2].text.ToString() != \"*---\" {\n\t\tt.Errorf(\"%s %d\", tokens, len(tokens))\n\t}\n}\n\nfunc TestDelimiterRegexRegexCaret(t *testing.T) {\n\tdelim := delimiterRegexp(`(^\\s*|\\s+)`)\n\ttokens := Tokenize(\"foo  bar baz\", delim)\n\tif delim.str != nil ||\n\t\tlen(tokens) != 4 ||\n\t\ttokens[0].text.ToString() != \"\" ||\n\t\ttokens[1].text.ToString() != \"foo  \" ||\n\t\ttokens[2].text.ToString() != \"bar \" ||\n\t\ttokens[3].text.ToString() != \"baz\" {\n\t\tt.Errorf(\"%s %d\", tokens, len(tokens))\n\t}\n}\n\nfunc TestSplitNth(t *testing.T) {\n\t{\n\t\tranges, _ := splitNth(\"..\")\n\t\tif len(ranges) != 1 ||\n\t\t\tranges[0].begin != rangeEllipsis ||\n\t\t\tranges[0].end != rangeEllipsis {\n\t\t\tt.Errorf(\"%v\", ranges)\n\t\t}\n\t}\n\t{\n\t\tranges, _ := splitNth(\"..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1\")\n\t\tif len(ranges) != 10 ||\n\t\t\tranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||\n\t\t\tranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||\n\t\t\tranges[2].begin != 2 || ranges[2].end != 3 ||\n\t\t\tranges[3].begin != 4 || ranges[3].end != rangeEllipsis ||\n\t\t\tranges[4].begin != -3 || ranges[4].end != -2 ||\n\t\t\tranges[5].begin != rangeEllipsis || ranges[5].end != rangeEllipsis ||\n\t\t\tranges[6].begin != 2 || ranges[6].end != 2 ||\n\t\t\tranges[7].begin != -2 || ranges[7].end != -2 ||\n\t\t\tranges[8].begin != 2 || ranges[8].end != -2 ||\n\t\t\tranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {\n\t\t\tt.Errorf(\"%v\", ranges)\n\t\t}\n\t}\n}\n\nfunc TestIrrelevantNth(t *testing.T) {\n\tindex := 0\n\t{\n\t\topts := defaultOptions()\n\t\twords := []string{\"--nth\", \"..\", \"-x\"}\n\t\tparseOptions(&index, opts, words)\n\t\tpostProcessOptions(opts)\n\t\tif len(opts.Nth) != 0 {\n\t\t\tt.Errorf(\"nth should be empty: %v\", opts.Nth)\n\t\t}\n\t}\n\tfor _, words := range [][]string{{\"--nth\", \"..,3\", \"+x\"}, {\"--nth\", \"3,1..\", \"+x\"}, {\"--nth\", \"..-1,1\", \"+x\"}} {\n\t\t{\n\t\t\topts := defaultOptions()\n\t\t\tparseOptions(&index, opts, words)\n\t\t\tpostProcessOptions(opts)\n\t\t\tif len(opts.Nth) != 0 {\n\t\t\t\tt.Errorf(\"nth should be empty: %v\", opts.Nth)\n\t\t\t}\n\t\t}\n\t\t{\n\t\t\topts := defaultOptions()\n\t\t\twords = append(words, \"-x\")\n\t\t\tparseOptions(&index, opts, words)\n\t\t\tpostProcessOptions(opts)\n\t\t\tif len(opts.Nth) != 2 {\n\t\t\t\tt.Errorf(\"nth should not be empty: %v\", opts.Nth)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestParseKeys(t *testing.T) {\n\tpairs, _, _ := parseKeyChords(\"ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE\", \"\")\n\tcheckEvent := func(e tui.Event, s string) {\n\t\tif pairs[e] != s {\n\t\t\tt.Errorf(\"%s != %s\", pairs[e], s)\n\t\t}\n\t}\n\tcheck := func(et tui.EventType, s string) {\n\t\tcheckEvent(et.AsEvent(), s)\n\t}\n\tif len(pairs) != 12 {\n\t\tt.Error(12)\n\t}\n\tcheck(tui.CtrlZ, \"ctrl-z\")\n\tcheck(tui.F2, \"f2\")\n\tcheck(tui.CtrlG, \"ctrl-G\")\n\tcheckEvent(tui.AltKey('z'), \"alt-z\")\n\tcheckEvent(tui.Key('@'), \"@\")\n\tcheckEvent(tui.AltKey('a'), \"Alt-a\")\n\tcheckEvent(tui.Key('!'), \"!\")\n\tcheckEvent(tui.Key('J'), \"J\")\n\tcheckEvent(tui.Key('g'), \"g\")\n\tcheckEvent(tui.CtrlAltKey('a'), \"ctrl-alt-a\")\n\tcheckEvent(tui.CtrlAltKey('m'), \"ALT-enter\")\n\tcheckEvent(tui.AltKey(' '), \"alt-SPACE\")\n\n\t// Synonyms\n\tpairs, _, _ = parseKeyChords(\"enter,Return,space,tab,btab,esc,up,down,left,right\", \"\")\n\tif len(pairs) != 9 {\n\t\tt.Error(9)\n\t}\n\tcheck(tui.Enter, \"Return\")\n\tcheckEvent(tui.Key(' '), \"space\")\n\tcheck(tui.Tab, \"tab\")\n\tcheck(tui.ShiftTab, \"btab\")\n\tcheck(tui.Esc, \"esc\")\n\tcheck(tui.Up, \"up\")\n\tcheck(tui.Down, \"down\")\n\tcheck(tui.Left, \"left\")\n\tcheck(tui.Right, \"right\")\n\n\tpairs, _, _ = parseKeyChords(\"Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace\", \"\")\n\tif len(pairs) != 11 {\n\t\tt.Error(11)\n\t}\n\tcheck(tui.Tab, \"Ctrl-I\")\n\tcheck(tui.PageUp, \"page-up\")\n\tcheck(tui.PageDown, \"Page-Down\")\n\tcheck(tui.Home, \"Home\")\n\tcheck(tui.End, \"End\")\n\tcheck(tui.AltBackspace, \"Alt-BSpace\")\n\tcheck(tui.ShiftLeft, \"shift-left\")\n\tcheck(tui.ShiftRight, \"shift-right\")\n\tcheck(tui.ShiftTab, \"shift-tab\")\n\tcheck(tui.Enter, \"Enter\")\n\tcheck(tui.Backspace, \"bspace\")\n}\n\nfunc TestParseKeysWithComma(t *testing.T) {\n\tcheckN := func(a int, b int) {\n\t\tif a != b {\n\t\t\tt.Errorf(\"%d != %d\", a, b)\n\t\t}\n\t}\n\tcheck := func(pairs map[tui.Event]string, e tui.Event, s string) {\n\t\tif pairs[e] != s {\n\t\t\tt.Errorf(\"%s != %s\", pairs[e], s)\n\t\t}\n\t}\n\n\tpairs, _, _ := parseKeyChords(\",\", \"\")\n\tcheckN(len(pairs), 1)\n\tcheck(pairs, tui.Key(','), \",\")\n\n\tpairs, _, _ = parseKeyChords(\",,a,b\", \"\")\n\tcheckN(len(pairs), 3)\n\tcheck(pairs, tui.Key('a'), \"a\")\n\tcheck(pairs, tui.Key('b'), \"b\")\n\tcheck(pairs, tui.Key(','), \",\")\n\n\tpairs, _, _ = parseKeyChords(\"a,b,,\", \"\")\n\tcheckN(len(pairs), 3)\n\tcheck(pairs, tui.Key('a'), \"a\")\n\tcheck(pairs, tui.Key('b'), \"b\")\n\tcheck(pairs, tui.Key(','), \",\")\n\n\tpairs, _, _ = parseKeyChords(\"a,,,b\", \"\")\n\tcheckN(len(pairs), 3)\n\tcheck(pairs, tui.Key('a'), \"a\")\n\tcheck(pairs, tui.Key('b'), \"b\")\n\tcheck(pairs, tui.Key(','), \",\")\n\n\tpairs, _, _ = parseKeyChords(\"a,,,b,c\", \"\")\n\tcheckN(len(pairs), 4)\n\tcheck(pairs, tui.Key('a'), \"a\")\n\tcheck(pairs, tui.Key('b'), \"b\")\n\tcheck(pairs, tui.Key('c'), \"c\")\n\tcheck(pairs, tui.Key(','), \",\")\n\n\tpairs, _, _ = parseKeyChords(\",,,\", \"\")\n\tcheckN(len(pairs), 1)\n\tcheck(pairs, tui.Key(','), \",\")\n\n\tpairs, _, _ = parseKeyChords(\",ALT-,,\", \"\")\n\tcheckN(len(pairs), 1)\n\tcheck(pairs, tui.AltKey(','), \"ALT-,\")\n}\n\nfunc TestBind(t *testing.T) {\n\tkeymap := defaultKeymap()\n\tcheck := func(event tui.Event, arg1 string, types ...actionType) {\n\t\tif len(keymap[event]) != len(types) {\n\t\t\tt.Errorf(\"invalid number of actions for %v (%d != %d)\",\n\t\t\t\tevent, len(types), len(keymap[event]))\n\t\t\treturn\n\t\t}\n\t\tfor idx, action := range keymap[event] {\n\t\t\tif types[idx] != action.t {\n\t\t\t\tt.Errorf(\"invalid action type (%d != %d)\", types[idx], action.t)\n\t\t\t}\n\t\t}\n\t\tif len(arg1) > 0 && keymap[event][0].a != arg1 {\n\t\t\tt.Errorf(\"invalid action argument: (%s != %s)\", arg1, keymap[event][0].a)\n\t\t}\n\t}\n\tcheck(tui.CtrlA.AsEvent(), \"\", actBeginningOfLine)\n\tparseKeymap(keymap,\n\t\t\"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,\"+\n\t\t\t\"f1:execute(ls {+})+abort+execute(echo \\n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,\"+\n\t\t\t\"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,\"+\n\t\t\t\"x:Execute(foo+bar),X:execute/bar+baz/\"+\n\t\t\t\",f1:+first,f1:+top\"+\n\t\t\t\",,:abort,::accept,+:execute:++\\nfoobar,Y:execute(baz)+up\")\n\tcheck(tui.CtrlA.AsEvent(), \"\", actKillLine)\n\tcheck(tui.CtrlB.AsEvent(), \"\", actToggleSort, actUp, actDown)\n\tcheck(tui.Key('c'), \"\", actPageUp)\n\tcheck(tui.Key(','), \"\", actAbort)\n\tcheck(tui.Key(':'), \"\", actAccept)\n\tcheck(tui.AltKey('z'), \"\", actPageDown)\n\tcheck(tui.F1.AsEvent(), \"ls {+}\", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)\n\tcheck(tui.F2.AsEvent(), \"echo {}, {}, {}\", actExecute)\n\tcheck(tui.F3.AsEvent(), \"echo '({})'\", actExecute)\n\tcheck(tui.F4.AsEvent(), \"less {}\", actExecute)\n\tcheck(tui.Key('x'), \"foo+bar\", actExecute)\n\tcheck(tui.Key('X'), \"bar+baz\", actExecute)\n\tcheck(tui.AltKey('a'), \"echo (,),[,],/,:,;,%,{}\", actExecuteMulti)\n\tcheck(tui.AltKey('b'), \"echo (,),[,],/,:,@,%,{}\", actExecute)\n\tcheck(tui.Key('+'), \"++\\nfoobar,Y:execute(baz)+up\", actExecute)\n\n\tfor idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {\n\t\tparseKeymap(keymap, fmt.Sprintf(\"%d:execute%cfoobar%c\", idx%10, char, char))\n\t\tcheck(tui.Key([]rune(fmt.Sprintf(\"%d\", idx%10))[0]), \"foobar\", actExecute)\n\t}\n\n\tparseKeymap(keymap, \"f1:abort\")\n\tcheck(tui.F1.AsEvent(), \"\", actAbort)\n}\n\nfunc TestColorSpec(t *testing.T) {\n\tvar base *tui.ColorTheme\n\ttheme := tui.Dark256\n\tbase, dark, _ := parseTheme(theme, \"dark\")\n\tif *dark != *base {\n\t\tt.Errorf(\"incorrect base theme returned\")\n\t}\n\tif *dark != *theme {\n\t\tt.Errorf(\"colors should be equivalent\")\n\t}\n\tif dark == theme {\n\t\tt.Errorf(\"point should not be equivalent\")\n\t}\n\n\tbase, light, _ := parseTheme(theme, \"dark,light\")\n\tif *light != *base {\n\t\tt.Errorf(\"incorrect base theme returned\")\n\t}\n\tif *light == *theme {\n\t\tt.Errorf(\"should not be equivalent\")\n\t}\n\tif *light != *tui.Light256 {\n\t\tt.Errorf(\"colors should be equivalent\")\n\t}\n\tif light == theme {\n\t\tt.Errorf(\"point should not be equivalent\")\n\t}\n\n\t_, customized, _ := parseTheme(theme, \"fg:231,bg:232\")\n\tif customized.Fg.Color != 231 || customized.Bg.Color != 232 {\n\t\tt.Errorf(\"color not customized\")\n\t}\n\tif *tui.Dark256 == *customized {\n\t\tt.Errorf(\"colors should not be equivalent\")\n\t}\n\tcustomized.Fg = tui.Dark256.Fg\n\tcustomized.Bg = tui.Dark256.Bg\n\tif *tui.Dark256 != *customized {\n\t\tt.Errorf(\"colors should now be equivalent: %v, %v\", tui.Dark256, customized)\n\t}\n\n\t_, customized, _ = parseTheme(theme, \"fg:231,dark   bg:232\")\n\tif customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {\n\t\tt.Errorf(\"color not customized\")\n\t}\n}\n\nfunc TestDefaultCtrlNP(t *testing.T) {\n\tindex := 0\n\tcheck := func(words []string, et tui.EventType, expected actionType) {\n\t\te := et.AsEvent()\n\t\topts := defaultOptions()\n\t\tparseOptions(&index, opts, words)\n\t\tpostProcessOptions(opts)\n\t\tif opts.Keymap[e][0].t != expected {\n\t\t\tt.Error()\n\t\t}\n\t}\n\tcheck([]string{}, tui.CtrlN, actDownMatch)\n\tcheck([]string{}, tui.CtrlP, actUpMatch)\n\n\tcheck([]string{\"--bind=ctrl-n:accept\"}, tui.CtrlN, actAccept)\n\tcheck([]string{\"--bind=ctrl-p:accept\"}, tui.CtrlP, actAccept)\n\n\tf, _ := os.CreateTemp(\"\", \"fzf-history\")\n\tf.Close()\n\thist := \"--history=\" + f.Name()\n\tcheck([]string{hist}, tui.CtrlN, actNextHistory)\n\tcheck([]string{hist}, tui.CtrlP, actPrevHistory)\n\n\tcheck([]string{hist, \"--bind=ctrl-n:accept\"}, tui.CtrlN, actAccept)\n\tcheck([]string{hist, \"--bind=ctrl-n:accept\"}, tui.CtrlP, actPrevHistory)\n\n\tcheck([]string{hist, \"--bind=ctrl-p:accept\"}, tui.CtrlN, actNextHistory)\n\tcheck([]string{hist, \"--bind=ctrl-p:accept\"}, tui.CtrlP, actAccept)\n}\n\nfunc optsFor(words ...string) *Options {\n\tindex := 0\n\topts := defaultOptions()\n\tparseOptions(&index, opts, words)\n\tpostProcessOptions(opts)\n\treturn opts\n}\n\nfunc TestToggle(t *testing.T) {\n\topts := optsFor()\n\tif opts.ToggleSort {\n\t\tt.Error()\n\t}\n\n\topts = optsFor(\"--bind=a:toggle-sort\")\n\tif !opts.ToggleSort {\n\t\tt.Error()\n\t}\n\n\topts = optsFor(\"--bind=a:toggle-sort\", \"--bind=a:up\")\n\tif opts.ToggleSort {\n\t\tt.Error()\n\t}\n}\n\nfunc TestPreviewOpts(t *testing.T) {\n\topts := optsFor()\n\tif !(opts.Preview.command == \"\" &&\n\t\topts.Preview.hidden == false &&\n\t\topts.Preview.wrap == false &&\n\t\topts.Preview.position == posRight &&\n\t\topts.Preview.size.percent == true &&\n\t\topts.Preview.size.size == 50) {\n\t\tt.Error()\n\t}\n\topts = optsFor(\"--preview\", \"cat {}\", \"--preview-window=left:15,hidden,wrap:+{1}-/2\")\n\tif !(opts.Preview.command == \"cat {}\" &&\n\t\topts.Preview.hidden == true &&\n\t\topts.Preview.wrap == true &&\n\t\topts.Preview.position == posLeft &&\n\t\topts.Preview.scroll == \"+{1}-/2\" &&\n\t\topts.Preview.size.percent == false &&\n\t\topts.Preview.size.size == 15) {\n\t\tt.Error(opts.Preview)\n\t}\n\topts = optsFor(\"--preview-window=up,15,wrap,hidden,+{1}+3-1-2/2\", \"--preview-window=down\", \"--preview-window=cycle\")\n\tif !(opts.Preview.command == \"\" &&\n\t\topts.Preview.hidden == true &&\n\t\topts.Preview.wrap == true &&\n\t\topts.Preview.cycle == true &&\n\t\topts.Preview.position == posDown &&\n\t\topts.Preview.scroll == \"+{1}+3-1-2/2\" &&\n\t\topts.Preview.size.percent == false &&\n\t\topts.Preview.size.size == 15) {\n\t\tt.Error(opts.Preview.size.size)\n\t}\n\topts = optsFor(\"--preview-window=up:15:wrap:hidden\")\n\tif !(opts.Preview.command == \"\" &&\n\t\topts.Preview.hidden == true &&\n\t\topts.Preview.wrap == true &&\n\t\topts.Preview.position == posUp &&\n\t\topts.Preview.size.percent == false &&\n\t\topts.Preview.size.size == 15) {\n\t\tt.Error(opts.Preview)\n\t}\n\topts = optsFor(\"--preview=foo\", \"--preview-window=up\", \"--preview-window=default:70%\")\n\tif !(opts.Preview.command == \"foo\" &&\n\t\topts.Preview.position == posRight &&\n\t\topts.Preview.size.percent == true &&\n\t\topts.Preview.size.size == 70) {\n\t\tt.Error(opts.Preview)\n\t}\n\n\t// wrap-word tests\n\topts = optsFor(\"--preview-window=wrap-word\")\n\tif !(opts.Preview.wrap == true && opts.Preview.wrapWord == true) {\n\t\tt.Errorf(\"wrap-word: wrap=%v, wrapWord=%v\", opts.Preview.wrap, opts.Preview.wrapWord)\n\t}\n\topts = optsFor(\"--preview-window=wrap-word,nowrap\")\n\tif !(opts.Preview.wrap == false && opts.Preview.wrapWord == false) {\n\t\tt.Errorf(\"wrap-word,nowrap: wrap=%v, wrapWord=%v\", opts.Preview.wrap, opts.Preview.wrapWord)\n\t}\n\topts = optsFor(\"--preview-window=wrap-word,wrap\")\n\tif !(opts.Preview.wrap == true && opts.Preview.wrapWord == false) {\n\t\tt.Errorf(\"wrap-word,wrap: wrap=%v, wrapWord=%v\", opts.Preview.wrap, opts.Preview.wrapWord)\n\t}\n}\n\nfunc TestPreviewWrapSign(t *testing.T) {\n\t// Default: no preview wrap sign override\n\topts := optsFor()\n\tif opts.PreviewWrapSign != nil {\n\t\tt.Errorf(\"expected nil PreviewWrapSign, got %v\", *opts.PreviewWrapSign)\n\t}\n\n\t// --preview-wrap-sign sets PreviewWrapSign\n\topts = optsFor(\"--preview-wrap-sign\", \">> \")\n\tif opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != \">> \" {\n\t\tt.Errorf(\"expected '>> ', got %v\", opts.PreviewWrapSign)\n\t}\n\n\t// --preview-wrap-sign is independent of --wrap-sign\n\topts = optsFor(\"--wrap-sign\", \"| \", \"--preview-wrap-sign\", \">> \")\n\tif opts.WrapSign == nil || *opts.WrapSign != \"| \" {\n\t\tt.Errorf(\"expected WrapSign '| ', got %v\", opts.WrapSign)\n\t}\n\tif opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != \">> \" {\n\t\tt.Errorf(\"expected PreviewWrapSign '>> ', got %v\", opts.PreviewWrapSign)\n\t}\n\n\t// --preview-wrap-sign without --wrap-sign\n\topts = optsFor(\"--preview-wrap-sign\", \"→ \")\n\tif opts.WrapSign != nil {\n\t\tt.Errorf(\"expected nil WrapSign, got %v\", *opts.WrapSign)\n\t}\n\tif opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != \"→ \" {\n\t\tt.Errorf(\"expected PreviewWrapSign '→ ', got %v\", opts.PreviewWrapSign)\n\t}\n\n\t// Last --preview-wrap-sign wins\n\topts = optsFor(\"--preview-wrap-sign\", \"A \", \"--preview-wrap-sign\", \"B \")\n\tif opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != \"B \" {\n\t\tt.Errorf(\"expected PreviewWrapSign 'B ', got %v\", opts.PreviewWrapSign)\n\t}\n\n\t// Empty string is allowed\n\topts = optsFor(\"--preview-wrap-sign\", \"\")\n\tif opts.PreviewWrapSign == nil || *opts.PreviewWrapSign != \"\" {\n\t\tt.Errorf(\"expected empty PreviewWrapSign, got %v\", opts.PreviewWrapSign)\n\t}\n}\n\nfunc TestAdditiveExpect(t *testing.T) {\n\topts := optsFor(\"--expect=a\", \"--expect\", \"b\", \"--expect=c\")\n\tif len(opts.Expect) != 3 {\n\t\tt.Error(opts.Expect)\n\t}\n}\n\nfunc TestValidateSign(t *testing.T) {\n\ttestCases := []struct {\n\t\tinputSign string\n\t\tisValid   bool\n\t}{\n\t\t{\"> \", true},\n\t\t{\"아\", true},\n\t\t{\"😀\", true},\n\t\t{\">>>\", false},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\terr := validateSign(testCase.inputSign, \"\", 2)\n\t\tif testCase.isValid && err != nil {\n\t\t\tt.Errorf(\"Input sign `%s` caused error\", testCase.inputSign)\n\t\t}\n\n\t\tif !testCase.isValid && err == nil {\n\t\t\tt.Errorf(\"Input sign `%s` did not cause error\", testCase.inputSign)\n\t\t}\n\t}\n}\n\nfunc TestParseSingleActionList(t *testing.T) {\n\tactions, _ := parseSingleActionList(\"Execute@foo+bar,baz@+up+up+reload:down+down\")\n\tif len(actions) != 4 {\n\t\tt.Errorf(\"Invalid number of actions parsed:%d\", len(actions))\n\t}\n\tif actions[0].t != actExecute || actions[0].a != \"foo+bar,baz\" {\n\t\tt.Errorf(\"Invalid action parsed: %v\", actions[0])\n\t}\n\tif actions[1].t != actUp || actions[2].t != actUp {\n\t\tt.Errorf(\"Invalid action parsed: %v / %v\", actions[1], actions[2])\n\t}\n\tif actions[3].t != actReload || actions[3].a != \"down+down\" {\n\t\tt.Errorf(\"Invalid action parsed: %v\", actions[3])\n\t}\n}\n\nfunc TestParseSingleActionListError(t *testing.T) {\n\t_, err := parseSingleActionList(\"change-query(foobar)baz\")\n\tif err == nil {\n\t\tt.Errorf(\"Failed to detect error\")\n\t}\n}\n\nfunc TestMaskActionContents(t *testing.T) {\n\toriginal := \":execute((f)(o)(o)(b)(a)(r))+change-query@qu@ry@+up,x:reload:hello:world\"\n\texpected := \":execute                    +change-query       +up,x:reload            \"\n\tmasked := maskActionContents(original)\n\tif masked != expected {\n\t\tt.Errorf(\"Not masked: %s\", masked)\n\t}\n}\n"
  },
  {
    "path": "src/pattern.go",
    "content": "package fzf\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/junegunn/fzf/src/algo\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n// fuzzy\n// 'exact\n// ^prefix-exact\n// suffix-exact$\n// !inverse-exact\n// !'inverse-fuzzy\n// !^inverse-prefix-exact\n// !inverse-suffix-exact$\n\ntype termType int\n\nconst (\n\ttermFuzzy termType = iota\n\ttermExact\n\ttermExactBoundary\n\ttermPrefix\n\ttermSuffix\n\ttermEqual\n)\n\ntype term struct {\n\ttyp           termType\n\tinv           bool\n\ttext          []rune\n\tcaseSensitive bool\n\tnormalize     bool\n}\n\n// String returns the string representation of a term.\nfunc (t term) String() string {\n\treturn fmt.Sprintf(\"term{typ: %d, inv: %v, text: []rune(%q), caseSensitive: %v}\", t.typ, t.inv, string(t.text), t.caseSensitive)\n}\n\ntype termSet []term\n\n// Pattern represents search pattern\ntype Pattern struct {\n\tfuzzy         bool\n\tfuzzyAlgo     algo.Algo\n\textended      bool\n\tcaseSensitive bool\n\tnormalize     bool\n\tforward       bool\n\twithPos       bool\n\ttext          []rune\n\ttermSets      []termSet\n\tsortable      bool\n\tcacheable     bool\n\tcacheKey      string\n\tdelimiter     Delimiter\n\tnth           []Range\n\trevision      revision\n\tprocFun       [6]algo.Algo\n\tcache         *ChunkCache\n\tdenylist      map[int32]struct{}\n\tstartIndex    int32\n\tdirectAlgo    algo.Algo\n\tdirectTerm    *term\n}\n\nvar _splitRegex *regexp.Regexp\n\nfunc init() {\n\t_splitRegex = regexp.MustCompile(\" +\")\n}\n\n// BuildPattern builds Pattern object from the given arguments\nfunc BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,\n\twithPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision revision, runes []rune, denylist map[int32]struct{}, startIndex int32) *Pattern {\n\n\tvar asString string\n\tif extended {\n\t\tasString = strings.TrimLeft(string(runes), \" \")\n\t\tfor strings.HasSuffix(asString, \" \") && !strings.HasSuffix(asString, \"\\\\ \") {\n\t\t\tasString = asString[:len(asString)-1]\n\t\t}\n\t} else {\n\t\tasString = string(runes)\n\t}\n\n\t// We can uniquely identify the pattern for a given string since\n\t// search mode and caseMode do not change while the program is running\n\tcached, found := patternCache[asString]\n\tif found {\n\t\treturn cached\n\t}\n\n\tcaseSensitive := true\n\tsortable := true\n\ttermSets := []termSet{}\n\n\tif extended {\n\t\ttermSets = parseTerms(fuzzy, caseMode, normalize, asString)\n\t\t// We should not sort the result if there are only inverse search terms\n\t\tsortable = false\n\tLoop:\n\t\tfor _, termSet := range termSets {\n\t\t\tfor idx, term := range termSet {\n\t\t\t\tif !term.inv {\n\t\t\t\t\tsortable = true\n\t\t\t\t}\n\t\t\t\t// If the query contains inverse search terms or OR operators,\n\t\t\t\t// we cannot cache the search scope\n\t\t\t\tif !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {\n\t\t\t\t\tcacheable = false\n\t\t\t\t\tif sortable {\n\t\t\t\t\t\t// Can't break until we see at least one non-inverse term\n\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tlowerString := strings.ToLower(asString)\n\t\tnormalize = normalize &&\n\t\t\tlowerString == string(algo.NormalizeRunes([]rune(lowerString)))\n\t\tcaseSensitive = caseMode == CaseRespect ||\n\t\t\tcaseMode == CaseSmart && lowerString != asString\n\t\tif !caseSensitive {\n\t\t\tasString = lowerString\n\t\t}\n\t}\n\n\tptr := &Pattern{\n\t\tfuzzy:         fuzzy,\n\t\tfuzzyAlgo:     fuzzyAlgo,\n\t\textended:      extended,\n\t\tcaseSensitive: caseSensitive,\n\t\tnormalize:     normalize,\n\t\tforward:       forward,\n\t\twithPos:       withPos,\n\t\ttext:          []rune(asString),\n\t\ttermSets:      termSets,\n\t\tsortable:      sortable,\n\t\tcacheable:     cacheable,\n\t\tnth:           nth,\n\t\trevision:      revision,\n\t\tdelimiter:     delimiter,\n\t\tcache:         cache,\n\t\tdenylist:      denylist,\n\t\tstartIndex:    startIndex,\n\t}\n\n\tptr.cacheKey = ptr.buildCacheKey()\n\tptr.directAlgo, ptr.directTerm = ptr.buildDirectAlgo(fuzzyAlgo)\n\tptr.procFun[termFuzzy] = fuzzyAlgo\n\tptr.procFun[termEqual] = algo.EqualMatch\n\tptr.procFun[termExact] = algo.ExactMatchNaive\n\tptr.procFun[termExactBoundary] = algo.ExactMatchBoundary\n\tptr.procFun[termPrefix] = algo.PrefixMatch\n\tptr.procFun[termSuffix] = algo.SuffixMatch\n\n\tpatternCache[asString] = ptr\n\treturn ptr\n}\n\nfunc parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {\n\tstr = strings.ReplaceAll(str, \"\\\\ \", \"\\t\")\n\ttokens := _splitRegex.Split(str, -1)\n\tsets := []termSet{}\n\tset := termSet{}\n\tswitchSet := false\n\tafterBar := false\n\tfor _, token := range tokens {\n\t\ttyp, inv, text := termFuzzy, false, strings.ReplaceAll(token, \"\\t\", \" \")\n\t\tlowerText := strings.ToLower(text)\n\t\tcaseSensitive := caseMode == CaseRespect ||\n\t\t\tcaseMode == CaseSmart && text != lowerText\n\t\tnormalizeTerm := normalize &&\n\t\t\tlowerText == string(algo.NormalizeRunes([]rune(lowerText)))\n\t\tif !caseSensitive {\n\t\t\ttext = lowerText\n\t\t}\n\t\tif !fuzzy {\n\t\t\ttyp = termExact\n\t\t}\n\n\t\tif len(set) > 0 && !afterBar && text == \"|\" {\n\t\t\tswitchSet = false\n\t\t\tafterBar = true\n\t\t\tcontinue\n\t\t}\n\t\tafterBar = false\n\n\t\tif strings.HasPrefix(text, \"!\") {\n\t\t\tinv = true\n\t\t\ttyp = termExact\n\t\t\ttext = text[1:]\n\t\t}\n\n\t\tif text != \"$\" && strings.HasSuffix(text, \"$\") {\n\t\t\ttyp = termSuffix\n\t\t\ttext = text[:len(text)-1]\n\t\t}\n\n\t\tif len(text) > 2 && strings.HasPrefix(text, \"'\") && strings.HasSuffix(text, \"'\") {\n\t\t\ttyp = termExactBoundary\n\t\t\ttext = text[1 : len(text)-1]\n\t\t} else if strings.HasPrefix(text, \"'\") {\n\t\t\t// Flip exactness\n\t\t\tif fuzzy && !inv {\n\t\t\t\ttyp = termExact\n\t\t\t} else {\n\t\t\t\ttyp = termFuzzy\n\t\t\t}\n\t\t\ttext = text[1:]\n\t\t} else if strings.HasPrefix(text, \"^\") {\n\t\t\tif typ == termSuffix {\n\t\t\t\ttyp = termEqual\n\t\t\t} else {\n\t\t\t\ttyp = termPrefix\n\t\t\t}\n\t\t\ttext = text[1:]\n\t\t}\n\n\t\tif len(text) > 0 {\n\t\t\tif switchSet {\n\t\t\t\tsets = append(sets, set)\n\t\t\t\tset = termSet{}\n\t\t\t}\n\t\t\ttextRunes := []rune(text)\n\t\t\tif normalizeTerm {\n\t\t\t\ttextRunes = algo.NormalizeRunes(textRunes)\n\t\t\t}\n\t\t\tset = append(set, term{\n\t\t\t\ttyp:           typ,\n\t\t\t\tinv:           inv,\n\t\t\t\ttext:          textRunes,\n\t\t\t\tcaseSensitive: caseSensitive,\n\t\t\t\tnormalize:     normalizeTerm})\n\t\t\tswitchSet = true\n\t\t}\n\t}\n\tif len(set) > 0 {\n\t\tsets = append(sets, set)\n\t}\n\treturn sets\n}\n\n// IsEmpty returns true if the pattern is effectively empty\nfunc (p *Pattern) IsEmpty() bool {\n\tif len(p.denylist) > 0 {\n\t\treturn false\n\t}\n\tif !p.extended {\n\t\treturn len(p.text) == 0\n\t}\n\treturn len(p.termSets) == 0\n}\n\n// AsString returns the search query in string type\nfunc (p *Pattern) AsString() string {\n\treturn string(p.text)\n}\n\nfunc (p *Pattern) buildCacheKey() string {\n\tif !p.extended {\n\t\treturn p.AsString()\n\t}\n\tcacheableTerms := []string{}\n\tfor _, termSet := range p.termSets {\n\t\tif len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {\n\t\t\tcacheableTerms = append(cacheableTerms, string(termSet[0].text))\n\t\t}\n\t}\n\treturn strings.Join(cacheableTerms, \"\\t\")\n}\n\n// buildDirectAlgo returns the algo function and term for the direct fast path\n// in matchChunk. Returns (nil, nil) if the pattern is not suitable.\n// Requirements: extended mode, single term set with single non-inverse fuzzy term, no nth.\nfunc (p *Pattern) buildDirectAlgo(fuzzyAlgo algo.Algo) (algo.Algo, *term) {\n\tif !p.extended || len(p.nth) > 0 {\n\t\treturn nil, nil\n\t}\n\tif len(p.termSets) == 1 && len(p.termSets[0]) == 1 {\n\t\tt := &p.termSets[0][0]\n\t\tif !t.inv && t.typ == termFuzzy {\n\t\t\treturn fuzzyAlgo, t\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// CacheKey is used to build string to be used as the key of result cache\nfunc (p *Pattern) CacheKey() string {\n\treturn p.cacheKey\n}\n\n// Match returns the list of matches Items in the given Chunk\nfunc (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {\n\tcacheKey := p.CacheKey()\n\n\t// Bitmap cache: exact match or prefix/suffix\n\tvar cachedBitmap *ChunkBitmap\n\tif p.cacheable {\n\t\tcachedBitmap = p.cache.Lookup(chunk, cacheKey)\n\t}\n\tif cachedBitmap == nil {\n\t\tcachedBitmap = p.cache.Search(chunk, cacheKey)\n\t}\n\n\tmatches, bitmap := p.matchChunk(chunk, cachedBitmap, slab)\n\n\tif p.cacheable {\n\t\tp.cache.Add(chunk, cacheKey, bitmap, len(matches))\n\t}\n\treturn matches\n}\n\nfunc (p *Pattern) matchChunk(chunk *Chunk, cachedBitmap *ChunkBitmap, slab *util.Slab) ([]Result, ChunkBitmap) {\n\tmatches := []Result{}\n\tvar bitmap ChunkBitmap\n\n\t// Skip header items in chunks that contain them\n\tstartIdx := 0\n\tif p.startIndex > 0 && chunk.count > 0 && chunk.items[0].Index() < p.startIndex {\n\t\tstartIdx = int(p.startIndex - chunk.items[0].Index())\n\t\tif startIdx >= chunk.count {\n\t\t\treturn matches, bitmap\n\t\t}\n\t}\n\n\thasCachedBitmap := cachedBitmap != nil\n\n\t// Fast path: single fuzzy term, no nth, no denylist.\n\t// Calls the algo function directly, bypassing MatchItem/extendedMatch/iter\n\t// and avoiding per-match []Offset heap allocation.\n\tif p.directAlgo != nil && len(p.denylist) == 0 {\n\t\tt := p.directTerm\n\t\tfor idx := startIdx; idx < chunk.count; idx++ {\n\t\t\tif hasCachedBitmap && cachedBitmap[idx/64]&(uint64(1)<<(idx%64)) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward,\n\t\t\t\t&chunk.items[idx].text, t.text, p.withPos, slab)\n\t\t\tif res.Start >= 0 {\n\t\t\t\tbitmap[idx/64] |= uint64(1) << (idx % 64)\n\t\t\t\tmatches = append(matches, buildResultFromBounds(\n\t\t\t\t\t&chunk.items[idx], res.Score,\n\t\t\t\t\tint(res.Start), int(res.End), int(res.End), true))\n\t\t\t}\n\t\t}\n\t\treturn matches, bitmap\n\t}\n\n\tif len(p.denylist) == 0 {\n\t\tfor idx := startIdx; idx < chunk.count; idx++ {\n\t\t\tif hasCachedBitmap && cachedBitmap[idx/64]&(uint64(1)<<(idx%64)) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match.item != nil {\n\t\t\t\tbitmap[idx/64] |= uint64(1) << (idx % 64)\n\t\t\t\tmatches = append(matches, match)\n\t\t\t}\n\t\t}\n\t\treturn matches, bitmap\n\t}\n\n\tfor idx := startIdx; idx < chunk.count; idx++ {\n\t\tif hasCachedBitmap && cachedBitmap[idx/64]&(uint64(1)<<(idx%64)) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif _, prs := p.denylist[chunk.items[idx].Index()]; prs {\n\t\t\tcontinue\n\t\t}\n\t\tif match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match.item != nil {\n\t\t\tbitmap[idx/64] |= uint64(1) << (idx % 64)\n\t\t\tmatches = append(matches, match)\n\t\t}\n\t}\n\treturn matches, bitmap\n}\n\n// MatchItem returns the match result if the Item is a match.\n// A zero-value Result (with item == nil) indicates no match.\nfunc (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (Result, []Offset, *[]int) {\n\tif p.extended {\n\t\tif offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {\n\t\t\treturn buildResult(item, offsets, bonus), offsets, pos\n\t\t}\n\t\treturn Result{}, nil, nil\n\t}\n\toffset, bonus, pos := p.basicMatch(item, withPos, slab)\n\tif sidx := offset[0]; sidx >= 0 {\n\t\toffsets := []Offset{offset}\n\t\treturn buildResult(item, offsets, bonus), offsets, pos\n\t}\n\treturn Result{}, nil, nil\n}\n\nfunc (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {\n\tvar input []Token\n\tif len(p.nth) == 0 {\n\t\tinput = []Token{{text: &item.text, prefixLength: 0}}\n\t} else {\n\t\tinput = p.transformInput(item)\n\t}\n\tif p.fuzzy {\n\t\treturn p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)\n\t}\n\treturn p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)\n}\n\nfunc (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {\n\tvar input []Token\n\tif len(p.nth) == 0 {\n\t\tinput = []Token{{text: &item.text, prefixLength: 0}}\n\t} else {\n\t\tinput = p.transformInput(item)\n\t}\n\toffsets := []Offset{}\n\tvar totalScore int\n\tvar allPos *[]int\n\tif withPos {\n\t\tallPos = &[]int{}\n\t}\n\tfor _, termSet := range p.termSets {\n\t\tvar offset Offset\n\t\tvar currentScore int\n\t\tmatched := false\n\t\tfor _, term := range termSet {\n\t\t\tpfun := p.procFun[term.typ]\n\t\t\toff, score, pos := p.iter(pfun, input, term.caseSensitive, term.normalize, p.forward, term.text, withPos, slab)\n\t\t\tif sidx := off[0]; sidx >= 0 {\n\t\t\t\tif term.inv {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toffset, currentScore = off, score\n\t\t\t\tmatched = true\n\t\t\t\tif withPos {\n\t\t\t\t\tif pos != nil {\n\t\t\t\t\t\t*allPos = append(*allPos, *pos...)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor idx := off[0]; idx < off[1]; idx++ {\n\t\t\t\t\t\t\t*allPos = append(*allPos, int(idx))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t} else if term.inv {\n\t\t\t\toffset, currentScore = Offset{0, 0}, 0\n\t\t\t\tmatched = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif matched {\n\t\t\toffsets = append(offsets, offset)\n\t\t\ttotalScore += currentScore\n\t\t}\n\t}\n\treturn offsets, totalScore, allPos\n}\n\nfunc (p *Pattern) transformInput(item *Item) []Token {\n\tif item.transformed != nil {\n\t\ttransformed := *item.transformed\n\t\tif transformed.revision == p.revision {\n\t\t\treturn transformed.tokens\n\t\t}\n\t}\n\n\ttokens := Tokenize(item.text.ToString(), p.delimiter)\n\tret := Transform(tokens, p.nth)\n\t// Strip the last delimiter to allow suffix match\n\tif len(ret) > 0 && !p.delimiter.IsAwk() {\n\t\tchars := ret[len(ret)-1].text\n\t\tstripped := StripLastDelimiter(chars.ToString(), p.delimiter)\n\t\tnewChars := util.ToChars(stringBytes(stripped))\n\t\tret[len(ret)-1].text = &newChars\n\t}\n\titem.transformed = &transformed{p.revision, ret}\n\treturn ret\n}\n\nfunc (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, *[]int) {\n\tfor _, part := range tokens {\n\t\tif res, pos := pfun(caseSensitive, normalize, forward, part.text, pattern, withPos, slab); res.Start >= 0 {\n\t\t\tsidx := int32(res.Start) + part.prefixLength\n\t\t\teidx := int32(res.End) + part.prefixLength\n\t\t\tif pos != nil {\n\t\t\t\tfor idx := range *pos {\n\t\t\t\t\t(*pos)[idx] += int(part.prefixLength)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Offset{sidx, eidx}, res.Score, pos\n\t\t}\n\t}\n\treturn Offset{-1, -1}, 0, nil\n}\n"
  },
  {
    "path": "src/pattern_test.go",
    "content": "package fzf\n\nimport (\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/algo\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nvar slab *util.Slab\n\nfunc init() {\n\tslab = util.MakeSlab(slab16Size, slab32Size)\n}\n\nfunc TestParseTermsExtended(t *testing.T) {\n\tterms := parseTerms(true, CaseSmart, false,\n\t\t\"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | zzz$ | !ZZZ |\")\n\tif len(terms) != 9 ||\n\t\tterms[0][0].typ != termFuzzy || terms[0][0].inv ||\n\t\tterms[1][0].typ != termExact || terms[1][0].inv ||\n\t\tterms[2][0].typ != termPrefix || terms[2][0].inv ||\n\t\tterms[3][0].typ != termSuffix || terms[3][0].inv ||\n\t\tterms[4][0].typ != termExact || !terms[4][0].inv ||\n\t\tterms[5][0].typ != termFuzzy || !terms[5][0].inv ||\n\t\tterms[6][0].typ != termPrefix || !terms[6][0].inv ||\n\t\tterms[7][0].typ != termSuffix || !terms[7][0].inv ||\n\t\tterms[7][1].typ != termEqual || terms[7][1].inv ||\n\t\tterms[8][0].typ != termPrefix || terms[8][0].inv ||\n\t\tterms[8][1].typ != termExact || terms[8][1].inv ||\n\t\tterms[8][2].typ != termSuffix || terms[8][2].inv ||\n\t\tterms[8][3].typ != termExact || !terms[8][3].inv {\n\t\tt.Errorf(\"%v\", terms)\n\t}\n\tfor _, termSet := range terms[:8] {\n\t\tterm := termSet[0]\n\t\tif len(term.text) != 3 {\n\t\t\tt.Errorf(\"%v\", term)\n\t\t}\n\t}\n}\n\nfunc TestParseTermsExtendedExact(t *testing.T) {\n\tterms := parseTerms(false, CaseSmart, false,\n\t\t\"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$\")\n\tif len(terms) != 8 ||\n\t\tterms[0][0].typ != termExact || terms[0][0].inv || len(terms[0][0].text) != 3 ||\n\t\tterms[1][0].typ != termFuzzy || terms[1][0].inv || len(terms[1][0].text) != 3 ||\n\t\tterms[2][0].typ != termPrefix || terms[2][0].inv || len(terms[2][0].text) != 3 ||\n\t\tterms[3][0].typ != termSuffix || terms[3][0].inv || len(terms[3][0].text) != 3 ||\n\t\tterms[4][0].typ != termExact || !terms[4][0].inv || len(terms[4][0].text) != 3 ||\n\t\tterms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||\n\t\tterms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||\n\t\tterms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {\n\t\tt.Errorf(\"%v\", terms)\n\t}\n}\n\nfunc TestParseTermsEmpty(t *testing.T) {\n\tterms := parseTerms(true, CaseSmart, false, \"' ^ !' !^\")\n\tif len(terms) != 0 {\n\t\tt.Errorf(\"%v\", terms)\n\t}\n}\n\nfunc buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,\n\twithPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {\n\treturn BuildPattern(NewChunkCache(), make(map[string]*Pattern),\n\t\tfuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,\n\t\twithPos, cacheable, nth, delimiter, revision{}, runes, nil, 0)\n}\n\nfunc TestExact(t *testing.T) {\n\tpattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,\n\t\t[]Range{}, Delimiter{}, []rune(\"'abc\"))\n\tchars := util.ToChars([]byte(\"aabbcc abc\"))\n\tres, pos := algo.ExactMatchNaive(\n\t\tpattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)\n\tif res.Start != 7 || res.End != 10 {\n\t\tt.Errorf(\"%v / %d / %d\", pattern.termSets, res.Start, res.End)\n\t}\n\tif pos != nil {\n\t\tt.Errorf(\"pos is expected to be nil\")\n\t}\n}\n\nfunc TestEqual(t *testing.T) {\n\tpattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(\"^AbC$\"))\n\n\tmatch := func(str string, sidxExpected int, eidxExpected int) {\n\t\tchars := util.ToChars([]byte(str))\n\t\tres, pos := algo.EqualMatch(\n\t\t\tpattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)\n\t\tif res.Start != sidxExpected || res.End != eidxExpected {\n\t\t\tt.Errorf(\"%v / %d / %d\", pattern.termSets, res.Start, res.End)\n\t\t}\n\t\tif pos != nil {\n\t\t\tt.Errorf(\"pos is expected to be nil\")\n\t\t}\n\t}\n\tmatch(\"ABC\", -1, -1)\n\tmatch(\"AbC\", 0, 3)\n\tmatch(\"AbC  \", 0, 3)\n\tmatch(\" AbC \", 1, 4)\n\tmatch(\"  AbC\", 2, 5)\n}\n\nfunc TestCaseSensitivity(t *testing.T) {\n\tpat1 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(\"abc\"))\n\tpat2 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(\"Abc\"))\n\tpat3 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune(\"abc\"))\n\tpat4 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune(\"Abc\"))\n\tpat5 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune(\"abc\"))\n\tpat6 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune(\"Abc\"))\n\n\tif string(pat1.text) != \"abc\" || pat1.caseSensitive != false ||\n\t\tstring(pat2.text) != \"Abc\" || pat2.caseSensitive != true ||\n\t\tstring(pat3.text) != \"abc\" || pat3.caseSensitive != false ||\n\t\tstring(pat4.text) != \"abc\" || pat4.caseSensitive != false ||\n\t\tstring(pat5.text) != \"abc\" || pat5.caseSensitive != true ||\n\t\tstring(pat6.text) != \"Abc\" || pat6.caseSensitive != true {\n\t\tt.Error(\"Invalid case conversion\")\n\t}\n}\n\nfunc TestOrigTextAndTransformed(t *testing.T) {\n\tpattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(\"jg\"))\n\ttokens := Tokenize(\"junegunn\", Delimiter{})\n\ttrans := Transform(tokens, []Range{{1, 1}})\n\n\torigBytes := []byte(\"junegunn.choi\")\n\tfor _, extended := range []bool{false, true} {\n\t\tchunk := Chunk{count: 1}\n\t\tchunk.items[0] = Item{\n\t\t\ttext:        util.ToChars([]byte(\"junegunn\")),\n\t\t\torigText:    &origBytes,\n\t\t\ttransformed: &transformed{pattern.revision, trans}}\n\t\tpattern.extended = extended\n\t\tmatches, _ := pattern.matchChunk(&chunk, nil, slab) // No cache\n\t\tif !(matches[0].item.text.ToString() == \"junegunn\" &&\n\t\t\tstring(*matches[0].item.origText) == \"junegunn.choi\" &&\n\t\t\treflect.DeepEqual((*matches[0].item.transformed).tokens, trans)) {\n\t\t\tt.Error(\"Invalid match result\", matches)\n\t\t}\n\n\t\tmatch, offsets, pos := pattern.MatchItem(&chunk.items[0], true, slab)\n\t\tif !(match.item.text.ToString() == \"junegunn\" &&\n\t\t\tstring(*match.item.origText) == \"junegunn.choi\" &&\n\t\t\toffsets[0][0] == 0 && offsets[0][1] == 5 &&\n\t\t\treflect.DeepEqual((*match.item.transformed).tokens, trans)) {\n\t\t\tt.Error(\"Invalid match result\", match, offsets, extended)\n\t\t}\n\t\tif !((*pos)[0] == 4 && (*pos)[1] == 0) {\n\t\t\tt.Error(\"Invalid pos array\", *pos)\n\t\t}\n\t}\n}\n\nfunc TestCacheKey(t *testing.T) {\n\ttest := func(extended bool, patStr string, expected string, cacheable bool) {\n\t\tpat := buildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))\n\t\tif pat.CacheKey() != expected {\n\t\t\tt.Errorf(\"Expected: %s, actual: %s\", expected, pat.CacheKey())\n\t\t}\n\t\tif pat.cacheable != cacheable {\n\t\t\tt.Errorf(\"Expected: %t, actual: %t (%s)\", cacheable, pat.cacheable, patStr)\n\t\t}\n\t}\n\ttest(false, \"foo !bar\", \"foo !bar\", true)\n\ttest(false, \"foo | bar !baz\", \"foo | bar !baz\", true)\n\ttest(true, \"foo  bar  baz\", \"foo\\tbar\\tbaz\", true)\n\ttest(true, \"foo !bar\", \"foo\", false)\n\ttest(true, \"foo !bar   baz\", \"foo\\tbaz\", false)\n\ttest(true, \"foo | bar baz\", \"baz\", false)\n\ttest(true, \"foo | bar | baz\", \"\", false)\n\ttest(true, \"foo | bar !baz\", \"\", false)\n\ttest(true, \"| | foo\", \"\", false)\n\ttest(true, \"| | | foo\", \"foo\", false)\n}\n\nfunc TestCacheable(t *testing.T) {\n\ttest := func(fuzzy bool, str string, expected string, cacheable bool) {\n\t\tpat := buildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))\n\t\tif pat.CacheKey() != expected {\n\t\t\tt.Errorf(\"Expected: %s, actual: %s\", expected, pat.CacheKey())\n\t\t}\n\t\tif cacheable != pat.cacheable {\n\t\t\tt.Errorf(\"Invalid Pattern.cacheable for \\\"%s\\\": %v (expected: %v)\", str, pat.cacheable, cacheable)\n\t\t}\n\t}\n\ttest(true, \"foo bar\", \"foo\\tbar\", true)\n\ttest(true, \"foo 'bar\", \"foo\\tbar\", false)\n\ttest(true, \"foo !bar\", \"foo\", false)\n\n\ttest(false, \"foo bar\", \"foo\\tbar\", true)\n\ttest(false, \"foo 'bar\", \"foo\", false)\n\ttest(false, \"foo '\", \"foo\", true)\n\ttest(false, \"foo 'bar\", \"foo\", false)\n\ttest(false, \"foo !bar\", \"foo\", false)\n}\n\nfunc buildChunks(numChunks int) []*Chunk {\n\tchunks := make([]*Chunk, numChunks)\n\twords := []string{\n\t\t\"src/main/java/com/example/service/UserService.java\",\n\t\t\"src/test/java/com/example/service/UserServiceTest.java\",\n\t\t\"docs/api/reference/endpoints.md\",\n\t\t\"lib/internal/utils/string_helper.go\",\n\t\t\"pkg/server/http/handler/auth.go\",\n\t\t\"build/output/release/app.exe\",\n\t\t\"config/production/database.yml\",\n\t\t\"scripts/deploy/kubernetes/setup.sh\",\n\t\t\"vendor/github.com/junegunn/fzf/src/core.go\",\n\t\t\"node_modules/.cache/babel/transform.js\",\n\t}\n\tfor ci := range numChunks {\n\t\tchunks[ci] = &Chunk{count: chunkSize}\n\t\tfor i := range chunkSize {\n\t\t\ttext := words[(ci*chunkSize+i)%len(words)]\n\t\t\tchunks[ci].items[i] = Item{text: util.ToChars([]byte(text))}\n\t\t\tchunks[ci].items[i].text.Index = int32(ci*chunkSize + i)\n\t\t}\n\t}\n\treturn chunks\n}\n\nfunc buildPatternWith(cache *ChunkCache, runes []rune) *Pattern {\n\treturn BuildPattern(cache, make(map[string]*Pattern),\n\t\ttrue, algo.FuzzyMatchV2, true, CaseSmart, false, true,\n\t\tfalse, true, []Range{}, Delimiter{}, revision{}, runes, nil, 0)\n}\n\nfunc TestBitmapCacheBenefit(t *testing.T) {\n\tnumChunks := 100\n\tchunks := buildChunks(numChunks)\n\tqueries := []string{\"s\", \"se\", \"ser\", \"serv\", \"servi\"}\n\n\t// 1. Run all queries with shared cache (simulates incremental typing)\n\tcache := NewChunkCache()\n\tfor _, q := range queries {\n\t\tpat := buildPatternWith(cache, []rune(q))\n\t\tfor _, chunk := range chunks {\n\t\t\tpat.Match(chunk, slab)\n\t\t}\n\t}\n\n\t// 2. GC and measure memory with cache populated\n\truntime.GC()\n\truntime.GC()\n\tvar memWith runtime.MemStats\n\truntime.ReadMemStats(&memWith)\n\n\t// 3. Clear cache, GC, measure again\n\tcache.Clear()\n\truntime.GC()\n\truntime.GC()\n\tvar memWithout runtime.MemStats\n\truntime.ReadMemStats(&memWithout)\n\n\tcacheMem := int64(memWith.Alloc) - int64(memWithout.Alloc)\n\tt.Logf(\"Chunks: %d, Queries: %d\", numChunks, len(queries))\n\tt.Logf(\"Cache memory: %d bytes (%.1f KB)\", cacheMem, float64(cacheMem)/1024)\n\tt.Logf(\"Per-chunk-per-query: %.0f bytes\", float64(cacheMem)/float64(numChunks*len(queries)))\n\n\t// 4. Verify correctness: cached vs uncached produce same results\n\tcache2 := NewChunkCache()\n\tfor _, q := range queries {\n\t\tpat := buildPatternWith(cache2, []rune(q))\n\t\tfor _, chunk := range chunks {\n\t\t\tpat.Match(chunk, slab)\n\t\t}\n\t}\n\tfor _, q := range queries {\n\t\tpatCached := buildPatternWith(cache2, []rune(q))\n\t\tpatFresh := buildPatternWith(NewChunkCache(), []rune(q))\n\t\tvar countCached, countFresh int\n\t\tfor _, chunk := range chunks {\n\t\t\tcountCached += len(patCached.Match(chunk, slab))\n\t\t\tcountFresh += len(patFresh.Match(chunk, slab))\n\t\t}\n\t\tif countCached != countFresh {\n\t\t\tt.Errorf(\"query=%q: cached=%d, fresh=%d\", q, countCached, countFresh)\n\t\t}\n\t\tt.Logf(\"query=%q: matches=%d\", q, countCached)\n\t}\n}\n\nfunc BenchmarkWithCache(b *testing.B) {\n\tnumChunks := 100\n\tchunks := buildChunks(numChunks)\n\tqueries := []string{\"s\", \"se\", \"ser\", \"serv\", \"servi\"}\n\n\tb.Run(\"cached\", func(b *testing.B) {\n\t\tfor range b.N {\n\t\t\tcache := NewChunkCache()\n\t\t\tfor _, q := range queries {\n\t\t\t\tpat := buildPatternWith(cache, []rune(q))\n\t\t\t\tfor _, chunk := range chunks {\n\t\t\t\t\tpat.Match(chunk, slab)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n\n\tb.Run(\"uncached\", func(b *testing.B) {\n\t\tfor range b.N {\n\t\t\tfor _, q := range queries {\n\t\t\t\tcache := NewChunkCache()\n\t\t\t\tpat := buildPatternWith(cache, []rune(q))\n\t\t\t\tfor _, chunk := range chunks {\n\t\t\t\t\tpat.Match(chunk, slab)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "src/protector/protector.go",
    "content": "//go:build !openbsd\n\npackage protector\n\n// Protect calls OS specific protections like pledge on OpenBSD\nfunc Protect() {}\n"
  },
  {
    "path": "src/protector/protector_openbsd.go",
    "content": "//go:build openbsd\n\npackage protector\n\nimport \"golang.org/x/sys/unix\"\n\n// Protect calls OS specific protections like pledge on OpenBSD\nfunc Protect() {\n\tunix.PledgePromises(\"stdio cpath dpath wpath rpath inet fattr unix tty proc exec\")\n}\n"
  },
  {
    "path": "src/proxy.go",
    "content": "package fzf\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nconst becomeSuffix = \".become\"\n\nfunc escapeSingleQuote(str string) string {\n\treturn \"'\" + strings.ReplaceAll(str, \"'\", \"'\\\\''\") + \"'\"\n}\n\nfunc fifo(name string) (string, error) {\n\tns := time.Now().UnixNano()\n\toutput := filepath.Join(os.TempDir(), fmt.Sprintf(\"fzf-%s-%d\", name, ns))\n\toutput, err := mkfifo(output, 0600)\n\tif err != nil {\n\t\treturn output, err\n\t}\n\treturn output, nil\n}\n\nfunc runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) {\n\toutput, err := fifo(\"proxy-output\")\n\tif err != nil {\n\t\treturn ExitError, err\n\t}\n\tdefer os.Remove(output)\n\n\t// Take the output\n\tgo func() {\n\t\twithOutputPipe(output, func(outputFile io.ReadCloser) {\n\t\t\tif opts.Output == nil {\n\t\t\t\tio.Copy(os.Stdout, outputFile)\n\t\t\t} else {\n\t\t\t\treader := bufio.NewReader(outputFile)\n\t\t\t\tsep := opts.PrintSep[0]\n\t\t\t\tfor {\n\t\t\t\t\titem, err := reader.ReadString(sep)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\topts.Output <- item\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}()\n\n\tvar command, input string\n\tcommandPrefix += ` --no-force-tty-in --proxy-script \"$0\"`\n\tif opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {\n\t\tcommand = fmt.Sprintf(`%s > %q`, commandPrefix, output)\n\t} else {\n\t\tinput, err = fifo(\"proxy-input\")\n\t\tif err != nil {\n\t\t\treturn ExitError, err\n\t\t}\n\t\tdefer os.Remove(input)\n\n\t\tgo func() {\n\t\t\twithInputPipe(input, func(inputFile io.WriteCloser) {\n\t\t\t\tif opts.Input == nil {\n\t\t\t\t\tio.Copy(inputFile, os.Stdin)\n\t\t\t\t} else {\n\t\t\t\t\tfor item := range opts.Input {\n\t\t\t\t\t\tfmt.Fprint(inputFile, item+opts.PrintSep)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}()\n\n\t\tif withExports {\n\t\t\tcommand = fmt.Sprintf(`%s < %q > %q`, commandPrefix, input, output)\n\t\t} else {\n\t\t\t// For mintty: cannot directly read named pipe from Go code\n\t\t\tcommand = fmt.Sprintf(`command cat %q | %s > %q`, input, commandPrefix, output)\n\t\t}\n\t}\n\n\t// Write the command to a temporary file and run it with sh to ensure POSIX compliance.\n\tvar exports []string\n\tneedBash := false\n\tif withExports {\n\t\t// Nullify FZF_DEFAULT_* variables as tmux popup may inject them even when undefined.\n\t\texports = []string{\"FZF_DEFAULT_COMMAND=\", \"FZF_DEFAULT_OPTS=\", \"FZF_DEFAULT_OPTS_FILE=\"}\n\t\tvalidIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)\n\t\tfor _, pairStr := range os.Environ() {\n\t\t\tpair := strings.SplitN(pairStr, \"=\", 2)\n\t\t\tif validIdentifier.MatchString(pair[0]) {\n\t\t\t\texports = append(exports, fmt.Sprintf(\"export %s=%s\", pair[0], escapeSingleQuote(pair[1])))\n\t\t\t} else if strings.HasPrefix(pair[0], \"BASH_FUNC_\") && strings.HasSuffix(pair[0], \"%%\") {\n\t\t\t\tname := pair[0][10 : len(pair[0])-2]\n\t\t\t\texports = append(exports, name+pair[1])\n\t\t\t\texports = append(exports, \"export -f \"+name)\n\t\t\t\tneedBash = true\n\t\t\t}\n\t\t}\n\t}\n\ttemp := WriteTemporaryFile(append(exports, command), \"\\n\")\n\tdefer os.Remove(temp)\n\n\tcmd, err := cmdBuilder(temp, needBash)\n\tif err != nil {\n\t\treturn ExitError, err\n\t}\n\tcmd.Stderr = os.Stderr\n\tintChan := make(chan os.Signal, 1)\n\tdefer close(intChan)\n\tgo func() {\n\t\tif sig, valid := <-intChan; valid {\n\t\t\tcmd.Process.Signal(sig)\n\t\t}\n\t}()\n\tsignal.Notify(intChan, os.Interrupt)\n\tif err := cmd.Run(); err != nil {\n\t\tif exitError, ok := err.(*exec.ExitError); ok {\n\t\t\tcode := exitError.ExitCode()\n\t\t\tif code == ExitBecome {\n\t\t\t\tbecomeFile := temp + becomeSuffix\n\t\t\t\tdata, err := os.ReadFile(becomeFile)\n\t\t\t\tos.Remove(becomeFile)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn ExitError, err\n\t\t\t\t}\n\t\t\t\telems := strings.Split(string(data), \"\\x00\")\n\t\t\t\tif len(elems) < 1 {\n\t\t\t\t\treturn ExitError, errors.New(\"invalid become command\")\n\t\t\t\t}\n\t\t\t\tcommand := elems[0]\n\t\t\t\tenv := []string{}\n\t\t\t\tif len(elems) > 1 {\n\t\t\t\t\tenv = elems[1:]\n\t\t\t\t}\n\t\t\t\texecutor := util.NewExecutor(opts.WithShell)\n\t\t\t\tttyin, err := tui.TtyIn(opts.TtyDefault)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn ExitError, err\n\t\t\t\t}\n\t\t\t\tos.Remove(temp)\n\t\t\t\tos.Remove(input)\n\t\t\t\tos.Remove(output)\n\t\t\t\texecutor.Become(ttyin, env, command)\n\t\t\t}\n\t\t\treturn code, err\n\t\t}\n\t}\n\n\treturn ExitOk, nil\n}\n"
  },
  {
    "path": "src/proxy_unix.go",
    "content": "//go:build !windows\n\npackage fzf\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc sh(bash bool) (string, error) {\n\tif bash {\n\t\treturn \"bash\", nil\n\t}\n\treturn \"sh\", nil\n}\n\nfunc mkfifo(path string, mode uint32) (string, error) {\n\treturn path, unix.Mkfifo(path, mode)\n}\n\nfunc withOutputPipe(output string, task func(io.ReadCloser)) error {\n\toutputFile, err := os.OpenFile(output, os.O_RDONLY, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttask(outputFile)\n\toutputFile.Close()\n\treturn nil\n}\n\nfunc withInputPipe(input string, task func(io.WriteCloser)) error {\n\tinputFile, err := os.OpenFile(input, os.O_WRONLY, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttask(inputFile)\n\tinputFile.Close()\n\treturn nil\n}\n"
  },
  {
    "path": "src/proxy_windows.go",
    "content": "//go:build windows\n\npackage fzf\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n)\n\nvar shPath atomic.Value\n\nfunc sh(bash bool) (string, error) {\n\tif cached := shPath.Load(); cached != nil {\n\t\treturn cached.(string), nil\n\t}\n\n\tname := \"sh\"\n\tif bash {\n\t\tname = \"bash\"\n\t}\n\tcmd := exec.Command(\"cygpath\", \"-w\", \"/usr/bin/\"+name)\n\tbytes, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsh := strings.TrimSpace(string(bytes))\n\tshPath.Store(sh)\n\treturn sh, nil\n}\n\nfunc mkfifo(path string, mode uint32) (string, error) {\n\tm := strconv.FormatUint(uint64(mode), 8)\n\tsh, err := sh(false)\n\tif err != nil {\n\t\treturn path, err\n\t}\n\tcmd := exec.Command(sh, \"-c\", fmt.Sprintf(`command mkfifo -m %s %q`, m, path))\n\tif err := cmd.Run(); err != nil {\n\t\treturn path, err\n\t}\n\treturn path + \".lnk\", nil\n}\n\nfunc withOutputPipe(output string, task func(io.ReadCloser)) error {\n\tsh, err := sh(false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd := exec.Command(sh, \"-c\", fmt.Sprintf(`command cat %q`, output))\n\toutputFile, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := cmd.Start(); err != nil {\n\t\treturn err\n\t}\n\n\ttask(outputFile)\n\tcmd.Wait()\n\treturn nil\n}\n\nfunc withInputPipe(input string, task func(io.WriteCloser)) error {\n\tsh, err := sh(false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd := exec.Command(sh, \"-c\", fmt.Sprintf(`command cat - > %q`, input))\n\tinputFile, err := cmd.StdinPipe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := cmd.Start(); err != nil {\n\t\treturn err\n\t}\n\ttask(inputFile)\n\tinputFile.Close()\n\tcmd.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "src/reader.go",
    "content": "package fzf\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/charlievieth/fastwalk\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n// Reader reads from command or standard input\ntype Reader struct {\n\tpusher   func([]byte) bool\n\texecutor *util.Executor\n\teventBox *util.EventBox\n\tdelimNil bool\n\tevent    int32\n\tfinChan  chan bool\n\tmutex    sync.Mutex\n\tkilled   bool\n\ttermFunc func()\n\tcommand  *string\n\twait     bool\n}\n\n// NewReader returns new Reader object\nfunc NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {\n\treturn &Reader{\n\t\tpusher,\n\t\texecutor,\n\t\teventBox,\n\t\tdelimNil,\n\t\tint32(EvtReady),\n\t\tmake(chan bool, 1),\n\t\tsync.Mutex{},\n\t\tfalse,\n\t\tfunc() { os.Stdin.Close() },\n\t\tnil,\n\t\twait}\n}\n\nfunc (r *Reader) startEventPoller() {\n\tgo func() {\n\t\tptr := &r.event\n\t\tpollInterval := readerPollIntervalMin\n\t\tfor {\n\t\t\tif atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {\n\t\t\t\tr.eventBox.Set(EvtReadNew, (*string)(nil))\n\t\t\t\tpollInterval = readerPollIntervalMin\n\t\t\t} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {\n\t\t\t\tif r.wait {\n\t\t\t\t\tr.finChan <- true\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tpollInterval += readerPollIntervalStep\n\t\t\t\tif pollInterval > readerPollIntervalMax {\n\t\t\t\t\tpollInterval = readerPollIntervalMax\n\t\t\t\t}\n\t\t\t}\n\t\t\ttime.Sleep(pollInterval)\n\t\t}\n\t}()\n}\n\nfunc (r *Reader) fin(success bool) {\n\tatomic.StoreInt32(&r.event, int32(EvtReadFin))\n\tif r.wait {\n\t\t<-r.finChan\n\t}\n\n\tr.mutex.Lock()\n\tret := r.command\n\tif success || r.killed {\n\t\tret = nil\n\t}\n\tr.mutex.Unlock()\n\n\tr.eventBox.Set(EvtReadFin, ret)\n}\n\nfunc (r *Reader) terminate() {\n\tr.mutex.Lock()\n\tr.killed = true\n\tif r.termFunc != nil {\n\t\tr.termFunc()\n\t\tr.termFunc = nil\n\t}\n\tr.mutex.Unlock()\n}\n\nfunc (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) {\n\tr.event = int32(EvtReady)\n\tr.startEventPoller()\n\tsuccess := r.readFromCommand(command.command, environ, func() {\n\t\treadyChan <- true\n\t})\n\tr.fin(success)\n\tremoveFiles(command.tempFiles)\n}\n\nfunc (r *Reader) readChannel(inputChan chan string) bool {\n\tfor {\n\t\titem, more := <-inputChan\n\t\tif !more {\n\t\t\tbreak\n\t\t}\n\t\tif r.pusher([]byte(item)) {\n\t\t\tatomic.StoreInt32(&r.event, int32(EvtReadNew))\n\t\t}\n\t}\n\treturn true\n}\n\n// ReadSource reads data from the default command or from standard input\nfunc (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) {\n\tr.startEventPoller()\n\tvar success bool\n\tsignalReady := func() {\n\t\tif readyChan != nil {\n\t\t\treadyChan <- true\n\t\t}\n\t}\n\tif inputChan != nil {\n\t\tsignalReady()\n\t\tsuccess = r.readChannel(inputChan)\n\t} else if len(initCmd) > 0 {\n\t\tsuccess = r.readFromCommand(initCmd, initEnv, signalReady)\n\t} else if util.IsTty(os.Stdin) {\n\t\tcmd := os.Getenv(\"FZF_DEFAULT_COMMAND\")\n\t\tif len(cmd) == 0 {\n\t\t\tsignalReady()\n\t\t\tsuccess = r.readFiles(roots, opts, ignores)\n\t\t} else {\n\t\t\tsuccess = r.readFromCommand(cmd, initEnv, signalReady)\n\t\t}\n\t} else {\n\t\tsignalReady()\n\t\tsuccess = r.readFromStdin()\n\t}\n\tr.fin(success)\n}\n\nfunc (r *Reader) feed(src io.Reader) {\n\t/*\n\t\treaderSlabSize, ae := strconv.Atoi(os.Getenv(\"SLAB_KB\"))\n\t\tif ae != nil {\n\t\t\treaderSlabSize = 128 * 1024\n\t\t} else {\n\t\t\treaderSlabSize *= 1024\n\t\t}\n\t\treaderBufferSize, be := strconv.Atoi(os.Getenv(\"BUF_KB\"))\n\t\tif be != nil {\n\t\t\treaderBufferSize = 64 * 1024\n\t\t} else {\n\t\t\treaderBufferSize *= 1024\n\t\t}\n\t*/\n\n\tdelim := byte('\\n')\n\ttrimCR := util.IsWindows()\n\tif r.delimNil {\n\t\tdelim = '\\000'\n\t\ttrimCR = false\n\t}\n\n\tslab := make([]byte, readerSlabSize)\n\tleftover := []byte{}\n\tvar err error\n\tfor {\n\t\tn := 0\n\t\tscope := slab[:min(len(slab), readerBufferSize)]\n\t\tfor range 100 {\n\t\t\tn, err = src.Read(scope)\n\t\t\tif n > 0 || err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// We're not making any progress after 100 tries. Stop.\n\t\tif n == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tbuf := slab[:n]\n\t\tslab = slab[n:]\n\n\t\tfor len(buf) > 0 {\n\t\t\tif i := bytes.IndexByte(buf, delim); i >= 0 {\n\t\t\t\t// Found the delimiter\n\t\t\t\tslice := buf[:i+1]\n\t\t\t\tbuf = buf[i+1:]\n\t\t\t\tif trimCR && len(slice) >= 2 && slice[len(slice)-2] == byte('\\r') {\n\t\t\t\t\tslice = slice[:len(slice)-2]\n\t\t\t\t} else {\n\t\t\t\t\tslice = slice[:len(slice)-1]\n\t\t\t\t}\n\t\t\t\tif len(leftover) > 0 {\n\t\t\t\t\tslice = append(leftover, slice...)\n\t\t\t\t\tleftover = []byte{}\n\t\t\t\t}\n\t\t\t\tif (err == nil || len(slice) > 0) && r.pusher(slice) {\n\t\t\t\t\tatomic.StoreInt32(&r.event, int32(EvtReadNew))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Could not find the delimiter in the buffer\n\t\t\t\t//   NOTE: We can further optimize this by keeping track of the cursor\n\t\t\t\t//   position in the slab so that a straddling item that doesn't go\n\t\t\t\t//   beyond the boundary of a slab doesn't need to be copied to\n\t\t\t\t//   another buffer. However, the performance gain is negligible in\n\t\t\t\t//   practice (< 0.1%) and is not\n\t\t\t\t//   worth the added complexity.\n\t\t\t\tleftover = append(leftover, buf...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif err == io.EOF {\n\t\t\tleftover = append(leftover, buf...)\n\t\t\tbreak\n\t\t}\n\n\t\tif len(slab) == 0 {\n\t\t\tslab = make([]byte, readerSlabSize)\n\t\t}\n\t}\n\tif len(leftover) > 0 && r.pusher(leftover) {\n\t\tatomic.StoreInt32(&r.event, int32(EvtReadNew))\n\t}\n}\n\nfunc (r *Reader) readFromStdin() bool {\n\tr.feed(os.Stdin)\n\treturn true\n}\n\nfunc isSymlinkToDir(path string, de os.DirEntry) bool {\n\tif de.Type()&fs.ModeSymlink == 0 {\n\t\treturn false\n\t}\n\tif s, err := os.Stat(path); err == nil {\n\t\treturn s.IsDir()\n\t}\n\treturn false\n}\n\nfunc trimPath(path string) string {\n\tbytes := stringBytes(path)\n\n\tfor len(bytes) > 1 && bytes[0] == '.' && (bytes[1] == '/' || bytes[1] == '\\\\') {\n\t\tbytes = bytes[2:]\n\t}\n\n\tif len(bytes) == 0 {\n\t\treturn \".\"\n\t}\n\n\treturn byteString(bytes)\n}\n\nfunc (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool {\n\tconf := fastwalk.Config{\n\t\tFollow: opts.follow,\n\t\t// Use forward slashes when running a Windows binary under WSL or MSYS\n\t\tToSlash: fastwalk.DefaultToSlash(),\n\t\tSort:    fastwalk.SortFilesFirst,\n\t}\n\n\t// When following symlinks, precompute the absolute real paths of walker\n\t// roots so we can skip symlinks that point to an ancestor. fastwalk's\n\t// built-in loop detection (shouldTraverse) catches loops on the second\n\t// pass, but a single pass through a symlink like z: -> / already\n\t// traverses the entire root filesystem, causing severe resource\n\t// exhaustion. Skipping ancestor symlinks prevents this entirely.\n\tvar absRoots []string\n\tif opts.follow {\n\t\tfor _, root := range roots {\n\t\t\tif real, err := filepath.EvalSymlinks(root); err == nil {\n\t\t\t\tif abs, err := filepath.Abs(real); err == nil {\n\t\t\t\t\tabsRoots = append(absRoots, filepath.Clean(abs))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tignoresBase := []string{}\n\tignoresFull := []string{}\n\tignoresSuffix := []string{}\n\tsep := string(os.PathSeparator)\n\tif _, ok := os.LookupEnv(\"MSYSTEM\"); ok {\n\t\tsep = \"/\"\n\t}\n\tfor _, ignore := range ignores {\n\t\tif strings.ContainsRune(ignore, os.PathSeparator) {\n\t\t\tif strings.HasPrefix(ignore, sep) {\n\t\t\t\tignoresSuffix = append(ignoresSuffix, ignore)\n\t\t\t} else {\n\t\t\t\t// 'foo/bar' should match\n\t\t\t\t// * 'foo/bar'\n\t\t\t\t// * 'baz/foo/bar'\n\t\t\t\t// * but NOT 'bazfoo/bar'\n\t\t\t\tignoresFull = append(ignoresFull, ignore)\n\t\t\t\tignoresSuffix = append(ignoresSuffix, sep+ignore)\n\t\t\t}\n\t\t} else {\n\t\t\tignoresBase = append(ignoresBase, ignore)\n\t\t}\n\t}\n\tfn := func(path string, de os.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tpath = trimPath(path)\n\t\tif path != \".\" {\n\t\t\tisDirSymlink := isSymlinkToDir(path, de)\n\t\t\tif isDirSymlink && !opts.follow {\n\t\t\t\treturn filepath.SkipDir\n\t\t\t}\n\t\t\t// Skip symlinks whose target is an ancestor of (or equal to)\n\t\t\t// any walker root. Following such symlinks would traverse a\n\t\t\t// superset of the tree we're already walking.\n\t\t\tif isDirSymlink && len(absRoots) > 0 {\n\t\t\t\tif target, err := filepath.EvalSymlinks(path); err == nil {\n\t\t\t\t\tif abs, err := filepath.Abs(target); err == nil {\n\t\t\t\t\t\tabs = filepath.Clean(abs)\n\t\t\t\t\t\tif abs == string(os.PathSeparator) {\n\t\t\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, absRoot := range absRoots {\n\t\t\t\t\t\t\tif absRoot == abs || strings.HasPrefix(absRoot, abs+string(os.PathSeparator)) {\n\t\t\t\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tisDir := de.IsDir() || isDirSymlink\n\t\t\tif isDir {\n\t\t\t\tbase := filepath.Base(path)\n\t\t\t\tif !opts.hidden && base[0] == '.' && base != \"..\" {\n\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t}\n\t\t\t\tif slices.Contains(ignoresBase, base) {\n\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t}\n\t\t\t\tif slices.Contains(ignoresFull, path) {\n\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t}\n\t\t\t\tfor _, ignore := range ignoresSuffix {\n\t\t\t\t\tif strings.HasSuffix(path, ignore) {\n\t\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif path != sep {\n\t\t\t\t\tpath += sep\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {\n\t\t\t\tatomic.StoreInt32(&r.event, int32(EvtReadNew))\n\t\t\t}\n\t\t}\n\t\tr.mutex.Lock()\n\t\tdefer r.mutex.Unlock()\n\t\tif r.killed {\n\t\t\treturn context.Canceled\n\t\t}\n\t\treturn nil\n\t}\n\tnoerr := true\n\tfor _, root := range roots {\n\t\tnoerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil)\n\t}\n\treturn noerr\n}\n\nfunc (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {\n\tr.mutex.Lock()\n\n\tr.killed = false\n\tr.termFunc = nil\n\tr.command = &command\n\texec := r.executor.ExecCommand(command, true)\n\tif environ != nil {\n\t\texec.Env = environ\n\t}\n\texecOut, err := exec.StdoutPipe()\n\tif err != nil || exec.Start() != nil {\n\t\tsignalReady()\n\t\tr.mutex.Unlock()\n\t\treturn false\n\t}\n\n\t// Function to call to terminate the running command\n\tr.termFunc = func() {\n\t\texecOut.Close()\n\t\tutil.KillCommand(exec)\n\t}\n\n\tsignalReady()\n\tr.mutex.Unlock()\n\n\tr.feed(execOut)\n\treturn exec.Wait() == nil\n}\n"
  },
  {
    "path": "src/reader_test.go",
    "content": "package fzf\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc TestReadFromCommand(t *testing.T) {\n\tstrs := []string{}\n\teb := util.NewEventBox()\n\texec := util.NewExecutor(\"\")\n\treader := NewReader(\n\t\tfunc(s []byte) bool { strs = append(strs, string(s)); return true },\n\t\teb, exec, false, true)\n\n\treader.startEventPoller()\n\n\t// Check EventBox\n\tif eb.Peek(EvtReadNew) {\n\t\tt.Error(\"EvtReadNew should not be set yet\")\n\t}\n\n\t// Normal command\n\tcounter := 0\n\tready := func() {\n\t\tcounter++\n\t}\n\treader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))\n\tif len(strs) != 2 || strs[0] != \"abc\" || strs[1] != \"def\" || counter != 1 {\n\t\tt.Errorf(\"%s\", strs)\n\t}\n\n\t// Check EventBox again\n\teb.WaitFor(EvtReadFin)\n\n\t// Wait should return immediately\n\teb.Wait(func(events *util.Events) {\n\t\tevents.Clear()\n\t})\n\n\t// EventBox is cleared\n\tif eb.Peek(EvtReadNew) {\n\t\tt.Error(\"EvtReadNew should not be set yet\")\n\t}\n\n\t// Make sure that event poller is finished\n\ttime.Sleep(readerPollIntervalMax)\n\n\t// Restart event poller\n\treader.startEventPoller()\n\n\t// Failing command\n\treader.fin(reader.readFromCommand(`no-such-command`, nil, ready))\n\tstrs = []string{}\n\tif len(strs) > 0 || counter != 2 {\n\t\tt.Errorf(\"%s\", strs)\n\t}\n\n\t// Check EventBox again\n\tif eb.Peek(EvtReadNew) {\n\t\tt.Error(\"Command failed. EvtReadNew should not be set\")\n\t}\n\tif !eb.Peek(EvtReadFin) {\n\t\tt.Error(\"EvtReadFin should be set\")\n\t}\n}\n"
  },
  {
    "path": "src/result.go",
    "content": "package fzf\n\nimport (\n\t\"math\"\n\t\"sort\"\n\t\"unicode\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n// Offset holds two 32-bit integers denoting the offsets of a matched substring\ntype Offset [2]int32\n\ntype colorOffset struct {\n\toffset [2]int32\n\tcolor  tui.ColorPair\n\tmatch  bool\n\turl    *url\n}\n\nfunc (co colorOffset) IsFullBgMarker(at int32) bool {\n\treturn at == co.offset[0] && at == co.offset[1] && co.color.Attr()&tui.FullBg > 0\n}\n\ntype Result struct {\n\titem   *Item\n\tpoints [4]uint16\n}\n\nfunc buildResult(item *Item, offsets []Offset, score int) Result {\n\tif len(offsets) > 1 {\n\t\tsort.Sort(ByOrder(offsets))\n\t}\n\n\tminBegin := math.MaxUint16\n\tminEnd := math.MaxUint16\n\tmaxEnd := 0\n\tvalidOffsetFound := false\n\tfor _, offset := range offsets {\n\t\tb, e := int(offset[0]), int(offset[1])\n\t\tif b < e {\n\t\t\tminBegin = min(b, minBegin)\n\t\t\tminEnd = min(e, minEnd)\n\t\t\tmaxEnd = max(e, maxEnd)\n\t\t\tvalidOffsetFound = true\n\t\t}\n\t}\n\n\treturn buildResultFromBounds(item, score, minBegin, minEnd, maxEnd, validOffsetFound)\n}\n\n// buildResultFromBounds builds a Result from pre-computed offset bounds.\nfunc buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int, validOffsetFound bool) Result {\n\tresult := Result{item: item}\n\tnumChars := item.text.Length()\n\n\tfor idx, criterion := range sortCriteria {\n\t\tval := uint16(math.MaxUint16)\n\t\tswitch criterion {\n\t\tcase byScore:\n\t\t\t// Higher is better\n\t\t\tval = math.MaxUint16 - util.AsUint16(score)\n\t\tcase byChunk:\n\t\t\tif validOffsetFound {\n\t\t\t\tb := minBegin\n\t\t\t\te := maxEnd\n\t\t\t\tfor ; b >= 1; b-- {\n\t\t\t\t\tif unicode.IsSpace(item.text.Get(b - 1)) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor ; e < numChars; e++ {\n\t\t\t\t\tif unicode.IsSpace(item.text.Get(e)) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tval = util.AsUint16(e - b)\n\t\t\t}\n\t\tcase byLength:\n\t\t\tval = item.TrimLength()\n\t\tcase byPathname:\n\t\t\tif validOffsetFound {\n\t\t\t\tlastDelim := -1\n\t\t\t\ts := item.text.ToString()\n\t\t\t\tfor i := len(s) - 1; i >= 0; i-- {\n\t\t\t\t\tif s[i] == '/' || s[i] == '\\\\' {\n\t\t\t\t\t\tlastDelim = i\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif lastDelim <= minBegin {\n\t\t\t\t\tval = util.AsUint16(minBegin - lastDelim)\n\t\t\t\t}\n\t\t\t}\n\t\tcase byBegin, byEnd:\n\t\t\tif validOffsetFound {\n\t\t\t\twhitePrefixLen := 0\n\t\t\t\tfor idx := range numChars {\n\t\t\t\t\tr := item.text.Get(idx)\n\t\t\t\t\twhitePrefixLen = idx\n\t\t\t\t\tif idx == minBegin || !unicode.IsSpace(r) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif criterion == byBegin {\n\t\t\t\t\tval = util.AsUint16(minEnd - whitePrefixLen)\n\t\t\t\t} else {\n\t\t\t\t\tval = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/(int(item.TrimLength())+1))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresult.points[3-idx] = val\n\t}\n\n\treturn result\n}\n\n// Sort criteria to use. Never changes once fzf is started.\nvar sortCriteria []criterion\n\n// Index returns ordinal index of the Item\nfunc (result *Result) Index() int32 {\n\treturn result.item.Index()\n}\n\nfunc minRank() Result {\n\treturn Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}\n}\n\nfunc (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, nthOverlay tui.Attr, hidden bool) []colorOffset {\n\titemColors := result.item.Colors()\n\n\t// No ANSI codes\n\tif len(itemColors) == 0 && len(nthOffsets) == 0 {\n\t\toffsets := make([]colorOffset, len(matchOffsets))\n\t\tfor i, off := range matchOffsets {\n\t\t\toffsets[i] = colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true}\n\t\t}\n\t\treturn offsets\n\t}\n\n\t// Find max column\n\tvar maxCol int32\n\tfor _, off := range append(matchOffsets, nthOffsets...) {\n\t\tif off[1] > maxCol {\n\t\t\tmaxCol = off[1]\n\t\t}\n\t}\n\tfor _, ansi := range itemColors {\n\t\tif ansi.offset[1] > maxCol {\n\t\t\tmaxCol = ansi.offset[1]\n\t\t}\n\t}\n\n\ttype cellInfo struct {\n\t\tindex int\n\t\tcolor bool\n\t\tmatch bool\n\t\tnth   bool\n\t\tfbg   tui.Color\n\t}\n\n\tcols := make([]cellInfo, maxCol+1)\n\tfor idx := range cols {\n\t\tcols[idx].fbg = -1\n\t}\n\tfor colorIndex, ansi := range itemColors {\n\t\tif ansi.offset[0] == ansi.offset[1] && ansi.color.attr&tui.FullBg > 0 {\n\t\t\tcols[ansi.offset[0]].fbg = ansi.color.lbg\n\t\t} else {\n\t\t\tfor i := ansi.offset[0]; i < ansi.offset[1]; i++ {\n\t\t\t\tcols[i] = cellInfo{colorIndex, true, false, false, cols[i].fbg}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, off := range matchOffsets {\n\t\tfor i := off[0]; i < off[1]; i++ {\n\t\t\tcols[i].match = true\n\t\t}\n\t}\n\n\tfor _, off := range nthOffsets {\n\t\tfor i := off[0]; i < off[1]; i++ {\n\t\t\tcols[i].nth = true\n\t\t}\n\t}\n\n\t// sort.Sort(ByOrder(offsets))\n\n\t// Merge offsets\n\t// ------------  ----  --  ----\n\t//   ++++++++      ++++++++++\n\t// --++++++++--  --++++++++++---\n\tcurr := cellInfo{0, false, false, false, -1}\n\tstart := 0\n\tansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {\n\t\tif !theme.Colored {\n\t\t\treturn tui.NewColorPair(-1, -1, ansi.color.attr).MergeAttr(base)\n\t\t}\n\t\t// fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular\n\t\tif base.ShouldStripColors() {\n\t\t\treturn base\n\t\t}\n\t\tfg := ansi.color.fg\n\t\tbg := ansi.color.bg\n\t\tif fg == -1 {\n\t\t\tfg = colBase.Fg()\n\t\t}\n\t\tif bg == -1 {\n\t\t\tbg = colBase.Bg()\n\t\t}\n\t\treturn tui.NewColorPair(fg, bg, ansi.color.attr).WithUl(ansi.color.ul).MergeAttr(base)\n\t}\n\tfgAttr := tui.ColNormal.Attr()\n\tnthAttrFinal := fgAttr.Merge(attrNth).Merge(nthOverlay)\n\tnthBase := colBase.WithNewAttr(nthAttrFinal)\n\n\tvar colors []colorOffset\n\tadd := func(idx int) {\n\t\tif curr.fbg >= 0 {\n\t\t\tcolors = append(colors, colorOffset{\n\t\t\t\toffset: [2]int32{int32(start), int32(start)},\n\t\t\t\tcolor:  tui.NewColorPair(-1, curr.fbg, tui.FullBg),\n\t\t\t\tmatch:  false,\n\t\t\t\turl:    nil})\n\t\t}\n\t\tif (curr.color || curr.nth || curr.match) && idx > start {\n\t\t\tif curr.match {\n\t\t\t\tvar color tui.ColorPair\n\t\t\t\tif curr.nth {\n\t\t\t\t\tcolor = nthBase.Merge(colMatch)\n\t\t\t\t} else {\n\t\t\t\t\tcolor = colBase.Merge(colMatch)\n\t\t\t\t}\n\t\t\t\tvar url *url\n\t\t\t\tif curr.color {\n\t\t\t\t\tansi := itemColors[curr.index]\n\t\t\t\t\turl = ansi.color.url\n\t\t\t\t\torigColor := ansiToColorPair(ansi, colMatch)\n\t\t\t\t\t// hl or hl+ only sets the foreground color, so colMatch is the\n\t\t\t\t\t// combination of either [hl and bg] or [hl+ and bg+].\n\t\t\t\t\t//\n\t\t\t\t\t// If the original text already has background color, and the\n\t\t\t\t\t// foreground color of colMatch is -1, we shouldn't only apply the\n\t\t\t\t\t// background color of colMatch.\n\t\t\t\t\t// e.g. echo -e \"\\x1b[32;7mfoo\\x1b[mbar\" | fzf --ansi --color bg+:1,hl+:-1:underline\n\t\t\t\t\t//      echo -e \"\\x1b[42mfoo\\x1b[mbar\" | fzf --ansi --color bg+:1,hl+:-1:underline\n\t\t\t\t\tif color.Fg().IsDefault() && origColor.HasBg() {\n\t\t\t\t\t\tcolor = origColor\n\t\t\t\t\t\tif curr.nth {\n\t\t\t\t\t\t\tcolor = color.WithAttr((attrNth &^ tui.AttrRegular).Merge(nthOverlay))\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcolor = origColor.MergeNonDefault(color)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcolors = append(colors, colorOffset{\n\t\t\t\t\toffset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})\n\t\t\t} else if curr.color {\n\t\t\t\tansi := itemColors[curr.index]\n\t\t\t\tbase := colBase\n\t\t\t\tif curr.nth {\n\t\t\t\t\tbase = nthBase\n\t\t\t\t}\n\t\t\t\tif hidden {\n\t\t\t\t\tbase = base.WithFg(theme.Nomatch)\n\t\t\t\t}\n\t\t\t\tcolor := ansiToColorPair(ansi, base)\n\t\t\t\tcolors = append(colors, colorOffset{\n\t\t\t\t\toffset: [2]int32{int32(start), int32(idx)},\n\t\t\t\t\tcolor:  color,\n\t\t\t\t\tmatch:  false,\n\t\t\t\t\turl:    ansi.color.url})\n\t\t\t} else {\n\t\t\t\tcolor := nthBase\n\t\t\t\tif hidden {\n\t\t\t\t\tcolor = color.WithFg(theme.Nomatch)\n\t\t\t\t}\n\t\t\t\tcolors = append(colors, colorOffset{\n\t\t\t\t\toffset: [2]int32{int32(start), int32(idx)},\n\t\t\t\t\tcolor:  color,\n\t\t\t\t\tmatch:  false,\n\t\t\t\t\turl:    nil})\n\t\t\t}\n\t\t}\n\t}\n\tfor idx, col := range cols {\n\t\tif col != curr {\n\t\t\tadd(idx)\n\t\t\tstart = idx\n\t\t\tcurr = col\n\t\t}\n\t}\n\tadd(int(maxCol))\n\treturn colors\n}\n\n// ByOrder is for sorting substring offsets\ntype ByOrder []Offset\n\nfunc (a ByOrder) Len() int {\n\treturn len(a)\n}\n\nfunc (a ByOrder) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n\nfunc (a ByOrder) Less(i, j int) bool {\n\tioff := a[i]\n\tjoff := a[j]\n\treturn (ioff[0] < joff[0]) || (ioff[0] == joff[0]) && (ioff[1] <= joff[1])\n}\n\n// ByRelevance is for sorting Items\ntype ByRelevance []Result\n\nfunc (a ByRelevance) Len() int {\n\treturn len(a)\n}\n\nfunc (a ByRelevance) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n\nfunc (a ByRelevance) Less(i, j int) bool {\n\treturn compareRanks(a[i], a[j], false)\n}\n\n// ByRelevanceTac is for sorting Items\ntype ByRelevanceTac []Result\n\nfunc (a ByRelevanceTac) Len() int {\n\treturn len(a)\n}\n\nfunc (a ByRelevanceTac) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n\nfunc (a ByRelevanceTac) Less(i, j int) bool {\n\treturn compareRanks(a[i], a[j], true)\n}\n\n// radixSortResults sorts Results by their points key using LSD radix sort.\n// O(n) time complexity vs O(n log n) for comparison sort.\n// The sort is stable, so equal-key items maintain original (item-index) order.\n// For tac mode, runs of equal keys are reversed after sorting.\nfunc radixSortResults(a []Result, tac bool, scratch []Result) []Result {\n\tn := len(a)\n\tif n < 128 {\n\t\tif tac {\n\t\t\tsort.Sort(ByRelevanceTac(a))\n\t\t} else {\n\t\t\tsort.Sort(ByRelevance(a))\n\t\t}\n\t\treturn scratch[:0]\n\t}\n\n\tif cap(scratch) < n {\n\t\tscratch = make([]Result, n)\n\t}\n\tbuf := scratch[:n]\n\tsrc, dst := a, buf\n\tscattered := 0\n\n\tfor pass := range 8 {\n\t\tshift := uint(pass) * 8\n\n\t\tvar count [256]int\n\t\tfor i := range src {\n\t\t\tcount[byte(sortKey(&src[i])>>shift)]++\n\t\t}\n\n\t\t// Skip if all items have the same byte value at this position\n\t\tif count[byte(sortKey(&src[0])>>shift)] == n {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar offset [256]int\n\t\tfor i := 1; i < 256; i++ {\n\t\t\toffset[i] = offset[i-1] + count[i-1]\n\t\t}\n\n\t\tfor i := range src {\n\t\t\tb := byte(sortKey(&src[i]) >> shift)\n\t\t\tdst[offset[b]] = src[i]\n\t\t\toffset[b]++\n\t\t}\n\n\t\tsrc, dst = dst, src\n\t\tscattered++\n\t}\n\n\t// If odd number of scatters, data is in buf, copy back to a\n\tif scattered%2 == 1 {\n\t\tcopy(a, src)\n\t}\n\n\t// Handle tac: reverse runs of equal keys so equal-key items\n\t// are in reverse item-index order\n\tif tac {\n\t\ti := 0\n\t\tfor i < n {\n\t\t\tki := sortKey(&a[i])\n\t\t\tj := i + 1\n\t\t\tfor j < n && sortKey(&a[j]) == ki {\n\t\t\t\tj++\n\t\t\t}\n\t\t\tif j-i > 1 {\n\t\t\t\tfor l, r := i, j-1; l < r; l, r = l+1, r-1 {\n\t\t\t\t\ta[l], a[r] = a[r], a[l]\n\t\t\t\t}\n\t\t\t}\n\t\t\ti = j\n\t\t}\n\t}\n\treturn scratch\n}\n"
  },
  {
    "path": "src/result_others.go",
    "content": "//go:build !386 && !amd64 && !arm64\n\npackage fzf\n\nfunc compareRanks(irank Result, jrank Result, tac bool) bool {\n\tfor idx := 3; idx >= 0; idx-- {\n\t\tleft := irank.points[idx]\n\t\tright := jrank.points[idx]\n\t\tif left < right {\n\t\t\treturn true\n\t\t} else if left > right {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn (irank.item.Index() <= jrank.item.Index()) != tac\n}\n\nfunc sortKey(r *Result) uint64 {\n\treturn uint64(r.points[0]) | uint64(r.points[1])<<16 | uint64(r.points[2])<<32 | uint64(r.points[3])<<48\n}\n"
  },
  {
    "path": "src/result_test.go",
    "content": "package fzf\n\nimport (\n\t\"math\"\n\t\"math/rand\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc withIndex(i *Item, index int) *Item {\n\t(*i).text.Index = int32(index)\n\treturn i\n}\n\nfunc TestOffsetSort(t *testing.T) {\n\toffsets := []Offset{\n\t\t{3, 5}, {2, 7},\n\t\t{1, 3}, {2, 9}}\n\tsort.Sort(ByOrder(offsets))\n\n\tif offsets[0][0] != 1 || offsets[0][1] != 3 ||\n\t\toffsets[1][0] != 2 || offsets[1][1] != 7 ||\n\t\toffsets[2][0] != 2 || offsets[2][1] != 9 ||\n\t\toffsets[3][0] != 3 || offsets[3][1] != 5 {\n\t\tt.Error(\"Invalid order:\", offsets)\n\t}\n}\n\nfunc TestRankComparison(t *testing.T) {\n\trank := func(vals ...uint16) Result {\n\t\treturn Result{\n\t\t\tpoints: [4]uint16{vals[0], vals[1], vals[2], vals[3]},\n\t\t\titem:   &Item{text: util.Chars{Index: int32(vals[4])}}}\n\t}\n\tif compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||\n\t\t!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||\n\t\t!compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), false) ||\n\t\t!compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {\n\t\tt.Error(\"Invalid order\")\n\t}\n\n\tif compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), true) ||\n\t\t!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||\n\t\t!compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), true) ||\n\t\t!compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {\n\t\tt.Error(\"Invalid order (tac)\")\n\t}\n}\n\n// Match length, string length, index\nfunc TestResultRank(t *testing.T) {\n\t// FIXME global\n\tsortCriteria = []criterion{byScore, byLength}\n\n\tstr := []rune(\"foo\")\n\titem1 := buildResult(\n\t\twithIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)\n\tif item1.points[3] != math.MaxUint16-2 || // Bonus\n\t\titem1.points[2] != 3 || // Length\n\t\titem1.points[1] != 0 || // Unused\n\t\titem1.points[0] != 0 || // Unused\n\t\titem1.item.Index() != 1 {\n\t\tt.Error(item1)\n\t}\n\t// Only differ in index\n\titem2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)\n\n\titems := []Result{item1, item2}\n\tsort.Sort(ByRelevance(items))\n\tif items[0] != item2 || items[1] != item1 {\n\t\tt.Error(items)\n\t}\n\n\titems = []Result{item2, item1, item1, item2}\n\tsort.Sort(ByRelevance(items))\n\tif items[0] != item2 || items[1] != item2 ||\n\t\titems[2] != item1 || items[3] != item1 {\n\t\tt.Error(items, item1, item1.item.Index(), item2, item2.item.Index())\n\t}\n\n\t// Sort by relevance\n\titem3 := buildResult(\n\t\twithIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)\n\titem4 := buildResult(\n\t\twithIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)\n\titem5 := buildResult(\n\t\twithIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)\n\titem6 := buildResult(\n\t\twithIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)\n\titems = []Result{item1, item2, item3, item4, item5, item6}\n\tsort.Sort(ByRelevance(items))\n\tif !(items[0] == item6 && items[1] == item5 &&\n\t\titems[2] == item4 && items[3] == item3 &&\n\t\titems[4] == item2 && items[5] == item1) {\n\t\tt.Error(items, item1, item2, item3, item4, item5, item6)\n\t}\n}\n\nfunc TestChunkTiebreak(t *testing.T) {\n\t// FIXME global\n\tsortCriteria = []criterion{byScore, byChunk}\n\n\tscore := 100\n\ttest := func(input string, offset Offset, chunk string) {\n\t\titem := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)\n\t\tif !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {\n\t\t\tt.Error(item.points)\n\t\t}\n\t}\n\ttest(\"hello foobar goodbye\", Offset{8, 9}, \"foobar\")\n\ttest(\"hello foobar goodbye\", Offset{7, 18}, \"foobar goodbye\")\n\ttest(\"hello foobar goodbye\", Offset{0, 1}, \"hello\")\n\ttest(\"hello foobar goodbye\", Offset{5, 7}, \"hello foobar\") // TBD\n}\n\nfunc TestColorOffset(t *testing.T) {\n\t// ------------ 20 ----  --  ----\n\t//   ++++++++        ++++++++++\n\t// --++++++++--    --++++++++++---\n\n\toffsets := []Offset{{5, 15}, {10, 12}, {25, 35}}\n\titem := Result{\n\t\titem: &Item{\n\t\t\tcolors: &[]ansiOffset{\n\t\t\t\t{[2]int32{0, 20}, ansiState{1, 5, -1, 0, -1, nil}},\n\t\t\t\t{[2]int32{22, 27}, ansiState{2, 6, -1, tui.Bold, -1, nil}},\n\t\t\t\t{[2]int32{30, 32}, ansiState{3, 7, -1, 0, -1, nil}},\n\t\t\t\t{[2]int32{33, 40}, ansiState{4, 8, -1, tui.Bold, -1, nil}}}}}\n\n\tcolBase := tui.NewColorPair(89, 189, tui.AttrUndefined)\n\tcolMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)\n\tcolors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, 0, false)\n\tassert := func(idx int, b int32, e int32, c tui.ColorPair) {\n\t\to := colors[idx]\n\t\tif o.offset[0] != b || o.offset[1] != e || o.color != c {\n\t\t\tt.Error(o, b, e, c)\n\t\t}\n\t}\n\t// [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}\n\t//  {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}\n\t//  {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}\n\t//  {[35 40] {4 8 1}}]\n\tassert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\tassert(1, 5, 15, colMatch)\n\tassert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\tassert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))\n\tassert(4, 25, 27, colMatch.WithAttr(tui.Bold))\n\tassert(5, 27, 30, colMatch)\n\tassert(6, 30, 32, colMatch)\n\tassert(7, 32, 33, colMatch) // TODO: Should we merge consecutive blocks?\n\tassert(8, 33, 35, colMatch.WithAttr(tui.Bold))\n\tassert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))\n\n\tcolRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)\n\tcolUnderline := tui.NewColorPair(-1, -1, tui.Underline)\n\n\tnthOffsets := []Offset{{37, 39}, {42, 45}}\n\tfor _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {\n\t\tcolors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, 0, false)\n\n\t\t// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}\n\t\t//  {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}\n\t\t//  {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}\n\t\t//  {[35 37] {4 8 1}} {[37 39] {4 8 x|1}} {[39 40] {4 8 x|1}}]\n\t\tassert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\t\tassert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))\n\t\tassert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\t\tassert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))\n\t\tassert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))\n\t\tassert(5, 27, 30, colUnderline)\n\t\tassert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))\n\t\tassert(7, 32, 33, colUnderline)\n\t\tassert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))\n\t\tassert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold))\n\t\texpected := tui.Bold | attr\n\t\tif attr == tui.AttrRegular {\n\t\t\texpected = tui.Bold\n\t\t}\n\t\tassert(10, 37, 39, tui.NewColorPair(4, 8, expected))\n\t\tassert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))\n\t}\n\n\t// Test nthOverlay: simulates nth:regular with current-fg:underline\n\t// The overlay (underline) should survive even though nth:regular clears attrs.\n\t// Precedence: fg < nth < current-fg\n\tcolors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, tui.AttrRegular, tui.Underline, false)\n\n\t// nth regions should have Underline (from overlay), not cleared by AttrRegular\n\t// Non-nth regions keep colBase attrs (AttrUndefined)\n\tassert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\tassert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))\n\tassert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\tassert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))\n\tassert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))\n\tassert(5, 27, 30, colUnderline)\n\tassert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))\n\tassert(7, 32, 33, colUnderline)\n\tassert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))\n\tassert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold))\n\t// nth region within ANSI bold: AttrRegular clears, overlay adds Underline back\n\tassert(10, 37, 39, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))\n\tassert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))\n\n\t// Test nthOverlay with additive attrs: nth:strikethrough with selected-fg:bold\n\tcolors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, tui.StrikeThrough, tui.Bold, false)\n\n\t// Non-nth entries unchanged from overlay=0 case\n\tassert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))\n\tassert(5, 27, 30, colUnderline) // match only, no nth\n\tassert(7, 32, 33, colUnderline) // match only, no nth\n\t// nth region within ANSI bold: StrikeThrough|Bold merged with ANSI Bold\n\tassert(10, 37, 39, tui.NewColorPair(4, 8, tui.Bold|tui.StrikeThrough))\n}\n\nfunc TestRadixSortResults(t *testing.T) {\n\tsortCriteria = []criterion{byScore, byLength}\n\n\trng := rand.New(rand.NewSource(42))\n\n\tfor _, n := range []int{128, 256, 500, 1000} {\n\t\tfor _, tac := range []bool{false, true} {\n\t\t\t// Build items with random points and indices\n\t\t\titems := make([]*Item, n)\n\t\t\tfor i := range items {\n\t\t\t\titems[i] = &Item{text: util.Chars{Index: int32(i)}}\n\t\t\t}\n\n\t\t\tresults := make([]Result, n)\n\t\t\tfor i := range results {\n\t\t\t\tresults[i] = Result{\n\t\t\t\t\titem: items[i],\n\t\t\t\t\tpoints: [4]uint16{\n\t\t\t\t\t\tuint16(rng.Intn(256)),\n\t\t\t\t\t\tuint16(rng.Intn(256)),\n\t\t\t\t\t\tuint16(rng.Intn(256)),\n\t\t\t\t\t\tuint16(rng.Intn(256)),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make some duplicates to test stability\n\t\t\tfor i := 0; i < n/4; i++ {\n\t\t\t\tj := rng.Intn(n)\n\t\t\t\tk := rng.Intn(n)\n\t\t\t\tresults[j].points = results[k].points\n\t\t\t}\n\n\t\t\t// Copy for reference sort\n\t\t\texpected := make([]Result, n)\n\t\t\tcopy(expected, results)\n\t\t\tif tac {\n\t\t\t\tsort.Sort(ByRelevanceTac(expected))\n\t\t\t} else {\n\t\t\t\tsort.Sort(ByRelevance(expected))\n\t\t\t}\n\n\t\t\t// Radix sort\n\t\t\tvar scratch []Result\n\t\t\tscratch = radixSortResults(results, tac, scratch)\n\n\t\t\tfor i := range results {\n\t\t\t\tif results[i] != expected[i] {\n\t\t\t\t\tt.Errorf(\"n=%d tac=%v: mismatch at index %d: got item %d, want item %d\",\n\t\t\t\t\t\tn, tac, i, results[i].item.Index(), expected[i].item.Index())\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/result_x86.go",
    "content": "//go:build 386 || amd64 || arm64\n\npackage fzf\n\nimport \"unsafe\"\n\nfunc compareRanks(irank Result, jrank Result, tac bool) bool {\n\tleft := *(*uint64)(unsafe.Pointer(&irank.points[0]))\n\tright := *(*uint64)(unsafe.Pointer(&jrank.points[0]))\n\tif left < right {\n\t\treturn true\n\t} else if left > right {\n\t\treturn false\n\t}\n\treturn (irank.item.Index() <= jrank.item.Index()) != tac\n}\n\nfunc sortKey(r *Result) uint64 {\n\treturn *(*uint64)(unsafe.Pointer(&r.points[0]))\n}\n"
  },
  {
    "path": "src/server.go",
    "content": "package fzf\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/subtle\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nvar getRegex *regexp.Regexp\n\nfunc init() {\n\tgetRegex = regexp.MustCompile(`^GET /(?:\\?([a-z0-9=&]+))? HTTP`)\n}\n\ntype getParams struct {\n\tlimit  int\n\toffset int\n}\n\nconst (\n\tcrlf             = \"\\r\\n\"\n\thttpOk           = \"HTTP/1.1 200 OK\" + crlf\n\thttpBadRequest   = \"HTTP/1.1 400 Bad Request\" + crlf\n\thttpUnauthorized = \"HTTP/1.1 401 Unauthorized\" + crlf\n\thttpUnavailable  = \"HTTP/1.1 503 Service Unavailable\" + crlf\n\thttpReadTimeout  = 10 * time.Second\n\tchannelTimeout   = 2 * time.Second\n\tjsonContentType  = \"Content-Type: application/json\" + crlf\n\tmaxContentLength = 1024 * 1024\n)\n\ntype httpServer struct {\n\tapiKey        []byte\n\tactionChannel chan []*action\n\tgetHandler    func(getParams) string\n}\n\ntype listenAddress struct {\n\thost string\n\tport int\n\tsock string\n}\n\nfunc (addr listenAddress) IsLocal() bool {\n\treturn addr.host == \"localhost\" || addr.host == \"127.0.0.1\" || len(addr.sock) > 0\n}\n\nvar defaultListenAddr = listenAddress{\"localhost\", 0, \"\"}\n\nfunc parseListenAddress(address string) (listenAddress, error) {\n\tif strings.HasSuffix(address, \".sock\") {\n\t\treturn listenAddress{\"\", 0, address}, nil\n\t}\n\n\tparts := strings.SplitN(address, \":\", 3)\n\tif len(parts) == 1 {\n\t\tparts = []string{\"localhost\", parts[0]}\n\t}\n\tif len(parts) != 2 {\n\t\treturn defaultListenAddr, fmt.Errorf(\"invalid listen address: %s\", address)\n\t}\n\tportStr := parts[len(parts)-1]\n\tport, err := strconv.Atoi(portStr)\n\tif err != nil || port < 0 || port > 65535 {\n\t\treturn defaultListenAddr, fmt.Errorf(\"invalid listen port: %s\", portStr)\n\t}\n\tif len(parts[0]) == 0 {\n\t\tparts[0] = \"localhost\"\n\t}\n\treturn listenAddress{parts[0], port, \"\"}, nil\n}\n\nfunc startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {\n\thost := address.host\n\tport := address.port\n\tapiKey := os.Getenv(\"FZF_API_KEY\")\n\tif !address.IsLocal() && len(apiKey) == 0 {\n\t\treturn nil, port, errors.New(\"FZF_API_KEY is required to allow remote access\")\n\t}\n\n\tvar listener net.Listener\n\tvar err error\n\tif len(address.sock) > 0 {\n\t\tif _, err := os.Stat(address.sock); err == nil {\n\t\t\t// Check if the socket is already in use\n\t\t\tif conn, err := net.Dial(\"unix\", address.sock); err == nil {\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, 0, fmt.Errorf(\"socket already in use: %s\", address.sock)\n\t\t\t}\n\t\t\tos.Remove(address.sock)\n\t\t}\n\t\tlistener, err = net.Listen(\"unix\", address.sock)\n\t\tif err != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"failed to listen on %s\", address.sock)\n\t\t}\n\t\tos.Chmod(address.sock, 0600)\n\t} else {\n\t\taddrStr := fmt.Sprintf(\"%s:%d\", host, port)\n\t\tlistener, err = net.Listen(\"tcp\", addrStr)\n\t\tif err != nil {\n\t\t\treturn nil, port, fmt.Errorf(\"failed to listen on %s\", addrStr)\n\t\t}\n\t\tif port == 0 {\n\t\t\taddr := listener.Addr().String()\n\t\t\tparts := strings.Split(addr, \":\")\n\t\t\tif len(parts) < 2 {\n\t\t\t\treturn nil, port, fmt.Errorf(\"cannot extract port: %s\", addr)\n\t\t\t}\n\t\t\tvar err error\n\t\t\tport, err = strconv.Atoi(parts[len(parts)-1])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, port, err\n\t\t\t}\n\t\t}\n\t}\n\n\tserver := httpServer{\n\t\tapiKey:        []byte(apiKey),\n\t\tactionChannel: actionChannel,\n\t\tgetHandler:    getHandler,\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, net.ErrClosed) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconn.Write([]byte(server.handleHttpRequest(conn)))\n\t\t\tconn.Close()\n\t\t}\n\t}()\n\n\treturn listener, port, nil\n}\n\n// Here we are writing a simplistic HTTP server without using net/http\n// package to reduce the size of the binary.\n//\n// * No --listen:            2.8MB\n// * --listen with net/http: 5.7MB\n// * --listen w/o net/http:  3.3MB\nfunc (server *httpServer) handleHttpRequest(conn net.Conn) string {\n\tcontentLength := 0\n\tapiKey := \"\"\n\tbody := \"\"\n\tanswer := func(code string, message string) string {\n\t\tmessage += \"\\n\"\n\t\treturn code + fmt.Sprintf(\"Content-Length: %d%s\", len(message), crlf+crlf+message)\n\t}\n\tunauthorized := func(message string) string {\n\t\treturn answer(httpUnauthorized, message)\n\t}\n\tbad := func(message string) string {\n\t\treturn answer(httpBadRequest, message)\n\t}\n\tgood := func(message string) string {\n\t\treturn answer(httpOk+jsonContentType, message)\n\t}\n\tconn.SetReadDeadline(time.Now().Add(httpReadTimeout))\n\tscanner := bufio.NewScanner(conn)\n\tscanner.Split(func(data []byte, atEOF bool) (int, []byte, error) {\n\t\tfound := bytes.Index(data, []byte(crlf))\n\t\tif found >= 0 {\n\t\t\ttoken := data[:found+len(crlf)]\n\t\t\treturn len(token), token, nil\n\t\t}\n\t\tif atEOF || len(body)+len(data) >= contentLength {\n\t\t\treturn 0, data, bufio.ErrFinalToken\n\t\t}\n\t\treturn 0, nil, nil\n\t})\n\n\tsection := 0\n\tvar getMatch []string\nLoop:\n\tfor scanner.Scan() {\n\t\ttext := scanner.Text()\n\t\tswitch section {\n\t\tcase 0: // Request line\n\t\t\tgetMatch = getRegex.FindStringSubmatch(text)\n\t\t\tif len(getMatch) == 0 && !strings.HasPrefix(text, \"POST / HTTP\") {\n\t\t\t\treturn bad(\"invalid request method\")\n\t\t\t}\n\t\t\tsection++\n\t\tcase 1: // Request headers\n\t\t\tif text == crlf { // End of headers\n\t\t\t\tif len(getMatch) > 0 {\n\t\t\t\t\tbreak Loop\n\t\t\t\t}\n\t\t\t\tif contentLength == 0 {\n\t\t\t\t\treturn bad(\"content-length header missing\")\n\t\t\t\t}\n\t\t\t\tsection++\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpair := strings.SplitN(text, \":\", 2)\n\t\t\tif len(pair) == 2 {\n\t\t\t\tswitch strings.ToLower(pair[0]) {\n\t\t\t\tcase \"content-length\":\n\t\t\t\t\tlength, err := strconv.Atoi(strings.TrimSpace(pair[1]))\n\t\t\t\t\tif err != nil || length <= 0 || length > maxContentLength {\n\t\t\t\t\t\treturn bad(\"invalid content length\")\n\t\t\t\t\t}\n\t\t\t\t\tcontentLength = length\n\t\t\t\tcase \"x-api-key\":\n\t\t\t\t\tapiKey = strings.TrimSpace(pair[1])\n\t\t\t\t}\n\t\t\t}\n\t\tcase 2: // Request body\n\t\t\tbody += text\n\t\t}\n\t}\n\n\tif len(server.apiKey) != 0 && subtle.ConstantTimeCompare([]byte(apiKey), server.apiKey) != 1 {\n\t\treturn unauthorized(\"invalid api key\")\n\t}\n\n\tif len(getMatch) > 0 {\n\t\tresponse := server.getHandler(parseGetParams(getMatch[1]))\n\t\tif len(response) > 0 {\n\t\t\treturn good(response)\n\t\t}\n\t\treturn answer(httpUnavailable+jsonContentType, `{\"error\":\"timeout\"}`)\n\t}\n\n\tif len(body) < contentLength {\n\t\treturn bad(\"incomplete request\")\n\t}\n\tbody = body[:contentLength]\n\n\tactions, err := parseSingleActionList(strings.Trim(string(body), \"\\r\\n\"))\n\tif err != nil {\n\t\treturn bad(err.Error())\n\t}\n\tif len(actions) == 0 {\n\t\treturn bad(\"no action specified\")\n\t}\n\n\tselect {\n\tcase server.actionChannel <- actions:\n\tcase <-time.After(channelTimeout):\n\t\treturn httpUnavailable + crlf\n\t}\n\treturn httpOk + crlf\n}\n\nfunc parseGetParams(query string) getParams {\n\tparams := getParams{limit: 100, offset: 0}\n\tfor _, pair := range strings.Split(query, \"&\") {\n\t\tparts := strings.SplitN(pair, \"=\", 2)\n\t\tif len(parts) == 2 {\n\t\t\tswitch parts[0] {\n\t\t\tcase \"limit\", \"offset\":\n\t\t\t\tif val, err := strconv.Atoi(parts[1]); err == nil {\n\t\t\t\t\tif parts[0] == \"limit\" {\n\t\t\t\t\t\tparams.limit = val\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparams.offset = val\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn params\n}\n"
  },
  {
    "path": "src/terminal.go",
    "content": "package fzf\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"math\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/rivo/uniseg\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\n// import \"github.com/pkg/profile\"\n\n/*\nPlaceholder regex is used to extract placeholders from fzf's template\nstrings. Acts as input validation for parsePlaceholder function.\nDescribes the syntax, but it is fairly lenient.\n\nThe following pseudo regex has been reverse engineered from the\nimplementation. It is overly strict, but better describes what's possible.\nAs such it is not useful for validation, but rather to generate test\ncases for example.\n\n\t\\\\?(?:                                      # escaped type\n\t    {\\+?s?f?r?RANGE(?:,RANGE)*}             # token type\n\t    {q[:s?RANGE]}                           # query type\n\t    |{\\+?n?f?}                              # item type (notice no mandatory element inside brackets)\n\t)\n\tRANGE = (?:\n\t    (?:-?[0-9]+)?\\.\\.(?:-?[0-9]+)?          # ellipsis syntax for token range (x..y)\n\t    |-?[0-9]+                               # shorthand syntax (x..x)\n\t)\n*/\nvar placeholder *regexp.Regexp\nvar whiteSuffix *regexp.Regexp\nvar offsetComponentRegex *regexp.Regexp\nvar offsetTrimCharsRegex *regexp.Regexp\nvar passThroughBeginRegex *regexp.Regexp\nvar passThroughEndTmuxRegex *regexp.Regexp\nvar ttyin *os.File\n\nconst clearCode string = \"\\x1b[2J\"\n\n// Number of maximum focus events to process synchronously\nconst maxFocusEvents = 10000\n\n// execute-silent and transform* actions will block user input for this duration.\n// After this duration, users can press CTRL-C to terminate the command.\nconst blockDuration = 1 * time.Second\n\nfunc init() {\n\tplaceholder = regexp.MustCompile(`\\\\?(?:{[+*sfr]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{[+*]?f?nf?})`)\n\twhiteSuffix = regexp.MustCompile(`\\s*$`)\n\toffsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)\n\toffsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)\n\n\t// Parts of the preview output that should be passed through to the terminal\n\t// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it\n\t// * https://sw.kovidgoyal.net/kitty/graphics-protocol\n\t// * https://en.wikipedia.org/wiki/Sixel\n\t// * https://iterm2.com/documentation-images.html\n\t/*\n\t\tpassThroughRegex = regexp.MustCompile(`\n\t\t\t  \\x1bPtmux;\\x1b\\x1b  .*?  [^\\x1b]\\x1b\\\\\n\t\t\t| \\x1b(_G|P[0-9;]*q)  .*?  \\x1b\\\\\\r?\n\t\t\t| \\x1b]1337;          .*?  (\\a|\\x1b\\\\)\n\t\t`)\n\t*/\n\tpassThroughBeginRegex = regexp.MustCompile(`\\x1bPtmux;\\x1b\\x1b|\\x1b(_G|P[0-9;]*q)|\\x1b]1337;`)\n\tpassThroughEndTmuxRegex = regexp.MustCompile(`[^\\x1b]\\x1b\\\\`)\n}\n\ntype jumpMode int\n\nconst (\n\tjumpDisabled jumpMode = iota\n\tjumpEnabled\n\tjumpAcceptEnabled\n)\n\ntype resumableState int\n\nconst (\n\tdisabledState resumableState = iota\n\tpausedState\n\tenabledState\n)\n\nfunc (s resumableState) Enabled() bool {\n\treturn s == enabledState\n}\n\nfunc (s *resumableState) Force(flag bool) {\n\tif flag {\n\t\t*s = enabledState\n\t} else {\n\t\t*s = disabledState\n\t}\n}\n\nfunc (s *resumableState) Set(flag bool) {\n\tif *s == disabledState {\n\t\treturn\n\t}\n\n\tif flag {\n\t\t*s = enabledState\n\t} else {\n\t\t*s = pausedState\n\t}\n}\n\ntype commandSpec struct {\n\tcommand   string\n\ttempFiles []string\n}\n\ntype quitSignal struct {\n\tcode int\n\terr  error\n}\n\ntype previewer struct {\n\tversion    int64\n\tlines      []string\n\toffset     int\n\tscrollable bool\n\tfinal      bool\n\tfollowing  resumableState\n\tspinner    string\n\tbar        []bool\n\txw         [2]int\n}\n\ntype previewed struct {\n\tversion   int64\n\tnumLines  int\n\toffset    int\n\tfilled    bool\n\timage     bool\n\twipe      bool\n\twireframe bool\n}\n\ntype eachLine struct {\n\tline string\n\terr  error\n}\n\ntype itemLine struct {\n\tvalid     bool\n\tfirstLine int\n\tnumLines  int\n\tcy        int\n\tcurrent   bool\n\tselected  bool\n\tlabel     string\n\tqueryLen  int\n\twidth     int\n\thasBar    bool\n\tresult    Result\n\tempty     bool\n\tother     bool\n\thidden    bool\n}\n\nfunc (t *Terminal) inListWindow() bool {\n\treturn t.window != t.inputWindow && t.window != t.headerWindow && t.window != t.headerLinesWindow && t.window != t.footerWindow\n}\n\nfunc (t *Terminal) markEmptyLine(line int) {\n\tif t.inListWindow() {\n\t\tt.prevLines[line] = itemLine{valid: true, firstLine: line, empty: true}\n\t}\n}\n\nfunc (t *Terminal) markOtherLine(line int) {\n\tif t.inListWindow() {\n\t\tt.prevLines[line] = itemLine{valid: true, firstLine: line, other: true}\n\t}\n}\n\ntype fitpad struct {\n\tfit int\n\tpad int\n}\n\ntype labelPrinter func(tui.Window, int)\n\ntype markerClass int\n\nconst (\n\tmarkerSingle markerClass = iota\n\tmarkerTop\n\tmarkerMiddle\n\tmarkerBottom\n)\n\ntype StatusItem struct {\n\tIndex int    `json:\"index\"`\n\tText  string `json:\"text\"`\n}\n\ntype Status struct {\n\tReading    bool         `json:\"reading\"`\n\tProgress   int          `json:\"progress\"`\n\tQuery      string       `json:\"query\"`\n\tPosition   int          `json:\"position\"`\n\tSort       bool         `json:\"sort\"`\n\tTotalCount int          `json:\"totalCount\"`\n\tMatchCount int          `json:\"matchCount\"`\n\tCurrent    *StatusItem  `json:\"current\"`\n\tMatches    []StatusItem `json:\"matches\"`\n\tSelected   []StatusItem `json:\"selected\"`\n}\n\ntype versionedCallback struct {\n\tversion  int64\n\tcallback func()\n}\n\ntype runningCmd struct {\n\tcmd       *exec.Cmd\n\ttempFiles []string\n}\n\n// Terminal represents terminal input/output\ntype Terminal struct {\n\tinitDelay            time.Duration\n\tinfoCommand          string\n\tinfoStyle            infoStyle\n\tinfoPrefix           string\n\twrap                 bool\n\twrapWord             bool\n\twrapSign             string\n\twrapSignWidth        int\n\tpreviewWrapSign      string\n\tpreviewWrapSignWidth int\n\tghost                string\n\tseparator            labelPrinter\n\tseparatorLen         int\n\tspinner              []string\n\tpromptString         string\n\tprompt               func()\n\tpromptLen            int\n\tborderLabel          labelPrinter\n\tborderLabelLen       int\n\tborderLabelOpts      labelOpts\n\tpreviewLabel         labelPrinter\n\tpreviewLabelLen      int\n\tpreviewLabelOpts     labelOpts\n\tinputLabel           labelPrinter\n\tinputLabelLen        int\n\tinputLabelOpts       labelOpts\n\theaderLabel          labelPrinter\n\theaderLabelLen       int\n\theaderLabelOpts      labelOpts\n\tfooterLabel          labelPrinter\n\tfooterLabelLen       int\n\tfooterLabelOpts      labelOpts\n\tgutterReverse        bool\n\tgutterRawReverse     bool\n\tpointer              string\n\tpointerLen           int\n\tpointerEmpty         string\n\tpointerEmptyRaw      string\n\tmarker               string\n\tmarkerLen            int\n\tmarkerEmpty          string\n\tmarkerMultiLine      [3]string\n\tqueryLen             [2]int\n\tlayout               layoutType\n\tfullscreen           bool\n\tkeepRight            bool\n\thscroll              bool\n\thscrollOff           int\n\tscrollOff            int\n\tgap                  int\n\tgapLine              labelPrinter\n\tgapLineLen           int\n\twordRubout           string\n\twordNext             string\n\tsubWordRubout        string\n\tsubWordNext          string\n\tcx                   int\n\tcy                   int\n\toffset               int\n\txoffset              int\n\tyanked               []rune\n\tinput                []rune\n\tinputOverride        *[]rune\n\tpasting              *[]rune\n\tmulti                int\n\tmultiLine            bool\n\tsort                 bool\n\ttoggleSort           bool\n\ttrack                trackOption\n\tidNth                []Range\n\ttrackKey             string\n\ttrackBlocked         bool\n\ttrackSync            bool\n\ttrackKeyCache        map[int32]bool\n\tpendingSelections    map[string]selectedItem\n\ttargetIndex          int32\n\tdelimiter            Delimiter\n\texpect               map[tui.Event]string\n\tkeymap               map[tui.Event][]*action\n\tkeymapOrg            map[tui.Event][]*action\n\tpressed              string\n\tprintQueue           []string\n\tprintQuery           bool\n\thistory              *History\n\tcycle                bool\n\thighlightLine        bool\n\theaderVisible        bool\n\theaderFirst          bool\n\theaderLines          int\n\theader               []Item\n\theader0              []string\n\tfooter               []string\n\tellipsis             string\n\tscrollbar            string\n\tpreviewScrollbar     string\n\tansi                 bool\n\tfreezeLeft           int\n\tfreezeRight          int\n\tnthAttr              tui.Attr\n\tnth                  []Range\n\tnthCurrent           []Range\n\twithNthDefault       string\n\twithNthExpr          string\n\twithNthEnabled       bool\n\tacceptNth            func([]Token, int32) string\n\ttabstop              int\n\tmargin               [4]sizeSpec\n\tpadding              [4]sizeSpec\n\tunicode              bool\n\tlistenAddr           *listenAddress\n\tlistenPort           *int\n\tlistener             net.Listener\n\tlistenUnsafe         bool\n\tborderShape          tui.BorderShape\n\tlistBorderShape      tui.BorderShape\n\tinputBorderShape     tui.BorderShape\n\theaderBorderShape    tui.BorderShape\n\theaderLinesShape     tui.BorderShape\n\tfooterBorderShape    tui.BorderShape\n\tlistLabel            labelPrinter\n\tlistLabelLen         int\n\tlistLabelOpts        labelOpts\n\tcleanExit            bool\n\texecutor             *util.Executor\n\tpaused               bool\n\tinputless            bool\n\tborder               tui.Window\n\twindow               tui.Window\n\tinputWindow          tui.Window\n\tinputBorder          tui.Window\n\theaderWindow         tui.Window\n\theaderBorder         tui.Window\n\theaderLinesWindow    tui.Window\n\theaderLinesBorder    tui.Window\n\tfooterWindow         tui.Window\n\tfooterBorder         tui.Window\n\twborder              tui.Window\n\tpborder              tui.Window\n\tpwindow              tui.Window\n\tborderWidth          int\n\tcount                int\n\tprogress             int\n\thasStartActions      bool\n\thasResultActions     bool\n\thasFocusActions      bool\n\thasLoadActions       bool\n\thasResizeActions     bool\n\ttriggerLoad          bool\n\tpendingReqList       bool\n\tfilterSelection      bool\n\treading              bool\n\trunning              *util.AtomicBool\n\tfailed               *string\n\tjumping              jumpMode\n\tjumpLabels           string\n\tprinter              func(string)\n\tprintsep             string\n\tmerger               *Merger\n\tpassMerger           *Merger\n\tresultMerger         *Merger\n\tmatchMap             map[int32]Result\n\tselected             map[int32]selectedItem\n\tversion              int64\n\trevision             revision\n\tbgVersion            int64\n\trunningCmds          *util.ConcurrentSet[*runningCmd]\n\treqBox               *util.EventBox\n\tinitialPreviewOpts   previewOpts\n\tpreviewOpts          previewOpts\n\tactivePreviewOpts    *previewOpts\n\tpreviewer            previewer\n\tpreviewed            previewed\n\tpreviewBox           *util.EventBox\n\teventBox             *util.EventBox\n\tmutex                sync.Mutex\n\tuiMutex              sync.Mutex\n\tinitFunc             func() error\n\tprevLines            []itemLine\n\tsuppress             bool\n\tstartChan            chan fitpad\n\tkillChan             chan bool\n\tkilledChan           chan bool\n\tserverInputChan      chan []*action\n\tcallbackChan         chan versionedCallback\n\tbgQueue              map[action][]func(bool)\n\tbgSemaphore          chan struct{}\n\tbgSemaphores         map[action]chan struct{}\n\tkeyChan              chan tui.Event\n\teventChan            chan tui.Event\n\tslab                 *util.Slab\n\ttheme                *tui.ColorTheme\n\ttui                  tui.Renderer\n\tttyDefault           string\n\tttyin                *os.File\n\texecuting            *util.AtomicBool\n\ttermSize             tui.TermSize\n\tlastAction           actionType\n\tlastKey              string\n\tlastFocus            int32\n\tareaLines            int\n\tareaColumns          int\n\tforcePreview         bool\n\tclickHeaderLine      int\n\tclickHeaderColumn    int\n\tclickFooterLine      int\n\tclickFooterColumn    int\n\tproxyScript          string\n\tnumLinesCache        map[int32]numLinesCacheValue\n\traw                  bool\n}\n\ntype numLinesCacheValue struct {\n\tatMost   int\n\tnumLines int\n}\n\ntype selectedItem struct {\n\tat   time.Time\n\titem *Item\n}\n\ntype byTimeOrder []selectedItem\n\nfunc (a byTimeOrder) Len() int {\n\treturn len(a)\n}\n\nfunc (a byTimeOrder) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n\nfunc (a byTimeOrder) Less(i, j int) bool {\n\treturn a[i].at.Before(a[j].at)\n}\n\n// EventTypes are listed in the order of their priority.\nconst (\n\treqResize util.EventType = iota\n\treqReinit\n\treqFullRedraw\n\treqRedraw\n\n\treqJump\n\treqPrompt\n\treqInfo\n\treqHeader\n\treqFooter\n\treqList\n\treqRedrawInputLabel\n\treqRedrawHeaderLabel\n\treqRedrawFooterLabel\n\treqRedrawListLabel\n\treqRedrawBorderLabel\n\treqRedrawPreviewLabel\n\n\treqPreviewReady\n\treqPreviewEnqueue\n\treqPreviewDisplay\n\treqPreviewRefresh\n\treqPreviewDelayed\n\n\treqActivate\n\treqClose\n\treqPrintQuery\n\treqBecome\n\treqQuit\n\treqFatal\n)\n\nfunc isTerminalEvent(et util.EventType) bool {\n\tswitch et {\n\tcase reqClose, reqPrintQuery, reqBecome, reqQuit, reqFatal:\n\t\treturn true\n\t}\n\treturn false\n}\n\ntype action struct {\n\tt actionType\n\ta string\n}\n\n//go:generate stringer -type=actionType\ntype actionType int\n\nconst (\n\tactIgnore actionType = iota\n\tactStart\n\tactClick\n\tactInvalid\n\tactBracketedPasteBegin\n\tactBracketedPasteEnd\n\tactChar\n\tactMouse\n\tactBeginningOfLine\n\tactAbort\n\tactAccept\n\tactAcceptNonEmpty\n\tactAcceptOrPrintQuery\n\tactBackwardChar\n\tactBackwardDeleteChar\n\tactBackwardDeleteCharEof\n\tactBackwardWord\n\tactBackwardSubWord\n\tactCancel\n\n\tactChangeBorderLabel\n\tactChangeGhost\n\tactChangeHeader\n\tactChangeHeaderLines\n\tactChangeFooter\n\tactChangeHeaderLabel\n\tactChangeFooterLabel\n\tactChangeInputLabel\n\tactChangeListLabel\n\tactChangeMulti\n\tactChangeNth\n\tactChangeWithNth\n\tactChangePointer\n\tactChangePreview\n\tactChangePreviewLabel\n\tactChangePreviewWindow\n\tactChangePrompt\n\tactChangeQuery\n\n\tactClearScreen\n\tactClearQuery\n\tactClearSelection\n\tactClose\n\tactDeleteChar\n\tactDeleteCharEof\n\tactEndOfLine\n\tactFatal\n\tactForwardChar\n\tactForwardWord\n\tactForwardSubWord\n\tactKillLine\n\tactKillWord\n\tactKillSubWord\n\tactUnixLineDiscard\n\tactUnixWordRubout\n\tactYank\n\tactBackwardKillWord\n\tactBackwardKillSubWord\n\tactSelectAll\n\tactDeselectAll\n\tactToggle\n\tactToggleSearch\n\tactToggleAll\n\tactToggleDown\n\tactToggleUp\n\tactToggleIn\n\tactToggleOut\n\tactToggleTrack\n\tactToggleTrackCurrent\n\tactToggleHeader\n\tactToggleWrap\n\tactToggleWrapWord\n\tactToggleMultiLine\n\tactToggleHscroll\n\tactToggleRaw\n\tactEnableRaw\n\tactDisableRaw\n\tactTrackCurrent\n\tactToggleInput\n\tactHideInput\n\tactShowInput\n\tactUntrackCurrent\n\tactDown\n\tactDownMatch\n\tactUp\n\tactUpMatch\n\tactPageUp\n\tactPageDown\n\tactPosition\n\tactHalfPageUp\n\tactHalfPageDown\n\tactOffsetUp\n\tactOffsetDown\n\tactOffsetMiddle\n\tactJump\n\tactJumpAccept // XXX Deprecated in favor of jump:accept binding\n\tactPrintQuery // XXX Deprecated (not very useful, just use --print-query)\n\tactRefreshPreview\n\tactReplaceQuery\n\tactToggleSort\n\tactShowPreview\n\tactHidePreview\n\tactTogglePreview\n\tactTogglePreviewWrap\n\tactTogglePreviewWrapWord\n\n\tactTransform\n\tactTransformBorderLabel\n\tactTransformGhost\n\tactTransformHeader\n\tactTransformHeaderLines\n\tactTransformFooter\n\tactTransformHeaderLabel\n\tactTransformFooterLabel\n\tactTransformInputLabel\n\tactTransformListLabel\n\tactTransformNth\n\tactTransformWithNth\n\tactTransformPointer\n\tactTransformPreviewLabel\n\tactTransformPrompt\n\tactTransformQuery\n\tactTransformSearch\n\n\tactTrigger\n\n\tactBgTransform\n\tactBgTransformBorderLabel\n\tactBgTransformGhost\n\tactBgTransformHeader\n\tactBgTransformHeaderLines\n\tactBgTransformFooter\n\tactBgTransformHeaderLabel\n\tactBgTransformFooterLabel\n\tactBgTransformInputLabel\n\tactBgTransformListLabel\n\tactBgTransformNth\n\tactBgTransformWithNth\n\tactBgTransformPointer\n\tactBgTransformPreviewLabel\n\tactBgTransformPrompt\n\tactBgTransformQuery\n\tactBgTransformSearch\n\n\tactBgCancel\n\n\tactSearch\n\tactPreview\n\tactPreviewTop\n\tactPreviewBottom\n\tactPreviewUp\n\tactPreviewDown\n\tactPreviewPageUp\n\tactPreviewPageDown\n\tactPreviewHalfPageUp\n\tactPreviewHalfPageDown\n\tactPrevHistory\n\tactPrevSelected\n\tactPrint\n\tactPut\n\tactNextHistory\n\tactNextSelected\n\tactExecute\n\tactExecuteSilent\n\tactExecuteMulti // Deprecated\n\tactSigStop\n\tactBest\n\tactFirst\n\tactLast\n\tactReload\n\tactReloadSync\n\tactDisableSearch\n\tactEnableSearch\n\tactSelect\n\tactDeselect\n\tactUnbind\n\tactRebind\n\tactToggleBind\n\tactBecome\n\tactShowHeader\n\tactHideHeader\n\tactBell\n\tactExclude\n\tactExcludeMulti\n\tactAsync\n)\n\nfunc (a actionType) Name() string {\n\treturn util.ToKebabCase(a.String()[3:])\n}\n\nfunc processExecution(action actionType) bool {\n\tswitch action {\n\tcase actTransform,\n\t\tactTransformBorderLabel,\n\t\tactTransformGhost,\n\t\tactTransformHeader,\n\t\tactTransformHeaderLines,\n\t\tactTransformFooter,\n\t\tactTransformHeaderLabel,\n\t\tactTransformFooterLabel,\n\t\tactTransformInputLabel,\n\t\tactTransformListLabel,\n\t\tactTransformNth,\n\t\tactTransformWithNth,\n\t\tactTransformPointer,\n\t\tactTransformPreviewLabel,\n\t\tactTransformPrompt,\n\t\tactTransformQuery,\n\t\tactTransformSearch,\n\t\tactBgTransform,\n\t\tactBgTransformBorderLabel,\n\t\tactBgTransformGhost,\n\t\tactBgTransformHeader,\n\t\tactBgTransformHeaderLines,\n\t\tactBgTransformFooter,\n\t\tactBgTransformHeaderLabel,\n\t\tactBgTransformFooterLabel,\n\t\tactBgTransformInputLabel,\n\t\tactBgTransformListLabel,\n\t\tactBgTransformNth,\n\t\tactBgTransformWithNth,\n\t\tactBgTransformPointer,\n\t\tactBgTransformPreviewLabel,\n\t\tactBgTransformPrompt,\n\t\tactBgTransformQuery,\n\t\tactBgTransformSearch,\n\t\tactPreview,\n\t\tactChangePreview,\n\t\tactRefreshPreview,\n\t\tactExecute,\n\t\tactExecuteSilent,\n\t\tactExecuteMulti,\n\t\tactReload,\n\t\tactReloadSync,\n\t\tactBecome:\n\t\treturn true\n\t}\n\treturn false\n}\n\ntype placeholderFlags struct {\n\tplus          bool\n\tasterisk      bool\n\tpreserveSpace bool\n\tnumber        bool\n\tforceUpdate   bool\n\tfile          bool\n\traw           bool\n}\n\ntype withNthSpec struct {\n\tfn func([]Token, int32) string // nil = clear (restore original)\n}\n\ntype searchRequest struct {\n\tsort        bool\n\tsync        bool\n\tnth         *[]Range\n\twithNth     *withNthSpec\n\theaderLines *int\n\tcommand     *commandSpec\n\tenviron     []string\n\tchanged     bool\n\tdenylist    []int32\n\trevision    revision\n}\n\ntype previewRequest struct {\n\ttemplate     string\n\tscrollOffset int\n\tlist         [3][]*Item // current, select, and all matched items\n\tenv          []string\n\tquery        string\n}\n\ntype previewResult struct {\n\tversion int64\n\tlines   []string\n\toffset  int\n\tspinner string\n}\n\nfunc toActions(types ...actionType) []*action {\n\tactions := make([]*action, len(types))\n\tfor idx, t := range types {\n\t\tactions[idx] = &action{t: t, a: \"\"}\n\t}\n\treturn actions\n}\n\nfunc defaultKeymap() map[tui.Event][]*action {\n\tkeymap := make(map[tui.Event][]*action)\n\tadd := func(e tui.EventType, a actionType) {\n\t\tkeymap[e.AsEvent()] = toActions(a)\n\t}\n\taddEvent := func(e tui.Event, a actionType) {\n\t\tkeymap[e] = toActions(a)\n\t}\n\n\tadd(tui.Fatal, actFatal)\n\tadd(tui.Invalid, actInvalid)\n\tadd(tui.BracketedPasteBegin, actBracketedPasteBegin)\n\tadd(tui.BracketedPasteEnd, actBracketedPasteEnd)\n\tadd(tui.CtrlA, actBeginningOfLine)\n\tadd(tui.CtrlB, actBackwardChar)\n\tadd(tui.CtrlC, actAbort)\n\tadd(tui.CtrlG, actAbort)\n\tadd(tui.CtrlQ, actAbort)\n\tadd(tui.Esc, actAbort)\n\tadd(tui.CtrlD, actDeleteCharEof)\n\tadd(tui.CtrlE, actEndOfLine)\n\tadd(tui.CtrlF, actForwardChar)\n\tadd(tui.Backspace, actBackwardDeleteChar)\n\tadd(tui.CtrlBackspace, actBackwardDeleteChar)\n\tadd(tui.Tab, actToggleDown)\n\tadd(tui.ShiftTab, actToggleUp)\n\tadd(tui.CtrlJ, actDown)\n\tadd(tui.CtrlK, actUp)\n\tadd(tui.CtrlL, actClearScreen)\n\tadd(tui.Enter, actAccept)\n\tadd(tui.CtrlN, actDownMatch)\n\tadd(tui.CtrlP, actUpMatch)\n\tadd(tui.AltDown, actDownMatch)\n\tadd(tui.AltUp, actUpMatch)\n\tadd(tui.CtrlU, actUnixLineDiscard)\n\tadd(tui.CtrlW, actUnixWordRubout)\n\tadd(tui.CtrlY, actYank)\n\tif !util.IsWindows() {\n\t\tadd(tui.CtrlZ, actSigStop)\n\t}\n\tadd(tui.CtrlSlash, actToggleWrapWord)\n\taddEvent(tui.AltKey('/'), actToggleWrapWord)\n\n\taddEvent(tui.AltKey('b'), actBackwardWord)\n\tadd(tui.ShiftLeft, actBackwardWord)\n\taddEvent(tui.AltKey('f'), actForwardWord)\n\tadd(tui.ShiftRight, actForwardWord)\n\taddEvent(tui.AltKey('d'), actKillWord)\n\tadd(tui.AltBackspace, actBackwardKillWord)\n\n\tadd(tui.Up, actUp)\n\tadd(tui.Down, actDown)\n\tadd(tui.Left, actBackwardChar)\n\tadd(tui.Right, actForwardChar)\n\n\tadd(tui.Home, actBeginningOfLine)\n\tadd(tui.End, actEndOfLine)\n\tadd(tui.Delete, actDeleteChar)\n\tadd(tui.PageUp, actPageUp)\n\tadd(tui.PageDown, actPageDown)\n\n\tadd(tui.ShiftUp, actPreviewUp)\n\tadd(tui.ShiftDown, actPreviewDown)\n\n\tadd(tui.Mouse, actMouse)\n\tadd(tui.LeftClick, actClick)\n\tadd(tui.RightClick, actToggle)\n\tadd(tui.SLeftClick, actToggle)\n\tadd(tui.SRightClick, actToggle)\n\n\tadd(tui.ScrollUp, actUp)\n\tadd(tui.ScrollDown, actDown)\n\tkeymap[tui.SScrollUp.AsEvent()] = toActions(actToggle, actUp)\n\tkeymap[tui.SScrollDown.AsEvent()] = toActions(actToggle, actDown)\n\n\tadd(tui.PreviewScrollUp, actPreviewUp)\n\tadd(tui.PreviewScrollDown, actPreviewDown)\n\treturn keymap\n}\n\nfunc trimQuery(query string) []rune {\n\treturn []rune(strings.ReplaceAll(query, \"\\t\", \" \"))\n}\n\nfunc mayTriggerPreview(opts *Options) bool {\n\tif opts.ListenAddr != nil {\n\t\treturn true\n\t}\n\tfor _, actions := range opts.Keymap {\n\t\tfor _, action := range actions {\n\t\t\tswitch action.t {\n\t\t\tcase actPreview, actChangePreview, actTransform, actBgTransform:\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc makeSpinner(unicode bool) []string {\n\tif unicode {\n\t\treturn []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}\n\t}\n\treturn []string{`-`, `\\`, `|`, `/`, `-`, `\\`, `|`, `/`}\n}\n\nfunc evaluateHeight(opts *Options, termHeight int) int {\n\tsize := opts.Height.size\n\tif opts.Height.percent {\n\t\tif opts.Height.inverse {\n\t\t\tsize = 100 - size\n\t\t}\n\t\treturn max(int(size*float64(termHeight)/100.0), opts.MinHeight)\n\t}\n\tif opts.Height.inverse {\n\t\tsize = float64(termHeight) - size\n\t}\n\treturn int(size)\n}\n\n// NewTerminal returns new Terminal object\nfunc NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) (*Terminal, error) {\n\tinput := trimQuery(opts.Query)\n\tvar delay time.Duration\n\tif opts.Sync {\n\t\tdelay = 0\n\t} else if opts.Tac {\n\t\tdelay = initialDelayTac\n\t} else {\n\t\tdelay = initialDelay\n\t}\n\tvar previewBox *util.EventBox\n\t// We need to start the previewer even when --preview option is not specified\n\t// * if HTTP server is enabled\n\t// * if 'preview' or 'change-preview' action is bound to a key\n\t// * if 'transform' action is bound to a key\n\tif len(opts.Preview.command) > 0 || mayTriggerPreview(opts) {\n\t\tpreviewBox = util.NewEventBox()\n\t}\n\tvar renderer tui.Renderer\n\tfullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)\n\tvar err error\n\t// Reuse ttyin if available to avoid having multiple file descriptors open\n\t// when you run fzf multiple times in your Go program. Closing it is known to\n\t// cause problems with 'become' action and invalid terminal state after exit.\n\tif ttyin == nil {\n\t\tif ttyin, err = tui.TtyIn(opts.TtyDefault); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif fullscreen {\n\t\tif tui.HasFullscreenRenderer() {\n\t\t\trenderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop)\n\t\t} else {\n\t\t\trenderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,\n\t\t\t\ttrue, func(h int) int { return h })\n\t\t}\n\t} else {\n\t\tmaxHeightFunc := func(termHeight int) int {\n\t\t\t// Minimum height required to render fzf excluding margin and padding\n\t\t\teffectiveMinHeight := minHeight\n\t\t\tif previewBox != nil && opts.Preview.aboveOrBelow() {\n\t\t\t\teffectiveMinHeight += 1 + borderLines(opts.Preview.Border())\n\t\t\t}\n\t\t\tif opts.noSeparatorLine() {\n\t\t\t\teffectiveMinHeight--\n\t\t\t}\n\t\t\teffectiveMinHeight += borderLines(opts.BorderShape)\n\t\t\treturn min(termHeight, max(evaluateHeight(opts, termHeight), effectiveMinHeight))\n\t\t}\n\t\trenderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif opts.Inputless {\n\t\trenderer.HideCursor()\n\t}\n\twordRubout := \"[^\\\\pL\\\\pN][\\\\pL\\\\pN]\"\n\twordNext := \"[\\\\pL\\\\pN][^\\\\pL\\\\pN]|(.$)\"\n\tsubWordRubout := \"[a-z][A-Z]|[^\\\\pL\\\\pN][\\\\pL\\\\pN]\"\n\tsubWordNext := \"[a-z][A-Z]|[\\\\pL\\\\pN][^\\\\pL\\\\pN]|(.$)\"\n\tif opts.FileWord {\n\t\tsep := regexp.QuoteMeta(string(os.PathSeparator))\n\t\twordRubout = fmt.Sprintf(\"%s[^%s]\", sep, sep)\n\t\twordNext = fmt.Sprintf(\"[^%s]%s|(.$)\", sep, sep)\n\t}\n\tkeymapCopy := maps.Clone(opts.Keymap)\n\n\tem := EmptyMerger(revision{})\n\tt := Terminal{\n\t\tinitDelay:          delay,\n\t\tinfoCommand:        opts.InfoCommand,\n\t\tinfoStyle:          opts.InfoStyle,\n\t\tinfoPrefix:         opts.InfoPrefix,\n\t\tghost:              opts.Ghost,\n\t\tseparator:          nil,\n\t\tspinner:            makeSpinner(opts.Unicode),\n\t\tpromptString:       opts.Prompt,\n\t\tqueryLen:           [2]int{0, 0},\n\t\tlayout:             opts.Layout,\n\t\tfullscreen:         fullscreen,\n\t\tkeepRight:          opts.KeepRight,\n\t\thscroll:            opts.Hscroll,\n\t\thscrollOff:         opts.HscrollOff,\n\t\tscrollOff:          opts.ScrollOff,\n\t\tpointer:            *opts.Pointer,\n\t\tpointerLen:         uniseg.StringWidth(*opts.Pointer),\n\t\tmarker:             *opts.Marker,\n\t\tmarkerLen:          uniseg.StringWidth(*opts.Marker),\n\t\tmarkerMultiLine:    *opts.MarkerMulti,\n\t\twordRubout:         wordRubout,\n\t\twordNext:           wordNext,\n\t\tsubWordRubout:      subWordRubout,\n\t\tsubWordNext:        subWordNext,\n\t\tcx:                 len(input),\n\t\tcy:                 0,\n\t\toffset:             0,\n\t\txoffset:            0,\n\t\tyanked:             []rune{},\n\t\tinput:              input,\n\t\tmulti:              opts.Multi,\n\t\tmultiLine:          opts.ReadZero && opts.MultiLine,\n\t\twrap:               opts.Wrap,\n\t\twrapWord:           opts.WrapWord,\n\t\tsort:               opts.Sort > 0,\n\t\ttoggleSort:         opts.ToggleSort,\n\t\ttrack:              opts.Track,\n\t\tidNth:              opts.IdNth,\n\t\ttargetIndex:        minItem.Index(),\n\t\tdelimiter:          opts.Delimiter,\n\t\texpect:             opts.Expect,\n\t\tkeymap:             opts.Keymap,\n\t\tkeymapOrg:          keymapCopy,\n\t\tpressed:            \"\",\n\t\tprintQuery:         opts.PrintQuery,\n\t\thistory:            opts.History,\n\t\tmargin:             opts.Margin,\n\t\tpadding:            opts.Padding,\n\t\tunicode:            opts.Unicode,\n\t\tlistenAddr:         opts.ListenAddr,\n\t\tlistenUnsafe:       opts.Unsafe,\n\t\tborderShape:        opts.BorderShape,\n\t\tlistBorderShape:    opts.ListBorderShape,\n\t\tinputBorderShape:   opts.InputBorderShape,\n\t\theaderBorderShape:  opts.HeaderBorderShape,\n\t\theaderLinesShape:   opts.HeaderLinesShape,\n\t\tfooterBorderShape:  opts.FooterBorderShape,\n\t\tborderWidth:        1,\n\t\tlistLabel:          nil,\n\t\tlistLabelOpts:      opts.ListLabel,\n\t\tborderLabel:        nil,\n\t\tborderLabelOpts:    opts.BorderLabel,\n\t\tpreviewLabel:       nil,\n\t\tpreviewLabelOpts:   opts.PreviewLabel,\n\t\tinputLabel:         nil,\n\t\tinputLabelOpts:     opts.InputLabel,\n\t\theaderLabel:        nil,\n\t\theaderLabelOpts:    opts.HeaderLabel,\n\t\tfooterLabel:        nil,\n\t\tfooterLabelOpts:    opts.FooterLabel,\n\t\tcleanExit:          opts.ClearOnExit,\n\t\texecutor:           executor,\n\t\tpaused:             opts.Phony,\n\t\tinputless:          opts.Inputless,\n\t\tcycle:              opts.Cycle,\n\t\thighlightLine:      opts.CursorLine,\n\t\theaderVisible:      true,\n\t\theaderFirst:        opts.HeaderFirst,\n\t\theaderLines:        opts.HeaderLines,\n\t\tgap:                opts.Gap,\n\t\theader:             []Item{},\n\t\tfooter:             opts.Footer,\n\t\theader0:            opts.Header,\n\t\tansi:               opts.Ansi,\n\t\tfreezeLeft:         opts.FreezeLeft,\n\t\tfreezeRight:        opts.FreezeRight,\n\t\tnthAttr:            opts.Theme.Nth.Attr,\n\t\tnth:                opts.Nth,\n\t\tnthCurrent:         opts.Nth,\n\t\twithNthDefault:     opts.WithNthExpr,\n\t\twithNthExpr:        opts.WithNthExpr,\n\t\twithNthEnabled:     opts.WithNth != nil,\n\t\ttabstop:            opts.Tabstop,\n\t\traw:                opts.Raw,\n\t\thasStartActions:    false,\n\t\thasResultActions:   false,\n\t\thasFocusActions:    false,\n\t\thasLoadActions:     false,\n\t\ttriggerLoad:        false,\n\t\treading:            true,\n\t\trunning:            util.NewAtomicBool(true),\n\t\tfailed:             nil,\n\t\tjumping:            jumpDisabled,\n\t\tjumpLabels:         opts.JumpLabels,\n\t\tprinter:            opts.Printer,\n\t\tprintsep:           opts.PrintSep,\n\t\tproxyScript:        opts.ProxyScript,\n\t\tmerger:             em,\n\t\tpassMerger:         em,\n\t\tresultMerger:       em,\n\t\tmatchMap:           make(map[int32]Result),\n\t\tselected:           make(map[int32]selectedItem),\n\t\trunningCmds:        util.NewConcurrentSet[*runningCmd](),\n\t\treqBox:             util.NewEventBox(),\n\t\tinitialPreviewOpts: opts.Preview,\n\t\tpreviewOpts:        opts.Preview,\n\t\tactivePreviewOpts:  &opts.Preview,\n\t\tpreviewer:          previewer{0, []string{}, 0, false, true, disabledState, \"\", []bool{}, [2]int{0, 0}},\n\t\tpreviewed:          previewed{0, 0, 0, false, false, false, false},\n\t\tpreviewBox:         previewBox,\n\t\teventBox:           eventBox,\n\t\tmutex:              sync.Mutex{},\n\t\tuiMutex:            sync.Mutex{},\n\t\tsuppress:           true,\n\t\tslab:               util.MakeSlab(slab16Size, slab32Size),\n\t\ttheme:              opts.Theme,\n\t\tstartChan:          make(chan fitpad, 1),\n\t\tkillChan:           make(chan bool),\n\t\tkilledChan:         make(chan bool),\n\t\tserverInputChan:    make(chan []*action, 100),\n\t\tcallbackChan:       make(chan versionedCallback, maxBgProcesses),\n\t\tbgQueue:            make(map[action][]func(bool)),\n\t\tbgSemaphore:        make(chan struct{}, maxBgProcesses),\n\t\tbgSemaphores:       make(map[action]chan struct{}),\n\t\tkeyChan:            make(chan tui.Event),\n\t\teventChan:          make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)\n\t\ttui:                renderer,\n\t\tttyDefault:         opts.TtyDefault,\n\t\tttyin:              ttyin,\n\t\tinitFunc:           func() error { return renderer.Init() },\n\t\texecuting:          util.NewAtomicBool(false),\n\t\tlastAction:         actStart,\n\t\tlastFocus:          minItem.Index(),\n\t\tnumLinesCache:      make(map[int32]numLinesCacheValue)}\n\tif opts.AcceptNth != nil {\n\t\tt.acceptNth = opts.AcceptNth(t.delimiter)\n\t}\n\n\tbaseTheme := opts.BaseTheme\n\tif baseTheme == nil {\n\t\tbaseTheme = renderer.DefaultTheme()\n\t}\n\t// This should be called before accessing tui.Color*\n\ttui.InitTheme(opts.Theme, baseTheme, opts.Bold, opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())\n\n\t// Gutter character\n\tvar gutterChar, gutterRawChar string\n\tif opts.Gutter != nil {\n\t\tgutterChar = *opts.Gutter\n\t} else if t.unicode {\n\t\tgutterChar = \"▌\"\n\t} else {\n\t\tgutterChar = \" \"\n\t\tt.gutterReverse = true\n\t}\n\n\tif opts.GutterRaw != nil {\n\t\tgutterRawChar = *opts.GutterRaw\n\t} else if t.unicode {\n\t\tgutterRawChar = \"▖\"\n\t} else {\n\t\tgutterRawChar = \":\"\n\t\tt.gutterRawReverse = false\n\t}\n\n\tt.prompt, t.promptLen = t.parsePrompt(opts.Prompt)\n\t// Pre-calculated empty pointer and marker signs\n\tif t.pointerLen == 0 {\n\t\tt.pointerEmpty = \"\"\n\t\tt.pointerEmptyRaw = \"\"\n\t} else {\n\t\tt.pointerEmpty = gutterChar + strings.Repeat(\" \", max(0, t.pointerLen-1))\n\t\tt.pointerEmptyRaw = gutterRawChar + strings.Repeat(\" \", max(0, t.pointerLen-1))\n\t}\n\tt.markerEmpty = strings.Repeat(\" \", t.markerLen)\n\n\t// Labels\n\tt.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false)\n\tt.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)\n\tt.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)\n\tt.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(opts.InputLabel.label, &tui.ColInputLabel, false)\n\tt.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(opts.HeaderLabel.label, &tui.ColHeaderLabel, false)\n\tt.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(opts.FooterLabel.label, &tui.ColFooterLabel, false)\n\n\t// Determine border shape\n\tif t.borderShape == tui.BorderLine {\n\t\tif t.fullscreen {\n\t\t\tt.borderShape = tui.BorderNone\n\t\t} else {\n\t\t\tt.borderShape = tui.BorderTop\n\t\t}\n\t}\n\n\t// Determine input border shape\n\tif t.inputBorderShape == tui.BorderLine {\n\t\tif t.layout == layoutReverse {\n\t\t\tt.inputBorderShape = tui.BorderBottom\n\t\t} else {\n\t\t\tt.inputBorderShape = tui.BorderTop\n\t\t}\n\t}\n\n\t// Determine header border shape\n\tif t.headerBorderShape == tui.BorderLine {\n\t\tif t.layout == layoutReverse {\n\t\t\tt.headerBorderShape = tui.BorderBottom\n\t\t} else {\n\t\t\tt.headerBorderShape = tui.BorderTop\n\t\t}\n\t}\n\n\t// Determine header lines border shape\n\tif t.headerLinesShape == tui.BorderLine {\n\t\tif t.layout == layoutDefault {\n\t\t\tt.headerLinesShape = tui.BorderTop\n\t\t} else {\n\t\t\tt.headerLinesShape = tui.BorderBottom\n\t\t}\n\t}\n\n\t// Determine footer border shape\n\tif t.footerBorderShape == tui.BorderLine {\n\t\tif t.layout == layoutReverse {\n\t\t\tt.footerBorderShape = tui.BorderTop\n\t\t} else {\n\t\t\tt.footerBorderShape = tui.BorderBottom\n\t\t}\n\t}\n\n\t// Disable separator by default if input border is set\n\tif opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {\n\t\tbar := \"─\"\n\t\tif opts.Separator != nil {\n\t\t\tbar = *opts.Separator\n\t\t} else if !t.unicode {\n\t\t\tbar = \"-\"\n\t\t}\n\t\tt.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)\n\t}\n\n\t// Gap line\n\tif t.gap > 0 && len(*opts.GapLine) > 0 {\n\t\tt.gapLine, t.gapLineLen = t.ansiLabelPrinter(*opts.GapLine, &tui.ColGapLine, true)\n\t}\n\n\tif opts.Ellipsis != nil {\n\t\tt.ellipsis = *opts.Ellipsis\n\t} else if t.unicode {\n\t\tt.ellipsis = \"··\"\n\t} else {\n\t\tt.ellipsis = \"..\"\n\t}\n\n\tif t.unicode {\n\t\tt.wrapSign = \"↳ \"\n\t\tt.borderWidth = uniseg.StringWidth(\"│\")\n\t} else {\n\t\tt.wrapSign = \"> \"\n\t}\n\tif opts.WrapSign != nil {\n\t\tt.wrapSign = *opts.WrapSign\n\t}\n\tt.wrapSign, t.wrapSignWidth = t.processTabsStr(t.wrapSign, 0)\n\tt.previewWrapSign = t.wrapSign\n\tt.previewWrapSignWidth = t.wrapSignWidth\n\tif opts.PreviewWrapSign != nil {\n\t\tt.previewWrapSign, t.previewWrapSignWidth = t.processTabsStr(*opts.PreviewWrapSign, 0)\n\t}\n\tif opts.Scrollbar == nil {\n\t\tif t.unicode && t.borderWidth == 1 {\n\t\t\tt.scrollbar = \"│\"\n\t\t} else {\n\t\t\tt.scrollbar = \"|\"\n\t\t}\n\t\tt.previewScrollbar = t.scrollbar\n\t} else {\n\t\trunes := []rune(*opts.Scrollbar)\n\t\tif len(runes) > 0 {\n\t\t\tt.scrollbar = string(runes[0])\n\t\t\tt.previewScrollbar = t.scrollbar\n\t\t\tif len(runes) > 1 {\n\t\t\t\tt.previewScrollbar = string(runes[1])\n\t\t\t}\n\t\t}\n\t}\n\n\tvar resizeActions []*action\n\tresizeActions, t.hasResizeActions = t.keymap[tui.Resize.AsEvent()]\n\tif t.tui.ShouldEmitResizeEvent() {\n\t\tt.keymap[tui.Resize.AsEvent()] = append(toActions(actClearScreen), resizeActions...)\n\t}\n\t_, t.hasStartActions = t.keymap[tui.Start.AsEvent()]\n\t_, t.hasResultActions = t.keymap[tui.Result.AsEvent()]\n\t_, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()]\n\t_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]\n\n\tif t.listenAddr != nil {\n\t\tlistener, port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.dumpStatus)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tt.listener = listener\n\t\tif port > 0 {\n\t\t\tt.listenPort = &port\n\t\t}\n\t}\n\n\tif t.hasStartActions {\n\t\tt.eventChan <- tui.Start.AsEvent()\n\t}\n\n\treturn &t, nil\n}\n\nfunc (t *Terminal) deferActivation() bool {\n\treturn t.initDelay == 0 && (t.hasStartActions || t.hasLoadActions || t.hasResultActions || t.hasFocusActions)\n}\n\nfunc (t *Terminal) environ() []string {\n\treturn t.environImpl(false)\n}\n\nfunc (t *Terminal) environForPreview() []string {\n\treturn t.environImpl(true)\n}\n\nfunc (t *Terminal) environImpl(forPreview bool) []string {\n\tenv := os.Environ()\n\tif t.listenAddr != nil && len(t.listenAddr.sock) > 0 {\n\t\tenv = append(env, \"FZF_SOCK=\"+t.listenAddr.sock)\n\t}\n\tif t.listenPort != nil {\n\t\tenv = append(env, fmt.Sprintf(\"FZF_PORT=%d\", *t.listenPort))\n\t}\n\tenv = append(env, \"FZF_QUERY=\"+string(t.input))\n\tenv = append(env, \"FZF_ACTION=\"+t.lastAction.Name())\n\tenv = append(env, \"FZF_KEY=\"+t.lastKey)\n\tenv = append(env, \"FZF_PROMPT=\"+string(t.promptString))\n\tenv = append(env, \"FZF_GHOST=\"+string(t.ghost))\n\tenv = append(env, \"FZF_POINTER=\"+string(t.pointer))\n\tenv = append(env, \"FZF_PREVIEW_LABEL=\"+t.previewLabelOpts.label)\n\tenv = append(env, \"FZF_BORDER_LABEL=\"+t.borderLabelOpts.label)\n\tenv = append(env, \"FZF_LIST_LABEL=\"+t.listLabelOpts.label)\n\tenv = append(env, \"FZF_INPUT_LABEL=\"+t.inputLabelOpts.label)\n\tenv = append(env, \"FZF_HEADER_LABEL=\"+t.headerLabelOpts.label)\n\tdirection := \"down\"\n\tif t.layout == layoutDefault {\n\t\tdirection = \"up\"\n\t}\n\tenv = append(env, \"FZF_DIRECTION=\"+direction)\n\tif len(t.nthCurrent) > 0 {\n\t\tenv = append(env, \"FZF_NTH=\"+RangesToString(t.nthCurrent))\n\t}\n\tif len(t.withNthExpr) > 0 {\n\t\tenv = append(env, \"FZF_WITH_NTH=\"+t.withNthExpr)\n\t}\n\tif t.raw {\n\t\tval := \"0\"\n\t\tif t.isCurrentItemMatch() {\n\t\t\tval = \"1\"\n\t\t}\n\t\tenv = append(env, \"FZF_RAW=\"+val)\n\t}\n\tinputState := \"enabled\"\n\tif t.inputless {\n\t\tinputState = \"hidden\"\n\t} else if t.paused {\n\t\tinputState = \"disabled\"\n\t}\n\tif t.wrap {\n\t\tif t.wrapWord {\n\t\t\tenv = append(env, \"FZF_WRAP=word\")\n\t\t} else {\n\t\t\tenv = append(env, \"FZF_WRAP=char\")\n\t\t}\n\t}\n\tenv = append(env, \"FZF_INPUT_STATE=\"+inputState)\n\tenv = append(env, fmt.Sprintf(\"FZF_TOTAL_COUNT=%d\", t.count))\n\tenv = append(env, fmt.Sprintf(\"FZF_MATCH_COUNT=%d\", t.resultMerger.Length()))\n\tenv = append(env, fmt.Sprintf(\"FZF_SELECT_COUNT=%d\", len(t.selected)))\n\tenv = append(env, fmt.Sprintf(\"FZF_LINES=%d\", t.areaLines))\n\tenv = append(env, fmt.Sprintf(\"FZF_COLUMNS=%d\", t.areaColumns))\n\tenv = append(env, fmt.Sprintf(\"FZF_POS=%d\", min(t.merger.Length(), t.cy+1)))\n\tenv = append(env, fmt.Sprintf(\"FZF_CLICK_HEADER_LINE=%d\", t.clickHeaderLine))\n\tenv = append(env, fmt.Sprintf(\"FZF_CLICK_HEADER_COLUMN=%d\", t.clickHeaderColumn))\n\tenv = append(env, fmt.Sprintf(\"FZF_CLICK_FOOTER_LINE=%d\", t.clickFooterLine))\n\tenv = append(env, fmt.Sprintf(\"FZF_CLICK_FOOTER_COLUMN=%d\", t.clickFooterColumn))\n\tenv = t.addClickHeaderWord(env)\n\tenv = t.addClickFooterWord(env)\n\n\t// Add preview environment variables if preview is enabled\n\tpwindowSize := t.pwindowSize()\n\tif pwindowSize.Lines > 0 {\n\t\tlines := fmt.Sprintf(\"LINES=%d\", pwindowSize.Lines)\n\t\tcolumns := fmt.Sprintf(\"COLUMNS=%d\", pwindowSize.Columns)\n\t\tif forPreview {\n\t\t\tenv = append(env, lines)\n\t\t\tenv = append(env, columns)\n\t\t}\n\t\tenv = append(env, \"FZF_PREVIEW_\"+lines)\n\t\tenv = append(env, \"FZF_PREVIEW_\"+columns)\n\t\tenv = append(env, fmt.Sprintf(\"FZF_PREVIEW_TOP=%d\", t.tui.Top()+t.pwindow.Top()))\n\t\tenv = append(env, fmt.Sprintf(\"FZF_PREVIEW_LEFT=%d\", t.pwindow.Left()))\n\t}\n\n\treturn env\n}\n\nfunc borderLines(shape tui.BorderShape) int {\n\tlines := 0\n\tif shape.HasTop() {\n\t\tlines++\n\t}\n\tif shape.HasBottom() {\n\t\tlines++\n\t}\n\treturn lines\n}\n\nfunc borderColumns(shape tui.BorderShape, borderWidth int) int {\n\tcolumns := 0\n\tif shape.HasLeft() {\n\t\tcolumns += 1 + borderWidth\n\t}\n\tif shape.HasRight() {\n\t\tcolumns += 1 + borderWidth\n\t}\n\treturn columns\n}\n\nfunc (t *Terminal) visibleHeaderLines() int {\n\tif !t.headerVisible {\n\t\treturn 0\n\t}\n\treturn len(t.header0) + t.headerLines\n}\n\nfunc (t *Terminal) visibleHeaderLinesInList() int {\n\tif t.headerWindow != nil || t.headerLinesWindow != nil {\n\t\treturn 0\n\t}\n\treturn t.visibleHeaderLines()\n}\n\nfunc (t *Terminal) visibleInputLinesInList() int {\n\tif t.inputWindow != nil || t.inputless {\n\t\treturn 0\n\t}\n\tif t.noSeparatorLine() {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// Extra number of lines needed to display fzf\nfunc (t *Terminal) extraLines() int {\n\textra := 0\n\tif !t.inputless {\n\t\textra++\n\t\tif !t.noSeparatorLine() {\n\t\t\textra++\n\t\t}\n\t\tif t.inputBorderShape.Visible() {\n\t\t\textra += borderLines(t.inputBorderShape)\n\t\t}\n\t}\n\tif t.listBorderShape.Visible() {\n\t\textra += borderLines(t.listBorderShape)\n\t}\n\tif t.headerVisible {\n\t\tif t.hasHeaderWindow() {\n\t\t\textra += borderLines(t.headerBorderShape)\n\t\t}\n\t\textra += len(t.header0)\n\t\tif w, shape := t.determineHeaderLinesShape(); w {\n\t\t\textra += borderLines(shape)\n\t\t}\n\t\textra += t.headerLines\n\t}\n\tif len(t.footer) > 0 {\n\t\textra += borderLines(t.footerBorderShape)\n\t\textra += len(t.footer)\n\t}\n\treturn extra\n}\n\nfunc (t *Terminal) MaxFitAndPad() (int, int) {\n\t_, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()\n\tpadHeight := marginInt[0] + marginInt[2] + paddingInt[0] + paddingInt[2]\n\tfit := screenHeight - padHeight - t.extraLines()\n\treturn fit, padHeight\n}\n\nfunc (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool) (labelPrinter, int) {\n\t// Nothing to do\n\tif len(str) == 0 {\n\t\treturn nil, 0\n\t}\n\n\t// Extract ANSI color codes\n\tstr = firstLine(str)\n\ttext, colors, _ := extractColor(str, nil, nil)\n\trunes := []rune(text)\n\n\t// Simpler printer for strings without ANSI colors or tab characters\n\tif colors == nil && !strings.ContainsRune(text, '\\t') {\n\t\tlength := util.StringWidth(text)\n\t\tif length == 0 {\n\t\t\treturn nil, 0\n\t\t}\n\t\tprintFn := func(window tui.Window, limit int) {\n\t\t\tellipsis := []rune{}\n\t\t\tellipsisWidth := 0\n\t\t\tif !fill {\n\t\t\t\tellipsis, ellipsisWidth = util.Truncate(t.ellipsis, limit)\n\t\t\t}\n\t\t\tif length > limit {\n\t\t\t\ttrimmedRunes, _ := t.trimRight(runes, limit-ellipsisWidth)\n\t\t\t\twindow.CPrint(*color, string(trimmedRunes)+string(ellipsis))\n\t\t\t} else if fill {\n\t\t\t\twindow.CPrint(*color, util.RepeatToFill(text, length, limit))\n\t\t\t} else {\n\t\t\t\twindow.CPrint(*color, text)\n\t\t\t}\n\t\t}\n\t\treturn printFn, length\n\t}\n\n\t// Printer that correctly handles ANSI color codes and tab characters\n\titem := &Item{text: util.RunesToChars(runes), colors: colors}\n\tlength := t.displayWidth(runes)\n\tif length == 0 {\n\t\treturn nil, 0\n\t}\n\tresult := Result{item: item}\n\tvar offsets []colorOffset\n\tprintFn := func(window tui.Window, limit int) {\n\t\tif offsets == nil {\n\t\t\t// tui.Col* are not initialized until renderer.Init()\n\t\t\toffsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr, 0, false)\n\t\t}\n\t\tfor limit > 0 {\n\t\t\tif length > limit {\n\t\t\t\ttrimmedRunes, _ := t.trimRight(runes, limit)\n\t\t\t\tt.printColoredString(window, trimmedRunes, offsets, *color)\n\t\t\t\tbreak\n\t\t\t} else if fill {\n\t\t\t\tt.printColoredString(window, runes, offsets, *color)\n\t\t\t\tlimit -= length\n\t\t\t} else {\n\t\t\t\tt.printColoredString(window, runes, offsets, *color)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn printFn, length\n}\n\n// Temporarily switch 'window' so that we can use the existing windows with\n// a different window\nfunc (t *Terminal) withWindow(w tui.Window, f func()) {\n\tprevWindow := t.window\n\tif w != nil {\n\t\tt.window = w\n\t}\n\tf()\n\tt.window = prevWindow\n}\n\nfunc (t *Terminal) parsePrompt(prompt string) (func(), int) {\n\tvar state *ansiState\n\tprompt = firstLine(prompt)\n\ttrimmed, colors, _ := extractColor(prompt, state, nil)\n\titem := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}\n\n\t// \"Prompt>  \"\n\t//  -------    // Do not apply ANSI attributes to the trailing whitespaces\n\t//             // unless the part has a non-default ANSI state\n\tloc := whiteSuffix.FindStringIndex(trimmed)\n\tif loc != nil {\n\t\tblankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{tui.ColPrompt.Fg(), tui.ColPrompt.Bg(), -1, tui.AttrClear, -1, nil}}\n\t\tif item.colors != nil {\n\t\t\tlastColor := (*item.colors)[len(*item.colors)-1]\n\t\t\tif lastColor.offset[1] < int32(loc[1]) {\n\t\t\t\tblankState.offset[0] = lastColor.offset[1]\n\t\t\t\tcolors := append(*item.colors, blankState)\n\t\t\t\titem.colors = &colors\n\t\t\t}\n\t\t} else {\n\t\t\tcolors := []ansiOffset{blankState}\n\t\t\titem.colors = &colors\n\t\t}\n\t}\n\toutput := func() {\n\t\twrap := t.wrap\n\t\tt.wrap = false\n\t\tt.withWindow(t.inputWindow, func() {\n\t\t\tline := t.promptLine()\n\t\t\tpreTask := func(markerClass) int {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\tt.printHighlighted(\n\t\t\t\tResult{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, false, line, line, true, preTask, nil, 0)\n\t\t})\n\t\tt.wrap = wrap\n\t}\n\t_, promptLen := t.processTabsStr(trimmed, 0)\n\n\treturn output, promptLen\n}\n\nfunc (t *Terminal) noSeparatorLine() bool {\n\treturn t.inputless || noSeparatorLine(t.infoStyle, t.separatorLen > 0)\n}\n\nfunc getScrollbar(perLine int, total int, height int, offset int) (int, int) {\n\tif total == 0 || total*perLine <= height {\n\t\treturn 0, 0\n\t}\n\tbarLength := max(1, height*height/(total*perLine))\n\tvar barStart int\n\tif total == height {\n\t\tbarStart = 0\n\t} else {\n\t\tbarStart = min(height-barLength, (height*perLine-barLength)*offset/(total*perLine-height))\n\t}\n\treturn barLength, barStart\n}\n\nfunc (t *Terminal) barCol() int {\n\tif len(t.scrollbar) == 0 && !t.listBorderShape.HasRight() && !t.borderShape.HasRight() && !t.hasPreviewWindowOnRight() {\n\t\treturn 0\n\t}\n\treturn 1\n}\n\nfunc (t *Terminal) wrapCols() int {\n\tif !t.wrap {\n\t\treturn 0 // No wrap\n\t}\n\treturn max(t.window.Width()-(t.pointerLen+t.markerLen+t.barCol()), 1)\n}\n\nfunc (t *Terminal) clearNumLinesCache() {\n\tt.numLinesCache = make(map[int32]numLinesCacheValue)\n}\n\n// Number of lines the item takes including the gap\nfunc (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {\n\tvar numLines int\n\tif !t.wrap && !t.multiLine {\n\t\tnumLines = 1 + t.gap\n\t\treturn numLines, numLines > atMost\n\t}\n\tif cached, prs := t.numLinesCache[item.Index()]; prs {\n\t\t// Can we use this cache? Let's be conservative.\n\t\tif cached.atMost <= atMost {\n\t\t\treturn cached.numLines, false\n\t\t}\n\t}\n\tvar overflow bool\n\tif !t.wrap && t.multiLine {\n\t\tnumLines, overflow = item.text.NumLines(atMost)\n\t} else {\n\t\tvar lines [][]rune\n\t\tlines, overflow = item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop, t.wrapWord)\n\t\tnumLines = len(lines)\n\t}\n\tnumLines += t.gap\n\tif !overflow {\n\t\tt.numLinesCache[item.Index()] = numLinesCacheValue{atMost, numLines}\n\t}\n\treturn numLines, overflow || numLines > atMost\n}\n\nfunc (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) {\n\tif !t.wrap && !t.multiLine {\n\t\ttext := make([]rune, item.text.Length())\n\t\tcopy(text, item.text.ToRunes())\n\t\treturn [][]rune{text}, false\n\t}\n\treturn item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop, t.wrapWord)\n}\n\n// Estimate the average number of lines per item. Instead of going through all\n// items, we only check a few items around the current cursor position.\nfunc (t *Terminal) avgNumLines() int {\n\tif !t.wrap && !t.multiLine {\n\t\treturn 1\n\t}\n\n\tmaxItems := t.maxItems()\n\tnumLines := 0\n\tcount := 0\n\ttotal := t.merger.Length()\n\toffset := max(0, min(t.offset, total-maxItems-1))\n\tfor idx := 0; idx < maxItems && idx+offset < total; idx++ {\n\t\tresult := t.merger.Get(idx + offset)\n\t\tlines, _ := t.numItemLines(result.item, maxItems)\n\t\tnumLines += lines\n\t\tcount++\n\t}\n\tif count == 0 {\n\t\treturn 1\n\t}\n\treturn numLines / count\n}\n\nfunc (t *Terminal) getScrollbar() (int, int) {\n\treturn getScrollbar(t.avgNumLines(), t.merger.Length(), t.maxItems(), t.offset)\n}\n\n// Input returns current query string\nfunc (t *Terminal) Input() (bool, []rune) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\tpaused := t.paused\n\tsrc := t.input\n\tif t.inputOverride != nil {\n\t\tpaused = false\n\t\tsrc = *t.inputOverride\n\t}\n\treturn paused, copySlice(src)\n}\n\n// PauseRendering blocks the terminal from reading items.\n// Must be paired with ResumeRendering.\nfunc (t *Terminal) PauseRendering() {\n\tt.mutex.Lock()\n}\n\n// ResumeRendering releases the lock acquired by PauseRendering.\nfunc (t *Terminal) ResumeRendering() {\n\tt.mutex.Unlock()\n}\n\n// UpdateCount updates the count information\nfunc (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {\n\tt.mutex.Lock()\n\tt.count = cnt\n\tif t.hasLoadActions && t.reading && final {\n\t\tt.triggerLoad = true\n\t}\n\tt.reading = !final\n\tt.failed = failedCommand\n\tsuppressed := t.suppress\n\tt.mutex.Unlock()\n\tt.reqBox.Set(reqInfo, nil)\n\n\t// We want to defer activating the interface when --sync is used and any of\n\t// start, load, or result events are bound\n\tif suppressed && final && !t.deferActivation() {\n\t\tt.reqBox.Set(reqActivate, nil)\n\t}\n}\n\nfunc (t *Terminal) changeHeader(header string) bool {\n\tvar lines []string\n\tif len(header) > 0 {\n\t\tlines = strings.Split(strings.TrimSuffix(header, \"\\n\"), \"\\n\")\n\t}\n\tneedFullRedraw := len(t.header0) != len(lines)\n\tt.header0 = lines\n\tt.clickHeaderLine = 0\n\tt.clickHeaderColumn = 0\n\treturn needFullRedraw\n}\n\nfunc (t *Terminal) changeFooter(footer string) {\n\tvar lines []string\n\tif len(footer) > 0 {\n\t\tlines = strings.Split(strings.TrimSuffix(footer, \"\\n\"), \"\\n\")\n\t}\n\tt.footer = lines\n\tt.clickFooterLine = 0\n\tt.clickFooterColumn = 0\n}\n\n// UpdateHeader updates the header\nfunc (t *Terminal) UpdateHeader(header []Item) {\n\tt.mutex.Lock()\n\t// Pad to t.headerLines so that click coordinate mapping works correctly\n\tif len(header) < t.headerLines {\n\t\tpadded := make([]Item, t.headerLines)\n\t\tcopy(padded, header)\n\t\theader = padded\n\t}\n\tt.header = header\n\tt.mutex.Unlock()\n\tt.reqBox.Set(reqHeader, nil)\n}\n\n// UpdateProgress updates the search progress\nfunc (t *Terminal) UpdateProgress(progress float32) {\n\tt.mutex.Lock()\n\tnewProgress := int(progress * 100)\n\tchanged := t.progress != newProgress\n\tt.progress = newProgress\n\tt.mutex.Unlock()\n\n\tif changed {\n\t\tt.reqBox.Set(reqInfo, nil)\n\t}\n}\n\n// UpdateList updates Merger to display the list\nfunc (t *Terminal) UpdateList(result MatchResult) {\n\tmerger := result.merger\n\tt.mutex.Lock()\n\tprevIndex := minItem.Index()\n\tnewRevision := merger.Revision()\n\tif t.revision.compatible(newRevision) && t.track != trackDisabled {\n\t\tif t.merger.Length() > 0 {\n\t\t\tprevIndex = t.currentIndex()\n\t\t} else if merger.Length() > 0 {\n\t\t\tprevIndex = merger.First().item.Index()\n\t\t}\n\t}\n\tif t.targetIndex != minItem.Index() {\n\t\tprevIndex = t.targetIndex\n\t\tt.targetIndex = minItem.Index()\n\t}\n\tt.progress = 100\n\tt.merger = merger\n\tt.resultMerger = merger\n\tt.passMerger = result.passMerger\n\tif t.raw {\n\t\tt.merger = result.passMerger\n\t\tt.matchMap = t.resultMerger.ToMap()\n\t} else {\n\t\tt.merger = result.merger\n\t\tt.matchMap = make(map[int32]Result)\n\t}\n\tif t.revision != newRevision {\n\t\tif !t.revision.compatible(newRevision) {\n\t\t\t// Reloaded: capture selection keys for restoration, then clear (reload-sync only)\n\t\t\tif t.trackSync && len(t.idNth) > 0 && t.multi > 0 && len(t.selected) > 0 {\n\t\t\t\tt.pendingSelections = make(map[string]selectedItem, len(t.selected))\n\t\t\t\tfor _, sel := range t.selected {\n\t\t\t\t\tkey := t.trackKeyFor(sel.item, t.idNth)\n\t\t\t\t\tt.pendingSelections[key] = sel\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.selected = make(map[int32]selectedItem)\n\t\t\tt.clearNumLinesCache()\n\t\t} else {\n\t\t\t// Trimmed by --tail: filter selection by index\n\t\t\tfiltered := make(map[int32]selectedItem)\n\t\t\tminIndex := merger.minIndex\n\t\t\tmaxIndex := merger.maxIndex\n\t\t\tfor k, v := range t.selected {\n\t\t\t\tvar included bool\n\t\t\t\tif maxIndex > minIndex {\n\t\t\t\t\tincluded = k >= minIndex && k < maxIndex\n\t\t\t\t} else if maxIndex < minIndex { // int32 overflow [==>   <==]\n\t\t\t\t\tincluded = k >= minIndex || k < maxIndex\n\t\t\t\t}\n\t\t\t\tif included {\n\t\t\t\t\tfiltered[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.selected = filtered\n\t\t}\n\t\tt.revision = newRevision\n\t\tt.version++\n\n\t\t// Filter out selections that no longer match after with-nth change.\n\t\t// Must be inside the revision check so we don't consume the flag\n\t\t// on a stale EvtSearchFin from a previous search.\n\t\tif t.filterSelection && t.multi > 0 && len(t.selected) > 0 {\n\t\t\tmatchMap := t.resultMerger.ToMap()\n\t\t\tfiltered := make(map[int32]selectedItem)\n\t\t\tfor k, v := range t.selected {\n\t\t\t\tif _, matched := matchMap[k]; matched {\n\t\t\t\t\tfiltered[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.selected = filtered\n\t\t}\n\t\tt.filterSelection = false\n\t}\n\tif t.triggerLoad {\n\t\tt.triggerLoad = false\n\t\tt.pendingReqList = true\n\t\tt.eventChan <- tui.Load.AsEvent()\n\t}\n\t// Search for the tracked item by nth key\n\t// - reload (async): search eagerly, unblock as soon as match is found\n\t// - reload-sync: wait until stream is complete before searching\n\ttrackWasBlocked := t.trackBlocked\n\tif len(t.trackKey) > 0 && (!t.trackSync || !t.reading) {\n\t\tfound := false\n\t\tfor i := 0; i < t.merger.Length(); i++ {\n\t\t\titem := t.merger.Get(i).item\n\t\t\tidx := item.Index()\n\t\t\tmatch, ok := t.trackKeyCache[idx]\n\t\t\tif !ok {\n\t\t\t\tmatch = t.trackKeyFor(item, t.idNth) == t.trackKey\n\t\t\t\tt.trackKeyCache[idx] = match\n\t\t\t}\n\t\t\tif match {\n\t\t\t\tt.cy = i\n\t\t\t\tif t.track.Current() {\n\t\t\t\t\tt.track.index = idx\n\t\t\t\t}\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found || !t.reading {\n\t\t\tt.unblockTrack()\n\t\t}\n\t} else if prevIndex >= 0 {\n\t\tpos := t.cy - t.offset\n\t\tcount := t.merger.Length()\n\t\ti := t.merger.FindIndex(prevIndex)\n\t\tif i >= 0 {\n\t\t\tt.cy = i\n\t\t\tt.offset = t.cy - pos\n\t\t} else if t.track.Current() {\n\t\t\tt.track = trackDisabled\n\t\t\tt.cy = pos\n\t\t\tt.offset = 0\n\t\t} else if t.cy > count {\n\t\t\t// Try to keep the vertical position when the list shrinks\n\t\t\tt.cy = count - min(count, t.maxItems()) + pos\n\t\t}\n\t}\n\t// Restore selections by id-nth key after reload completes\n\tif !t.reading && t.pendingSelections != nil {\n\t\tfor i := 0; i < t.merger.Length() && len(t.pendingSelections) > 0; i++ {\n\t\t\titem := t.merger.Get(i).item\n\t\t\tkey := t.trackKeyFor(item, t.idNth)\n\t\t\tif sel, found := t.pendingSelections[key]; found {\n\t\t\t\tt.selected[item.Index()] = selectedItem{sel.at, item}\n\t\t\t\tdelete(t.pendingSelections, key)\n\t\t\t}\n\t\t}\n\t\tt.pendingSelections = nil\n\t}\n\tneedActivation := false\n\tif !t.reading {\n\t\tswitch t.resultMerger.Length() {\n\t\tcase 0:\n\t\t\tzero := tui.Zero.AsEvent()\n\t\t\tif _, prs := t.keymap[zero]; prs {\n\t\t\t\tt.pendingReqList = true\n\t\t\t\tt.eventChan <- zero\n\t\t\t}\n\t\t\t// --sync, only 'focus' is bound, but no items to focus\n\t\t\tneedActivation = t.suppress && !t.hasResultActions && !t.hasLoadActions && t.hasFocusActions\n\t\tcase 1:\n\t\t\tone := tui.One.AsEvent()\n\t\t\tif _, prs := t.keymap[one]; prs {\n\t\t\t\tt.pendingReqList = true\n\t\t\t\tt.eventChan <- one\n\t\t\t}\n\t\t}\n\t}\n\tif t.hasResultActions {\n\t\tt.pendingReqList = true\n\t\tt.eventChan <- tui.Result.AsEvent()\n\t}\n\tupdateList := !t.trackBlocked && !t.pendingReqList\n\tupdatePrompt := trackWasBlocked && !t.trackBlocked\n\tt.mutex.Unlock()\n\n\tt.reqBox.Set(reqInfo, nil)\n\tif updateList {\n\t\tt.reqBox.Set(reqList, nil)\n\t}\n\tif updatePrompt {\n\t\tt.reqBox.Set(reqPrompt, nil)\n\t}\n\tif needActivation {\n\t\tt.reqBox.Set(reqActivate, nil)\n\t}\n}\n\nfunc (t *Terminal) output() bool {\n\tif t.printQuery {\n\t\tt.printer(string(t.input))\n\t}\n\tif len(t.expect) > 0 {\n\t\tt.printer(t.pressed)\n\t}\n\tfor _, s := range t.printQueue {\n\t\tt.printer(s)\n\t}\n\ttransform := func(item *Item) string {\n\t\treturn item.AsString(t.ansi)\n\t}\n\tif t.acceptNth != nil {\n\t\ttransform = func(item *Item) string {\n\t\t\treturn item.acceptNth(t.ansi, t.delimiter, t.acceptNth)\n\t\t}\n\t}\n\tfound := len(t.selected) > 0\n\tif !found {\n\t\tcurrent := t.currentItem()\n\t\tif current != nil {\n\t\t\tt.printer(transform(current))\n\t\t\tfound = true\n\t\t}\n\t} else {\n\t\tfor _, sel := range t.sortSelected() {\n\t\t\tt.printer(transform(sel.item))\n\t\t}\n\t}\n\treturn found\n}\n\nfunc (t *Terminal) sortSelected() []selectedItem {\n\tsels := make([]selectedItem, 0, len(t.selected))\n\tfor _, sel := range t.selected {\n\t\tsels = append(sels, sel)\n\t}\n\tsort.Sort(byTimeOrder(sels))\n\treturn sels\n}\n\nfunc (t *Terminal) displayWidth(runes []rune) int {\n\twidth, _ := util.RunesWidth(runes, 0, t.tabstop, math.MaxInt32)\n\treturn width\n}\n\nfunc (t *Terminal) displayWidthWithPrefix(str string, prefixWidth int) int {\n\twidth, _ := util.RunesWidth([]rune(str), prefixWidth, t.tabstop, math.MaxInt32)\n\treturn width\n}\n\nconst (\n\tminWidth  = 4\n\tminHeight = 3\n)\n\nfunc calculateSize(base int, size sizeSpec, occupied int, minSize int) int {\n\tmax := base - occupied\n\tif max < minSize {\n\t\tmax = minSize\n\t}\n\tif size.percent {\n\t\treturn util.Constrain(int(float64(base)*0.01*size.size), minSize, max)\n\t}\n\treturn util.Constrain(int(size.size)+minSize-1, minSize, max)\n}\n\nfunc (t *Terminal) minPreviewSize(opts *previewOpts) (int, int) {\n\tminPreviewWidth := 1 + borderColumns(opts.Border(), t.borderWidth)\n\tminPreviewHeight := 1 + borderLines(opts.Border())\n\n\tswitch opts.position {\n\tcase posLeft, posRight:\n\t\tif len(t.scrollbar) > 0 && !opts.Border().HasRight() {\n\t\t\t// Need a column to show scrollbar\n\t\t\tminPreviewWidth++\n\t\t}\n\t}\n\n\treturn minPreviewWidth, minPreviewHeight\n}\n\nfunc (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {\n\tscreenWidth := t.tui.MaxX()\n\tscreenHeight := t.tui.MaxY()\n\tmarginInt := [4]int{}  // TRBL\n\tpaddingInt := [4]int{} // TRBL\n\tsizeSpecToInt := func(index int, spec sizeSpec) int {\n\t\tif spec.percent {\n\t\t\tvar max float64\n\t\t\tif index%2 == 0 {\n\t\t\t\tmax = float64(screenHeight)\n\t\t\t} else {\n\t\t\t\tmax = float64(screenWidth)\n\t\t\t}\n\t\t\treturn int(max * spec.size * 0.01)\n\t\t}\n\t\treturn int(spec.size)\n\t}\n\tfor idx, sizeSpec := range t.padding {\n\t\tpaddingInt[idx] = sizeSpecToInt(idx, sizeSpec)\n\t}\n\n\tbw := t.borderWidth\n\textraMargin := [4]int{} // TRBL\n\tfor idx, sizeSpec := range t.margin {\n\t\tswitch t.borderShape {\n\t\tcase tui.BorderHorizontal:\n\t\t\textraMargin[idx] += 1 - idx%2\n\t\tcase tui.BorderVertical:\n\t\t\textraMargin[idx] += (1 + bw) * (idx % 2)\n\t\tcase tui.BorderTop:\n\t\t\tif idx == 0 {\n\t\t\t\textraMargin[idx]++\n\t\t\t}\n\t\tcase tui.BorderRight:\n\t\t\tif idx == 1 {\n\t\t\t\textraMargin[idx] += 1 + bw\n\t\t\t}\n\t\tcase tui.BorderBottom:\n\t\t\tif idx == 2 {\n\t\t\t\textraMargin[idx]++\n\t\t\t}\n\t\tcase tui.BorderLeft:\n\t\t\tif idx == 3 {\n\t\t\t\textraMargin[idx] += 1 + bw\n\t\t\t}\n\t\tcase tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:\n\t\t\textraMargin[idx] += 1 + bw*(idx%2)\n\t\t}\n\t\tmarginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]\n\t}\n\n\tadjust := func(idx1 int, idx2 int, maximum int, minimum int) {\n\t\tif minimum > maximum {\n\t\t\tminimum = maximum\n\t\t}\n\t\tmargin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]\n\t\tif maximum-margin < minimum {\n\t\t\tdesired := maximum - minimum\n\t\t\tpaddingInt[idx1] = desired * paddingInt[idx1] / margin\n\t\t\tpaddingInt[idx2] = desired * paddingInt[idx2] / margin\n\t\t\tmarginInt[idx1] = max(extraMargin[idx1], desired*marginInt[idx1]/margin)\n\t\t\tmarginInt[idx2] = max(extraMargin[idx2], desired*marginInt[idx2]/margin)\n\t\t}\n\t}\n\n\tminAreaWidth := minWidth\n\tminAreaHeight := minHeight\n\tif t.inputless {\n\t\tminAreaHeight--\n\t}\n\tif t.noSeparatorLine() {\n\t\tminAreaHeight--\n\t}\n\tif t.needPreviewWindow() {\n\t\tminPreviewWidth, minPreviewHeight := t.minPreviewSize(t.activePreviewOpts)\n\t\tswitch t.activePreviewOpts.position {\n\t\tcase posUp, posDown:\n\t\t\tminAreaHeight += minPreviewHeight\n\t\t\tminAreaWidth = max(minPreviewWidth, minAreaWidth)\n\t\tcase posLeft, posRight:\n\t\t\tminAreaWidth += minPreviewWidth\n\t\t\tminAreaHeight = max(minPreviewHeight, minAreaHeight)\n\t\t}\n\t}\n\tadjust(1, 3, screenWidth, minAreaWidth)\n\tadjust(0, 2, screenHeight, minAreaHeight)\n\n\treturn screenWidth, screenHeight, marginInt, paddingInt\n}\n\nfunc (t *Terminal) forceRerenderList() {\n\tt.prevLines = make([]itemLine, len(t.prevLines))\n}\n\nfunc (t *Terminal) hasHeaderWindow() bool {\n\tif !t.headerVisible {\n\t\treturn false\n\t}\n\tif t.hasHeaderLinesWindow() {\n\t\treturn len(t.header0) > 0\n\t}\n\tif t.headerBorderShape.Visible() {\n\t\treturn len(t.header0)+t.headerLines > 0\n\t}\n\treturn t.inputBorderShape.Visible()\n}\n\nfunc (t *Terminal) hasHeaderLinesWindow() bool {\n\tw, _ := t.determineHeaderLinesShape()\n\treturn w\n}\n\nfunc (t *Terminal) determineHeaderLinesShape() (bool, tui.BorderShape) {\n\tif !t.headerVisible || t.headerLines == 0 {\n\t\treturn false, tui.BorderNone\n\t}\n\n\t// --header-lines-border is set\n\tif t.headerLinesShape != tui.BorderUndefined {\n\t\treturn true, t.headerLinesShape\n\t}\n\n\t// --header-lines-border is not set, determine if we should use\n\t// the style of --header-border\n\tshape := tui.BorderNone\n\tif len(t.header0) == 0 {\n\t\tshape = t.headerBorderShape\n\t}\n\tif shape == tui.BorderNone {\n\t\tshape = tui.BorderPhantom\n\t}\n\n\t// --layout reverse-list is set\n\tif t.layout == layoutReverseList {\n\t\treturn true, shape\n\t}\n\n\t// Use header window instead\n\tif len(t.header0) == 0 {\n\t\treturn false, t.headerBorderShape\n\t}\n\n\t// We have both types of headers, and we want to separate the two\n\tif t.headerFirst {\n\t\treturn true, shape\n\t}\n\n\treturn false, tui.BorderNone\n}\n\nfunc (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {\n\tt.clearNumLinesCache()\n\tt.forcePreview = forcePreview\n\tscreenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()\n\twidth := screenWidth - marginInt[1] - marginInt[3]\n\theight := screenHeight - marginInt[0] - marginInt[2]\n\n\tt.prevLines = make([]itemLine, screenHeight)\n\tif t.border != nil && redrawBorder {\n\t\tt.border = nil\n\t}\n\tif t.window != nil {\n\t\tt.window = nil\n\t}\n\tif t.wborder != nil {\n\t\tt.wborder = nil\n\t}\n\tif t.headerWindow != nil {\n\t\tt.headerWindow = nil\n\t}\n\tif t.headerBorder != nil {\n\t\tt.headerBorder = nil\n\t}\n\tif t.footerWindow != nil {\n\t\tt.footerWindow = nil\n\t}\n\tif t.footerBorder != nil {\n\t\tt.footerBorder = nil\n\t}\n\tif t.headerLinesWindow != nil {\n\t\tt.headerLinesWindow = nil\n\t}\n\tif t.headerLinesBorder != nil {\n\t\tt.headerLinesBorder = nil\n\t}\n\tif t.inputWindow != nil {\n\t\tt.inputWindow = nil\n\t}\n\tif t.inputBorder != nil {\n\t\tt.inputBorder = nil\n\t}\n\tif t.pborder != nil {\n\t\tt.pborder = nil\n\t}\n\thadPreviewWindow := t.hasPreviewWindow()\n\tif hadPreviewWindow {\n\t\tt.pwindow = nil\n\t}\n\t// Reset preview version so that full redraw occurs\n\tt.previewed.version = 0\n\n\tbw := t.borderWidth\n\toffsets := [4]int{} // TRWH\n\tif t.borderShape.HasTop() {\n\t\toffsets[0] -= 1\n\t\toffsets[3] += 1\n\t}\n\tif t.borderShape.HasRight() {\n\t\toffsets[2] += 1 + bw\n\t}\n\tif t.borderShape.HasBottom() {\n\t\toffsets[3] += 1\n\t}\n\tif t.borderShape.HasLeft() {\n\t\toffsets[1] -= 1 + bw\n\t\toffsets[2] += 1 + bw\n\t}\n\tif t.border == nil && t.borderShape.Visible() {\n\t\tt.border = t.tui.NewWindow(\n\t\t\tmarginInt[0]+offsets[0], marginInt[3]+offsets[1], width+offsets[2], height+offsets[3],\n\t\t\ttui.WindowBase, tui.MakeBorderStyle(t.borderShape, t.unicode), true)\n\t}\n\n\t// Add padding to margin\n\tfor idx, val := range paddingInt {\n\t\tmarginInt[idx] += val\n\t}\n\twidth -= paddingInt[1] + paddingInt[3]\n\theight -= paddingInt[0] + paddingInt[2]\n\n\t// Adjust position and size of the list window if input border is set\n\tinputBorderHeight := 0\n\tavailableLines := height\n\n\tshift := 0\n\tshrink := 0\n\thasHeaderWindow := t.hasHeaderWindow()\n\thasFooterWindow := len(t.footer) > 0\n\thasHeaderLinesWindow, headerLinesShape := t.determineHeaderLinesShape()\n\thasInputWindow := !t.inputless && (t.inputBorderShape.Visible() || hasHeaderWindow || hasHeaderLinesWindow)\n\tinputWindowHeight := 2\n\tif t.noSeparatorLine() {\n\t\tinputWindowHeight--\n\t}\n\tif hasInputWindow {\n\t\tinputBorderHeight = util.Constrain(borderLines(t.inputBorderShape)+inputWindowHeight, 0, availableLines)\n\t\tif t.layout == layoutReverse {\n\t\t\tshift = inputBorderHeight\n\t\t\tshrink = inputBorderHeight\n\t\t} else {\n\t\t\tshrink = inputBorderHeight\n\t\t}\n\t\tavailableLines -= inputBorderHeight\n\t} else if !t.inputless {\n\t\tavailableLines -= inputWindowHeight\n\t}\n\n\t// FIXME: Needed?\n\tif t.needPreviewWindow() {\n\t\t_, minPreviewHeight := t.minPreviewSize(t.activePreviewOpts)\n\t\tswitch t.activePreviewOpts.position {\n\t\tcase posUp, posDown:\n\t\t\tavailableLines -= minPreviewHeight\n\t\t}\n\t}\n\n\t// Adjust position and size of the list window if header border is set\n\theaderBorderHeight := 0\n\tif hasHeaderWindow {\n\t\theaderWindowHeight := t.visibleHeaderLines()\n\t\tif hasHeaderLinesWindow {\n\t\t\theaderWindowHeight -= t.headerLines\n\t\t}\n\t\theaderBorderHeight = util.Constrain(borderLines(t.headerBorderShape)+headerWindowHeight, 0, availableLines)\n\t\tif t.layout == layoutReverse {\n\t\t\tshift += headerBorderHeight\n\t\t\tshrink += headerBorderHeight\n\t\t} else {\n\t\t\tshrink += headerBorderHeight\n\t\t}\n\t\tavailableLines -= headerBorderHeight\n\t}\n\n\theaderLinesHeight := 0\n\tif hasHeaderLinesWindow {\n\t\theaderLinesHeight = util.Constrain(borderLines(headerLinesShape)+t.headerLines, 0, availableLines)\n\t\tif t.layout != layoutDefault {\n\t\t\tshift += headerLinesHeight\n\t\t\tshrink += headerLinesHeight\n\t\t} else {\n\t\t\tshrink += headerLinesHeight\n\t\t}\n\t\tavailableLines -= headerLinesHeight\n\t}\n\n\tfooterBorderHeight := 0\n\tif hasFooterWindow {\n\t\t// Footer lines should not take all available lines\n\t\tfooterBorderHeight = util.Constrain(borderLines(t.footerBorderShape)+len(t.footer), 0, availableLines)\n\t\tshrink += footerBorderHeight\n\t\tif t.layout != layoutReverse {\n\t\t\tshift += footerBorderHeight\n\t\t}\n\t\tavailableLines -= footerBorderHeight\n\t}\n\n\t// Set up list border\n\thasListBorder := t.listBorderShape.Visible()\n\tinnerWidth := width\n\tinnerHeight := height\n\tinnerMarginInt := marginInt\n\tinnerBorderFn := func(top int, left int, width int, height int) {\n\t\tif hasListBorder {\n\t\t\tt.wborder = t.tui.NewWindow(\n\t\t\t\ttop+shift, left, width, height-shrink, tui.WindowList, tui.MakeBorderStyle(t.listBorderShape, t.unicode), false)\n\t\t}\n\t}\n\tif hasListBorder {\n\t\tif t.listBorderShape.HasTop() {\n\t\t\tinnerHeight--\n\t\t\tinnerMarginInt[0]++\n\t\t}\n\t\tif t.listBorderShape.HasBottom() {\n\t\t\tinnerHeight--\n\t\t}\n\t\tif t.listBorderShape.HasLeft() {\n\t\t\tinnerWidth -= 2\n\t\t\tinnerMarginInt[3] += 2\n\t\t}\n\t\tif t.listBorderShape.HasRight() {\n\t\t\tinnerWidth--\n\t\t}\n\t}\n\n\tt.areaLines = height\n\tt.areaColumns = width\n\n\t// If none of the inner borders has the right side, but the outer border does, increase the list width by 1 column\n\tlistStickToRight := t.borderShape.HasRight() && !t.listBorderShape.HasRight() && !t.inputBorderShape.HasRight() &&\n\t\t(!t.headerVisible || !t.headerBorderShape.HasRight() || t.visibleHeaderLines() == 0)\n\n\t// Set up preview window\n\tnoBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)\n\tif forcePreview || t.needPreviewWindow() {\n\t\tvar resizePreviewWindows func(previewOpts *previewOpts)\n\t\tresizePreviewWindows = func(previewOpts *previewOpts) {\n\t\t\tt.activePreviewOpts = previewOpts\n\t\t\tif previewOpts.size.size == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\thasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil\n\t\t\tcreatePreviewWindow := func(y int, x int, w int, h int) {\n\t\t\t\tpwidth := w\n\t\t\t\tpheight := h\n\t\t\t\tshape := previewOpts.Border()\n\t\t\t\tpreviewBorder := tui.MakeBorderStyle(shape, t.unicode)\n\t\t\t\tt.pborder = t.tui.NewWindow(y, x, w, h, tui.WindowPreview, previewBorder, false)\n\t\t\t\tpwidth -= borderColumns(shape, bw)\n\t\t\t\tpheight -= borderLines(shape)\n\t\t\t\tif shape.HasLeft() {\n\t\t\t\t\tx += 1 + bw\n\t\t\t\t}\n\t\t\t\tif shape.HasTop() {\n\t\t\t\t\ty += 1\n\t\t\t\t}\n\t\t\t\tif len(t.scrollbar) > 0 && !shape.HasRight() {\n\t\t\t\t\t// Need a column to show scrollbar\n\t\t\t\t\tpwidth -= 1\n\t\t\t\t}\n\t\t\t\tt.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true)\n\t\t\t\tt.pwindow.SetWrapSign(t.previewWrapSign, t.previewWrapSignWidth)\n\t\t\t\tif !hadPreviewWindow {\n\t\t\t\t\tt.pwindow.Erase()\n\t\t\t\t}\n\t\t\t}\n\t\t\tminPreviewWidth, minPreviewHeight := t.minPreviewSize(previewOpts)\n\t\t\tswitch previewOpts.position {\n\t\t\tcase posUp, posDown:\n\t\t\t\tminWindowHeight := minHeight\n\t\t\t\tif t.inputless {\n\t\t\t\t\tminWindowHeight--\n\t\t\t\t}\n\t\t\t\tif t.noSeparatorLine() {\n\t\t\t\t\tminWindowHeight--\n\t\t\t\t}\n\t\t\t\tpheight := calculateSize(height, previewOpts.size, minWindowHeight, minPreviewHeight)\n\t\t\t\tif hasThreshold && pheight < previewOpts.threshold {\n\t\t\t\t\tt.activePreviewOpts = previewOpts.alternative\n\t\t\t\t\tif forcePreview {\n\t\t\t\t\t\tpreviewOpts.alternative.hidden = false\n\t\t\t\t\t}\n\t\t\t\t\tif !previewOpts.alternative.hidden {\n\t\t\t\t\t\tresizePreviewWindows(previewOpts.alternative)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif forcePreview {\n\t\t\t\t\tpreviewOpts.hidden = false\n\t\t\t\t}\n\t\t\t\tif previewOpts.hidden {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlistStickToRight = listStickToRight && !previewOpts.Border().HasRight()\n\t\t\t\tif listStickToRight {\n\t\t\t\t\tinnerWidth++\n\t\t\t\t\twidth++\n\t\t\t\t}\n\n\t\t\t\tpheight = util.Constrain(pheight, minPreviewHeight, availableLines)\n\n\t\t\t\tif previewOpts.position == posUp {\n\t\t\t\t\tinnerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight)\n\t\t\t\t\tt.window = t.tui.NewWindow(\n\t\t\t\t\t\tinnerMarginInt[0]+pheight+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)\n\t\t\t\t\tcreatePreviewWindow(marginInt[0], marginInt[3], width, pheight)\n\t\t\t\t} else {\n\t\t\t\t\tinnerBorderFn(marginInt[0], marginInt[3], width, height-pheight)\n\t\t\t\t\tt.window = t.tui.NewWindow(\n\t\t\t\t\t\tinnerMarginInt[0]+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)\n\t\t\t\t\tcreatePreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)\n\t\t\t\t}\n\t\t\tcase posLeft, posRight:\n\t\t\t\tminListWidth := minWidth\n\t\t\t\tif t.listBorderShape.HasLeft() {\n\t\t\t\t\tminListWidth += 2\n\t\t\t\t}\n\t\t\t\tif t.listBorderShape.HasRight() {\n\t\t\t\t\tminListWidth++\n\t\t\t\t}\n\t\t\t\tpwidth := calculateSize(width, previewOpts.size, minListWidth, minPreviewWidth)\n\t\t\t\tif hasThreshold && pwidth < previewOpts.threshold {\n\t\t\t\t\tt.activePreviewOpts = previewOpts.alternative\n\t\t\t\t\tif forcePreview {\n\t\t\t\t\t\tpreviewOpts.alternative.hidden = false\n\t\t\t\t\t}\n\t\t\t\t\tif !previewOpts.alternative.hidden {\n\t\t\t\t\t\tresizePreviewWindows(previewOpts.alternative)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif forcePreview {\n\t\t\t\t\tpreviewOpts.hidden = false\n\t\t\t\t}\n\t\t\t\tif previewOpts.hidden {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif previewOpts.position == posLeft {\n\t\t\t\t\t// Put scrollbar closer to the right border for consistent look\n\t\t\t\t\tif t.borderShape.HasRight() && !hasListBorder {\n\t\t\t\t\t\tinnerWidth++\n\t\t\t\t\t}\n\t\t\t\t\t// Add a 1-column margin between the preview window and the main window\n\t\t\t\t\tm := 0\n\t\t\t\t\tif !hasListBorder {\n\t\t\t\t\t\tm = 1\n\t\t\t\t\t}\n\t\t\t\t\tt.window = t.tui.NewWindow(\n\t\t\t\t\t\tinnerMarginInt[0]+shift, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink, tui.WindowList, noBorder, true)\n\n\t\t\t\t\t// Clear characters on the margin\n\t\t\t\t\t// fzf --bind 'space:toggle-preview' --preview ':' --preview-window left,1\n\t\t\t\t\tif !hasListBorder {\n\t\t\t\t\t\tfor y := 0; y < innerHeight; y++ {\n\t\t\t\t\t\t\tt.window.Move(y, -1)\n\t\t\t\t\t\t\tt.window.Print(\" \")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// fzf --bind 'space:toggle-preview' --preview ':' --preview-window left,1,border-none\n\t\t\t\t\tif !previewOpts.Border().HasRight() {\n\t\t\t\t\t\tfor y := 0; y < innerHeight; y++ {\n\t\t\t\t\t\t\tt.window.Move(y, -2)\n\t\t\t\t\t\t\tt.window.Print(\" \")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tinnerBorderFn(marginInt[0], marginInt[3]+pwidth, width-pwidth, height)\n\t\t\t\t\tcreatePreviewWindow(marginInt[0], marginInt[3], pwidth, height)\n\t\t\t\t} else {\n\t\t\t\t\t// NOTE: Relaxed condition for the following cases\n\t\t\t\t\t//  fzf --preview 'seq 500' --preview-window border-left --border\n\t\t\t\t\t//  fzf --preview 'seq 500' --preview-window border-left --border --list-border\n\t\t\t\t\t//  fzf --preview 'seq 500' --preview-window border-left --border --input-border\n\t\t\t\t\tlistStickToRight = t.borderShape.HasRight() && !previewOpts.Border().HasRight()\n\t\t\t\t\tif listStickToRight {\n\t\t\t\t\t\tinnerWidth++\n\t\t\t\t\t\twidth++\n\t\t\t\t\t}\n\t\t\t\t\tinnerBorderFn(marginInt[0], marginInt[3], width-pwidth, height)\n\t\t\t\t\tt.window = t.tui.NewWindow(\n\t\t\t\t\t\tinnerMarginInt[0]+shift, innerMarginInt[3], innerWidth-pwidth, innerHeight-shrink, tui.WindowList, noBorder, true)\n\t\t\t\t\tx := marginInt[3] + width - pwidth\n\t\t\t\t\tcreatePreviewWindow(marginInt[0], x, pwidth, height)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresizePreviewWindows(&t.previewOpts)\n\n\t\tif t.borderShape.HasRight() && !listStickToRight {\n\t\t\t// Need to clear the extra margin between the borders\n\t\t\t// fzf --preview 'seq 1000' --preview-window border-left --bind space:change-preview-window:border-rounded --border vertical\n\t\t\t// fzf --preview 'seq 1000' --preview-window up,hidden --bind space:toggle-preview --border vertical\n\t\t\ty := 0\n\t\t\tif t.borderShape.HasTop() {\n\t\t\t\ty++\n\t\t\t}\n\t\t\tmaxY := t.border.Height()\n\t\t\tif t.borderShape.HasBottom() {\n\t\t\t\tmaxY--\n\t\t\t}\n\t\t\tfor ; y < maxY; y++ {\n\t\t\t\tt.border.Move(y, t.border.Width()-2)\n\t\t\t\tt.border.Print(\" \")\n\t\t\t}\n\t\t}\n\t} else {\n\t\tt.activePreviewOpts = &t.previewOpts\n\t}\n\n\t// Without preview window\n\tif t.window == nil {\n\t\tif listStickToRight {\n\t\t\t// Put scrollbar closer to the right border for consistent look\n\t\t\tinnerWidth++\n\t\t\twidth++\n\t\t}\n\t\tinnerBorderFn(marginInt[0], marginInt[3], width, height)\n\t\tt.window = t.tui.NewWindow(\n\t\t\tinnerMarginInt[0]+shift,\n\t\t\tinnerMarginInt[3],\n\t\t\tinnerWidth,\n\t\t\tinnerHeight-shrink, tui.WindowList, noBorder, true)\n\t}\n\n\tif len(t.scrollbar) == 0 {\n\t\tfor y := 0; y < t.window.Height(); y++ {\n\t\t\tt.window.Move(y, t.window.Width()-1)\n\t\t\tt.window.Print(\" \")\n\t\t}\n\t}\n\n\tcreateInnerWindow := func(b tui.Window, shape tui.BorderShape, windowType tui.WindowType, shift int) tui.Window {\n\t\ttop := b.Top()\n\t\tleft := b.Left() + shift\n\t\tif shape.HasTop() {\n\t\t\ttop++\n\t\t}\n\t\tif shape.HasLeft() {\n\t\t\tleft += t.borderWidth + 1\n\t\t}\n\t\twidth := b.Width() - borderColumns(shape, t.borderWidth) - shift\n\t\tif shape.HasRight() {\n\t\t\twidth++\n\t\t}\n\t\t// Make sure that the width does not exceed the list width\n\t\twidth = min(t.window.Width()+t.headerIndentImpl(0, shape), width)\n\t\theight := b.Height() - borderLines(shape)\n\t\treturn t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)\n\t}\n\n\t// Set up input border\n\tw := t.wborder\n\tif t.wborder == nil {\n\t\tw = t.window\n\t}\n\n\tif hasInputWindow {\n\t\tvar btop int\n\t\tif (hasHeaderWindow || hasHeaderLinesWindow) && t.headerFirst {\n\t\t\tswitch t.layout {\n\t\t\tcase layoutDefault:\n\t\t\t\tbtop = w.Top() + w.Height()\n\t\t\t\t// If both headers are present, the header lines are displayed with the list\n\t\t\t\tif hasHeaderWindow && hasHeaderLinesWindow {\n\t\t\t\t\tbtop += headerLinesHeight\n\t\t\t\t}\n\t\t\tcase layoutReverse:\n\t\t\t\tbtop = w.Top() - inputBorderHeight\n\t\t\t\tif hasHeaderWindow && hasHeaderLinesWindow {\n\t\t\t\t\tbtop -= headerLinesHeight\n\t\t\t\t}\n\t\t\tcase layoutReverseList:\n\t\t\t\tbtop = w.Top() + w.Height()\n\t\t\t}\n\t\t} else {\n\t\t\tswitch t.layout {\n\t\t\tcase layoutDefault:\n\t\t\t\tbtop = w.Top() + w.Height() + headerBorderHeight + headerLinesHeight\n\t\t\tcase layoutReverse:\n\t\t\t\tbtop = w.Top() - shrink + footerBorderHeight\n\t\t\tcase layoutReverseList:\n\t\t\t\tbtop = w.Top() + w.Height() + headerBorderHeight\n\t\t\t}\n\t\t}\n\t\tshift := 0\n\t\tif !t.inputBorderShape.HasLeft() && t.listBorderShape.HasLeft() {\n\t\t\tshift += t.borderWidth + 1\n\t\t}\n\t\tt.inputBorder = t.tui.NewWindow(\n\t\t\tbtop,\n\t\t\tw.Left(),\n\t\t\tw.Width(),\n\t\t\tinputBorderHeight, tui.WindowInput, tui.MakeBorderStyle(t.inputBorderShape, t.unicode), true)\n\t\tif shift > 0 && !t.inputBorderShape.Visible() {\n\t\t\t// Small box on the left to erase the residue\n\t\t\t// e.g.\n\t\t\t//  fzf --list-border --header-border --bind 'space:change-header(hello),enter:change-header()'\n\t\t\tt.tui.NewWindow(btop, w.Left(), shift, inputBorderHeight, tui.WindowInput, noBorder, false).Erase()\n\t\t}\n\t\tt.inputWindow = createInnerWindow(t.inputBorder, t.inputBorderShape, tui.WindowInput, shift)\n\t}\n\n\t// Set up header border\n\tif hasHeaderWindow {\n\t\tvar btop int\n\t\tif hasInputWindow && t.headerFirst {\n\t\t\tif t.layout == layoutReverse {\n\t\t\t\tbtop = w.Top() - shrink + footerBorderHeight\n\t\t\t} else if t.layout == layoutReverseList {\n\t\t\t\tbtop = w.Top() + w.Height() + inputBorderHeight\n\t\t\t} else {\n\t\t\t\tbtop = w.Top() + w.Height() + inputBorderHeight + headerLinesHeight\n\t\t\t}\n\t\t} else {\n\t\t\tif t.layout == layoutReverse {\n\t\t\t\tbtop = w.Top() - headerBorderHeight - headerLinesHeight\n\t\t\t} else if t.layout == layoutReverseList {\n\t\t\t\tbtop = w.Top() + w.Height()\n\t\t\t} else {\n\t\t\t\tbtop = w.Top() + w.Height() + headerLinesHeight\n\t\t\t}\n\t\t}\n\t\tt.headerBorder = t.tui.NewWindow(\n\t\t\tbtop,\n\t\t\tw.Left(),\n\t\t\tw.Width(),\n\t\t\theaderBorderHeight, tui.WindowHeader, tui.MakeBorderStyle(t.headerBorderShape, t.unicode), true)\n\t\tt.headerWindow = createInnerWindow(t.headerBorder, t.headerBorderShape, tui.WindowHeader, 0)\n\t}\n\n\t// Set up header lines border\n\tif hasHeaderLinesWindow {\n\t\tvar btop int\n\t\t// NOTE: We still have to handle --header-first here in case\n\t\t// --header-lines-border is set. Can't we just use header window instead\n\t\t// with the style? So we can display header label.\n\t\t//   fzf --header-lines 3 --header-label hello --header-border\n\t\t//   fzf --header-lines 3 --header-label hello --header-lines-border\n\t\theaderFirst := t.headerFirst && len(t.header0) == 0\n\n\t\tif headerFirst {\n\t\t\tif t.layout == layoutDefault {\n\t\t\t\tbtop = w.Top() + w.Height() + inputBorderHeight\n\t\t\t} else if t.layout == layoutReverse {\n\t\t\t\tbtop = w.Top() - headerLinesHeight - inputBorderHeight\n\t\t\t} else {\n\t\t\t\tbtop = w.Top() - headerLinesHeight\n\t\t\t}\n\t\t} else {\n\t\t\tif t.layout != layoutDefault {\n\t\t\t\tbtop = w.Top() - headerLinesHeight\n\t\t\t} else {\n\t\t\t\tbtop = w.Top() + w.Height()\n\t\t\t}\n\t\t}\n\t\tt.headerLinesBorder = t.tui.NewWindow(\n\t\t\tbtop,\n\t\t\tw.Left(),\n\t\t\tw.Width(),\n\t\t\theaderLinesHeight, tui.WindowHeader, tui.MakeBorderStyle(headerLinesShape, t.unicode), true)\n\t\tt.headerLinesWindow = createInnerWindow(t.headerLinesBorder, headerLinesShape, tui.WindowHeader, 0)\n\t}\n\n\t// Set up footer\n\tif hasFooterWindow {\n\t\tvar btop int\n\t\tif t.layout == layoutReverse {\n\t\t\tbtop = w.Top() + w.Height()\n\t\t} else if t.layout == layoutReverseList {\n\t\t\tbtop = w.Top() - footerBorderHeight - headerLinesHeight\n\t\t} else {\n\t\t\tbtop = w.Top() - footerBorderHeight\n\t\t}\n\t\tt.footerBorder = t.tui.NewWindow(\n\t\t\tbtop,\n\t\t\tw.Left(),\n\t\t\tw.Width(),\n\t\t\tfooterBorderHeight, tui.WindowFooter, tui.MakeBorderStyle(t.footerBorderShape, t.unicode), true)\n\t\tt.footerWindow = createInnerWindow(t.footerBorder, t.footerBorderShape, tui.WindowFooter, 0)\n\t}\n\n\t// Print border label\n\tt.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, false)\n\tt.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)\n\tt.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), false)\n\tt.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false)\n\tt.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, false)\n\tt.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, false)\n}\n\nfunc (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {\n\tif window == nil {\n\t\treturn\n\t}\n\n\tif window.Height() == 0 {\n\t\treturn\n\t}\n\n\tswitch borderShape {\n\tcase tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:\n\t\tif redrawBorder {\n\t\t\twindow.DrawHBorder()\n\t\t}\n\t\tif render == nil {\n\t\t\treturn\n\t\t}\n\t\tvar col int\n\t\tif opts.column == 0 {\n\t\t\tcol = max(0, (window.Width()-length)/2)\n\t\t} else if opts.column < 0 {\n\t\t\tcol = max(0, window.Width()+opts.column+1-length)\n\t\t} else {\n\t\t\tcol = min(opts.column-1, window.Width()-length)\n\t\t}\n\t\trow := 0\n\t\tif borderShape == tui.BorderBottom || opts.bottom {\n\t\t\trow = window.Height() - 1\n\t\t}\n\t\twindow.Move(row, col)\n\t\trender(window, window.Width())\n\t}\n}\n\nfunc (t *Terminal) move(y int, x int, clear bool) {\n\th := t.window.Height()\n\n\tswitch t.layout {\n\tcase layoutDefault:\n\t\ty = h - y - 1\n\tcase layoutReverseList:\n\t\tif !t.inListWindow() && t.window != t.headerLinesWindow {\n\t\t\t// From bottom to top\n\t\t\ty = h - y - 1\n\t\t} else {\n\t\t\t/*\n\t\t\t * List 1\n\t\t\t * List 2\n\t\t\t * Header 1\n\t\t\t * Header 2\n\t\t\t * Input 2\n\t\t\t * Input 1\n\t\t\t */\n\t\t\ti := t.visibleInputLinesInList()\n\t\t\tn := t.visibleHeaderLinesInList()\n\t\t\tif i > 0 && y < i {\n\t\t\t\ty = h - y - 1\n\t\t\t} else if n > 0 && y < i+n {\n\t\t\t\ty = h - y - 1\n\t\t\t} else {\n\t\t\t\t// Top to bottom\n\t\t\t\ty -= n + i\n\t\t\t}\n\t\t}\n\t}\n\n\tif clear {\n\t\tt.window.MoveAndClear(y, x)\n\t} else {\n\t\tt.window.Move(y, x)\n\t}\n}\n\nfunc (t *Terminal) truncateQuery() {\n\t// We're limiting the length of the query not to make fzf unresponsive when\n\t// the user accidentally pastes a huge chunk of text. Therefore, we're not\n\t// interested in the exact display width of the query. We just limit the\n\t// number of runes.\n\tt.input = t.input[:min(len(t.input), maxPatternLength)]\n\tt.cx = util.Constrain(t.cx, 0, len(t.input))\n}\n\nfunc (t *Terminal) updatePromptOffset() ([]rune, []rune) {\n\tw := t.window\n\tif t.inputWindow != nil {\n\t\tw = t.inputWindow\n\t}\n\tmaxWidth := max(1, w.Width()-t.promptLen-1)\n\n\t_, overflow := t.trimLeft(t.input[:t.cx], maxWidth, 0)\n\tminOffset := int(overflow)\n\tmaxOffset := minOffset + (maxWidth-max(0, maxWidth-t.cx))/2\n\tt.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)\n\tbefore, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth, 0)\n\tbeforeLen := t.displayWidth(before)\n\tafter, _ := t.trimRight(t.input[t.cx:], maxWidth-beforeLen)\n\tafterLen := t.displayWidth(after)\n\tt.queryLen = [2]int{beforeLen, afterLen}\n\treturn before, after\n}\n\nfunc (t *Terminal) promptLine() int {\n\tif t.inputWindow != nil {\n\t\treturn 0\n\t}\n\tif t.headerFirst {\n\t\tmax := t.window.Height() - 1\n\t\tif max <= 0 { // Extremely short terminal\n\t\t\treturn 0\n\t\t}\n\t\tif !t.noSeparatorLine() {\n\t\t\tmax--\n\t\t}\n\t\treturn min(t.visibleHeaderLinesInList(), max)\n\t}\n\treturn 0\n}\n\nfunc (t *Terminal) placeCursor() {\n\tif t.inputless {\n\t\treturn\n\t}\n\tx := t.promptLen + t.queryLen[0]\n\tif t.inputWindow != nil {\n\t\ty := t.inputWindow.Height() - 1\n\t\tif t.layout == layoutReverse {\n\t\t\ty = 0\n\t\t}\n\t\tx = min(x, t.inputWindow.Width()-1)\n\t\tt.inputWindow.Move(y, x)\n\t\treturn\n\t}\n\tx = min(x, t.window.Width()-1)\n\tt.move(t.promptLine(), x, false)\n}\n\nfunc (t *Terminal) printPrompt() {\n\tif t.inputless {\n\t\treturn\n\t}\n\tw := t.window\n\tif t.inputWindow != nil {\n\t\tw = t.inputWindow\n\t}\n\tif w.Height() == 0 {\n\t\treturn\n\t}\n\tt.prompt()\n\n\tbefore, after := t.updatePromptOffset()\n\tif len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {\n\t\tmaxWidth := max(1, w.Width()-t.promptLen-1)\n\t\trunes, _ := t.trimRight([]rune(t.ghost), maxWidth)\n\t\tw.CPrint(tui.ColGhost, string(runes))\n\t\treturn\n\t}\n\n\tcolor := tui.ColInput\n\tif t.paused {\n\t\tcolor = tui.ColDisabled\n\t} else if t.trackBlocked {\n\t\tcolor = color.WithAttr(tui.Dim)\n\t}\n\tw.CPrint(color, string(before))\n\tw.CPrint(color, string(after))\n}\n\nfunc (t *Terminal) trimMessage(message string, maxWidth int) string {\n\tif len(message) <= maxWidth {\n\t\treturn message\n\t}\n\trunes, _ := t.trimRight([]rune(message), maxWidth-2)\n\treturn string(runes) + strings.Repeat(\".\", util.Constrain(maxWidth, 0, 2))\n}\n\nfunc (t *Terminal) printInfo() {\n\tif t.inputless {\n\t\treturn\n\t}\n\tt.withWindow(t.inputWindow, func() { t.printInfoImpl() })\n}\n\nfunc (t *Terminal) printInfoImpl() {\n\tif t.window.Width() <= 1 || t.window.Height() == 0 {\n\t\treturn\n\t}\n\tpos := 0\n\tline := t.promptLine()\n\tmaxHeight := t.window.Height()\n\tmove := func(y int, x int, clear bool) bool {\n\t\tif y < 0 || y >= maxHeight {\n\t\t\treturn false\n\t\t}\n\t\tt.move(y, x, clear)\n\t\tt.markOtherLine(y)\n\t\treturn true\n\t}\n\tprintSpinner := func() {\n\t\tif t.reading {\n\t\t\tduration := int64(spinnerDuration)\n\t\t\tidx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration\n\t\t\tt.window.CPrint(tui.ColSpinner, t.spinner[idx])\n\t\t} else {\n\t\t\tt.window.Print(\" \") // Clear spinner\n\t\t}\n\t}\n\tprintInfoPrefix := func() {\n\t\tstr := t.infoPrefix\n\t\tmaxWidth := t.window.Width() - pos\n\t\twidth := util.StringWidth(str)\n\t\tif width > maxWidth {\n\t\t\ttrimmed, _ := t.trimRight([]rune(str), maxWidth)\n\t\t\tstr = string(trimmed)\n\t\t\twidth = maxWidth\n\t\t}\n\t\tmove(line, pos, t.separatorLen == 0)\n\t\tif t.reading {\n\t\t\tt.window.CPrint(tui.ColSpinner, str)\n\t\t} else {\n\t\t\tt.window.CPrint(tui.ColPrompt, str)\n\t\t}\n\t\tpos += width\n\t}\n\tprintSeparator := func(fillLength int, pad bool) {\n\t\tif t.separatorLen > 0 {\n\t\t\tt.separator(t.window, fillLength)\n\t\t\tt.window.Print(\" \")\n\t\t} else if pad {\n\t\t\tt.window.Print(strings.Repeat(\" \", fillLength+1))\n\t\t}\n\t}\n\n\tif t.infoStyle == infoHidden {\n\t\tif t.separatorLen > 0 {\n\t\t\tif !move(line+1, 0, false) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tprintSeparator(t.window.Width()-1, false)\n\t\t}\n\t\treturn\n\t}\n\n\tfound := t.resultMerger.Length()\n\ttotal := max(found, t.count)\n\toutput := fmt.Sprintf(\"%d/%d\", found, total)\n\tif t.multi > 0 {\n\t\tif t.multi == maxMulti {\n\t\t\toutput += fmt.Sprintf(\" (%d)\", len(t.selected))\n\t\t} else {\n\t\t\toutput += fmt.Sprintf(\" (%d/%d)\", len(t.selected), t.multi)\n\t\t}\n\t}\n\tif t.progress > 0 && t.progress < 100 {\n\t\toutput += fmt.Sprintf(\" (%d%%)\", t.progress)\n\t}\n\tif t.toggleSort {\n\t\tif t.sort {\n\t\t\toutput += \" +S\"\n\t\t} else {\n\t\t\toutput += \" -S\"\n\t\t}\n\t}\n\tif t.track.Global() {\n\t\tif t.trackBlocked {\n\t\t\toutput += \" +T*\"\n\t\t} else {\n\t\t\toutput += \" +T\"\n\t\t}\n\t} else if t.track.Current() {\n\t\tif t.trackBlocked {\n\t\t\toutput += \" +t*\"\n\t\t} else {\n\t\t\toutput += \" +t\"\n\t\t}\n\t}\n\tif t.failed != nil && t.count == 0 {\n\t\toutput = fmt.Sprintf(\"[Command failed: %s]\", *t.failed)\n\t}\n\tvar outputPrinter labelPrinter\n\toutputLen := len(output)\n\tif t.infoCommand != \"\" {\n\t\toutput = t.executeCommand(t.infoCommand, false, true, true, true, output)\n\t\toutputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)\n\t}\n\n\tshiftLen := t.queryLen[0] + t.queryLen[1] + 1\n\tif shiftLen == 1 && len(t.ghost) > 0 {\n\t\tshiftLen = util.StringWidth(t.ghost)\n\t}\n\tswitch t.infoStyle {\n\tcase infoDefault:\n\t\tif !move(line+1, 0, t.separatorLen == 0) {\n\t\t\treturn\n\t\t}\n\t\tprintSpinner()\n\t\tt.window.Print(\" \") // Margin\n\t\tpos = 2\n\tcase infoRight:\n\t\tif !move(line+1, 0, false) {\n\t\t\treturn\n\t\t}\n\tcase infoInlineRight:\n\t\tpos = t.promptLen + shiftLen\n\tcase infoInline:\n\t\tpos = t.promptLen + shiftLen\n\t\tprintInfoPrefix()\n\t}\n\n\tif t.infoStyle == infoRight {\n\t\tmaxWidth := t.window.Width() - 1\n\t\tif t.reading {\n\t\t\t// Need space for spinner and a margin column\n\t\t\tmaxWidth -= 2\n\t\t}\n\t\tvar fillLength int\n\t\tif outputPrinter == nil {\n\t\t\toutput = t.trimMessage(output, maxWidth)\n\t\t\tfillLength = t.window.Width() - len(output) - 2\n\t\t} else {\n\t\t\tfillLength = t.window.Width() - outputLen - 2\n\t\t}\n\t\tif t.reading {\n\t\t\tif fillLength >= 2 {\n\t\t\t\tprintSeparator(fillLength-2, true)\n\t\t\t}\n\t\t\tprintSpinner()\n\t\t\tt.window.Print(\" \")\n\t\t} else if fillLength >= 0 {\n\t\t\tprintSeparator(fillLength, true)\n\t\t}\n\t\tif outputPrinter == nil {\n\t\t\tt.window.CPrint(tui.ColInfo, output)\n\t\t} else {\n\t\t\toutputPrinter(t.window, maxWidth-1)\n\t\t}\n\t\tif fillLength >= 0 {\n\t\t\tt.window.Print(\" \") // Margin\n\t\t}\n\t\treturn\n\t}\n\n\tif t.infoStyle == infoInlineRight {\n\t\tif len(t.infoPrefix) == 0 {\n\t\t\tmove(line, pos, false)\n\t\t\tnewPos := max(pos, t.window.Width()-outputLen-3)\n\t\t\tt.window.Print(strings.Repeat(\" \", newPos-pos))\n\t\t\tpos = newPos\n\t\t\tif pos < t.window.Width() {\n\t\t\t\tprintSpinner()\n\t\t\t\tpos++\n\t\t\t}\n\t\t\tif pos < t.window.Width()-1 {\n\t\t\t\tt.window.Print(\" \")\n\t\t\t\tpos++\n\t\t\t}\n\t\t} else {\n\t\t\tpos = max(pos, t.window.Width()-outputLen-util.StringWidth(t.infoPrefix)-1)\n\t\t\tprintInfoPrefix()\n\t\t}\n\t}\n\n\tmaxWidth := t.window.Width() - pos - 1\n\tif outputPrinter == nil {\n\t\toutput = t.trimMessage(output, maxWidth)\n\t\tt.window.CPrint(tui.ColInfo, output)\n\t} else {\n\t\toutputPrinter(t.window, maxWidth)\n\t}\n\tif t.infoStyle == infoInline && outputLen < maxWidth-1 && t.reading {\n\t\tt.window.Print(\" \")\n\t\tprintSpinner()\n\t\toutputLen += 2\n\t}\n\n\tif t.infoStyle == infoInlineRight {\n\t\tif t.separatorLen > 0 {\n\t\t\tif !move(line+1, 0, false) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tprintSeparator(t.window.Width()-1, false)\n\t\t}\n\t\treturn\n\t}\n\n\tfillLength := maxWidth - outputLen - 1\n\tif fillLength > 0 {\n\t\tt.window.CPrint(tui.ColSeparator, \" \")\n\t\tprintSeparator(fillLength, false)\n\t}\n}\n\nfunc (t *Terminal) resizeIfNeeded() bool {\n\t// Check if input border is used and input has changed\n\tif t.inputBorderShape.Visible() && t.inputWindow == nil && !t.inputless || t.inputWindow != nil && t.inputless {\n\t\tt.printAll()\n\t\treturn true\n\t}\n\n\t// Check footer window\n\tif len(t.footer) > 0 && (t.footerWindow == nil || t.footerWindow.Height() != len(t.footer)) ||\n\t\tlen(t.footer) == 0 && t.footerWindow != nil {\n\t\tt.printAll()\n\t\treturn true\n\t}\n\n\t// Check if the header borders are used and header has changed\n\tallHeaderLines := t.visibleHeaderLines()\n\tprimaryHeaderLines := allHeaderLines\n\tneedHeaderWindow := t.hasHeaderWindow()\n\tneedHeaderLinesWindow := t.hasHeaderLinesWindow()\n\tif needHeaderLinesWindow {\n\t\tprimaryHeaderLines -= t.headerLines\n\t}\n\t// FIXME: Full redraw is triggered if there are too many lines in the header\n\t// so that the header window cannot display all of them.\n\tif (needHeaderWindow && t.headerWindow == nil) ||\n\t\t(!needHeaderWindow && t.headerWindow != nil) ||\n\t\t(needHeaderWindow && t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||\n\t\t(needHeaderLinesWindow && t.headerLinesWindow == nil) ||\n\t\t(!needHeaderLinesWindow && t.headerLinesWindow != nil) ||\n\t\t(needHeaderLinesWindow && t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) {\n\t\tt.printAll()\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (t *Terminal) printHeader() {\n\tif !t.headerVisible {\n\t\treturn\n\t}\n\n\tt.withWindow(t.headerWindow, func() {\n\t\tvar headerItems []Item\n\t\tif !t.hasHeaderLinesWindow() {\n\t\t\theaderItems = t.header\n\t\t}\n\t\tt.printHeaderImpl(t.headerWindow, t.headerBorderShape, t.header0, headerItems)\n\t})\n\tif w, shape := t.determineHeaderLinesShape(); w {\n\t\tt.withWindow(t.headerLinesWindow, func() {\n\t\t\tt.printHeaderImpl(t.headerLinesWindow, shape, nil, t.header)\n\t\t})\n\t}\n}\n\nfunc (t *Terminal) printFooter() {\n\tif len(t.footer) == 0 {\n\t\treturn\n\t}\n\tindentSize := t.headerIndent(t.footerBorderShape)\n\tindent := strings.Repeat(\" \", indentSize)\n\tmax := min(len(t.footer), t.footerWindow.Height())\n\n\t// Wrapping is not supported for footer\n\twrap := t.wrap\n\tt.wrap = false\n\tt.withWindow(t.footerWindow, func() {\n\t\tvar state *ansiState\n\t\tfor idx, lineStr := range t.footer[:max] {\n\t\t\tline := idx\n\t\t\tif t.layout != layoutReverse {\n\t\t\t\tline = max - idx - 1\n\t\t\t}\n\t\t\ttrimmed, colors, newState := extractColor(lineStr, state, nil)\n\t\t\tstate = newState\n\t\t\titem := &Item{\n\t\t\t\ttext:   util.ToChars([]byte(trimmed)),\n\t\t\t\tcolors: colors}\n\n\t\t\tt.printHighlighted(Result{item: item},\n\t\t\t\ttui.ColFooter, tui.ColFooter, false, false, false, line, line, true,\n\t\t\t\tfunc(markerClass) int {\n\t\t\t\t\tt.footerWindow.Print(indent)\n\t\t\t\t\treturn indentSize\n\t\t\t\t}, nil, 0)\n\t\t}\n\t})\n\tt.wrap = wrap\n}\n\nfunc (t *Terminal) headerIndent(borderShape tui.BorderShape) int {\n\treturn t.headerIndentImpl(t.pointerLen+t.markerLen, borderShape)\n}\n\nfunc (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {\n\tindentSize := base\n\tif t.listBorderShape.HasLeft() {\n\t\tindentSize += 1 + t.borderWidth\n\t}\n\tif borderShape.HasLeft() {\n\t\tindentSize -= 1 + t.borderWidth\n\t\tif indentSize < 0 {\n\t\t\tindentSize = 0\n\t\t}\n\t}\n\treturn indentSize\n}\n\nfunc (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShape, lines1 []string, lines2 []Item) {\n\tmax := t.window.Height()\n\tif !t.inputless && t.inputWindow == nil && window == nil && t.headerFirst {\n\t\tmax--\n\t\tif !t.noSeparatorLine() {\n\t\t\tmax--\n\t\t}\n\t}\n\tvar state *ansiState\n\tneedReverse := false\n\tswitch t.layout {\n\tcase layoutDefault, layoutReverseList:\n\t\tneedReverse = true\n\t}\n\t// Wrapping is not supported for header\n\twrap := t.wrap\n\n\t// Align header with the list\n\t//   fzf --header-lines 3 --style full --no-list-border\n\t//   fzf --header-lines 3 --style full --no-header-border\n\t//   fzf --header-lines 3 --style full --no-header-border --no-input-border\n\tindentSize := t.pointerLen + t.markerLen\n\tif window != nil {\n\t\tindentSize = t.headerIndent(borderShape)\n\t}\n\tindent := strings.Repeat(\" \", indentSize)\n\tt.wrap = false\n\ttotalLines := len(lines1) + len(lines2)\n\tfor idx := 0; idx < totalLines; idx++ {\n\t\tline := idx\n\t\tif needReverse && idx < len(lines1) {\n\t\t\tline = len(lines1) - idx - 1\n\t\t}\n\t\tif !t.inputless && t.inputWindow == nil && window == nil && !t.headerFirst {\n\t\t\tline++\n\t\t\tif !t.noSeparatorLine() {\n\t\t\t\tline++\n\t\t\t}\n\t\t}\n\t\tif line >= max {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar item *Item\n\t\tif idx < len(lines1) {\n\t\t\ttrimmed, colors, newState := extractColor(lines1[idx], state, nil)\n\t\t\tstate = newState\n\t\t\titem = &Item{\n\t\t\t\ttext:   util.ToChars([]byte(trimmed)),\n\t\t\t\tcolors: colors}\n\t\t} else {\n\t\t\theaderItem := lines2[idx-len(lines1)]\n\t\t\titem = &headerItem\n\t\t}\n\n\t\tt.printHighlighted(Result{item: item},\n\t\t\ttui.ColHeader, tui.ColHeader, false, false, false, line, line, true,\n\t\t\tfunc(markerClass) int {\n\t\t\t\tt.window.Print(indent)\n\t\t\t\treturn indentSize\n\t\t\t}, nil, 0)\n\t}\n\tt.wrap = wrap\n}\n\nfunc (t *Terminal) canSpanMultiLines() bool {\n\treturn (t.multiLine || t.wrap || t.gap > 0) && t.inListWindow()\n}\n\nfunc (t *Terminal) renderBar(line int, barRange [2]int) {\n\t// If the screen is not filled with the list in non-multi-line mode,\n\t// scrollbar is not visible at all. But in multi-line mode, we may need\n\t// to redraw the scrollbar character at the end.\n\tif t.canSpanMultiLines() {\n\t\tt.prevLines[line].hasBar = t.printBar(line, true, barRange)\n\t}\n}\n\nfunc (t *Terminal) renderEmptyLine(line int, barRange [2]int) {\n\tt.move(line, 0, true)\n\tt.markEmptyLine(line)\n\tt.renderBar(line, barRange)\n}\n\nfunc (t *Terminal) gutter(current bool, alt bool) {\n\tvar color tui.ColorPair\n\tif current {\n\t\tcolor = tui.ColCurrentCursorEmpty\n\t} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {\n\t\tif alt {\n\t\t\tcolor = tui.ColAltCursorEmpty\n\t\t} else {\n\t\t\tcolor = tui.ColCursorEmpty\n\t\t}\n\t} else {\n\t\tif alt {\n\t\t\tcolor = tui.ColAltCursorEmptyChar\n\t\t} else {\n\t\t\tcolor = tui.ColCursorEmptyChar\n\t\t}\n\t}\n\tgutter := t.pointerEmpty\n\tif t.raw {\n\t\tgutter = t.pointerEmptyRaw\n\t}\n\tt.window.CPrint(color, gutter)\n}\n\nfunc (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {\n\tt.move(line, 0, false)\n\tt.gutter(false, false)\n\tt.window.Print(t.markerEmpty)\n\tx := t.pointerLen + t.markerLen\n\n\twidth := t.window.Width() - x - 1\n\tif drawLine && t.gapLine != nil {\n\t\tt.gapLine(t.window, width)\n\t} else {\n\t\tt.move(line, x, true)\n\t}\n\tt.markOtherLine(line)\n\tt.renderBar(line, barRange)\n\tt.prevLines[line].width = width\n}\n\nfunc (t *Terminal) printList() {\n\tt.constrain()\n\tbarLength, barStart := t.getScrollbar()\n\n\tmaxy := t.maxItems() - 1\n\tcount := t.merger.Length() - t.offset\n\n\t// Start line\n\tstartLine := t.promptLines() + t.visibleHeaderLinesInList()\n\tmaxy += startLine\n\n\tbarRange := [2]int{startLine + barStart, startLine + barStart + barLength}\n\tfor line, itemCount := startLine, 0; line <= maxy; line, itemCount = line+1, itemCount+1 {\n\t\tif itemCount < count {\n\t\t\titem := t.merger.Get(itemCount + t.offset)\n\t\t\tcurrent := itemCount == t.cy-t.offset\n\t\t\tline = t.printItem(item, line, maxy, itemCount, current, barRange)\n\t\t} else if !t.prevLines[line].empty {\n\t\t\tt.renderEmptyLine(line, barRange)\n\t\t}\n\t}\n}\n\nfunc (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool {\n\thasBar := lineNum >= barRange[0] && lineNum < barRange[1]\n\tif (hasBar != t.prevLines[lineNum].hasBar || forceRedraw) && t.window.Width() > 0 {\n\t\tif len(t.scrollbar) > 0 {\n\t\t\tt.move(lineNum, t.window.Width()-1, true)\n\t\t\tif hasBar {\n\t\t\t\tt.window.CPrint(tui.ColScrollbar, t.scrollbar)\n\t\t\t}\n\t\t}\n\t}\n\treturn hasBar\n}\n\nfunc (t *Terminal) printItem(result Result, line int, maxLine int, index int, current bool, barRange [2]int) int {\n\titem := result.item\n\tmatched := true\n\tvar matchResult Result\n\tif t.raw {\n\t\tif matchResult, matched = t.matchMap[item.Index()]; matched {\n\t\t\tresult = matchResult\n\t\t}\n\t}\n\n\t_, selected := t.selected[item.Index()]\n\tlabel := \"\"\n\textraWidth := 0\n\talt := false\n\taltBg := t.theme.AltBg\n\tselectedBg := selected && t.theme.SelectedBg != t.theme.ListBg\n\tif t.jumping != jumpDisabled {\n\t\tif index < len(t.jumpLabels) {\n\t\t\t// Striped\n\t\t\tif !altBg.IsColorDefined() {\n\t\t\t\taltBg = t.theme.DarkBg\n\t\t\t\talt = index%2 == 0\n\t\t\t} else {\n\t\t\t\talt = index%2 == 1\n\t\t\t}\n\t\t\tlabel = t.jumpLabels[index:index+1] + strings.Repeat(\" \", max(0, t.pointerLen-1))\n\t\t\tif t.pointerLen == 0 {\n\t\t\t\textraWidth = 1\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif current {\n\t\t\tlabel = t.pointer\n\t\t}\n\t\talt = !selectedBg && altBg.IsColorDefined() && index%2 == 1\n\t}\n\n\t// Avoid unnecessary redraw\n\tnumLines, _ := t.numItemLines(item, maxLine-line+1)\n\tnewLine := itemLine{valid: true, firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label,\n\t\tresult: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1], hidden: !matched}\n\tprevLine := t.prevLines[line]\n\tforceRedraw := !prevLine.valid || prevLine.other || prevLine.firstLine != newLine.firstLine\n\tprintBar := func(lineNum int, forceRedraw bool) bool {\n\t\treturn t.printBar(lineNum, forceRedraw, barRange)\n\t}\n\n\tif !forceRedraw &&\n\t\tprevLine.hidden == newLine.hidden &&\n\t\tprevLine.numLines == newLine.numLines &&\n\t\tprevLine.current == newLine.current &&\n\t\tprevLine.selected == newLine.selected &&\n\t\tprevLine.label == newLine.label &&\n\t\tprevLine.queryLen == newLine.queryLen &&\n\t\tprevLine.result == newLine.result {\n\t\tt.prevLines[line].hasBar = printBar(line, false)\n\t\treturn line + numLines - 1\n\t}\n\n\tmaxWidth := t.window.Width() - (t.pointerLen + t.markerLen + t.barCol())\n\tpostTask := func(lineNum int, width int, wrapped bool, forceRedraw bool, lbg tui.ColorPair) {\n\t\twidth += extraWidth\n\t\tif (current || selected || alt) && t.highlightLine || lbg.IsFullBgMarker() {\n\t\t\tcolor := tui.ColSelected\n\t\t\tif lbg.IsFullBgMarker() {\n\t\t\t\tcolor = lbg\n\t\t\t} else if current {\n\t\t\t\tcolor = tui.ColCurrent\n\t\t\t} else if alt {\n\t\t\t\tcolor = color.WithBg(altBg)\n\t\t\t}\n\t\t\tfillSpaces := maxWidth - width\n\t\t\tif wrapped {\n\t\t\t\tfillSpaces -= t.wrapSignWidth\n\t\t\t}\n\t\t\tif fillSpaces > 0 {\n\t\t\t\tt.window.CPrint(color, strings.Repeat(\" \", fillSpaces))\n\t\t\t}\n\t\t\tnewLine.width = maxWidth\n\t\t} else {\n\t\t\tvar fillSpaces int\n\t\t\tif forceRedraw {\n\t\t\t\tfillSpaces = maxWidth - width\n\t\t\t} else {\n\t\t\t\tfillSpaces = t.prevLines[lineNum].width - width\n\t\t\t}\n\t\t\tif wrapped {\n\t\t\t\tfillSpaces -= t.wrapSignWidth\n\t\t\t}\n\t\t\tif fillSpaces > 0 {\n\t\t\t\tt.window.Print(strings.Repeat(\" \", fillSpaces))\n\t\t\t}\n\t\t\tnewLine.width = width\n\t\t\tif wrapped {\n\t\t\t\tnewLine.width += t.wrapSignWidth\n\t\t\t}\n\t\t}\n\t\t// When width is 0, line is completely cleared. We need to redraw scrollbar\n\t\tnewLine.hasBar = printBar(lineNum, forceRedraw || width == 0)\n\t\tt.prevLines[lineNum] = newLine\n\t}\n\n\tvar finalLineNum int\n\tmarkerFor := func(markerClass markerClass) string {\n\t\tmarker := t.marker\n\t\tswitch markerClass {\n\t\tcase markerTop:\n\t\t\tmarker = t.markerMultiLine[0]\n\t\tcase markerMiddle:\n\t\t\tmarker = t.markerMultiLine[1]\n\t\tcase markerBottom:\n\t\t\tmarker = t.markerMultiLine[2]\n\t\t}\n\t\treturn marker\n\t}\n\tindentSize := t.pointerLen + t.markerLen\n\tif current {\n\t\tpreTask := func(marker markerClass) int {\n\t\t\tw := t.window.Width() - t.pointerLen\n\t\t\tif w < 0 {\n\t\t\t\treturn indentSize\n\t\t\t}\n\t\t\tif len(label) == 0 {\n\t\t\t\tt.gutter(true, false)\n\t\t\t} else {\n\t\t\t\tt.window.CPrint(tui.ColCurrentCursor, label)\n\t\t\t}\n\t\t\tif w-t.markerLen < 0 {\n\t\t\t\treturn indentSize\n\t\t\t}\n\t\t\tif selected {\n\t\t\t\tt.window.CPrint(tui.ColCurrentMarker, markerFor(marker))\n\t\t\t} else {\n\t\t\t\tt.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)\n\t\t\t}\n\t\t\treturn indentSize\n\t\t}\n\t\tcolCurrent := tui.ColCurrent\n\t\tnthOverlay := t.theme.NthCurrentAttr\n\t\tif selected {\n\t\t\tnthOverlay = t.theme.NthSelectedAttr.Merge(t.theme.NthCurrentAttr)\n\t\t\tbaseAttr := tui.ColNormal.Attr().Merge(t.theme.NthSelectedAttr).Merge(t.theme.NthCurrentAttr)\n\t\t\tcolCurrent = colCurrent.WithNewAttr(baseAttr)\n\t\t}\n\t\tfinalLineNum = t.printHighlighted(result, colCurrent, tui.ColCurrentMatch, true, true, !matched, line, maxLine, forceRedraw, preTask, postTask, nthOverlay)\n\t} else {\n\t\tpreTask := func(marker markerClass) int {\n\t\t\tw := t.window.Width() - t.pointerLen\n\t\t\tif w < 0 {\n\t\t\t\treturn indentSize\n\t\t\t}\n\t\t\tif len(label) == 0 {\n\t\t\t\tt.gutter(false, index%2 == 1)\n\t\t\t} else {\n\t\t\t\tt.window.CPrint(tui.ColCursor, label)\n\t\t\t}\n\t\t\tif w-t.markerLen < 0 {\n\t\t\t\treturn indentSize\n\t\t\t}\n\t\t\tif selected {\n\t\t\t\tt.window.CPrint(tui.ColMarker, markerFor(marker))\n\t\t\t} else {\n\t\t\t\tt.window.Print(t.markerEmpty)\n\t\t\t}\n\t\t\treturn indentSize\n\t\t}\n\t\tvar base, match tui.ColorPair\n\t\tif selected {\n\t\t\tbase = tui.ColSelected\n\t\t\tmatch = tui.ColSelectedMatch\n\t\t} else {\n\t\t\tbase = tui.ColNormal\n\t\t\tmatch = tui.ColMatch\n\t\t}\n\t\tif alt {\n\t\t\tbase = base.WithBg(altBg)\n\t\t\tmatch = match.WithBg(altBg)\n\t\t}\n\t\tvar nthOverlay tui.Attr\n\t\tif selected {\n\t\t\tnthOverlay = t.theme.NthSelectedAttr\n\t\t}\n\t\tfinalLineNum = t.printHighlighted(result, base, match, false, true, !matched, line, maxLine, forceRedraw, preTask, postTask, nthOverlay)\n\t}\n\tfor i := 0; i < t.gap && finalLineNum < maxLine; i++ {\n\t\tfinalLineNum++\n\t\tt.renderGapLine(finalLineNum, barRange, i == t.gap-1)\n\t}\n\treturn finalLineNum\n}\n\nfunc (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {\n\t// We start from the beginning to handle tab characters\n\t_, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)\n\tif overflowIdx >= 0 {\n\t\treturn runes[:overflowIdx], true\n\t}\n\treturn runes, false\n}\n\nfunc (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {\n\twidth, _ := util.RunesWidth(runes, prefixWidth, t.tabstop, limit)\n\treturn width\n}\n\nfunc (t *Terminal) trimLeft(runes []rune, width int, ellipsisWidth int) ([]rune, int32) {\n\twidth = max(0, width)\n\tvar trimmed int32\n\n\tstr := string(runes)\n\trunningSum := 0\n\trunningSumAdjusted := 0\n\t// We can't just subtract the width on each segment because there might be\n\t// a tab character afterwards. For example, with the tabstop = 8:\n\t//   1234____5678\n\t//   234_____5678\n\t//   34______5678\n\t//   4_______5678\n\t//   ________5678\n\t//   5678\n\t//   678\n\t//   78\n\t//   8\n\t// We need to look ahead, but not to the end to avoid performance hit.\n\ttype queuedSegment struct {\n\t\trs []rune\n\t\tw  int\n\t}\n\tallQueue := []queuedSegment{}\n\tqueuedWidth := 0\n\tlimit := width - ellipsisWidth\n\tprocessQueue := func() {\n\t\tfor idx, item := range allQueue {\n\t\t\tif runningSumAdjusted <= limit {\n\t\t\t\tallQueue = allQueue[idx:]\n\t\t\t\treturn\n\t\t\t}\n\t\t\trunningSumAdjusted -= item.w\n\t\t\trunes = runes[len(item.rs):]\n\t\t\ttrimmed += int32(len(item.rs))\n\t\t}\n\t\tallQueue = []queuedSegment{}\n\t}\n\n\tgr := uniseg.NewGraphemes(str)\n\tqueue := []queuedSegment{}\n\tfor gr.Next() {\n\t\ts := gr.Str()\n\t\trs := gr.Runes()\n\n\t\tvar w int\n\t\tif len(rs) == 1 && rs[0] == '\\t' {\n\t\t\tw = t.tabstop - runningSum%t.tabstop\n\t\t} else {\n\t\t\tw = util.StringWidth(string(rs))\n\t\t}\n\t\trunningSum += w\n\t\trunningSumAdjusted += w\n\t\tqueue = append(queue, queuedSegment{rs: rs, w: w})\n\t\tqueuedWidth += w\n\t\tif queuedWidth >= t.tabstop || s == \"\\t\" {\n\t\t\tqueuedWidth = 0\n\n\t\t\tif s == \"\\t\" {\n\t\t\t\tqueue[len(queue)-1].w = t.tabstop\n\t\t\t\tfor idx := range queue[:len(queue)-1] {\n\t\t\t\t\tqueue[idx].w = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\tallQueue = append(allQueue, queue...)\n\t\t\tqueue = []queuedSegment{}\n\t\t\tprocessQueue()\n\t\t}\n\t}\n\tallQueue = append(allQueue, queue...)\n\tprocessQueue()\n\treturn runes, trimmed\n}\n\nfunc (t *Terminal) overflow(runes []rune, max int) bool {\n\treturn t.displayWidthWithLimit(runes, 0, max) > max\n}\n\nfunc (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, hidden bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass) int, postTask func(int, int, bool, bool, tui.ColorPair), nthOverlay tui.Attr) int {\n\tvar displayWidth int\n\titem := result.item\n\tmatchOffsets := []Offset{}\n\tvar pos *[]int\n\tif match && t.resultMerger.pattern != nil {\n\t\t_, matchOffsets, pos = t.resultMerger.pattern.MatchItem(item, true, t.slab)\n\t}\n\tcharOffsets := matchOffsets\n\tif pos != nil {\n\t\trunes := item.text.ToRunes()\n\t\tcharOffsets = make([]Offset, len(*pos))\n\t\tfor idx, p := range *pos {\n\t\t\tgr := uniseg.NewGraphemes(string(runes[p:]))\n\t\t\tw := 1\n\t\t\tfor gr.Next() {\n\t\t\t\tw = len(gr.Runes())\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset := Offset{int32(p), int32(p + w)}\n\t\t\tcharOffsets[idx] = offset\n\t\t}\n\t\tsort.Sort(ByOrder(charOffsets))\n\t}\n\n\t// When postTask is nil, we're printing header lines. No need to care about nth.\n\tvar nthOffsets []Offset\n\tif postTask != nil {\n\t\twholeCovered := len(t.nthCurrent) == 0\n\t\tfor _, nth := range t.nthCurrent {\n\t\t\t// Do we still want to apply a different style when the current nth\n\t\t\t// covers the whole string? Probably not. And we can simplify the logic.\n\t\t\tif nth.IsFull() {\n\t\t\t\twholeCovered = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif wholeCovered && t.nthAttr&tui.AttrRegular > 0 {\n\t\t\t// But if 'nth' is set to 'regular', it's a sign that you're applying\n\t\t\t// a different style to the rest of the string. e.g. 'nth:regular,fg:dim'\n\t\t\t// In this case, we still need to apply it to clear the style.\n\t\t\tfgAttr := tui.ColNormal.Attr()\n\t\t\tnthAttrFinal := fgAttr.Merge(t.nthAttr).Merge(nthOverlay)\n\t\t\tcolBase = colBase.WithNewAttr(nthAttrFinal)\n\t\t}\n\t\tif !wholeCovered && t.nthAttr > 0 {\n\t\t\tvar tokens []Token\n\t\t\tif item.transformed != nil && item.transformed.revision == t.resultMerger.revision {\n\t\t\t\ttokens = item.transformed.tokens\n\t\t\t} else {\n\t\t\t\ttokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)\n\t\t\t}\n\t\t\tnthOffsets = make([]Offset, len(tokens))\n\t\t\tfor i, token := range tokens {\n\t\t\t\tstart := token.prefixLength\n\t\t\t\tlength := token.text.Length() - token.text.TrailingWhitespaces()\n\t\t\t\tend := start + int32(length)\n\t\t\t\tnthOffsets[i] = Offset{int32(start), int32(end)}\n\t\t\t}\n\t\t\tsort.Sort(ByOrder(nthOffsets))\n\t\t}\n\t}\n\tallOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, nthOverlay, hidden)\n\n\t// Determine split offset for horizontal scrolling with freeze\n\tsplitOffset1 := -1\n\tsplitOffset2 := -1\n\tif t.hscroll && !t.wrap {\n\t\tvar tokens []Token\n\t\tif t.freezeLeft > 0 || t.freezeRight > 0 {\n\t\t\ttokens = Tokenize(item.text.ToString(), t.delimiter)\n\t\t}\n\n\t\t// 0 | 1 | 2 | 3 | 4 | 5\n\t\t// ------>       <------\n\t\tif t.freezeLeft > 0 {\n\t\t\tif len(tokens) > 0 {\n\t\t\t\ttoken := tokens[min(t.freezeLeft, len(tokens))-1]\n\t\t\t\tsplitOffset1 = int(token.prefixLength) + token.text.Length() - token.text.TrailingWhitespaces()\n\t\t\t}\n\t\t}\n\t\tif t.freezeRight > 0 {\n\t\t\tindex := max(t.freezeLeft-1, len(tokens)-t.freezeRight-1)\n\t\t\tif index < 0 {\n\t\t\t\tsplitOffset2 = 0\n\t\t\t} else if index >= t.freezeLeft {\n\t\t\t\ttoken := tokens[index]\n\t\t\t\tdelimiter := strings.TrimLeftFunc(GetLastDelimiter(token.text.ToString(), t.delimiter), unicode.IsSpace)\n\t\t\t\tsplitOffset2 = int(token.prefixLength) + token.text.Length() - len([]rune(delimiter))\n\t\t\t}\n\t\t\tsplitOffset2 = max(splitOffset2, splitOffset1)\n\t\t}\n\t}\n\n\tmaxLines := 1\n\tif t.canSpanMultiLines() {\n\t\tmaxLines = maxLineNum - lineNum + 1\n\t}\n\tlines, overflow := t.itemLines(item, maxLines)\n\tnumItemLines := len(lines)\n\n\tfinalLineNum := lineNum\n\ttopCutoff := false\n\tskipLines := 0\n\twrapped := false\n\tif t.canSpanMultiLines() {\n\t\t// Cut off the upper lines in the 'default' layout\n\t\tif t.layout == layoutDefault && !current && maxLines == numItemLines && overflow {\n\t\t\tlines, _ = t.itemLines(item, math.MaxInt)\n\n\t\t\t// To see if the first visible line is wrapped, we need to check the last cut-off line\n\t\t\tprevLine := lines[len(lines)-maxLines-1]\n\t\t\tif len(prevLine) == 0 || prevLine[len(prevLine)-1] != '\\n' {\n\t\t\t\twrapped = true\n\t\t\t}\n\n\t\t\tskipLines = len(lines) - maxLines\n\t\t\ttopCutoff = true\n\t\t}\n\t}\n\tfrom := 0\n\tfor lineOffset := 0; lineOffset < len(lines) && (lineNum <= maxLineNum || maxLineNum == 0); lineOffset++ {\n\t\tline := lines[lineOffset]\n\t\tfinalLineNum = lineNum\n\t\toffsets := []colorOffset{}\n\t\tlbg := tui.NoColorPair()\n\t\tfor idx, offset := range allOffsets {\n\t\t\tlineEnd := int32(from + len(line))\n\t\t\tif offset.offset[0] >= lineEnd {\n\t\t\t\tif offset.IsFullBgMarker(lineEnd) {\n\t\t\t\t\tlbg = offset.color\n\t\t\t\t}\n\t\t\t\tallOffsets = allOffsets[idx:]\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif offset.offset[0] < int32(from) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif offset.offset[1] < lineEnd {\n\t\t\t\toffset.offset[0] -= int32(from)\n\t\t\t\toffset.offset[1] -= int32(from)\n\t\t\t\toffsets = append(offsets, offset)\n\t\t\t} else {\n\t\t\t\tif idx < len(allOffsets)-1 {\n\t\t\t\t\tnext := allOffsets[idx+1]\n\t\t\t\t\tif next.IsFullBgMarker(lineEnd) {\n\t\t\t\t\t\tlbg = next.color\n\t\t\t\t\t\tidx++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdupe := offset\n\t\t\t\tdupe.offset[0] = lineEnd\n\n\t\t\t\toffset.offset[0] -= int32(from)\n\t\t\t\toffset.offset[1] = lineEnd\n\t\t\t\toffsets = append(offsets, offset)\n\n\t\t\t\tallOffsets = append([]colorOffset{dupe}, allOffsets[idx+1:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tsplitOffsetLeft := 0\n\t\tif splitOffset1 >= 0 && splitOffset1 > from && splitOffset1 < from+len(line) {\n\t\t\tsplitOffsetLeft = splitOffset1 - from\n\t\t}\n\t\tsplitOffsetRight := -1\n\t\tif splitOffset2 >= 0 && splitOffset2 >= from && splitOffset2 < from+len(line) {\n\t\t\tsplitOffsetRight = splitOffset2 - from\n\t\t}\n\t\tfrom += len(line)\n\t\tif lineOffset < skipLines {\n\t\t\tcontinue\n\t\t}\n\t\tactualLineOffset := lineOffset - skipLines\n\n\t\tvar maxEnd int\n\t\tfor _, offset := range offsets {\n\t\t\tif offset.match {\n\t\t\t\tmaxEnd = max(maxEnd, int(offset.offset[1]))\n\t\t\t}\n\t\t}\n\n\t\tactualLineNum := lineNum\n\t\tif t.layout == layoutDefault {\n\t\t\tactualLineNum = (lineNum - actualLineOffset) + (numItemLines - actualLineOffset) - 1\n\t\t}\n\t\tt.move(actualLineNum, 0, forceRedraw && postTask == nil)\n\n\t\tindentSize := t.pointerLen + t.markerLen\n\t\tif preTask != nil {\n\t\t\tvar marker markerClass\n\t\t\tif numItemLines == 1 {\n\t\t\t\tif !overflow {\n\t\t\t\t\tmarker = markerSingle\n\t\t\t\t} else if topCutoff {\n\t\t\t\t\tmarker = markerBottom\n\t\t\t\t} else {\n\t\t\t\t\tmarker = markerTop\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif actualLineOffset == 0 { // First line\n\t\t\t\t\tif topCutoff {\n\t\t\t\t\t\tmarker = markerMiddle\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmarker = markerTop\n\t\t\t\t\t}\n\t\t\t\t} else if actualLineOffset == numItemLines-1 { // Last line\n\t\t\t\t\tif topCutoff || !overflow {\n\t\t\t\t\t\tmarker = markerBottom\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmarker = markerMiddle\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tmarker = markerMiddle\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tindentSize = preTask(marker)\n\t\t}\n\n\t\tmaxWidth := t.window.Width() - (indentSize + t.barCol())\n\t\twasWrapped := false\n\t\tif wrapped {\n\t\t\twrapSign := t.wrapSign\n\t\t\tif maxWidth < t.wrapSignWidth {\n\t\t\t\trunes, _ := util.Truncate(wrapSign, maxWidth)\n\t\t\t\twrapSign = string(runes)\n\t\t\t\tmaxWidth = 0\n\t\t\t} else {\n\t\t\t\tmaxWidth -= t.wrapSignWidth\n\t\t\t}\n\t\t\tt.window.CPrint(colBase.WithAttr(tui.Dim), wrapSign)\n\t\t\twrapped = false\n\t\t\twasWrapped = true\n\t\t}\n\n\t\tif len(line) > 0 && line[len(line)-1] == '\\n' && lineOffset < len(lines)-1 {\n\t\t\tline = line[:len(line)-1]\n\t\t} else {\n\t\t\twrapped = true\n\t\t}\n\n\t\tfrozenLeft := line[:splitOffsetLeft]\n\t\tmiddle := line[splitOffsetLeft:]\n\t\tfrozenRight := []rune{}\n\t\tif splitOffsetRight >= splitOffsetLeft {\n\t\t\tmiddle = line[splitOffsetLeft:splitOffsetRight]\n\t\t\tfrozenRight = line[splitOffsetRight:]\n\t\t}\n\t\tdisplayWidthSum := 0\n\t\tdisplayWidthLeft := 0\n\t\ttodo := [3]func(){}\n\t\tfor fidx, runes := range [][]rune{frozenLeft, frozenRight, middle} {\n\t\t\tif len(runes) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tshift := 0\n\t\t\tmaxe := maxEnd\n\t\t\toffs := make([]colorOffset, len(offsets))\n\t\t\tfor idx := range offsets {\n\t\t\t\toffs[idx] = offsets[idx]\n\t\t\t\tif fidx == 1 && splitOffsetRight > 0 {\n\t\t\t\t\tshift = splitOffsetRight\n\t\t\t\t} else if fidx == 2 && splitOffsetLeft > 0 {\n\t\t\t\t\tshift = splitOffsetLeft\n\t\t\t\t}\n\t\t\t\toffs[idx].offset[0] -= int32(shift)\n\t\t\t\toffs[idx].offset[1] -= int32(shift)\n\t\t\t}\n\t\t\tmaxe -= shift\n\t\t\tellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth)\n\t\t\tadjustedMaxWidth := maxWidth\n\t\t\tif fidx < 2 {\n\t\t\t\t// For frozen parts, reserve space for the ellipsis in the middle part\n\t\t\t\tadjustedMaxWidth -= ellipsisWidth\n\t\t\t}\n\t\t\tvar prefixWidth int\n\t\t\tif fidx == 2 {\n\t\t\t\tprefixWidth = displayWidthLeft\n\t\t\t}\n\t\t\tdisplayWidth = t.displayWidthWithLimit(runes, prefixWidth, adjustedMaxWidth)\n\t\t\tif !t.wrap && displayWidth > adjustedMaxWidth {\n\t\t\t\tmaxe = util.Constrain(maxe+min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))\n\t\t\t\ttransformOffsets := func(diff int32) {\n\t\t\t\t\tfor idx := range offs {\n\t\t\t\t\t\toffs[idx].offset[0] -= diff\n\t\t\t\t\t\toffs[idx].offset[1] -= diff\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif t.hscroll {\n\t\t\t\t\tif fidx == 1 || fidx == 2 && t.keepRight && pos == nil {\n\t\t\t\t\t\ttrimmed, diff := t.trimLeft(runes, maxWidth, ellipsisWidth)\n\t\t\t\t\t\ttransformOffsets(diff - int32(len(ellipsis)))\n\t\t\t\t\t\trunes = append(ellipsis, trimmed...)\n\t\t\t\t\t} else if fidx == 0 || !t.overflow(runes[:maxe], maxWidth-ellipsisWidth) {\n\t\t\t\t\t\t// Stri..\n\t\t\t\t\t\trunes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)\n\t\t\t\t\t\trunes = append(runes, ellipsis...)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Stri..\n\t\t\t\t\t\tif t.overflow(runes[maxe:], ellipsisWidth) {\n\t\t\t\t\t\t\trunes = append(runes[:maxe], ellipsis...)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// ..ri..\n\t\t\t\t\t\tvar diff int32\n\t\t\t\t\t\trunes, diff = t.trimLeft(runes, maxWidth, ellipsisWidth)\n\n\t\t\t\t\t\t// Transform offsets\n\t\t\t\t\t\ttransformOffsets(diff - int32(len(ellipsis)))\n\t\t\t\t\t\trunes = append(ellipsis, runes...)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\trunes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)\n\t\t\t\t\trunes = append(runes, ellipsis...)\n\t\t\t\t}\n\t\t\t\tdisplayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)\n\t\t\t}\n\t\t\tdisplayWidthSum += displayWidth\n\t\t\tif fidx == 0 {\n\t\t\t\tdisplayWidthLeft = displayWidth\n\t\t\t}\n\n\t\t\tif maxWidth > 0 {\n\t\t\t\tcolor := colBase\n\t\t\t\tif hidden {\n\t\t\t\t\tcolor = color.WithFg(t.theme.Nomatch)\n\t\t\t\t}\n\t\t\t\ttodo[fidx] = func() {\n\t\t\t\t\tt.printColoredString(t.window, runes, offs, color, prefixWidth)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tmaxWidth -= displayWidth\n\t\t}\n\t\tif todo[0] != nil {\n\t\t\ttodo[0]()\n\t\t}\n\t\tif todo[2] != nil {\n\t\t\ttodo[2]()\n\t\t}\n\t\tif todo[1] != nil {\n\t\t\ttodo[1]()\n\t\t}\n\t\tif postTask != nil {\n\t\t\tpostTask(actualLineNum, displayWidthSum, wasWrapped, forceRedraw, lbg)\n\t\t} else {\n\t\t\tt.markOtherLine(actualLineNum)\n\t\t}\n\t\tlineNum += 1\n\t}\n\n\treturn finalLineNum\n}\n\nfunc (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair, initialPrefixWidth ...int) {\n\tvar index int32\n\tvar substr string\n\tvar prefixWidth int\n\tif len(initialPrefixWidth) > 0 {\n\t\tprefixWidth = initialPrefixWidth[0]\n\t}\n\tmaxOffset := int32(len(text))\n\tvar url *url\n\tfor _, offset := range offsets {\n\t\tb := util.Constrain(offset.offset[0], index, maxOffset)\n\t\te := util.Constrain(offset.offset[1], index, maxOffset)\n\t\tif url != nil && offset.url != url {\n\t\t\turl = nil\n\t\t\twindow.LinkEnd()\n\t\t}\n\n\t\tsubstr, prefixWidth = t.processTabs(text[index:b], prefixWidth)\n\t\twindow.CPrint(colBase, substr)\n\n\t\tif b < e {\n\t\t\tsubstr, prefixWidth = t.processTabs(text[b:e], prefixWidth)\n\t\t\tif url == nil && offset.url != nil {\n\t\t\t\turl = offset.url\n\t\t\t\twindow.LinkBegin(url.uri, url.params)\n\t\t\t}\n\t\t\twindow.CPrint(offset.color, substr)\n\t\t}\n\n\t\tindex = e\n\t\tif index >= maxOffset {\n\t\t\tbreak\n\t\t}\n\t}\n\tif url != nil {\n\t\twindow.LinkEnd()\n\t}\n\tif index < maxOffset {\n\t\tsubstr, _ = t.processTabs(text[index:], prefixWidth)\n\t\twindow.CPrint(colBase, substr)\n\t}\n}\n\nfunc (t *Terminal) renderPreviewSpinner() {\n\tnumLines := len(t.previewer.lines)\n\tspin := t.previewer.spinner\n\tif len(spin) > 0 || t.previewer.scrollable {\n\t\tmaxWidth := t.pwindow.Width()\n\t\tif !t.previewer.scrollable || !t.activePreviewOpts.info {\n\t\t\tif maxWidth > 0 {\n\t\t\t\tt.pwindow.Move(0, maxWidth-1)\n\t\t\t\tt.pwindow.CPrint(tui.ColPreviewSpinner, spin)\n\t\t\t}\n\t\t} else {\n\t\t\toffsetString := fmt.Sprintf(\"%d/%d\", t.previewer.offset+1, numLines)\n\t\t\tif len(spin) > 0 {\n\t\t\t\tspin += \" \"\n\t\t\t\tmaxWidth -= 2\n\t\t\t}\n\t\t\toffsetRunes, _ := t.trimRight([]rune(offsetString), maxWidth)\n\t\t\tpos := maxWidth - t.displayWidth(offsetRunes)\n\t\t\tt.pwindow.Move(0, pos)\n\t\t\tif maxWidth > 0 {\n\t\t\t\tt.pwindow.CPrint(tui.ColPreviewSpinner, spin)\n\t\t\t\tt.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *Terminal) renderPreviewArea(unchanged bool) {\n\tif t.previewed.wipe && t.previewed.version != t.previewer.version {\n\t\tt.previewed.wipe = false\n\t\tt.pwindow.Erase()\n\t} else if unchanged {\n\t\tt.pwindow.MoveAndClear(0, 0) // Clear scroll offset display\n\t} else {\n\t\tt.previewed.filled = false\n\t\t// We don't erase the window here to avoid flickering during scroll.\n\t\t// However, tcell renderer uses double-buffering technique and there's no\n\t\t// flickering. So we just erase the window and make the rest of the code\n\t\t// simpler.\n\t\tif !t.pwindow.EraseMaybe() {\n\t\t\tt.pwindow.DrawBorder()\n\t\t\tt.pwindow.Move(0, 0)\n\t\t}\n\t}\n\n\theight := t.pwindow.Height()\n\tbody := t.previewer.lines\n\theaderLines := t.activePreviewOpts.headerLines\n\t// Do not enable preview header lines if it's value is too large\n\tif headerLines > 0 && headerLines < min(len(body), height) {\n\t\theader := t.previewer.lines[0:headerLines]\n\t\tbody = t.previewer.lines[headerLines:]\n\t\t// Always redraw header\n\t\tt.renderPreviewText(height, header, 0, false)\n\t\tt.pwindow.MoveAndClear(t.pwindow.Y(), 0)\n\t}\n\tt.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)\n\n\tif !unchanged {\n\t\tt.pwindow.FinishFill()\n\t}\n\n\tif len(t.scrollbar) == 0 {\n\t\treturn\n\t}\n\n\teffectiveHeight := height - headerLines\n\tbarLength, barStart := getScrollbar(1, len(body), effectiveHeight, min(len(body)-effectiveHeight, t.previewer.offset-headerLines))\n\tt.renderPreviewScrollbar(headerLines, barLength, barStart)\n}\n\nfunc (t *Terminal) makeImageBorder(width int, top bool) string {\n\ttl := \"┌\"\n\ttr := \"┐\"\n\tv := \"╎\"\n\th := \"╌\"\n\tif !t.unicode {\n\t\ttl = \"+\"\n\t\ttr = \"+\"\n\t\th = \"-\"\n\t\tv = \"|\"\n\t}\n\trepeat := max(0, width-2)\n\tif top {\n\t\treturn tl + strings.Repeat(h, repeat) + tr\n\t}\n\treturn v + strings.Repeat(\" \", repeat) + v\n}\n\nfunc findPassThrough(line string) []int {\n\tloc := passThroughBeginRegex.FindStringIndex(line)\n\tif loc == nil {\n\t\treturn nil\n\t}\n\n\trest := line[loc[0]:]\n\tafter := line[loc[1]:]\n\tif strings.HasPrefix(rest, \"\\x1bPtmux\") { // Tmux\n\t\teloc := passThroughEndTmuxRegex.FindStringIndex(after)\n\t\tif eloc == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn []int{loc[0], loc[1] + eloc[1]}\n\t} else if strings.HasPrefix(rest, \"\\x1b]1337;\") { // iTerm2\n\t\tindex := loc[1]\n\t\tfor {\n\t\t\tafter := line[index:]\n\t\t\tpos := strings.IndexAny(after, \"\\x1b\\a\")\n\t\t\tif pos < 0 {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif after[pos] == '\\a' {\n\t\t\t\treturn []int{loc[0], index + pos + 1}\n\t\t\t}\n\t\t\tif pos < len(after)-1 && after[pos+1] == '\\\\' {\n\t\t\t\treturn []int{loc[0], index + pos + 2}\n\t\t\t}\n\t\t\tindex += pos + 1\n\t\t}\n\t}\n\t// Kitty\n\tpos := strings.Index(after, \"\\x1b\\\\\")\n\tif pos < 0 {\n\t\treturn nil\n\t}\n\tif pos < len(after)-2 && after[pos+2] == '\\r' {\n\t\treturn []int{loc[0], loc[1] + pos + 3}\n\t}\n\treturn []int{loc[0], loc[1] + pos + 2}\n}\n\nfunc extractPassThroughs(line string) ([]string, string) {\n\tpassThroughs := []string{}\n\ttransformed := \"\"\n\tindex := 0\n\tfor {\n\t\trest := line[index:]\n\t\tloc := findPassThrough(rest)\n\t\tif loc == nil {\n\t\t\ttransformed += rest\n\t\t\tbreak\n\t\t}\n\t\tpassThroughs = append(passThroughs, rest[loc[0]:loc[1]])\n\t\ttransformed += line[index : index+loc[0]]\n\t\tindex += loc[1]\n\t}\n\n\treturn passThroughs, transformed\n}\n\n// followOffset computes the correct content-line offset for follow mode,\n// accounting for line wrapping in the preview window.\nfunc (t *Terminal) followOffset() int {\n\tlines := t.previewer.lines\n\theaderLines := t.activePreviewOpts.headerLines\n\theight := t.pwindow.Height() - headerLines\n\tif height <= 0 || len(lines) <= headerLines {\n\t\treturn headerLines\n\t}\n\n\tbody := lines[headerLines:]\n\tif !t.activePreviewOpts.wrap {\n\t\treturn max(t.previewer.offset, headerLines+len(body)-height)\n\t}\n\n\tmaxWidth := t.pwindow.Width()\n\tvisualLines := 0\n\tfor i := len(body) - 1; i >= 0; i-- {\n\t\th := t.previewLineHeight(body[i], maxWidth)\n\t\tif visualLines+h > height {\n\t\t\treturn min(len(lines)-1, headerLines+i+1)\n\t\t}\n\t\tvisualLines += h\n\t}\n\treturn headerLines\n}\n\n// previewLineHeight estimates the number of visual lines a preview content line\n// occupies when wrapping is enabled.\nfunc (t *Terminal) previewLineHeight(line string, maxWidth int) int {\n\tif maxWidth <= 0 {\n\t\treturn 1\n\t}\n\n\t// For word-wrap mode, count the sub-lines produced by word wrapping.\n\t// Each sub-line may still char-wrap if it contains a word longer than the width.\n\tif t.activePreviewOpts.wrapWord {\n\t\tsubLines := t.wordWrapAnsiLine(line, maxWidth, t.previewWrapSignWidth)\n\t\ttotal := 0\n\t\tfor i, sub := range subLines {\n\t\t\tprefixWidth := 0\n\t\t\tcols := maxWidth\n\t\t\tif i > 0 {\n\t\t\t\tprefixWidth = t.previewWrapSignWidth\n\t\t\t\tcols -= t.previewWrapSignWidth\n\t\t\t}\n\t\t\tw := t.ansiLineWidth(sub, prefixWidth)\n\t\t\tif cols <= 0 {\n\t\t\t\tcols = 1\n\t\t\t}\n\t\t\ttotal += max(1, (w+cols-1)/cols)\n\t\t}\n\t\treturn total\n\t}\n\n\t// For char-wrap, compute visible width and divide by available width.\n\tw := t.ansiLineWidth(line, 0)\n\tif w <= maxWidth {\n\t\treturn 1\n\t}\n\tremaining := w - maxWidth\n\tcontWidth := max(1, maxWidth-t.previewWrapSignWidth)\n\treturn 1 + (remaining+contWidth-1)/contWidth\n}\n\n// ansiLineWidth computes the display width of a string, skipping ANSI escape sequences.\n// prefixWidth is the visual offset where the content starts (e.g. wrap sign width for\n// continuation lines), used for correct tab stop alignment.\nfunc (t *Terminal) ansiLineWidth(line string, prefixWidth int) int {\n\tline = strings.TrimSuffix(line, \"\\n\")\n\ttrimmed, _, _ := extractColor(line, nil, nil)\n\t_, width := t.processTabsStr(trimmed, prefixWidth)\n\treturn width - prefixWidth\n}\n\nfunc (t *Terminal) wordWrapAnsiLine(line string, maxWidth int, wrapSignWidth int) []string {\n\tif maxWidth <= 0 {\n\t\treturn []string{line}\n\t}\n\n\tvar result []string\n\tlineStart := 0\n\twidth := 0\n\tlastSpaceStart := -1\n\tlastSpaceEnd := -1\n\twidthBeforeLastSpace := 0\n\tlastSpaceWidth := 0\n\tmax := maxWidth\n\tpos := 0\n\n\tfor pos < len(line) {\n\t\t// Find next ANSI escape sequence\n\t\tstart, end := nextAnsiEscapeSequence(line[pos:])\n\n\t\t// Determine the end of printable text before the next escape\n\t\tvar printableEnd int\n\t\tif start < 0 {\n\t\t\tprintableEnd = len(line)\n\t\t} else {\n\t\t\tprintableEnd = pos + start\n\t\t}\n\n\t\t// Process printable characters using grapheme clusters\n\t\tgr := uniseg.NewGraphemes(line[pos:printableEnd])\n\t\tfor gr.Next() {\n\t\t\tgStart, gEnd := gr.Positions()\n\t\t\tw := gr.Width()\n\t\t\tstr := gr.Str()\n\n\t\t\tif str == \"\\t\" {\n\t\t\t\tw = t.tabstop - (width % t.tabstop)\n\t\t\t}\n\n\t\t\tif str == \" \" || str == \"\\t\" {\n\t\t\t\tlastSpaceStart = pos + gStart\n\t\t\t\tlastSpaceEnd = pos + gEnd\n\t\t\t\twidthBeforeLastSpace = width\n\t\t\t\tlastSpaceWidth = w\n\t\t\t}\n\n\t\t\twidth += w\n\n\t\t\tif width > max && lastSpaceEnd > lineStart {\n\t\t\t\tresult = append(result, line[lineStart:lastSpaceStart])\n\t\t\t\tlineStart = lastSpaceEnd\n\t\t\t\twidth -= widthBeforeLastSpace + lastSpaceWidth\n\t\t\t\tlastSpaceStart = -1\n\t\t\t\tlastSpaceEnd = -1\n\t\t\t\twidthBeforeLastSpace = 0\n\t\t\t\tmax = maxWidth - wrapSignWidth\n\t\t\t}\n\t\t}\n\t\tpos = printableEnd\n\n\t\t// Skip the ANSI escape sequence\n\t\tif start >= 0 {\n\t\t\tpos += end - start\n\t\t}\n\t}\n\n\tresult = append(result, line[lineStart:])\n\treturn result\n}\n\nfunc (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {\n\tmaxWidth := t.pwindow.Width()\n\tvar ansi *ansiState\n\tspinnerRedraw := t.pwindow.Y() == 0\n\twiped := false\n\timage := false\n\twireframe := false\n\tvar index int\n\tvar line string\nLoop:\n\tfor index, line = range lines {\n\t\tvar lbg tui.Color = -1\n\t\tif ansi != nil {\n\t\t\tansi.lbg = -1\n\t\t}\n\n\t\tpassThroughs, line := extractPassThroughs(line)\n\t\tline = strings.TrimLeft(strings.TrimRight(line, \"\\r\\n\"), \"\\r\")\n\n\t\tif lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {\n\t\t\tt.previewed.filled = true\n\t\t\tt.previewer.scrollable = true\n\t\t\tbreak\n\t\t} else if lineNo >= 0 {\n\t\t\tx := t.pwindow.X()\n\t\t\ty := t.pwindow.Y()\n\t\t\tif spinnerRedraw && lineNo > 0 {\n\t\t\t\tspinnerRedraw = false\n\t\t\t\tt.renderPreviewSpinner()\n\t\t\t\tt.pwindow.Move(y, x)\n\t\t\t}\n\t\t\tfor idx, passThrough := range passThroughs {\n\t\t\t\t// Handling Sixel/iTerm image\n\t\t\t\trequiredLines := 0\n\t\t\t\tisSixel := strings.HasPrefix(passThrough, \"\\x1bP\")\n\t\t\t\tisItermImage := strings.HasPrefix(passThrough, \"\\x1b]1337;\")\n\t\t\t\tisImage := isSixel || isItermImage\n\t\t\t\tif isImage {\n\t\t\t\t\tt.previewed.wipe = true\n\t\t\t\t\t// NOTE: We don't have a good way to get the height of an iTerm image,\n\t\t\t\t\t// so we assume that it requires the full height of the preview\n\t\t\t\t\t// window.\n\t\t\t\t\trequiredLines = height\n\n\t\t\t\t\tif isSixel && t.termSize.PxHeight > 0 {\n\t\t\t\t\t\trows := strings.Count(passThrough, \"-\")\n\t\t\t\t\t\trequiredLines = int(math.Ceil(float64(rows*6*t.termSize.Lines) / float64(t.termSize.PxHeight)))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Render wireframe when the image cannot be displayed entirely\n\t\t\t\tif requiredLines > 0 && y+requiredLines > height {\n\t\t\t\t\ttop := true\n\t\t\t\t\tfor ; y < height; y++ {\n\t\t\t\t\t\tt.pwindow.MoveAndClear(y, 0)\n\t\t\t\t\t\tt.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), -1, tui.AttrRegular, t.makeImageBorder(maxWidth, top))\n\t\t\t\t\t\ttop = false\n\t\t\t\t\t}\n\t\t\t\t\twireframe = true\n\t\t\t\t\tt.previewed.filled = true\n\t\t\t\t\tt.previewer.scrollable = true\n\t\t\t\t\tbreak Loop\n\t\t\t\t}\n\n\t\t\t\t// Clear previous wireframe or any other text\n\t\t\t\tif (t.previewed.wireframe || isImage && !t.previewed.image) && !wiped {\n\t\t\t\t\twiped = true\n\t\t\t\t\tfor i := y + 1; i < height; i++ {\n\t\t\t\t\t\tt.pwindow.MoveAndClear(i, 0)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\timage = image || isImage\n\t\t\t\tif idx == 0 {\n\t\t\t\t\tt.pwindow.MoveAndClear(y, x)\n\t\t\t\t} else {\n\t\t\t\t\tt.pwindow.Move(y, x)\n\t\t\t\t}\n\t\t\t\tt.tui.PassThrough(passThrough)\n\n\t\t\t\tif requiredLines > 0 {\n\t\t\t\t\tif y+requiredLines == height {\n\t\t\t\t\t\tt.pwindow.Move(height-1, maxWidth-1)\n\t\t\t\t\t\tt.previewed.filled = true\n\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t}\n\t\t\t\t\tt.pwindow.MoveAndClear(y+requiredLines, 0)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(passThroughs) > 0 && len(line) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Pre-split line into sub-lines for word wrapping\n\t\t\tvar subLines []string\n\t\t\tif t.activePreviewOpts.wrapWord {\n\t\t\t\tsubLines = t.wordWrapAnsiLine(line, maxWidth, t.previewWrapSignWidth)\n\t\t\t} else {\n\t\t\t\tsubLines = []string{line}\n\t\t\t}\n\n\t\t\tvar fillRet tui.FillReturn\n\t\t\twrap := t.activePreviewOpts.wrap\n\t\t\tprintWrapSign := func() {\n\t\t\t\tif t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), -1, tui.Dim, t.previewWrapSign) == tui.FillNextLine {\n\t\t\t\t\tt.pwindow.Move(t.pwindow.Y()-1, t.pwindow.Width())\n\t\t\t\t}\n\t\t\t\tfillRet = tui.FillContinue\n\t\t\t}\n\t\t\tfor subIdx, subLine := range subLines {\n\t\t\t\t// Render wrap sign for continuation sub-lines\n\t\t\t\tif subIdx > 0 {\n\t\t\t\t\tif fillRet == tui.FillContinue {\n\t\t\t\t\t\tfillRet = t.pwindow.Fill(\"\\n\")\n\t\t\t\t\t\tif fillRet == tui.FillSuspend {\n\t\t\t\t\t\t\tt.previewed.filled = true\n\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tprintWrapSign()\n\t\t\t\t}\n\n\t\t\t\tprefixWidth := t.pwindow.X()\n\t\t\t\tvar url *url\n\t\t\t\t_, _, ansi = extractColor(subLine, ansi, func(str string, ansi *ansiState) bool {\n\t\t\t\t\tif len(str) > 0 && fillRet == tui.FillNextLine {\n\t\t\t\t\t\tprintWrapSign()\n\t\t\t\t\t\tprefixWidth = t.pwindow.X()\n\t\t\t\t\t}\n\t\t\t\t\ttrimmed := []rune(str)\n\t\t\t\t\tisTrimmed := false\n\t\t\t\t\tif !wrap {\n\t\t\t\t\t\ttrimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())\n\t\t\t\t\t}\n\t\t\t\t\tif url == nil && ansi != nil && ansi.url != nil {\n\t\t\t\t\t\turl = ansi.url\n\t\t\t\t\t\tt.pwindow.LinkBegin(url.uri, url.params)\n\t\t\t\t\t}\n\t\t\t\t\tif url != nil && (ansi == nil || ansi.url == nil) {\n\t\t\t\t\t\turl = nil\n\t\t\t\t\t\tt.pwindow.LinkEnd()\n\t\t\t\t\t}\n\t\t\t\t\tif ansi != nil {\n\t\t\t\t\t\tlbg = ansi.lbg\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlbg = -1\n\t\t\t\t\t}\n\t\t\t\t\tstr, width := t.processTabs(trimmed, prefixWidth)\n\t\t\t\t\tif width > prefixWidth {\n\t\t\t\t\t\tprefixWidth = width\n\t\t\t\t\t\tcolored := ansi != nil && ansi.colored()\n\t\t\t\t\t\tif t.theme.Colored && colored {\n\t\t\t\t\t\t\tfillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.ul, ansi.attr, str)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tattr := tui.AttrRegular\n\t\t\t\t\t\t\tif colored {\n\t\t\t\t\t\t\t\tattr = ansi.attr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), -1, attr, str)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn !isTrimmed &&\n\t\t\t\t\t\t(fillRet == tui.FillContinue || wrap && fillRet == tui.FillNextLine)\n\t\t\t\t})\n\t\t\t\tif url != nil {\n\t\t\t\t\tt.pwindow.LinkEnd()\n\t\t\t\t}\n\n\t\t\t\tif fillRet == tui.FillSuspend {\n\t\t\t\t\tt.previewed.filled = true\n\t\t\t\t\tbreak Loop\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tt.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width() || t.previewed.filled\n\t\t\tif fillRet == tui.FillNextLine {\n\t\t\t\tcontinue\n\t\t\t} else if fillRet == tui.FillSuspend {\n\t\t\t\tt.previewed.filled = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif unchanged && lineNo == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif t.theme.Colored && lbg >= 0 {\n\t\t\t\tfillRet = t.pwindow.CFill(-1, lbg, -1, tui.AttrRegular,\n\t\t\t\t\tstrings.Repeat(\" \", t.pwindow.Width()-t.pwindow.X())+\"\\n\")\n\t\t\t} else {\n\t\t\t\tfillRet = t.pwindow.Fill(\"\\n\")\n\t\t\t}\n\t\t\tif fillRet == tui.FillSuspend {\n\t\t\t\tt.previewed.filled = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tlineNo++\n\t}\n\tt.previewer.scrollable = t.previewer.scrollable || t.previewed.filled || index < len(lines)-1\n\tt.previewed.image = image\n\tt.previewed.wireframe = wireframe\n}\n\nfunc (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {\n\theight := t.pwindow.Height()\n\tw := t.pborder.Width()\n\txw := [2]int{t.pwindow.Left(), t.pwindow.Width()}\n\tredraw := false\n\tif len(t.previewer.bar) != height || t.previewer.xw != xw {\n\t\tredraw = true\n\t\tt.previewer.bar = make([]bool, height)\n\t\tt.previewer.xw = xw\n\t}\n\txshift := -1 - t.borderWidth\n\tif !t.activePreviewOpts.Border().HasRight() {\n\t\txshift = -1\n\t}\n\tyshift := 1\n\tif !t.activePreviewOpts.Border().HasTop() {\n\t\tyshift = 0\n\t}\n\tfor i := yoff; i < height; i++ {\n\t\tx := w + xshift\n\t\ty := i + yshift\n\n\t\t// Avoid unnecessary redraws\n\t\tbar := i >= yoff+barStart && i < yoff+barStart+barLength\n\t\tif !redraw && bar == t.previewer.bar[i] && !t.tui.NeedScrollbarRedraw() {\n\t\t\tcontinue\n\t\t}\n\n\t\tt.previewer.bar[i] = bar\n\t\tt.pborder.Move(y, x)\n\t\tif i >= yoff+barStart && i < yoff+barStart+barLength {\n\t\t\tt.pborder.CPrint(tui.ColPreviewScrollbar, t.previewScrollbar)\n\t\t} else {\n\t\t\tt.pborder.CPrint(tui.ColPreviewScrollbar, \" \")\n\t\t}\n\t}\n}\n\nfunc (t *Terminal) printPreview() {\n\tif !t.hasPreviewWindow() || t.pwindow.Height() == 0 {\n\t\treturn\n\t}\n\tnumLines := len(t.previewer.lines)\n\theight := t.pwindow.Height()\n\tunchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&\n\t\tt.previewer.version == t.previewed.version &&\n\t\tt.previewer.offset == t.previewed.offset\n\tt.previewer.scrollable = t.previewer.offset > t.activePreviewOpts.headerLines || numLines > height\n\tt.renderPreviewArea(unchanged)\n\tt.renderPreviewSpinner()\n\tt.previewed.numLines = numLines\n\tt.previewed.version = t.previewer.version\n\tt.previewed.offset = t.previewer.offset\n}\n\nfunc (t *Terminal) printPreviewDelayed() {\n\tif !t.hasPreviewWindow() || len(t.previewer.lines) > 0 && t.previewed.version == t.previewer.version {\n\t\treturn\n\t}\n\n\tt.previewer.scrollable = false\n\tt.renderPreviewArea(true)\n\n\tmessage := t.trimMessage(\"Loading ..\", t.pwindow.Width())\n\tpos := t.pwindow.Width() - len(message)\n\tt.pwindow.Move(0, pos)\n\tt.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)\n}\n\nfunc (t *Terminal) processTabsStr(input string, prefixWidth int) (string, int) {\n\tvar strbuf strings.Builder\n\tl := prefixWidth\n\tgr := uniseg.NewGraphemes(input)\n\tfor gr.Next() {\n\t\trs := gr.Runes()\n\t\tstr := string(rs)\n\t\tvar w int\n\t\tif len(rs) == 1 && rs[0] == '\\t' {\n\t\t\tw = t.tabstop - l%t.tabstop\n\t\t\tstrbuf.WriteString(strings.Repeat(\" \", w))\n\t\t} else {\n\t\t\tw = util.StringWidth(str)\n\t\t\tstrbuf.WriteString(str)\n\t\t}\n\t\tl += w\n\t}\n\treturn strbuf.String(), l\n}\n\nfunc (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {\n\treturn t.processTabsStr(string(runes), prefixWidth)\n}\n\nfunc (t *Terminal) printAll() {\n\tt.resizeWindows(t.forcePreview, true)\n\tt.printList()\n\tt.printPrompt()\n\tt.printInfo()\n\tt.printHeader()\n\tt.printFooter()\n\tt.printPreview()\n}\n\nfunc (t *Terminal) flush() {\n\tt.placeCursor()\n\tif !t.suppress {\n\t\twindows := make([]tui.Window, 0, 9)\n\t\tif t.border != nil {\n\t\t\twindows = append(windows, t.border)\n\t\t}\n\t\tif t.pborder != nil {\n\t\t\twindows = append(windows, t.pborder)\n\t\t}\n\t\tif t.pwindow != nil {\n\t\t\twindows = append(windows, t.pwindow)\n\t\t}\n\t\tif t.wborder != nil {\n\t\t\twindows = append(windows, t.wborder)\n\t\t}\n\t\tif t.window != nil {\n\t\t\twindows = append(windows, t.window)\n\t\t}\n\t\tif t.headerBorder != nil {\n\t\t\twindows = append(windows, t.headerBorder)\n\t\t}\n\t\tif t.headerWindow != nil {\n\t\t\twindows = append(windows, t.headerWindow)\n\t\t}\n\t\tif t.headerLinesBorder != nil {\n\t\t\twindows = append(windows, t.headerLinesBorder)\n\t\t}\n\t\tif t.headerLinesWindow != nil {\n\t\t\twindows = append(windows, t.headerLinesWindow)\n\t\t}\n\t\tif t.inputBorder != nil {\n\t\t\twindows = append(windows, t.inputBorder)\n\t\t}\n\t\tif t.inputWindow != nil {\n\t\t\twindows = append(windows, t.inputWindow)\n\t\t}\n\t\tt.tui.RefreshWindows(windows)\n\t}\n}\n\nfunc (t *Terminal) delChar() bool {\n\tif len(t.input) > 0 && t.cx < len(t.input) {\n\t\tt.input = append(t.input[:t.cx], t.input[t.cx+1:]...)\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc findLastMatch(pattern string, str string) int {\n\trx, err := regexp.Compile(pattern)\n\tif err != nil {\n\t\treturn -1\n\t}\n\tlocs := rx.FindAllStringIndex(str, -1)\n\tif locs == nil {\n\t\treturn -1\n\t}\n\tprefix := []rune(str[:locs[len(locs)-1][0]])\n\treturn len(prefix)\n}\n\nfunc findFirstMatch(pattern string, str string) int {\n\trx, err := regexp.Compile(pattern)\n\tif err != nil {\n\t\treturn -1\n\t}\n\tloc := rx.FindStringIndex(str)\n\tif loc == nil {\n\t\treturn -1\n\t}\n\tprefix := []rune(str[:loc[0]])\n\treturn len(prefix)\n}\n\nfunc copySlice(slice []rune) []rune {\n\tret := make([]rune, len(slice))\n\tcopy(ret, slice)\n\treturn ret\n}\n\nfunc (t *Terminal) rubout(pattern string) {\n\tpcx := t.cx\n\tafter := t.input[t.cx:]\n\tt.cx = findLastMatch(pattern, string(t.input[:t.cx])) + 1\n\tt.yanked = copySlice(t.input[t.cx:pcx])\n\tt.input = append(t.input[:t.cx], after...)\n}\n\nfunc keyMatch(key tui.Event, event tui.Event) bool {\n\treturn event.Type == key.Type && event.Char == key.Char ||\n\t\tkey.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double\n}\n\nfunc parsePlaceholder(match string) (bool, string, placeholderFlags) {\n\tflags := placeholderFlags{}\n\n\tif match[0] == '\\\\' {\n\t\t// Escaped placeholder pattern\n\t\treturn true, match[1:], flags\n\t}\n\n\tif strings.HasPrefix(match, \"{fzf:\") {\n\t\t// {fzf:*} are not determined by the current item\n\t\tflags.forceUpdate = true\n\t\treturn false, match, flags\n\t}\n\n\ttrimmed := \"\"\n\tfor _, char := range match[1:] {\n\t\tswitch char {\n\t\tcase '*':\n\t\t\tflags.asterisk = true\n\t\tcase '+':\n\t\t\tflags.plus = true\n\t\tcase 's':\n\t\t\tflags.preserveSpace = true\n\t\tcase 'n':\n\t\t\tflags.number = true\n\t\tcase 'f':\n\t\t\tflags.file = true\n\t\tcase 'r':\n\t\t\tflags.raw = true\n\t\tcase 'q':\n\t\t\tflags.forceUpdate = true\n\t\t\ttrimmed += string(char)\n\t\tdefault:\n\t\t\ttrimmed += string(char)\n\t\t}\n\t}\n\n\tmatchWithoutFlags := \"{\" + trimmed\n\n\treturn false, matchWithoutFlags, flags\n}\n\nfunc hasPreviewFlags(template string) (slot bool, plus bool, asterisk bool, forceUpdate bool) {\n\tfor _, match := range placeholder.FindAllString(template, -1) {\n\t\tescaped, _, flags := parsePlaceholder(match)\n\t\tif escaped {\n\t\t\tcontinue\n\t\t}\n\t\tslot = true\n\t\tplus = plus || flags.plus\n\t\tasterisk = asterisk || flags.asterisk\n\t\tforceUpdate = forceUpdate || flags.forceUpdate\n\t}\n\treturn\n}\n\ntype replacePlaceholderParams struct {\n\ttemplate   string\n\tstripAnsi  bool\n\tdelimiter  Delimiter\n\tprintsep   string\n\tforcePlus  bool\n\tquery      string\n\tallItems   [3][]*Item // current, select, and all matched items\n\tlastAction actionType\n\tprompt     string\n\texecutor   *util.Executor\n}\n\nfunc (t *Terminal) replacePlaceholderInInitialCommand(template string) (string, []string) {\n\treturn t.replacePlaceholder(template, false, string(t.input), [3][]*Item{nil, nil, nil})\n}\n\nfunc (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list [3][]*Item) (string, []string) {\n\treturn replacePlaceholder(replacePlaceholderParams{\n\t\ttemplate:   template,\n\t\tstripAnsi:  t.ansi,\n\t\tdelimiter:  t.delimiter,\n\t\tprintsep:   t.printsep,\n\t\tforcePlus:  forcePlus,\n\t\tquery:      input,\n\t\tallItems:   list,\n\t\tlastAction: t.lastAction,\n\t\tprompt:     t.promptString,\n\t\texecutor:   t.executor,\n\t})\n}\n\nfunc (t *Terminal) evaluateScrollOffset() int {\n\tif t.pwindow == nil {\n\t\treturn 0\n\t}\n\n\t// We only need the current item to calculate the scroll offset\n\tcurrent := []*Item{t.currentItem()}\n\tif current[0] == nil {\n\t\tcurrent = nil\n\t}\n\treplaced, tempFiles := t.replacePlaceholder(t.activePreviewOpts.scroll, false, \"\", [3][]*Item{current, nil, nil})\n\tremoveFiles(tempFiles)\n\toffsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, \"\")\n\n\tatoi := func(s string) int {\n\t\tn, e := strconv.Atoi(s)\n\t\tif e != nil {\n\t\t\treturn 0\n\t\t}\n\t\treturn n\n\t}\n\n\tbase := -1\n\theight := max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines)\n\tfor _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {\n\t\tif strings.HasPrefix(component, \"-/\") {\n\t\t\tcomponent = component[1:]\n\t\t}\n\t\tif component[0] == '/' {\n\t\t\tdenom := atoi(component[1:])\n\t\t\tif denom != 0 {\n\t\t\t\tbase -= height / denom\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tbase += atoi(component)\n\t}\n\treturn max(0, base)\n}\n\nfunc replacePlaceholder(params replacePlaceholderParams) (string, []string) {\n\ttempFiles := []string{}\n\tcurrent := params.allItems[0]\n\tselected := params.allItems[1]\n\tmatched := params.allItems[2]\n\n\t// replace placeholders one by one\n\treplaced := placeholder.ReplaceAllStringFunc(params.template, func(match string) string {\n\t\tescaped, match, flags := parsePlaceholder(match)\n\n\t\t// this function implements the effects a placeholder has on items\n\t\tvar replace func(*Item) string\n\n\t\t// placeholder types (escaped, query type, item type, token type)\n\t\tswitch {\n\t\tcase escaped:\n\t\t\treturn match\n\t\tcase match == \"{q}\" || match == \"{fzf:query}\":\n\t\t\treturn params.executor.QuoteEntry(params.query)\n\t\tcase strings.HasPrefix(match, \"{q:\"):\n\t\t\tif nth, err := splitNth(match[3 : len(match)-1]); err == nil {\n\t\t\t\telems, prefixLength := awkTokenizer(params.query)\n\t\t\t\ttokens := withPrefixLengths(elems, prefixLength)\n\t\t\t\ttrans := Transform(tokens, nth)\n\t\t\t\tresult := JoinTokens(trans)\n\t\t\t\tif !flags.preserveSpace {\n\t\t\t\t\tresult = strings.TrimSpace(result)\n\t\t\t\t}\n\t\t\t\treturn params.executor.QuoteEntry(result)\n\t\t\t}\n\n\t\t\treturn match\n\t\tcase match == \"{}\":\n\t\t\treplace = func(item *Item) string {\n\t\t\t\tswitch {\n\t\t\t\tcase flags.number:\n\t\t\t\t\tn := item.text.Index\n\t\t\t\t\tif n == minItem.Index() {\n\t\t\t\t\t\t// NOTE: Item index should normally be positive, but if there's no\n\t\t\t\t\t\t// match, it will be set to math.MinInt32, and we don't want to\n\t\t\t\t\t\t// show that value. However, int32 can overflow, especially when\n\t\t\t\t\t\t// `--tail` is used with an endless input stream, and the index of\n\t\t\t\t\t\t// an item actually can be math.MinInt32. In that case, you're\n\t\t\t\t\t\t// getting an incorrect value, but we're going to ignore that for\n\t\t\t\t\t\t// now.\n\t\t\t\t\t\treturn \"''\"\n\t\t\t\t\t}\n\t\t\t\t\treturn strconv.Itoa(int(n))\n\t\t\t\tcase flags.file || flags.raw:\n\t\t\t\t\treturn item.AsString(params.stripAnsi)\n\t\t\t\tdefault:\n\t\t\t\t\treturn params.executor.QuoteEntry(item.AsString(params.stripAnsi))\n\t\t\t\t}\n\t\t\t}\n\t\tcase match == \"{fzf:action}\":\n\t\t\treturn params.lastAction.Name()\n\t\tcase match == \"{fzf:prompt}\":\n\t\t\treturn params.executor.QuoteEntry(params.prompt)\n\t\tdefault:\n\t\t\t// token type and also failover (below)\n\t\t\trangeExpressions := strings.Split(match[1:len(match)-1], \",\")\n\t\t\tranges := make([]Range, len(rangeExpressions))\n\t\t\tfor idx, s := range rangeExpressions {\n\t\t\t\tr, ok := ParseRange(&s) // ellipsis (x..y) and shorthand (x..x) range syntax\n\t\t\t\tif !ok {\n\t\t\t\t\t// Invalid expression, just return the original string in the template\n\t\t\t\t\treturn match\n\t\t\t\t}\n\t\t\t\tranges[idx] = r\n\t\t\t}\n\n\t\t\treplace = func(item *Item) string {\n\t\t\t\ttokens := Tokenize(item.AsString(params.stripAnsi), params.delimiter)\n\t\t\t\ttrans := Transform(tokens, ranges)\n\t\t\t\tstr := JoinTokens(trans)\n\n\t\t\t\t// trim the last delimiter\n\t\t\t\tif params.delimiter.str != nil {\n\t\t\t\t\tstr = strings.TrimSuffix(str, *params.delimiter.str)\n\t\t\t\t} else if params.delimiter.regex != nil {\n\t\t\t\t\tdelims := params.delimiter.regex.FindAllStringIndex(str, -1)\n\t\t\t\t\t// make sure the delimiter is at the very end of the string\n\t\t\t\t\tif len(delims) > 0 && delims[len(delims)-1][1] == len(str) {\n\t\t\t\t\t\tstr = str[:delims[len(delims)-1][0]]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !flags.preserveSpace {\n\t\t\t\t\tstr = strings.TrimSpace(str)\n\t\t\t\t}\n\t\t\t\tif !flags.file && !flags.raw {\n\t\t\t\t\tstr = params.executor.QuoteEntry(str)\n\t\t\t\t}\n\t\t\t\treturn str\n\t\t\t}\n\t\t}\n\n\t\t// apply 'replace' function over proper set of items and return result\n\n\t\titems := current\n\t\tif flags.asterisk {\n\t\t\titems = matched\n\t\t} else if flags.plus || params.forcePlus {\n\t\t\titems = selected\n\t\t}\n\t\treplacements := make([]string, len(items))\n\n\t\tfor idx, item := range items {\n\t\t\treplacements[idx] = replace(item)\n\t\t}\n\n\t\tif flags.file {\n\t\t\tfile := WriteTemporaryFile(replacements, params.printsep)\n\t\t\ttempFiles = append(tempFiles, file)\n\t\t\treturn file\n\t\t}\n\t\treturn strings.Join(replacements, \" \")\n\t})\n\n\treturn replaced, tempFiles\n}\n\nfunc (t *Terminal) fullRedraw() {\n\tt.tui.Clear()\n\tt.tui.Refresh()\n\tt.printAll()\n}\n\nfunc (t *Terminal) captureLine(template string) string {\n\treturn t.executeCommand(template, false, true, true, true, \"\")\n}\n\nfunc (t *Terminal) captureLines(template string) string {\n\treturn t.executeCommand(template, false, true, true, false, \"\")\n}\n\nfunc (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(string)) {\n\t_, list := t.buildPlusList(a.a, false)\n\tcommand, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)\n\tversion := t.bgVersion\n\tcmd := t.executor.ExecCommand(command, true)\n\tcmd.Env = t.environ()\n\titem := func(proceed bool) {\n\t\tif proceed {\n\t\t\tout, _ := cmd.StdoutPipe()\n\t\t\treader := bufio.NewReader(out)\n\t\t\tvar output string\n\t\t\tif err := cmd.Start(); err == nil {\n\t\t\t\trunningCmd := runningCmd{cmd, tempFiles}\n\t\t\t\tt.runningCmds.Add(&runningCmd)\n\t\t\t\tif firstLineOnly {\n\t\t\t\t\toutput, _ = reader.ReadString('\\n')\n\t\t\t\t\toutput = strings.TrimRight(output, \"\\r\\n\")\n\t\t\t\t} else {\n\t\t\t\t\tbytes, _ := io.ReadAll(reader)\n\t\t\t\t\toutput = string(bytes)\n\t\t\t\t}\n\t\t\t\tcmd.Wait()\n\t\t\t\tt.runningCmds.Remove(&runningCmd)\n\t\t\t}\n\t\t\tt.callbackChan <- versionedCallback{version, func() { callback(output) }}\n\t\t}\n\t\tremoveFiles(tempFiles)\n\n\t}\n\tqueue, prs := t.bgQueue[a]\n\tif !prs {\n\t\tqueue = []func(bool){}\n\t}\n\tqueue = append(queue, item)\n\tt.bgQueue[a] = queue\n}\n\nfunc (t *Terminal) dispatchAsync() {\nLoop:\n\tfor a, queue := range t.bgQueue {\n\t\tdelete(t.bgQueue, a)\n\t\tif len(queue) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tsemaphore, prs := t.bgSemaphores[a]\n\t\tif !prs {\n\t\t\tsemaphore = make(chan struct{}, maxBgProcessesPerAction)\n\t\t\tt.bgSemaphores[a] = semaphore\n\t\t}\n\t\tfor _, item := range queue {\n\t\t\tselect {\n\t\t\t// Acquire local semaphore\n\t\t\tcase semaphore <- struct{}{}:\n\t\t\tdefault:\n\t\t\t\t// Failed to acquire local semaphore, putting only the last one back to the queue\n\t\t\t\tfor _, item := range queue[:len(queue)-1] {\n\t\t\t\t\titem(false)\n\t\t\t\t}\n\t\t\t\tt.bgQueue[a] = queue[len(queue)-1:]\n\t\t\t\tcontinue Loop\n\t\t\t}\n\t\t\ttodo := item\n\t\t\tgo func() {\n\t\t\t\t// Acquire global semaphore\n\t\t\t\tt.bgSemaphore <- struct{}{}\n\n\t\t\t\ttodo(true)\n\t\t\t\t// Release local semaphore\n\t\t\t\t<-semaphore\n\t\t\t\t// Release global semaphore\n\t\t\t\t<-t.bgSemaphore\n\t\t\t}()\n\t\t}\n\t}\n}\n\nfunc (t *Terminal) executeCommand(template string, forcePlus bool, background bool, capture bool, firstLineOnly bool, info string) string {\n\tline := \"\"\n\tvalid, list := t.buildPlusList(template, forcePlus)\n\t// 'capture' is used for transform-* and we don't want to\n\t// return an empty string in those cases\n\tif !valid && !capture {\n\t\treturn line\n\t}\n\tcommand, tempFiles := t.replacePlaceholder(template, forcePlus, string(t.input), list)\n\tcmd := t.executor.ExecCommand(command, false)\n\tcmd.Env = t.environ()\n\tif len(info) > 0 {\n\t\tcmd.Env = append(cmd.Env, \"FZF_INFO=\"+info)\n\t}\n\tt.executing.Set(true)\n\tif !background {\n\t\t// Open a separate handle for tty input\n\t\tif in, _ := tui.TtyIn(t.ttyDefault); in != nil {\n\t\t\tcmd.Stdin = in\n\t\t\tif in != os.Stdin {\n\t\t\t\tdefer in.Close()\n\t\t\t}\n\t\t}\n\n\t\tcmd.Stdout = os.Stdout\n\t\tif !util.IsTty(os.Stdout) {\n\t\t\tif out, _ := tui.TtyOut(t.ttyDefault); out != nil {\n\t\t\t\tcmd.Stdout = out\n\t\t\t\tdefer out.Close()\n\t\t\t}\n\t\t}\n\n\t\tcmd.Stderr = os.Stderr\n\t\tif !util.IsTty(os.Stderr) {\n\t\t\tif out, _ := tui.TtyOut(t.ttyDefault); out != nil {\n\t\t\t\tcmd.Stderr = out\n\t\t\t\tdefer out.Close()\n\t\t\t}\n\t\t}\n\n\t\tt.mutex.Unlock()\n\t\tif len(info) == 0 {\n\t\t\tt.uiMutex.Lock()\n\t\t}\n\t\tt.tui.Pause(true)\n\t\tcmd.Run()\n\t\tt.tui.Resume(true, false)\n\t\tt.mutex.Lock()\n\t\t// NOTE: Using t.reqBox.Set(reqFullRedraw...) instead can cause a deadlock\n\t\tt.fullRedraw()\n\t\tt.flush()\n\t} else {\n\t\tt.mutex.Unlock()\n\t\tif len(info) == 0 {\n\t\t\tt.uiMutex.Lock()\n\t\t}\n\t\tpaused := atomic.Int32{}\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-time.After(blockDuration):\n\t\t\t\tif paused.CompareAndSwap(0, 1) {\n\t\t\t\t\tt.tui.Pause(false)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t\tif capture {\n\t\t\tout, _ := cmd.StdoutPipe()\n\t\t\treader := bufio.NewReader(out)\n\t\t\tcmd.Start()\n\t\t\tif firstLineOnly {\n\t\t\t\tline, _ = reader.ReadString('\\n')\n\t\t\t\tline = strings.TrimRight(line, \"\\r\\n\")\n\t\t\t} else {\n\t\t\t\tbytes, _ := io.ReadAll(reader)\n\t\t\t\tline = string(bytes)\n\t\t\t}\n\t\t\tcmd.Wait()\n\t\t} else {\n\t\t\tcmd.Run()\n\t\t}\n\t\tcancel()\n\t\tif paused.CompareAndSwap(1, 2) {\n\t\t\tt.tui.Resume(false, false)\n\t\t}\n\t\tt.mutex.Lock()\n\n\t\t// Redraw prompt in case the user has typed something after blockDuration\n\t\tif paused.Load() > 0 {\n\t\t\t// NOTE: Using t.reqBox.Set(reqXXX...) instead can cause a deadlock\n\t\t\tt.printPrompt()\n\t\t\tif t.infoStyle == infoInline || t.infoStyle == infoInlineRight {\n\t\t\t\tt.printInfo()\n\t\t\t}\n\t\t}\n\t}\n\tif len(info) == 0 {\n\t\tt.uiMutex.Unlock()\n\t}\n\tt.executing.Set(false)\n\tremoveFiles(tempFiles)\n\treturn line\n}\n\nfunc (t *Terminal) hasPreviewer() bool {\n\treturn t.previewBox != nil\n}\n\nfunc (t *Terminal) needPreviewWindow() bool {\n\treturn t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.activePreviewOpts.Visible()\n}\n\n// Check if previewer is currently in action (invisible previewer with size 0 or visible previewer)\nfunc (t *Terminal) canPreview() bool {\n\treturn t.hasPreviewer() && (!t.activePreviewOpts.Visible() && !t.activePreviewOpts.hidden || t.hasPreviewWindow())\n}\n\nfunc (t *Terminal) hasPreviewWindow() bool {\n\treturn t.pwindow != nil\n}\n\nfunc (t *Terminal) hasPreviewWindowOnRight() bool {\n\treturn t.hasPreviewWindow() && t.activePreviewOpts.position == posRight\n}\n\nfunc (t *Terminal) currentItem() *Item {\n\tcnt := t.merger.Length()\n\tif t.cy >= 0 && cnt > 0 && cnt > t.cy {\n\t\treturn t.merger.Get(t.cy).item\n\t}\n\treturn nil\n}\n\nfunc (t *Terminal) isCurrentItemMatch() bool {\n\tcnt := t.merger.Length()\n\tif t.cy >= 0 && cnt > 0 && cnt > t.cy {\n\t\tif !t.raw {\n\t\t\treturn true\n\t\t}\n\t\titem := t.merger.Get(t.cy).item\n\t\treturn t.isItemMatch(item)\n\t}\n\treturn false\n}\n\nfunc (t *Terminal) isItemMatch(item *Item) bool {\n\t_, matched := t.matchMap[item.Index()]\n\treturn matched\n}\n\nfunc (t *Terminal) filterSelected() {\n\tfiltered := make(map[int32]selectedItem)\n\tfor k, v := range t.selected {\n\t\tif t.isItemMatch(v.item) {\n\t\t\tfiltered[k] = v\n\t\t}\n\t}\n\tt.selected = filtered\n}\n\nfunc (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*Item) {\n\tcurrent := t.currentItem()\n\tslot, plus, asterisk, forceUpdate := hasPreviewFlags(template)\n\tif !(!slot || forceUpdate || asterisk || (forcePlus || plus) && len(t.selected) > 0) {\n\t\tif current == nil {\n\t\t\t// Invalid\n\t\t\treturn false, [3][]*Item{nil, nil, nil}\n\t\t}\n\t\treturn true, [3][]*Item{{current}, {current}, nil}\n\t}\n\n\t// We would still want to update preview window even if there is no match if\n\t//   1. the command template contains {q}\n\t//   2. or it contains {+} and we have more than one item already selected.\n\t// To do so, we pass an empty Item instead of nil to trigger an update.\n\tif current == nil {\n\t\tcurrent = &minItem\n\t}\n\n\tvar all []*Item\n\tif asterisk {\n\t\tcnt := t.merger.Length()\n\t\tall = make([]*Item, cnt)\n\t\tfor i := range cnt {\n\t\t\tall[i] = t.merger.Get(i).item\n\t\t}\n\t}\n\n\tvar sels []*Item\n\tif len(t.selected) == 0 {\n\t\tsels = []*Item{current}\n\t} else if len(t.selected) > 0 {\n\t\tsels = make([]*Item, len(t.selected))\n\t\tfor i, sel := range t.sortSelected() {\n\t\t\tsels[i] = sel.item\n\t\t}\n\t}\n\treturn true, [3][]*Item{{current}, sels, all}\n}\n\nfunc (t *Terminal) selectItem(item *Item) bool {\n\tif len(t.selected) >= t.multi {\n\t\treturn false\n\t}\n\tif _, found := t.selected[item.Index()]; found {\n\t\treturn true\n\t}\n\n\tt.selected[item.Index()] = selectedItem{time.Now(), item}\n\tt.version++\n\n\treturn true\n}\n\nfunc (t *Terminal) selectItemChanged(item *Item) bool {\n\tif _, found := t.selected[item.Index()]; found {\n\t\treturn false\n\t}\n\treturn t.selectItem(item)\n}\n\nfunc (t *Terminal) deselectItem(item *Item) {\n\tdelete(t.selected, item.Index())\n\tt.version++\n}\n\nfunc (t *Terminal) deselectItemChanged(item *Item) bool {\n\tif _, found := t.selected[item.Index()]; found {\n\t\tt.deselectItem(item)\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (t *Terminal) toggleItem(item *Item) bool {\n\tif _, found := t.selected[item.Index()]; !found {\n\t\treturn t.selectItem(item)\n\t}\n\tt.deselectItem(item)\n\treturn true\n}\n\nfunc (t *Terminal) killPreview() {\n\tselect {\n\tcase t.killChan <- true:\n\t\t<-t.killedChan\n\tdefault:\n\t}\n}\n\nfunc (t *Terminal) cancelPreview() {\n\tselect {\n\tcase t.killChan <- false:\n\tdefault:\n\t}\n}\n\nfunc (t *Terminal) pwindowSize() tui.TermSize {\n\tif t.pwindow == nil {\n\t\treturn tui.TermSize{}\n\t}\n\tsize := tui.TermSize{Lines: t.pwindow.Height(), Columns: t.pwindow.Width()}\n\n\tif t.termSize.PxWidth > 0 {\n\t\tsize.PxWidth = size.Columns * t.termSize.PxWidth / t.termSize.Columns\n\t\tsize.PxHeight = size.Lines * t.termSize.PxHeight / t.termSize.Lines\n\t}\n\treturn size\n}\n\nfunc (t *Terminal) currentIndex() int32 {\n\tif currentItem := t.currentItem(); currentItem != nil {\n\t\treturn currentItem.Index()\n\t}\n\treturn minItem.Index()\n}\n\nfunc (t *Terminal) trackKeyFor(item *Item, nth []Range) string {\n\ttokens := Tokenize(item.AsString(t.ansi), t.delimiter)\n\treturn StripLastDelimiter(JoinTokens(Transform(tokens, nth)), t.delimiter)\n}\n\nfunc (t *Terminal) unblockTrack() {\n\tif t.trackBlocked {\n\t\tt.trackBlocked = false\n\t\tt.trackKey = \"\"\n\t\tt.trackKeyCache = nil\n\t\tif !t.inputless {\n\t\t\tt.tui.ShowCursor()\n\t\t}\n\t}\n}\n\nfunc (t *Terminal) addClickHeaderWord(env []string) []string {\n\t/*\n\t * echo $'HL1\\nHL2' | fzf --header-lines 3 --header $'H1\\nH2' --header-lines-border --bind 'click-header:preview:env | grep FZF_CLICK'\n\t *\n\t *   REVERSE      DEFAULT      REVERSE-LIST\n\t *   H1      1            1    HL1     1\n\t *   H2      2    HL2     2    HL2     2\n\t *   -------      HL1     3            3\n\t *   HL1     3    -------      -------\n\t *   HL2     4    H1      4    H1      4\n\t *           5    H2      5    H2      5\n\t */\n\tclickHeaderLine := t.clickHeaderLine - 1\n\tif clickHeaderLine < 0 {\n\t\t// Never clicked on the header\n\t\treturn env\n\t}\n\n\tnthBase := 0\n\t// Convert header items to strings for click handling\n\theaderStrs := make([]string, len(t.header))\n\tfor i, item := range t.header {\n\t\theaderStrs[i] = item.text.ToString()\n\t}\n\theaders := [2][]string{headerStrs, t.header0}\n\tif t.layout == layoutReverse {\n\t\theaders[0], headers[1] = headers[1], headers[0]\n\t}\n\tvar trimmedLine string\n\tvar words []Token\n\tvar lineNum int\n\tfor lineNum = 0; lineNum <= clickHeaderLine; lineNum++ {\n\t\tcurrentLine := lineNum == clickHeaderLine\n\t\tvar line string\n\t\tif lineNum < len(headers[0]) {\n\t\t\tindex := lineNum\n\t\t\tif t.layout == layoutDefault {\n\t\t\t\tindex = len(headers[0]) - index - 1\n\t\t\t}\n\t\t\tline = headers[0][index]\n\t\t} else if lineNum-len(headers[0]) < len(headers[1]) {\n\t\t\tline = headers[1][lineNum-len(headers[0])]\n\t\t}\n\t\tif currentLine && len(line) == 0 {\n\t\t\treturn env\n\t\t}\n\n\t\t// NOTE: We can't expand tabs here because the delimiter can contain tabs.\n\t\ttrimmedLine, _, _ = extractColor(line, nil, nil)\n\t\twords = Tokenize(trimmedLine, t.delimiter)\n\t\tif currentLine {\n\t\t\tbreak\n\t\t} else {\n\t\t\t// TODO: Counting can be incorrect when the delimiter contains new line\n\t\t\t// characters, and there are empty lines in the header.\n\t\t\tnthBase += len(words)\n\t\t}\n\t}\n\n\tcolNum := t.clickHeaderColumn - 1\n\tprefixWidth, prefixLength := 0, 0\n\tfor idx, token := range words {\n\t\tprefixWidth += t.displayWidthWithPrefix(trimmedLine[prefixLength:token.prefixLength], prefixWidth)\n\t\tprefixLength = int(token.prefixLength)\n\n\t\tword, _ := t.processTabs(token.text.ToRunes(), prefixWidth)\n\t\ttrimmed := strings.TrimRightFunc(word, unicode.IsSpace)\n\t\ttrimWidth := t.displayWidthWithPrefix(trimmed, prefixWidth)\n\n\t\t// Find the position of the first non-space character in the word\n\t\tminPos := strings.IndexFunc(trimmed, func(r rune) bool {\n\t\t\treturn !unicode.IsSpace(r)\n\t\t})\n\t\tif colNum >= minPos && colNum >= prefixWidth && colNum < prefixWidth+trimWidth {\n\t\t\tenv = append(env, fmt.Sprintf(\"FZF_CLICK_HEADER_WORD=%s\", trimmed))\n\t\t\tnth := fmt.Sprintf(\"FZF_CLICK_HEADER_NTH=%d\", nthBase+idx+1)\n\t\t\tif lineNum == len(t.header)+len(t.header0)-1 && idx == len(words)-1 {\n\t\t\t\tnth += \"..\"\n\t\t\t}\n\t\t\tenv = append(env, nth)\n\t\t\treturn env\n\t\t}\n\t}\n\treturn env\n}\n\nfunc (t *Terminal) addClickFooterWord(env []string) []string {\n\tclickFooterLine := t.clickFooterLine - 1\n\tif clickFooterLine < 0 || clickFooterLine >= len(t.footer) {\n\t\t// Never clicked on the footer\n\t\treturn env\n\t}\n\n\t// NOTE: Unlike in click-header, we don't use --delimiter here, since we're\n\t// only interested in the word, not nth. Does this make sense?\n\ttrimmed, _, _ := extractColor(t.footer[clickFooterLine], nil, nil)\n\ttrimmed, _ = t.processTabsStr(trimmed, 0)\n\twords := Tokenize(trimmed, Delimiter{})\n\tcolNum := t.clickFooterColumn - 1\n\tfor _, token := range words {\n\t\tprefixWidth := int(token.prefixLength)\n\t\tword := token.text.ToString()\n\t\ttrimmed := strings.TrimRightFunc(word, unicode.IsSpace)\n\t\ttrimWidth := t.displayWidthWithPrefix(trimmed, prefixWidth)\n\n\t\t// Find the position of the first non-space character in the word\n\t\tminPos := strings.IndexFunc(trimmed, func(r rune) bool {\n\t\t\treturn !unicode.IsSpace(r)\n\t\t})\n\t\tif colNum >= minPos && colNum >= prefixWidth && colNum < prefixWidth+trimWidth {\n\t\t\tenv = append(env, fmt.Sprintf(\"FZF_CLICK_FOOTER_WORD=%s\", trimmed))\n\t\t\treturn env\n\t\t}\n\t}\n\treturn env\n}\n\n// Loop is called to start Terminal I/O\nfunc (t *Terminal) Loop() error {\n\t// prof := profile.Start(profile.ProfilePath(\"/tmp/\"))\n\tfitpad := <-t.startChan\n\tfit := fitpad.fit\n\tif fit >= 0 {\n\t\tpad := fitpad.pad\n\t\tt.tui.Resize(func(termHeight int) int {\n\t\t\tcontentHeight := fit + t.extraLines()\n\t\t\tif t.needPreviewWindow() {\n\t\t\t\tif t.activePreviewOpts.aboveOrBelow() {\n\t\t\t\t\tif t.activePreviewOpts.size.percent {\n\t\t\t\t\t\tnewContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size))\n\t\t\t\t\t\tcontentHeight = max(contentHeight+1+borderLines(t.activePreviewOpts.Border()), newContentHeight)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.Border())\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Minimum height if preview window can appear\n\t\t\t\t\tcontentHeight = max(contentHeight, 1+borderLines(t.activePreviewOpts.Border()))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn min(termHeight, contentHeight+pad)\n\t\t})\n\t}\n\n\t// Context\n\tctx, cancel := context.WithCancel(context.Background())\n\n\t{ // Late initialization\n\t\tintChan := make(chan os.Signal, 1)\n\t\tsignal.Notify(intChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tcase s := <-intChan:\n\t\t\t\t\t// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself\n\t\t\t\t\tif !(s == os.Interrupt && t.executing.Get()) {\n\t\t\t\t\t\tt.reqBox.Set(reqQuit, nil)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tif !t.tui.ShouldEmitResizeEvent() {\n\t\t\tresizeChan := make(chan os.Signal, 1)\n\t\t\tnotifyOnResize(resizeChan) // Non-portable\n\t\t\tgo func() {\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase <-resizeChan:\n\t\t\t\t\t\tt.reqBox.Set(reqResize, nil)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\tt.mutex.Lock()\n\t\tif err := t.initFunc(); err != nil {\n\t\t\tt.mutex.Unlock()\n\t\t\tcancel()\n\t\t\tt.eventBox.Set(EvtQuit, quitSignal{ExitError, err})\n\t\t\treturn err\n\t\t}\n\t\tt.termSize = t.tui.Size()\n\t\tt.resizeWindows(false, false)\n\t\tt.window.Erase()\n\t\tt.mutex.Unlock()\n\n\t\tt.reqBox.Set(reqPrompt, nil)\n\t\tt.reqBox.Set(reqInfo, nil)\n\t\tt.reqBox.Set(reqHeader, nil)\n\t\tt.reqBox.Set(reqFooter, nil)\n\t\tif t.initDelay > 0 {\n\t\t\tgo func() {\n\t\t\t\ttimer := time.NewTimer(t.initDelay)\n\t\t\t\t<-timer.C\n\t\t\t\tt.reqBox.Set(reqActivate, nil)\n\t\t\t}()\n\t\t}\n\n\t\t// Keep the spinner spinning\n\t\tgo func() {\n\t\t\tfor t.running.Get() {\n\t\t\t\tt.mutex.Lock()\n\t\t\t\treading := t.reading\n\t\t\t\tt.mutex.Unlock()\n\t\t\t\ttime.Sleep(spinnerDuration)\n\t\t\t\tif reading {\n\t\t\t\t\tt.reqBox.Set(reqInfo, nil)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\tif t.hasPreviewer() {\n\t\tgo func() {\n\t\t\tvar version int64\n\t\t\tstop := false\n\t\t\tt.previewBox.WaitFor(reqPreviewReady)\n\t\t\tfor {\n\t\t\t\trequested := false\n\t\t\t\tvar items [3][]*Item\n\t\t\t\tvar commandTemplate string\n\t\t\t\tvar env []string\n\t\t\t\tvar query string\n\t\t\t\tinitialOffset := 0\n\t\t\t\tt.previewBox.Wait(func(events *util.Events) {\n\t\t\t\t\tfor req, value := range *events {\n\t\t\t\t\t\tswitch req {\n\t\t\t\t\t\tcase reqQuit:\n\t\t\t\t\t\t\tstop = true\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\tcase reqPreviewEnqueue:\n\t\t\t\t\t\t\trequest := value.(previewRequest)\n\t\t\t\t\t\t\tcommandTemplate = request.template\n\t\t\t\t\t\t\tinitialOffset = request.scrollOffset\n\t\t\t\t\t\t\titems = request.list\n\t\t\t\t\t\t\tenv = request.env\n\t\t\t\t\t\t\tquery = request.query\n\t\t\t\t\t\t\trequested = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tevents.Clear()\n\t\t\t\t})\n\t\t\t\tif stop {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif !requested {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tversion++\n\t\t\t\t// We don't display preview window if no match\n\t\t\t\tif items[0] != nil {\n\t\t\t\t\tcommand, tempFiles := t.replacePlaceholder(commandTemplate, false, query, items)\n\t\t\t\t\tcmd := t.executor.ExecCommand(command, true)\n\t\t\t\t\tcmd.Env = env\n\n\t\t\t\t\tout, _ := cmd.StdoutPipe()\n\t\t\t\t\tcmd.Stderr = cmd.Stdout\n\t\t\t\t\treader := bufio.NewReader(out)\n\t\t\t\t\teofChan := make(chan bool)\n\t\t\t\t\tfinishChan := make(chan bool, 1)\n\t\t\t\t\terr := cmd.Start()\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\treapChan := make(chan bool)\n\t\t\t\t\t\tlineChan := make(chan eachLine)\n\t\t\t\t\t\t// Goroutine 1 reads process output\n\t\t\t\t\t\tgo func() {\n\t\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\t\tline, err := reader.ReadString('\\n')\n\t\t\t\t\t\t\t\tlineChan <- eachLine{line, err}\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\teofChan <- true\n\t\t\t\t\t\t}()\n\n\t\t\t\t\t\t// Goroutine 2 periodically requests rendering\n\t\t\t\t\t\trendered := util.NewAtomicBool(false)\n\t\t\t\t\t\tgo func(version int64) {\n\t\t\t\t\t\t\tlines := []string{}\n\t\t\t\t\t\t\tspinner := makeSpinner(t.unicode)\n\t\t\t\t\t\t\tspinnerIndex := -1 // Delay initial rendering by an extra tick\n\t\t\t\t\t\t\tticker := time.NewTicker(previewChunkDelay)\n\t\t\t\t\t\t\toffset := initialOffset\n\t\t\t\t\t\tLoop:\n\t\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\t\tselect {\n\t\t\t\t\t\t\t\tcase <-ticker.C:\n\t\t\t\t\t\t\t\t\tif len(lines) > 0 && len(lines) >= initialOffset {\n\t\t\t\t\t\t\t\t\t\tif spinnerIndex >= 0 {\n\t\t\t\t\t\t\t\t\t\t\tspin := spinner[spinnerIndex%len(spinner)]\n\t\t\t\t\t\t\t\t\t\t\tt.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})\n\t\t\t\t\t\t\t\t\t\t\trendered.Set(true)\n\t\t\t\t\t\t\t\t\t\t\toffset = -1\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tspinnerIndex++\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase eachLine := <-lineChan:\n\t\t\t\t\t\t\t\t\tline := eachLine.line\n\t\t\t\t\t\t\t\t\terr := eachLine.err\n\t\t\t\t\t\t\t\t\tif len(line) > 0 {\n\t\t\t\t\t\t\t\t\t\tclearIndex := strings.Index(line, clearCode)\n\t\t\t\t\t\t\t\t\t\tif clearIndex >= 0 {\n\t\t\t\t\t\t\t\t\t\t\tlines = []string{}\n\t\t\t\t\t\t\t\t\t\t\tline = line[clearIndex+len(clearCode):]\n\t\t\t\t\t\t\t\t\t\t\tversion--\n\t\t\t\t\t\t\t\t\t\t\toffset = 0\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tlines = append(lines, line)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\t\tt.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, \"\"})\n\t\t\t\t\t\t\t\t\t\trendered.Set(true)\n\t\t\t\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tticker.Stop()\n\t\t\t\t\t\t\treapChan <- true\n\t\t\t\t\t\t}(version)\n\n\t\t\t\t\t\t// Goroutine 3 is responsible for cancelling running preview command\n\t\t\t\t\t\tgo func(version int64) {\n\t\t\t\t\t\t\ttimer := time.NewTimer(previewDelayed)\n\t\t\t\t\t\tLoop:\n\t\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\t\tselect {\n\t\t\t\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t\t\tcase <-timer.C:\n\t\t\t\t\t\t\t\t\tt.reqBox.Set(reqPreviewDelayed, version)\n\t\t\t\t\t\t\t\tcase immediately := <-t.killChan:\n\t\t\t\t\t\t\t\t\tif immediately {\n\t\t\t\t\t\t\t\t\t\tutil.KillCommand(cmd)\n\t\t\t\t\t\t\t\t\t\tt.killedChan <- true\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t// We can immediately kill a long-running preview program\n\t\t\t\t\t\t\t\t\t\t// once we started rendering its partial output\n\t\t\t\t\t\t\t\t\t\tdelay := previewCancelWait\n\t\t\t\t\t\t\t\t\t\tif rendered.Get() {\n\t\t\t\t\t\t\t\t\t\t\tdelay = 0\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\ttimer := time.NewTimer(delay)\n\t\t\t\t\t\t\t\t\t\tselect {\n\t\t\t\t\t\t\t\t\t\tcase <-timer.C:\n\t\t\t\t\t\t\t\t\t\t\tutil.KillCommand(cmd)\n\t\t\t\t\t\t\t\t\t\tcase <-finishChan:\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\ttimer.Stop()\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t\t\tcase <-finishChan:\n\t\t\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttimer.Stop()\n\t\t\t\t\t\t\treapChan <- true\n\t\t\t\t\t\t}(version)\n\n\t\t\t\t\t\t<-eofChan          // Goroutine 1 finished\n\t\t\t\t\t\tcmd.Wait()         // NOTE: We should not call Wait before EOF\n\t\t\t\t\t\tfinishChan <- true // Tell Goroutine 3 to stop\n\t\t\t\t\t\t<-reapChan         // Goroutine 2 and 3 finished\n\t\t\t\t\t\t<-reapChan\n\t\t\t\t\t\tremoveFiles(tempFiles)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Failed to start the command. Report the error immediately.\n\t\t\t\t\t\tt.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, \"\"})\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, \"\"})\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\trefreshPreview := func(command string) {\n\t\tif len(command) > 0 && t.canPreview() {\n\t\t\t_, list := t.buildPlusList(command, false)\n\t\t\tt.cancelPreview()\n\t\t\tt.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.evaluateScrollOffset(), list, t.environForPreview(), string(t.input)})\n\t\t}\n\t}\n\n\tgo func() { // Render loop\n\t\tvar focusedIndex = minItem.Index()\n\t\tvar version int64 = -1\n\t\trunning := true\n\t\tcode := ExitError\n\t\texit := func(getCode func() int) {\n\t\t\tif t.hasPreviewer() {\n\t\t\t\tt.previewBox.Set(reqQuit, nil)\n\t\t\t}\n\t\t\tif t.listener != nil {\n\t\t\t\tt.listener.Close()\n\t\t\t}\n\t\t\tt.tui.Close()\n\t\t\tcode = getCode()\n\t\t\tif code <= ExitNoMatch && t.history != nil {\n\t\t\t\tt.history.append(string(t.input))\n\t\t\t}\n\t\t\tt.runningCmds.ForEach(func(cmd *runningCmd) {\n\t\t\t\tutil.KillCommand(cmd.cmd)\n\t\t\t\tremoveFiles(cmd.tempFiles)\n\t\t\t})\n\t\t\trunning = false\n\t\t\tt.mutex.Unlock()\n\t\t}\n\n\t\tfor running {\n\t\t\tt.reqBox.Wait(func(events *util.Events) {\n\t\t\t\tdefer events.Clear()\n\n\t\t\t\t// Sort events.\n\t\t\t\t// e.g. Make sure that reqPrompt is processed before reqInfo\n\t\t\t\tkeys := make([]int, 0, len(*events))\n\t\t\t\tfor key := range *events {\n\t\t\t\t\tkeys = append(keys, int(key))\n\t\t\t\t}\n\t\t\t\tsort.Ints(keys)\n\n\t\t\t\t// t.uiMutex must be locked first to avoid deadlock. Execute actions\n\t\t\t\t// will 1. unlock t.mutex to allow GET endpoint and 2. lock t.uiMutex\n\t\t\t\t// to block rendering during the execution.\n\t\t\t\t//\n\t\t\t\t// T1           T2 (good)       |  T1            T2 (bad)\n\t\t\t\t//               L t.uiMutex    |\n\t\t\t\t//  L t.mutex                   |   L t.mutex\n\t\t\t\t//  U t.mutex                   |   U t.mutex\n\t\t\t\t//               L t.mutex      |                 L t.mutex\n\t\t\t\t//               U t.mutex      |   L t.uiMutex\n\t\t\t\t//               U t.uiMutex    |                 L t.uiMutex!!\n\t\t\t\t//  L t.uiMutex                 |\n\t\t\t\t//                              |   L t.mutex!!\n\t\t\t\t//  L t.mutex                   |   U t.uiMutex\n\t\t\t\t//  U t.uiMutex                 |\n\t\t\t\tt.uiMutex.Lock()\n\t\t\t\tt.mutex.Lock()\n\t\t\t\tinfo := false\n\t\t\t\theader := false\n\t\t\t\tfooter := false\n\t\t\t\tfor _, key := range keys {\n\t\t\t\t\treq := util.EventType(key)\n\t\t\t\t\tvalue := (*events)[req]\n\t\t\t\t\tswitch req {\n\t\t\t\t\tcase reqPrompt:\n\t\t\t\t\t\tt.printPrompt()\n\t\t\t\t\t\tif t.infoStyle == infoInline || t.infoStyle == infoInlineRight {\n\t\t\t\t\t\t\tinfo = true\n\t\t\t\t\t\t}\n\t\t\t\t\tcase reqInfo:\n\t\t\t\t\t\tinfo = true\n\t\t\t\t\tcase reqList:\n\t\t\t\t\t\tt.printList()\n\t\t\t\t\t\tcurrentIndex := t.currentIndex()\n\t\t\t\t\t\tif t.track.Current() && t.track.index != currentIndex {\n\t\t\t\t\t\t\tt.track = trackDisabled\n\t\t\t\t\t\t\tinfo = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfocusChanged := focusedIndex != currentIndex\n\t\t\t\t\t\tif (t.hasFocusActions || t.infoCommand != \"\") && focusChanged && currentIndex != t.lastFocus {\n\t\t\t\t\t\t\tt.lastFocus = currentIndex\n\t\t\t\t\t\t\tt.eventChan <- tui.Focus.AsEvent()\n\t\t\t\t\t\t\tif t.infoCommand != \"\" {\n\t\t\t\t\t\t\t\tinfo = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif focusChanged || version != t.version {\n\t\t\t\t\t\t\tversion = t.version\n\t\t\t\t\t\t\tfocusedIndex = currentIndex\n\t\t\t\t\t\t\trefreshPreview(t.previewOpts.command)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase reqJump:\n\t\t\t\t\t\tif t.merger.Length() == 0 {\n\t\t\t\t\t\t\tt.jumping = jumpDisabled\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.printList()\n\t\t\t\t\tcase reqHeader:\n\t\t\t\t\t\theader = true\n\t\t\t\t\tcase reqFooter:\n\t\t\t\t\t\tfooter = true\n\t\t\t\t\tcase reqActivate:\n\t\t\t\t\t\tt.suppress = false\n\t\t\t\t\t\tif t.hasPreviewer() {\n\t\t\t\t\t\t\tt.previewBox.Set(reqPreviewReady, nil)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase reqRedrawInputLabel:\n\t\t\t\t\t\tt.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, true)\n\t\t\t\t\tcase reqRedrawHeaderLabel:\n\t\t\t\t\t\tt.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true)\n\t\t\t\t\tcase reqRedrawFooterLabel:\n\t\t\t\t\t\tt.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true)\n\t\t\t\t\tcase reqRedrawListLabel:\n\t\t\t\t\t\tt.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true)\n\t\t\t\t\tcase reqRedrawBorderLabel:\n\t\t\t\t\t\tt.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)\n\t\t\t\t\tcase reqRedrawPreviewLabel:\n\t\t\t\t\t\tt.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), true)\n\t\t\t\t\tcase reqReinit, reqResize, reqFullRedraw, reqRedraw:\n\t\t\t\t\t\tif req == reqReinit {\n\t\t\t\t\t\t\tt.tui.Resume(t.fullscreen, true)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif req == reqResize {\n\t\t\t\t\t\t\tt.termSize = t.tui.Size()\n\t\t\t\t\t\t}\n\t\t\t\t\t\twasHidden := t.pwindow == nil\n\t\t\t\t\t\tif req == reqRedraw {\n\t\t\t\t\t\t\tt.printAll()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.fullRedraw()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif wasHidden && t.hasPreviewWindow() {\n\t\t\t\t\t\t\trefreshPreview(t.previewOpts.command)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif req == reqResize && t.hasResizeActions {\n\t\t\t\t\t\t\tt.eventChan <- tui.Resize.AsEvent()\n\t\t\t\t\t\t}\n\t\t\t\t\tcase reqClose:\n\t\t\t\t\t\texit(func() int {\n\t\t\t\t\t\t\tif t.output() {\n\t\t\t\t\t\t\t\treturn ExitOk\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn ExitNoMatch\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase reqPreviewDisplay:\n\t\t\t\t\t\tresult := value.(previewResult)\n\t\t\t\t\t\tif t.previewer.version != result.version {\n\t\t\t\t\t\t\tt.previewer.version = result.version\n\t\t\t\t\t\t\tt.previewer.following.Force(t.activePreviewOpts.follow)\n\t\t\t\t\t\t\tif t.previewer.following.Enabled() {\n\t\t\t\t\t\t\t\tt.previewer.offset = 0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.previewer.lines = result.lines\n\t\t\t\t\t\tt.previewer.spinner = result.spinner\n\t\t\t\t\t\tif t.hasPreviewWindow() && t.previewer.following.Enabled() {\n\t\t\t\t\t\t\tt.previewer.offset = t.followOffset()\n\t\t\t\t\t\t} else if result.offset >= 0 {\n\t\t\t\t\t\t\tt.previewer.offset = util.Constrain(result.offset, t.activePreviewOpts.headerLines, len(t.previewer.lines)-1)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.printPreview()\n\t\t\t\t\tcase reqPreviewRefresh:\n\t\t\t\t\t\tt.printPreview()\n\t\t\t\t\tcase reqPreviewDelayed:\n\t\t\t\t\t\tt.previewer.version = value.(int64)\n\t\t\t\t\t\tt.printPreviewDelayed()\n\t\t\t\t\tcase reqPrintQuery:\n\t\t\t\t\t\texit(func() int {\n\t\t\t\t\t\t\tt.printer(string(t.input))\n\t\t\t\t\t\t\treturn ExitOk\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase reqBecome:\n\t\t\t\t\t\texit(func() int { return ExitBecome })\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase reqQuit:\n\t\t\t\t\t\texit(func() int { return ExitInterrupt })\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase reqFatal:\n\t\t\t\t\t\texit(func() int { return ExitError })\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (info || header || footer) && !t.resizeIfNeeded() {\n\t\t\t\t\tif info {\n\t\t\t\t\t\tt.printInfo()\n\t\t\t\t\t}\n\t\t\t\t\tif header {\n\t\t\t\t\t\tt.printHeader()\n\t\t\t\t\t}\n\t\t\t\t\tif footer {\n\t\t\t\t\t\tt.printFooter()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tt.flush()\n\t\t\t\tt.mutex.Unlock()\n\t\t\t\tt.uiMutex.Unlock()\n\t\t\t})\n\t\t}\n\n\t\tt.running.Set(false)\n\t\tt.killPreview()\n\t\tcancel()\n\t\tt.eventBox.Set(EvtQuit, quitSignal{code, nil})\n\t}()\n\n\tlooping := true\n\tbarrier := make(chan bool)\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-barrier:\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase t.keyChan <- t.tui.GetChar(t.listenAddr != nil):\n\t\t\t}\n\t\t}\n\t}()\n\tpreviewDraggingPos := -1\n\tbarDragging := false\n\tpbarDragging := false\n\tpborderDragging := -1\n\twasDown := false\n\tpmx, pmy := -1, -1\n\tneedBarrier := true\n\n\t// If an action is bound to 'start', we're going to process it before reading\n\t// user input.\n\tif !t.hasStartActions {\n\t\tbarrier <- true\n\t\tneedBarrier = false\n\t}\n\n\t// These variables are defined outside the loop to be accessible from closures\n\tevents := []util.EventType{}\n\tchanged := false\n\tvar newNth *[]Range\n\tvar newWithNth *withNthSpec\n\tvar newHeaderLines *int\n\treq := func(evts ...util.EventType) {\n\t\tfor _, event := range evts {\n\t\t\tevents = append(events, event)\n\t\t\tif isTerminalEvent(event) {\n\t\t\t\tlooping = false\n\t\t\t}\n\t\t}\n\t}\n\n\t// The main event loop\n\tfor loopIndex := int64(0); looping; loopIndex++ {\n\t\tvar newCommand *commandSpec\n\t\tvar reloadSync bool\n\t\tevents = []util.EventType{}\n\t\tchanged = false\n\t\tnewNth = nil\n\t\tnewWithNth = nil\n\t\tnewHeaderLines = nil\n\t\tbeof := false\n\t\tqueryChanged := false\n\t\tdenylist := []int32{}\n\n\t\t// Special handling of --sync. Activate the interface on the second tick.\n\t\tif loopIndex == 1 && t.deferActivation() {\n\t\t\tt.reqBox.Set(reqActivate, nil)\n\t\t}\n\n\t\tif loopIndex > 0 && needBarrier {\n\t\t\tbarrier <- true\n\t\t\tneedBarrier = false\n\t\t}\n\n\t\tvar event tui.Event\n\t\tactions := []*action{}\n\t\tcallbacks := []versionedCallback{}\n\t\tselect {\n\t\tcase event = <-t.keyChan:\n\t\t\tneedBarrier = true\n\t\tcase event = <-t.eventChan:\n\t\t\t// Drain channel to process all queued events at once without rendering\n\t\t\t// the intermediate states\n\t\tDrain:\n\t\t\tfor {\n\t\t\t\tif eventActions, prs := t.keymap[event]; prs {\n\t\t\t\t\tactions = append(actions, eventActions...)\n\t\t\t\t}\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase event = <-t.eventChan:\n\t\t\t\t\t\tcontinue Drain\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak Drain\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase serverActions := <-t.serverInputChan:\n\t\t\tevent = tui.Invalid.AsEvent()\n\t\t\tif t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe {\n\t\t\t\tactions = serverActions\n\t\t\t} else {\n\t\t\t\tfor _, action := range serverActions {\n\t\t\t\t\tif !processExecution(action.t) {\n\t\t\t\t\t\tactions = append(actions, action)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, action := range actions {\n\t\t\t\tif action.t == actExecute {\n\t\t\t\t\tt.tui.CancelGetChar()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase callback := <-t.callbackChan:\n\t\t\tevent = tui.Invalid.AsEvent()\n\t\t\tactions = append(actions, &action{t: actAsync})\n\t\t\tcallbacks = append(callbacks, callback)\n\t\tDrainCallback:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase callback = <-t.callbackChan:\n\t\t\t\t\tcallbacks = append(callbacks, callback)\n\t\t\t\t\tcontinue DrainCallback\n\t\t\t\tdefault:\n\t\t\t\t\tbreak DrainCallback\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tt.mutex.Lock()\n\t\tfor key, ret := range t.expect {\n\t\t\tif keyMatch(key, event) {\n\t\t\t\tt.pressed = ret\n\t\t\t\tt.mutex.Unlock()\n\t\t\t\tt.reqBox.Set(reqClose, nil)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\ttriggering := map[tui.Event]struct{}{}\n\t\tpreviousInput := t.input\n\t\tpreviousCx := t.cx\n\t\tpreviousVersion := t.version\n\t\tt.lastKey = event.KeyName()\n\t\tupdatePreviewWindow := func(forcePreview bool) {\n\t\t\tt.resizeWindows(forcePreview, false)\n\t\t\treq(reqPrompt, reqList, reqInfo, reqHeader, reqFooter)\n\t\t}\n\t\ttoggle := func() bool {\n\t\t\tcurrent := t.currentItem()\n\t\t\tif current != nil && t.toggleItem(current) {\n\t\t\t\treq(reqInfo)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\tscrollPreviewTo := func(newOffset int) {\n\t\t\tif !t.previewer.scrollable {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnumLines := len(t.previewer.lines)\n\t\t\theaderLines := t.activePreviewOpts.headerLines\n\t\t\tif t.activePreviewOpts.cycle {\n\t\t\t\toffsetRange := numLines - headerLines\n\t\t\t\tnewOffset = ((newOffset-headerLines)+offsetRange)%offsetRange + headerLines\n\t\t\t}\n\t\t\tnewOffset = util.Constrain(newOffset, headerLines, numLines-1)\n\t\t\tif t.previewer.offset != newOffset {\n\t\t\t\tt.previewer.offset = newOffset\n\t\t\t\tt.previewer.following.Set(t.previewer.offset >= numLines-(t.pwindow.Height()-headerLines))\n\t\t\t\treq(reqPreviewRefresh)\n\t\t\t}\n\t\t}\n\t\tscrollPreviewBy := func(amount int) {\n\t\t\tscrollPreviewTo(t.previewer.offset + amount)\n\t\t}\n\n\t\tactionsFor := func(eventType tui.EventType) []*action {\n\t\t\treturn t.keymap[eventType.AsEvent()]\n\t\t}\n\n\t\tvar doAction func(*action) bool\n\t\tdoActions := func(actions []*action) bool {\n\t\t\tfor iter := 0; iter <= maxFocusEvents; iter++ {\n\t\t\t\tcurrentIndex := t.currentIndex()\n\t\t\t\tfor _, action := range actions {\n\t\t\t\t\tif !doAction(action) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\t// A terminal action performed. We should stop processing more.\n\t\t\t\t\tif !looping {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && iter < maxFocusEvents {\n\t\t\t\t\tif newIndex := t.currentIndex(); newIndex != currentIndex {\n\t\t\t\t\t\tt.lastFocus = newIndex\n\t\t\t\t\t\tif t.infoCommand != \"\" {\n\t\t\t\t\t\t\treq(reqInfo)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tactions = onFocus\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t\tdoAction = func(a *action) bool {\n\t\t\t// Keep track of the current query before the action is executed,\n\t\t\t// so we can restore it when the input section is hidden (--no-input).\n\t\t\t// * By doing this, we don't have to add a conditional branch to each\n\t\t\t//   query modifying action.\n\t\t\t// * We restore the query after each action instead of after a set of\n\t\t\t//   actions to allow changing the query even when the input is hidden\n\t\t\t//     e.g. fzf --no-input --bind 'space:show-input+change-query(foo)+hide-input'\n\t\t\tcurrentInput := t.input\n\t\t\tcapture := func(firstLineOnly bool, callback func(string)) {\n\t\t\t\tif a.t >= actBgTransform {\n\t\t\t\t\t// bg-transform-*\n\t\t\t\t\tt.captureAsync(*a, firstLineOnly, callback)\n\t\t\t\t} else if a.t >= actTransform {\n\t\t\t\t\t// transform-*\n\t\t\t\t\tif firstLineOnly {\n\t\t\t\t\t\tcallback(t.captureLine(a.a))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcallback(t.captureLines(a.a))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// change-*\n\t\t\t\t\tcallback(a.a)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// When track-blocked, only allow abort/cancel and track-disabling actions\n\t\t\tif t.trackBlocked && a.t != actToggleTrack && a.t != actToggleTrackCurrent && a.t != actUntrackCurrent {\n\t\t\t\tif a.t == actAbort || a.t == actCancel {\n\t\t\t\t\tt.unblockTrack()\n\t\t\t\t\treq(reqPrompt, reqInfo)\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}\n\t\tAction:\n\t\t\tswitch a.t {\n\t\t\tcase actIgnore, actStart, actClick:\n\t\t\tcase actAsync:\n\t\t\t\tfor _, callback := range callbacks {\n\t\t\t\t\tif t.bgVersion == callback.version {\n\t\t\t\t\t\tcallback.callback()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actBecome:\n\t\t\t\tvalid, list := t.buildPlusList(a.a, false)\n\t\t\t\tif valid {\n\t\t\t\t\t// We do not remove temp files in this case\n\t\t\t\t\tcommand, _ := t.replacePlaceholder(a.a, false, string(t.input), list)\n\t\t\t\t\tt.tui.Close()\n\t\t\t\t\tif t.history != nil {\n\t\t\t\t\t\tt.history.append(string(t.input))\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(t.proxyScript) > 0 {\n\t\t\t\t\t\tdata := strings.Join(append([]string{command}, t.environ()...), \"\\x00\")\n\t\t\t\t\t\tos.WriteFile(t.proxyScript+becomeSuffix, []byte(data), 0600)\n\t\t\t\t\t\treq(reqBecome)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.executor.Become(t.ttyin, t.environ(), command)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actBell:\n\t\t\t\tt.tui.Bell()\n\t\t\tcase actExcludeMulti:\n\t\t\t\tif len(t.selected) > 0 {\n\t\t\t\t\tfor _, item := range t.sortSelected() {\n\t\t\t\t\t\tdenylist = append(denylist, item.item.Index())\n\t\t\t\t\t}\n\t\t\t\t\t// Clear selected items\n\t\t\t\t\tt.selected = make(map[int32]selectedItem)\n\t\t\t\t\tt.version++\n\t\t\t\t} else {\n\t\t\t\t\titem := t.currentItem()\n\t\t\t\t\tif item != nil {\n\t\t\t\t\t\tdenylist = append(denylist, item.Index())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tchanged = true\n\t\t\tcase actExclude:\n\t\t\t\tif item := t.currentItem(); item != nil {\n\t\t\t\t\tdenylist = append(denylist, item.Index())\n\t\t\t\t\tt.deselectItem(item)\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\tcase actExecute, actExecuteSilent:\n\t\t\t\tt.executeCommand(a.a, false, a.t == actExecuteSilent, false, false, \"\")\n\t\t\tcase actExecuteMulti:\n\t\t\t\tt.executeCommand(a.a, true, false, false, false, \"\")\n\t\t\tcase actInvalid:\n\t\t\t\tt.mutex.Unlock()\n\t\t\t\treturn false\n\t\t\tcase actBracketedPasteBegin:\n\t\t\t\tcurrent := []rune(t.input)\n\t\t\t\tt.pasting = &current\n\t\t\tcase actBracketedPasteEnd:\n\t\t\t\tif t.pasting != nil {\n\t\t\t\t\tqueryChanged = string(t.input) != string(*t.pasting)\n\t\t\t\t\tt.pasting = nil\n\t\t\t\t}\n\t\t\tcase actTogglePreview, actShowPreview, actHidePreview:\n\t\t\t\tvar act bool\n\t\t\t\tswitch a.t {\n\t\t\t\tcase actShowPreview:\n\t\t\t\t\tact = !t.hasPreviewWindow() && len(t.previewOpts.command) > 0\n\t\t\t\tcase actHidePreview:\n\t\t\t\t\tact = t.hasPreviewWindow()\n\t\t\t\tcase actTogglePreview:\n\t\t\t\t\tact = t.hasPreviewWindow() || len(t.previewOpts.command) > 0\n\t\t\t\t}\n\t\t\t\tif act {\n\t\t\t\t\tt.activePreviewOpts.Toggle()\n\t\t\t\t\tupdatePreviewWindow(false)\n\t\t\t\t\tif t.canPreview() {\n\t\t\t\t\t\tvalid, list := t.buildPlusList(t.previewOpts.command, false)\n\t\t\t\t\t\tif valid {\n\t\t\t\t\t\t\tt.cancelPreview()\n\t\t\t\t\t\t\tt.previewBox.Set(reqPreviewEnqueue,\n\t\t\t\t\t\t\t\tpreviewRequest{t.previewOpts.command, t.evaluateScrollOffset(), list, t.environForPreview(), string(t.input)})\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Discard the preview content so that it won't accidentally appear\n\t\t\t\t\t\t// when preview window is re-enabled and previewDelay is triggered\n\t\t\t\t\t\tt.previewer.lines = nil\n\n\t\t\t\t\t\t// Also kill the preview process if it's still running\n\t\t\t\t\t\tt.cancelPreview()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actTogglePreviewWrap, actTogglePreviewWrapWord:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tif a.t == actTogglePreviewWrapWord {\n\t\t\t\t\t\tt.activePreviewOpts.wrapWord = !t.activePreviewOpts.wrapWord\n\t\t\t\t\t\tt.activePreviewOpts.wrap = t.activePreviewOpts.wrapWord\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.activePreviewOpts.wrap = !t.activePreviewOpts.wrap\n\t\t\t\t\t\tif !t.activePreviewOpts.wrap {\n\t\t\t\t\t\t\tt.activePreviewOpts.wrapWord = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Reset preview version so that full redraw occurs\n\t\t\t\t\tt.previewed.version = 0\n\t\t\t\t\treq(reqPreviewRefresh)\n\t\t\t\t}\n\t\t\tcase actTransformPrompt, actBgTransformPrompt:\n\t\t\t\tcapture(true, func(prompt string) {\n\t\t\t\t\tt.promptString = prompt\n\t\t\t\t\tt.prompt, t.promptLen = t.parsePrompt(prompt)\n\t\t\t\t\treq(reqPrompt)\n\t\t\t\t})\n\t\t\tcase actTransformQuery, actBgTransformQuery:\n\t\t\t\tcapture(true, func(query string) {\n\t\t\t\t\tt.input = []rune(query)\n\t\t\t\t\tt.cx = len(t.input)\n\t\t\t\t})\n\t\t\tcase actToggleSort:\n\t\t\t\tt.sort = !t.sort\n\t\t\t\tchanged = true\n\t\t\tcase actPreviewTop:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewTo(0)\n\t\t\t\t}\n\t\t\tcase actPreviewBottom:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())\n\t\t\t\t}\n\t\t\tcase actPreviewUp:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewBy(-1)\n\t\t\t\t}\n\t\t\tcase actPreviewDown:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewBy(1)\n\t\t\t\t}\n\t\t\tcase actPreviewPageUp:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewBy(-t.pwindow.Height())\n\t\t\t\t}\n\t\t\tcase actPreviewPageDown:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewBy(t.pwindow.Height())\n\t\t\t\t}\n\t\t\tcase actPreviewHalfPageUp:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewBy(-t.pwindow.Height() / 2)\n\t\t\t\t}\n\t\t\tcase actPreviewHalfPageDown:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tscrollPreviewBy(t.pwindow.Height() / 2)\n\t\t\t\t}\n\t\t\tcase actBeginningOfLine:\n\t\t\t\tt.cx = 0\n\t\t\t\tt.xoffset = 0\n\t\t\tcase actBackwardChar:\n\t\t\t\tif t.cx > 0 {\n\t\t\t\t\tt.cx--\n\t\t\t\t}\n\t\t\tcase actPrintQuery:\n\t\t\t\treq(reqPrintQuery)\n\t\t\tcase actChangeHeaderLines, actTransformHeaderLines, actBgTransformHeaderLines:\n\t\t\t\tcapture(true, func(expr string) {\n\t\t\t\t\tif n, err := strconv.Atoi(expr); err == nil && n >= 0 && n != t.headerLines {\n\t\t\t\t\t\tt.headerLines = n\n\t\t\t\t\t\tnewHeaderLines = &n\n\t\t\t\t\t\tchanged = true\n\t\t\t\t\t\t// Deselect items that are now part of the header\n\t\t\t\t\t\tfor idx := range t.selected {\n\t\t\t\t\t\t\tif idx < int32(n) {\n\t\t\t\t\t\t\t\tdelete(t.selected, idx)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Tell UpdateList to reposition cursor to the current item\n\t\t\t\t\t\tt.targetIndex = t.currentIndex()\n\t\t\t\t\t\treq(reqList, reqPrompt, reqInfo, reqHeader)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangeMulti:\n\t\t\t\tmulti := t.multi\n\t\t\t\tif a.a == \"\" {\n\t\t\t\t\tmulti = maxMulti\n\t\t\t\t} else if n, e := strconv.Atoi(a.a); e == nil && n >= 0 {\n\t\t\t\t\tmulti = n\n\t\t\t\t}\n\t\t\t\tif t.multi > 0 && multi != t.multi {\n\t\t\t\t\tt.selected = make(map[int32]selectedItem)\n\t\t\t\t\tt.version++\n\t\t\t\t}\n\t\t\t\tt.multi = multi\n\t\t\t\treq(reqList, reqInfo)\n\t\t\tcase actChangeNth, actTransformNth, actBgTransformNth:\n\t\t\t\tcapture(true, func(expr string) {\n\t\t\t\t\t// Split nth expression\n\t\t\t\t\ttokens := strings.Split(expr, \"|\")\n\t\t\t\t\tif nth, err := splitNth(tokens[0]); err == nil || len(expr) == 0 {\n\t\t\t\t\t\t// Changed\n\t\t\t\t\t\tnewNth = &nth\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// The default\n\t\t\t\t\t\tnewNth = &t.nth\n\t\t\t\t\t}\n\t\t\t\t\t// Cycle\n\t\t\t\t\tif len(tokens) > 1 {\n\t\t\t\t\t\ta.a = strings.Join(append(tokens[1:], tokens[0]), \"|\")\n\t\t\t\t\t}\n\t\t\t\t\tif !compareRanges(t.nthCurrent, *newNth) {\n\t\t\t\t\t\tchanged = true\n\t\t\t\t\t\tt.nthCurrent = *newNth\n\t\t\t\t\t\tt.forceRerenderList()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangeWithNth, actTransformWithNth, actBgTransformWithNth:\n\t\t\t\tif !t.withNthEnabled {\n\t\t\t\t\tbreak Action\n\t\t\t\t}\n\t\t\t\tcapture(true, func(expr string) {\n\t\t\t\t\ttokens := strings.Split(expr, \"|\")\n\t\t\t\t\twithNthExpr := tokens[0]\n\t\t\t\t\tif len(tokens) > 1 {\n\t\t\t\t\t\ta.a = strings.Join(append(tokens[1:], tokens[0]), \"|\")\n\t\t\t\t\t}\n\t\t\t\t\t// Empty value restores the default --with-nth\n\t\t\t\t\tif len(withNthExpr) == 0 {\n\t\t\t\t\t\twithNthExpr = t.withNthDefault\n\t\t\t\t\t}\n\t\t\t\t\tif withNthExpr != t.withNthExpr {\n\t\t\t\t\t\tif factory, err := nthTransformer(withNthExpr); err == nil {\n\t\t\t\t\t\t\tnewWithNth = &withNthSpec{fn: factory(t.delimiter)}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.withNthExpr = withNthExpr\n\t\t\t\t\t\tt.filterSelection = true\n\t\t\t\t\t\tchanged = true\n\t\t\t\t\t\tt.clearNumLinesCache()\n\t\t\t\t\t\tt.forceRerenderList()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangeQuery:\n\t\t\t\tt.input = []rune(a.a)\n\t\t\t\tt.cx = len(t.input)\n\t\t\tcase actChangeHeader, actTransformHeader, actBgTransformHeader:\n\t\t\t\tcapture(false, func(header string) {\n\t\t\t\t\t// When a dedicated header window is not used, we may need to\n\t\t\t\t\t// update other elements as well.\n\t\t\t\t\tif t.changeHeader(header) {\n\t\t\t\t\t\treq(reqList, reqPrompt, reqInfo)\n\t\t\t\t\t}\n\t\t\t\t\treq(reqHeader)\n\t\t\t\t})\n\t\t\tcase actChangeFooter, actTransformFooter, actBgTransformFooter:\n\t\t\t\tcapture(false, func(footer string) {\n\t\t\t\t\tt.changeFooter(footer)\n\t\t\t\t\treq(reqFooter)\n\t\t\t\t})\n\t\t\tcase actChangeHeaderLabel, actTransformHeaderLabel, actBgTransformHeaderLabel:\n\t\t\t\tcapture(true, func(label string) {\n\t\t\t\t\tt.headerLabelOpts.label = label\n\t\t\t\t\tt.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)\n\t\t\t\t\treq(reqRedrawHeaderLabel)\n\t\t\t\t})\n\t\t\tcase actChangeFooterLabel, actTransformFooterLabel, actBgTransformFooterLabel:\n\t\t\t\tcapture(true, func(label string) {\n\t\t\t\t\tt.footerLabelOpts.label = label\n\t\t\t\t\tt.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)\n\t\t\t\t\treq(reqRedrawFooterLabel)\n\t\t\t\t})\n\t\t\tcase actChangeInputLabel, actTransformInputLabel, actBgTransformInputLabel:\n\t\t\t\tcapture(true, func(label string) {\n\t\t\t\t\tt.inputLabelOpts.label = label\n\t\t\t\t\tif t.inputBorder != nil {\n\t\t\t\t\t\tt.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)\n\t\t\t\t\t\treq(reqRedrawInputLabel)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangeListLabel, actTransformListLabel, actBgTransformListLabel:\n\t\t\t\tcapture(true, func(label string) {\n\t\t\t\t\tt.listLabelOpts.label = label\n\t\t\t\t\tif t.wborder != nil {\n\t\t\t\t\t\tt.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)\n\t\t\t\t\t\treq(reqRedrawListLabel)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangeBorderLabel, actTransformBorderLabel, actBgTransformBorderLabel:\n\t\t\t\tcapture(true, func(label string) {\n\t\t\t\t\tt.borderLabelOpts.label = label\n\t\t\t\t\tif t.border != nil {\n\t\t\t\t\t\tt.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)\n\t\t\t\t\t\treq(reqRedrawBorderLabel)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangePreviewLabel, actTransformPreviewLabel, actBgTransformPreviewLabel:\n\t\t\t\tcapture(true, func(label string) {\n\t\t\t\t\tt.previewLabelOpts.label = label\n\t\t\t\t\tif t.pborder != nil {\n\t\t\t\t\t\tt.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)\n\t\t\t\t\t\treq(reqRedrawPreviewLabel)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actTransform, actBgTransform:\n\t\t\t\tcapture(false, func(body string) {\n\t\t\t\t\tif actions, err := parseSingleActionList(strings.Trim(body, \"\\r\\n\")); err == nil {\n\t\t\t\t\t\t// NOTE: We're not properly passing the return value here\n\t\t\t\t\t\tdoActions(actions)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actBgCancel:\n\t\t\t\tt.bgVersion++\n\t\t\t\tt.runningCmds.ForEach(func(cmd *runningCmd) {\n\t\t\t\t\tutil.KillCommand(cmd.cmd)\n\t\t\t\t})\n\t\t\tcase actChangePrompt:\n\t\t\t\tt.promptString = a.a\n\t\t\t\tt.prompt, t.promptLen = t.parsePrompt(a.a)\n\t\t\t\treq(reqPrompt)\n\t\t\tcase actPreview:\n\t\t\t\tif !t.hasPreviewWindow() {\n\t\t\t\t\tupdatePreviewWindow(true)\n\t\t\t\t}\n\t\t\t\trefreshPreview(a.a)\n\t\t\tcase actRefreshPreview:\n\t\t\t\trefreshPreview(t.previewOpts.command)\n\t\t\tcase actReplaceQuery:\n\t\t\t\tcurrent := t.currentItem()\n\t\t\t\tif current != nil {\n\t\t\t\t\tt.input = current.text.ToRunes()\n\t\t\t\t\tt.cx = len(t.input)\n\t\t\t\t}\n\t\t\tcase actFatal:\n\t\t\t\treq(reqFatal)\n\t\t\tcase actAbort:\n\t\t\t\treq(reqQuit)\n\t\t\tcase actDeleteChar:\n\t\t\t\tt.delChar()\n\t\t\tcase actDeleteCharEof:\n\t\t\t\tif !t.delChar() && t.cx == 0 {\n\t\t\t\t\treq(reqQuit)\n\t\t\t\t}\n\t\t\tcase actEndOfLine:\n\t\t\t\tt.cx = len(t.input)\n\t\t\tcase actCancel:\n\t\t\t\tif len(t.input) == 0 {\n\t\t\t\t\treq(reqQuit)\n\t\t\t\t} else {\n\t\t\t\t\tt.yanked = t.input\n\t\t\t\t\tt.input = []rune{}\n\t\t\t\t\tt.cx = 0\n\t\t\t\t}\n\t\t\tcase actBackwardDeleteCharEof:\n\t\t\t\tif len(t.input) == 0 {\n\t\t\t\t\treq(reqQuit)\n\t\t\t\t} else if t.cx > 0 {\n\t\t\t\t\tt.input = append(t.input[:t.cx-1], t.input[t.cx:]...)\n\t\t\t\t\tt.cx--\n\t\t\t\t}\n\t\t\tcase actForwardChar:\n\t\t\t\tif t.cx < len(t.input) {\n\t\t\t\t\tt.cx++\n\t\t\t\t}\n\t\t\tcase actBackwardDeleteChar:\n\t\t\t\tbeof = len(t.input) == 0\n\t\t\t\tif t.cx > 0 {\n\t\t\t\t\tt.input = append(t.input[:t.cx-1], t.input[t.cx:]...)\n\t\t\t\t\tt.cx--\n\t\t\t\t}\n\t\t\tcase actSelectAll:\n\t\t\t\tif t.multi > 0 {\n\t\t\t\t\t// Limit the scope only to the matching items\n\t\t\t\t\tfor i := 0; i < t.resultMerger.Length(); i++ {\n\t\t\t\t\t\tif !t.selectItem(t.resultMerger.Get(i).item) {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treq(reqList, reqInfo)\n\t\t\t\t}\n\t\t\tcase actDeselectAll:\n\t\t\t\tif t.multi > 0 {\n\t\t\t\t\t// Also limit the scope only to the matching items, while this may\n\t\t\t\t\t// not be straightforward in raw mode.\n\t\t\t\t\tfor i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {\n\t\t\t\t\t\tt.deselectItem(t.resultMerger.Get(i).item)\n\t\t\t\t\t}\n\t\t\t\t\treq(reqList, reqInfo)\n\t\t\t\t}\n\t\t\tcase actClose:\n\t\t\t\tif t.hasPreviewWindow() {\n\t\t\t\t\tt.activePreviewOpts.Toggle()\n\t\t\t\t\tupdatePreviewWindow(false)\n\t\t\t\t} else {\n\t\t\t\t\treq(reqQuit)\n\t\t\t\t}\n\t\t\tcase actSelect:\n\t\t\t\tcurrent := t.currentItem()\n\t\t\t\tif t.multi > 0 && current != nil && t.selectItemChanged(current) {\n\t\t\t\t\treq(reqList, reqInfo)\n\t\t\t\t}\n\t\t\tcase actDeselect:\n\t\t\t\tcurrent := t.currentItem()\n\t\t\t\tif t.multi > 0 && current != nil && t.deselectItemChanged(current) {\n\t\t\t\t\treq(reqList, reqInfo)\n\t\t\t\t}\n\t\t\tcase actToggle:\n\t\t\t\tif t.multi > 0 && t.merger.Length() > 0 && toggle() {\n\t\t\t\t\treq(reqList)\n\t\t\t\t}\n\t\t\tcase actToggleAll:\n\t\t\t\tif t.multi > 0 {\n\t\t\t\t\tprevIndexes := make(map[int]struct{})\n\t\t\t\t\tfor i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {\n\t\t\t\t\t\titem := t.resultMerger.Get(i).item\n\t\t\t\t\t\tif _, found := t.selected[item.Index()]; found {\n\t\t\t\t\t\t\tprevIndexes[i] = struct{}{}\n\t\t\t\t\t\t\tt.deselectItem(item)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor i := 0; i < t.resultMerger.Length(); i++ {\n\t\t\t\t\t\tif _, found := prevIndexes[i]; !found {\n\t\t\t\t\t\t\titem := t.resultMerger.Get(i).item\n\t\t\t\t\t\t\tif !t.selectItem(item) {\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treq(reqList, reqInfo)\n\t\t\t\t}\n\t\t\tcase actToggleIn:\n\t\t\t\tif t.layout != layoutDefault {\n\t\t\t\t\treturn doAction(&action{t: actToggleUp})\n\t\t\t\t}\n\t\t\t\treturn doAction(&action{t: actToggleDown})\n\t\t\tcase actToggleOut:\n\t\t\t\tif t.layout != layoutDefault {\n\t\t\t\t\treturn doAction(&action{t: actToggleDown})\n\t\t\t\t}\n\t\t\t\treturn doAction(&action{t: actToggleUp})\n\t\t\tcase actToggleDown:\n\t\t\t\tif t.multi > 0 && t.merger.Length() > 0 && toggle() {\n\t\t\t\t\tt.vmove(-1, true)\n\t\t\t\t\treq(reqList)\n\t\t\t\t}\n\t\t\tcase actToggleUp:\n\t\t\t\tif t.multi > 0 && t.merger.Length() > 0 && toggle() {\n\t\t\t\t\tt.vmove(1, true)\n\t\t\t\t\treq(reqList)\n\t\t\t\t}\n\t\t\tcase actDown, actDownMatch, actUp, actUpMatch:\n\t\t\t\tdir := -1\n\t\t\t\tif a.t == actUp || a.t == actUpMatch {\n\t\t\t\t\tdir = 1\n\t\t\t\t}\n\t\t\t\tif t.raw && (a.t == actDownMatch || a.t == actUpMatch) {\n\t\t\t\t\tif t.resultMerger.Length() > 0 {\n\t\t\t\t\t\tprevCy := t.cy\n\t\t\t\t\t\tfor t.vmove(dir, true) && !t.isCurrentItemMatch() {\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !t.isCurrentItemMatch() {\n\t\t\t\t\t\t\tt.vset(prevCy)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.vmove(dir, true)\n\t\t\t\t}\n\t\t\t\treq(reqList)\n\t\t\tcase actToggleRaw, actEnableRaw, actDisableRaw:\n\t\t\t\tprevRaw := t.raw\n\t\t\t\tnewRaw := t.raw\n\t\t\t\tswitch a.t {\n\t\t\t\tcase actEnableRaw:\n\t\t\t\t\tnewRaw = true\n\t\t\t\tcase actDisableRaw:\n\t\t\t\t\tnewRaw = false\n\t\t\t\tcase actToggleRaw:\n\t\t\t\t\tnewRaw = !t.raw\n\t\t\t\t}\n\t\t\t\tif prevRaw == newRaw {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tprevPos := t.cy - t.offset\n\t\t\t\tprevIndex := t.currentIndex()\n\t\t\t\tif newRaw {\n\t\t\t\t\t// Build matchMap if not available\n\t\t\t\t\tif len(t.matchMap) == 0 {\n\t\t\t\t\t\tt.matchMap = t.resultMerger.ToMap()\n\t\t\t\t\t}\n\t\t\t\t\tt.merger = t.passMerger\n\t\t\t\t} else {\n\t\t\t\t\t// Find the closest matching item\n\t\t\t\t\tif !t.isCurrentItemMatch() && t.resultMerger.Length() > 1 {\n\t\t\t\t\t\tdistance := 0\n\t\t\t\t\tLoop:\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\tdistance++\n\t\t\t\t\t\t\tchecks := 0\n\t\t\t\t\t\t\tfor _, cy := range []int{t.cy + distance, t.cy - distance} {\n\t\t\t\t\t\t\t\tif cy >= 0 && cy < t.merger.Length() {\n\t\t\t\t\t\t\t\t\tchecks++\n\t\t\t\t\t\t\t\t\titem := t.merger.Get(cy).item\n\t\t\t\t\t\t\t\t\tif t.isItemMatch(item) {\n\t\t\t\t\t\t\t\t\t\tprevIndex = item.Index()\n\t\t\t\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif checks == 0 {\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tt.merger = t.resultMerger\n\n\t\t\t\t\t// Need to remove non-matching items from the selection\n\t\t\t\t\tif t.multi > 0 && len(t.selected) > 0 {\n\t\t\t\t\t\tt.filterSelected()\n\t\t\t\t\t\treq(reqInfo)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tt.raw = newRaw\n\n\t\t\t\t// Try to retain position\n\t\t\t\tif prevIndex != minItem.Index() {\n\t\t\t\t\tt.cy = max(0, t.merger.FindIndex(prevIndex))\n\t\t\t\t\tt.offset = t.cy - prevPos\n\t\t\t\t}\n\n\t\t\t\t// List needs to be rerendered\n\t\t\t\tt.forceRerenderList()\n\t\t\t\treq(reqList)\n\t\t\tcase actAccept:\n\t\t\t\treq(reqClose)\n\t\t\tcase actAcceptNonEmpty:\n\t\t\t\tif len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {\n\t\t\t\t\treq(reqClose)\n\t\t\t\t}\n\t\t\tcase actAcceptOrPrintQuery:\n\t\t\t\tif len(t.selected) > 0 || t.merger.Length() > 0 {\n\t\t\t\t\treq(reqClose)\n\t\t\t\t} else {\n\t\t\t\t\treq(reqPrintQuery)\n\t\t\t\t}\n\t\t\tcase actClearScreen:\n\t\t\t\treq(reqFullRedraw)\n\t\t\tcase actClearQuery:\n\t\t\t\tt.input = []rune{}\n\t\t\t\tt.cx = 0\n\t\t\tcase actClearSelection:\n\t\t\t\tif t.multi > 0 {\n\t\t\t\t\tt.selected = make(map[int32]selectedItem)\n\t\t\t\t\tt.version++\n\t\t\t\t\treq(reqList, reqInfo)\n\t\t\t\t}\n\t\t\tcase actFirst, actBest:\n\t\t\t\tif t.raw && a.t == actBest {\n\t\t\t\t\tif t.resultMerger.Length() > 0 {\n\t\t\t\t\t\tt.vset(t.merger.FindIndex(t.resultMerger.Get(0).item.Index()))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.vset(0)\n\t\t\t\t}\n\t\t\t\tt.constrain()\n\t\t\t\treq(reqList)\n\t\t\tcase actLast:\n\t\t\t\tt.vset(t.merger.Length() - 1)\n\t\t\t\tt.constrain()\n\t\t\t\treq(reqList)\n\t\t\tcase actPosition:\n\t\t\t\tif n, e := strconv.Atoi(a.a); e == nil {\n\t\t\t\t\tif n > 0 {\n\t\t\t\t\t\tn--\n\t\t\t\t\t} else if n < 0 {\n\t\t\t\t\t\tn += t.merger.Length()\n\t\t\t\t\t}\n\t\t\t\t\tt.vset(n)\n\t\t\t\t\tt.constrain()\n\t\t\t\t\treq(reqList)\n\t\t\t\t}\n\t\t\tcase actPut:\n\t\t\t\tstr := []rune(a.a)\n\t\t\t\tsuffix := copySlice(t.input[t.cx:])\n\t\t\t\tt.input = append(append(t.input[:t.cx], str...), suffix...)\n\t\t\t\tt.cx += len(str)\n\t\t\tcase actPrint:\n\t\t\t\tt.printQueue = append(t.printQueue, a.a)\n\t\t\tcase actUnixLineDiscard:\n\t\t\t\tbeof = len(t.input) == 0\n\t\t\t\tif t.cx > 0 {\n\t\t\t\t\tt.yanked = copySlice(t.input[:t.cx])\n\t\t\t\t\tt.input = t.input[t.cx:]\n\t\t\t\t\tt.cx = 0\n\t\t\t\t}\n\t\t\tcase actUnixWordRubout:\n\t\t\t\tbeof = len(t.input) == 0\n\t\t\t\tif t.cx > 0 {\n\t\t\t\t\tt.rubout(\"\\\\s\\\\S\")\n\t\t\t\t}\n\t\t\tcase actBackwardKillWord:\n\t\t\t\tbeof = len(t.input) == 0\n\t\t\t\tif t.cx > 0 {\n\t\t\t\t\tt.rubout(t.wordRubout)\n\t\t\t\t}\n\t\t\tcase actBackwardKillSubWord:\n\t\t\t\tbeof = len(t.input) == 0\n\t\t\t\tif t.cx > 0 {\n\t\t\t\t\tt.rubout(t.subWordRubout)\n\t\t\t\t}\n\t\t\tcase actYank:\n\t\t\t\tsuffix := copySlice(t.input[t.cx:])\n\t\t\t\tt.input = append(append(t.input[:t.cx], t.yanked...), suffix...)\n\t\t\t\tt.cx += len(t.yanked)\n\t\t\tcase actPageUp, actPageDown, actHalfPageUp, actHalfPageDown:\n\t\t\t\t// Calculate the number of lines to move\n\t\t\t\tmaxItems := t.maxItems()\n\t\t\t\tlinesToMove := maxItems - 1\n\t\t\t\tif a.t == actHalfPageUp || a.t == actHalfPageDown {\n\t\t\t\t\tlinesToMove = maxItems / 2\n\t\t\t\t}\n\t\t\t\t// Move at least one line even in a very short window\n\t\t\t\tlinesToMove = max(1, linesToMove)\n\n\t\t\t\t// Determine the direction of the movement\n\t\t\t\tdirection := -1\n\t\t\t\tif a.t == actPageUp || a.t == actHalfPageUp {\n\t\t\t\t\tdirection = 1\n\t\t\t\t}\n\n\t\t\t\t// In non-default layout, items are listed from top to bottom\n\t\t\t\tif t.layout != layoutDefault {\n\t\t\t\t\tdirection *= -1\n\t\t\t\t}\n\n\t\t\t\t// We can simply add the number of lines to the current position in\n\t\t\t\t// single-line mode\n\t\t\t\tif !t.canSpanMultiLines() {\n\t\t\t\t\tt.vset(t.cy + direction*linesToMove)\n\t\t\t\t\treq(reqList)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// But in multi-line mode, we need to carefully limit the amount of\n\t\t\t\t// vertical movement so that items are not skipped. In order to do\n\t\t\t\t// this, we calculate the minimum or maximum offset based on the\n\t\t\t\t// direction of the movement and the number of lines of the items\n\t\t\t\t// around the current scroll offset.\n\t\t\t\tvar minOffset, maxOffset, lineSum int\n\t\t\t\tif direction > 0 {\n\t\t\t\t\tmaxOffset = t.offset\n\t\t\t\t\tfor ; maxOffset < t.merger.Length(); maxOffset++ {\n\t\t\t\t\t\titemLines, _ := t.numItemLines(t.merger.Get(maxOffset).item, maxItems)\n\t\t\t\t\t\tlineSum += itemLines\n\t\t\t\t\t\tif lineSum >= maxItems {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tminOffset = t.offset\n\t\t\t\t\tfor ; minOffset >= 0 && minOffset < t.merger.Length(); minOffset-- {\n\t\t\t\t\t\titemLines, _ := t.numItemLines(t.merger.Get(minOffset).item, maxItems)\n\t\t\t\t\t\tlineSum += itemLines\n\t\t\t\t\t\tif lineSum >= maxItems {\n\t\t\t\t\t\t\tif lineSum > maxItems {\n\t\t\t\t\t\t\t\tminOffset++\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor i := 0; i < linesToMove; i++ {\n\t\t\t\t\tcy, offset := t.cy, t.offset\n\t\t\t\t\tt.vset(cy + direction)\n\t\t\t\t\tt.constrain()\n\t\t\t\t\tif cy == t.cy {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif i > 0 && (direction > 0 && t.offset > maxOffset ||\n\t\t\t\t\t\tdirection < 0 && t.offset < minOffset) {\n\t\t\t\t\t\tt.cy, t.offset = cy, offset\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treq(reqList)\n\t\t\tcase actOffsetUp, actOffsetDown:\n\t\t\t\tdiff := 1\n\t\t\t\tif a.t == actOffsetDown {\n\t\t\t\t\tdiff = -1\n\t\t\t\t}\n\t\t\t\tif t.layout != layoutDefault {\n\t\t\t\t\tdiff *= -1\n\t\t\t\t}\n\t\t\t\tt.offset += diff\n\t\t\t\tbefore := t.offset\n\t\t\t\tt.constrain()\n\t\t\t\tif before != t.offset {\n\t\t\t\t\tt.offset = before\n\t\t\t\t\tif t.layout != layoutDefault {\n\t\t\t\t\t\tdiff *= -1\n\t\t\t\t\t}\n\t\t\t\t\tt.vmove(diff, false)\n\t\t\t\t}\n\t\t\t\treq(reqList)\n\t\t\tcase actOffsetMiddle:\n\t\t\t\tsoff := t.scrollOff\n\t\t\t\tt.scrollOff = t.window.Height()\n\t\t\t\tt.constrain()\n\t\t\t\tt.scrollOff = soff\n\t\t\t\treq(reqList)\n\t\t\tcase actJump:\n\t\t\t\tt.jumping = jumpEnabled\n\t\t\t\treq(reqJump)\n\t\t\tcase actJumpAccept:\n\t\t\t\tt.jumping = jumpAcceptEnabled\n\t\t\t\treq(reqJump)\n\t\t\tcase actBackwardWord:\n\t\t\t\tt.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1\n\t\t\tcase actForwardWord:\n\t\t\t\tt.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1\n\t\t\tcase actBackwardSubWord:\n\t\t\t\tt.cx = findLastMatch(t.subWordRubout, string(t.input[:t.cx])) + 1\n\t\t\tcase actForwardSubWord:\n\t\t\t\tt.cx += findFirstMatch(t.subWordNext, string(t.input[t.cx:])) + 1\n\t\t\tcase actKillWord:\n\t\t\t\tncx := t.cx +\n\t\t\t\t\tfindFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1\n\t\t\t\tif ncx > t.cx {\n\t\t\t\t\tt.yanked = copySlice(t.input[t.cx:ncx])\n\t\t\t\t\tt.input = append(t.input[:t.cx], t.input[ncx:]...)\n\t\t\t\t}\n\t\t\tcase actKillSubWord:\n\t\t\t\tncx := t.cx +\n\t\t\t\t\tfindFirstMatch(t.subWordNext, string(t.input[t.cx:])) + 1\n\t\t\t\tif ncx > t.cx {\n\t\t\t\t\tt.yanked = copySlice(t.input[t.cx:ncx])\n\t\t\t\t\tt.input = append(t.input[:t.cx], t.input[ncx:]...)\n\t\t\t\t}\n\t\t\tcase actKillLine:\n\t\t\t\tif t.cx < len(t.input) {\n\t\t\t\t\tt.yanked = copySlice(t.input[t.cx:])\n\t\t\t\t\tt.input = t.input[:t.cx]\n\t\t\t\t}\n\t\t\tcase actChar:\n\t\t\t\tprefix := copySlice(t.input[:t.cx])\n\t\t\t\tt.input = append(append(prefix, event.Char), t.input[t.cx:]...)\n\t\t\t\tt.cx++\n\t\t\tcase actPrevHistory:\n\t\t\t\tif t.history != nil {\n\t\t\t\t\tt.history.override(string(t.input))\n\t\t\t\t\tt.input = trimQuery(t.history.previous())\n\t\t\t\t\tt.cx = len(t.input)\n\t\t\t\t}\n\t\t\tcase actNextHistory:\n\t\t\t\tif t.history != nil {\n\t\t\t\t\tt.history.override(string(t.input))\n\t\t\t\t\tt.input = trimQuery(t.history.next())\n\t\t\t\t\tt.cx = len(t.input)\n\t\t\t\t}\n\t\t\tcase actToggleSearch:\n\t\t\t\tt.paused = !t.paused\n\t\t\t\tchanged = !t.paused\n\t\t\t\treq(reqPrompt)\n\t\t\tcase actToggleTrack:\n\t\t\t\tswitch t.track {\n\t\t\t\tcase trackEnabled:\n\t\t\t\t\tt.track = trackDisabled\n\t\t\t\tcase trackDisabled:\n\t\t\t\t\tt.track = trackEnabled\n\t\t\t\t}\n\t\t\t\tt.unblockTrack()\n\t\t\t\treq(reqPrompt, reqInfo)\n\t\t\tcase actToggleTrackCurrent:\n\t\t\t\tif t.track.Current() {\n\t\t\t\t\tt.track = trackDisabled\n\t\t\t\t} else if t.track.Disabled() {\n\t\t\t\t\tt.track = trackCurrent(t.currentIndex())\n\t\t\t\t}\n\t\t\t\tt.unblockTrack()\n\t\t\t\treq(reqPrompt, reqInfo)\n\t\t\tcase actShowHeader:\n\t\t\t\tt.headerVisible = true\n\t\t\t\treq(reqList, reqInfo, reqPrompt, reqHeader)\n\t\t\tcase actHideHeader:\n\t\t\t\tt.headerVisible = false\n\t\t\t\treq(reqList, reqInfo, reqPrompt, reqHeader)\n\t\t\tcase actToggleHeader:\n\t\t\t\tt.headerVisible = !t.headerVisible\n\t\t\t\treq(reqList, reqInfo, reqPrompt, reqHeader)\n\t\t\tcase actToggleWrap, actToggleWrapWord:\n\t\t\t\tif a.t == actToggleWrapWord {\n\t\t\t\t\tt.wrapWord = !t.wrapWord\n\t\t\t\t\tt.wrap = t.wrapWord\n\t\t\t\t} else {\n\t\t\t\t\tt.wrap = !t.wrap\n\t\t\t\t\tif !t.wrap {\n\t\t\t\t\t\tt.wrapWord = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tt.clearNumLinesCache()\n\t\t\t\treq(reqList, reqHeader)\n\t\t\tcase actToggleMultiLine:\n\t\t\t\tt.multiLine = !t.multiLine\n\t\t\t\tt.clearNumLinesCache()\n\t\t\t\treq(reqList)\n\t\t\tcase actToggleHscroll:\n\t\t\t\t// Force re-rendering of the list\n\t\t\t\tt.forceRerenderList()\n\t\t\t\tt.hscroll = !t.hscroll\n\t\t\t\treq(reqList)\n\t\t\tcase actToggleInput, actShowInput, actHideInput:\n\t\t\t\tswitch a.t {\n\t\t\t\tcase actToggleInput:\n\t\t\t\t\tt.inputless = !t.inputless\n\t\t\t\tcase actShowInput:\n\t\t\t\t\tif !t.inputless {\n\t\t\t\t\t\tbreak Action\n\t\t\t\t\t}\n\t\t\t\t\tt.inputless = false\n\t\t\t\tcase actHideInput:\n\t\t\t\t\tif t.inputless {\n\t\t\t\t\t\tbreak Action\n\t\t\t\t\t}\n\t\t\t\t\tt.inputless = true\n\t\t\t\t}\n\t\t\t\tt.forceRerenderList()\n\t\t\t\tif t.inputless {\n\t\t\t\t\tt.tui.HideCursor()\n\t\t\t\t} else {\n\t\t\t\t\tt.tui.ShowCursor()\n\t\t\t\t}\n\t\t\t\treq(reqList, reqInfo, reqPrompt, reqHeader)\n\t\t\tcase actTrackCurrent:\n\t\t\t\t// Global tracking has higher priority\n\t\t\t\tif !t.track.Global() {\n\t\t\t\t\tt.track = trackCurrent(t.currentIndex())\n\t\t\t\t}\n\t\t\t\treq(reqInfo)\n\t\t\tcase actUntrackCurrent:\n\t\t\t\tif t.track.Current() {\n\t\t\t\t\tt.track = trackDisabled\n\t\t\t\t}\n\t\t\t\tt.unblockTrack()\n\t\t\t\treq(reqPrompt, reqInfo)\n\t\t\tcase actSearch:\n\t\t\t\toverride := []rune(a.a)\n\t\t\t\tt.inputOverride = &override\n\t\t\t\tchanged = true\n\t\t\tcase actTransformSearch, actBgTransformSearch:\n\t\t\t\tcapture(true, func(query string) {\n\t\t\t\t\toverride := []rune(query)\n\t\t\t\t\tt.inputOverride = &override\n\t\t\t\t\tchanged = true\n\t\t\t\t})\n\t\t\tcase actEnableSearch:\n\t\t\t\tt.paused = false\n\t\t\t\tchanged = true\n\t\t\t\treq(reqPrompt)\n\t\t\tcase actDisableSearch:\n\t\t\t\tt.paused = true\n\t\t\t\treq(reqPrompt)\n\t\t\tcase actTrigger:\n\t\t\t\tif _, chords, err := parseKeyChords(a.a, \"\"); err == nil {\n\t\t\t\t\tfor _, chord := range chords {\n\t\t\t\t\t\tif _, prs := triggering[chord]; prs {\n\t\t\t\t\t\t\t// Avoid recursive triggering\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif acts, prs := t.keymap[chord]; prs {\n\t\t\t\t\t\t\ttriggering[chord] = struct{}{}\n\t\t\t\t\t\t\tdoActions(acts)\n\t\t\t\t\t\t\tdelete(triggering, chord)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actSigStop:\n\t\t\t\tp, err := os.FindProcess(os.Getpid())\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.tui.Clear()\n\t\t\t\t\tt.tui.Pause(t.fullscreen)\n\t\t\t\t\tnotifyStop(p)\n\t\t\t\t\tt.mutex.Unlock()\n\t\t\t\t\tt.reqBox.Set(reqReinit, nil)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\tcase actMouse:\n\t\t\t\tme := event.MouseEvent\n\t\t\t\tmx, my := me.X, me.Y\n\t\t\t\tclick := !wasDown && me.Down\n\t\t\t\tclicked := wasDown && !me.Down && (mx == pmx && my == pmy)\n\t\t\t\twasDown = me.Down\n\t\t\t\tif click {\n\t\t\t\t\tpmx, pmy = mx, my\n\t\t\t\t}\n\t\t\t\tif !me.Down {\n\t\t\t\t\tbarDragging = false\n\t\t\t\t\tpbarDragging = false\n\t\t\t\t\tpborderDragging = -1\n\t\t\t\t\tpreviewDraggingPos = -1\n\t\t\t\t\tpmx, pmy = -1, -1\n\t\t\t\t}\n\n\t\t\t\t// Scrolling\n\t\t\t\tif me.S != 0 {\n\t\t\t\t\tif t.window.Enclose(my, mx) && t.merger.Length() > 0 {\n\t\t\t\t\t\tevt := tui.ScrollUp\n\t\t\t\t\t\tif me.Mod() {\n\t\t\t\t\t\t\tevt = tui.SScrollUp\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif me.S < 0 {\n\t\t\t\t\t\t\tevt = tui.ScrollDown\n\t\t\t\t\t\t\tif me.Mod() {\n\t\t\t\t\t\t\t\tevt = tui.SScrollDown\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn doActions(actionsFor(evt))\n\t\t\t\t\t} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {\n\t\t\t\t\t\tevt := tui.PreviewScrollUp\n\t\t\t\t\t\tif me.S < 0 {\n\t\t\t\t\t\t\tevt = tui.PreviewScrollDown\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn doActions(actionsFor(evt))\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Preview dragging\n\t\t\t\tif me.Down && (previewDraggingPos >= 0 || click && t.hasPreviewWindow() && t.pwindow.Enclose(my, mx)) {\n\t\t\t\t\tif previewDraggingPos > 0 {\n\t\t\t\t\t\tscrollPreviewBy(previewDraggingPos - my)\n\t\t\t\t\t}\n\t\t\t\t\tpreviewDraggingPos = my\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Preview scrollbar dragging\n\t\t\t\theaderLines := t.activePreviewOpts.headerLines\n\t\t\t\tpbarDragging = me.Down && (pbarDragging || click && t.hasPreviewWindow() && my >= t.pwindow.Top()+headerLines && my < t.pwindow.Top()+t.pwindow.Height() && mx == t.pwindow.Left()+t.pwindow.Width())\n\t\t\t\tif pbarDragging {\n\t\t\t\t\teffectiveHeight := t.pwindow.Height() - headerLines\n\t\t\t\t\tnumLines := len(t.previewer.lines) - headerLines\n\t\t\t\t\tbarLength, _ := getScrollbar(1, numLines, effectiveHeight, min(numLines-effectiveHeight, t.previewer.offset-headerLines))\n\t\t\t\t\tif barLength > 0 {\n\t\t\t\t\t\ty := my - t.pwindow.Top() - headerLines - barLength/2\n\t\t\t\t\t\ty = util.Constrain(y, 0, effectiveHeight-barLength)\n\t\t\t\t\t\t// offset = (total - maxItems) * barStart / (maxItems - barLength)\n\t\t\t\t\t\tt.previewer.offset = headerLines + int(math.Ceil(float64(y)*float64(numLines-effectiveHeight)/float64(effectiveHeight-barLength)))\n\t\t\t\t\t\tt.previewer.following.Set(t.previewer.offset >= numLines-effectiveHeight)\n\t\t\t\t\t\treq(reqPreviewRefresh)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Preview border dragging (resizing)\n\t\t\t\tif pborderDragging < 0 && click && t.hasPreviewWindow() {\n\t\t\t\t\tswitch t.activePreviewOpts.position {\n\t\t\t\t\tcase posUp:\n\t\t\t\t\t\tif t.pborder.Enclose(my, mx) && my == t.pborder.Top()+t.pborder.Height()-1 {\n\t\t\t\t\t\t\tpborderDragging = 0\n\t\t\t\t\t\t} else if t.listBorderShape.HasTop() && t.pborder.EncloseX(mx) && my == t.wborder.Top() {\n\t\t\t\t\t\t\tpborderDragging = 1\n\t\t\t\t\t\t}\n\t\t\t\t\tcase posDown:\n\t\t\t\t\t\tif t.pborder.Enclose(my, mx) && my == t.pborder.Top() {\n\t\t\t\t\t\t\tpborderDragging = 0\n\t\t\t\t\t\t} else if t.listBorderShape.HasBottom() && t.pborder.EncloseX(mx) && my == t.wborder.Top()+t.wborder.Height()-1 {\n\t\t\t\t\t\t\tpborderDragging = 1\n\t\t\t\t\t\t}\n\t\t\t\t\tcase posLeft:\n\t\t\t\t\t\tif t.pborder.Enclose(my, mx) && mx == t.pborder.Left()+t.pborder.Width()-1 {\n\t\t\t\t\t\t\tpborderDragging = 0\n\t\t\t\t\t\t} else if t.listBorderShape.HasLeft() && t.pborder.EncloseY(my) && mx == t.wborder.Left() {\n\t\t\t\t\t\t\tpborderDragging = 1\n\t\t\t\t\t\t}\n\t\t\t\t\tcase posRight:\n\t\t\t\t\t\tif t.pborder.Enclose(my, mx) && mx == t.pborder.Left() {\n\t\t\t\t\t\t\tpborderDragging = 0\n\t\t\t\t\t\t} else if t.listBorderShape.HasRight() && t.pborder.EncloseY(my) && mx == t.wborder.Left()+t.wborder.Width()-1 {\n\t\t\t\t\t\t\tpborderDragging = 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif pborderDragging >= 0 && t.hasPreviewWindow() {\n\t\t\t\t\tvar newSize int\n\t\t\t\t\tvar prevSize int\n\t\t\t\t\tswitch t.activePreviewOpts.position {\n\t\t\t\t\tcase posLeft:\n\t\t\t\t\t\tprevSize = t.pwindow.Width()\n\t\t\t\t\t\tdiff := t.pborder.Width() - prevSize\n\t\t\t\t\t\tnewSize = mx - t.pborder.Left() - diff + 1\n\t\t\t\t\tcase posUp:\n\t\t\t\t\t\tprevSize = t.pwindow.Height()\n\t\t\t\t\t\tdiff := t.pborder.Height() - prevSize\n\t\t\t\t\t\tnewSize = my - t.pborder.Top() - diff + 1\n\t\t\t\t\tcase posDown:\n\t\t\t\t\t\tprevSize = t.pwindow.Height()\n\t\t\t\t\t\toffset := my - t.pborder.Top()\n\t\t\t\t\t\tnewSize = prevSize - offset\n\t\t\t\t\tcase posRight:\n\t\t\t\t\t\tprevSize = t.pwindow.Width()\n\t\t\t\t\t\toffset := mx - t.pborder.Left()\n\t\t\t\t\t\tnewSize = prevSize - offset\n\t\t\t\t\t}\n\t\t\t\t\tnewSize -= pborderDragging\n\t\t\t\t\tif newSize < 1 {\n\t\t\t\t\t\tnewSize = 1\n\t\t\t\t\t}\n\n\t\t\t\t\tif prevSize == newSize {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tt.activePreviewOpts.size = sizeSpec{float64(newSize), false}\n\t\t\t\t\tupdatePreviewWindow(true)\n\t\t\t\t\treq(reqPreviewRefresh)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Inside the input window\n\t\t\t\tif t.inputWindow != nil && t.inputWindow.Enclose(my, mx) {\n\t\t\t\t\tmx -= t.inputWindow.Left()\n\t\t\t\t\tmy -= t.inputWindow.Top()\n\t\t\t\t\ty := t.inputWindow.Height() - 1\n\t\t\t\t\tif t.layout == layoutReverse {\n\t\t\t\t\t\ty = 0\n\t\t\t\t\t}\n\t\t\t\t\tmxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))\n\t\t\t\t\tif my == y && mxCons >= 0 {\n\t\t\t\t\t\tt.cx = mxCons + t.xoffset\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Inside the header window\n\t\t\t\tif clicked && t.headerVisible && t.headerWindow != nil && t.headerWindow.Enclose(my, mx) {\n\t\t\t\t\tmx -= t.headerWindow.Left() + t.headerIndent(t.headerBorderShape)\n\t\t\t\t\tmy -= t.headerWindow.Top()\n\t\t\t\t\tif mx < 0 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tt.clickHeaderLine = my + 1\n\t\t\t\t\tif t.layout != layoutReverse && t.headerLinesWindow != nil {\n\t\t\t\t\t\tt.clickHeaderLine += t.headerLines\n\t\t\t\t\t}\n\t\t\t\t\tt.clickHeaderColumn = mx + 1\n\t\t\t\t\treturn doActions(actionsFor(tui.ClickHeader))\n\t\t\t\t}\n\n\t\t\t\tif clicked && t.headerVisible && t.headerLinesWindow != nil && t.headerLinesWindow.Enclose(my, mx) {\n\t\t\t\t\t_, shape := t.determineHeaderLinesShape()\n\t\t\t\t\tmx -= t.headerLinesWindow.Left() + t.headerIndent(shape)\n\t\t\t\t\tmy -= t.headerLinesWindow.Top()\n\t\t\t\t\tif mx < 0 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tt.clickHeaderLine = my + 1\n\t\t\t\t\tif t.layout == layoutReverse {\n\t\t\t\t\t\tt.clickHeaderLine += len(t.header0)\n\t\t\t\t\t}\n\t\t\t\t\tt.clickHeaderColumn = mx + 1\n\t\t\t\t\treturn doActions(actionsFor(tui.ClickHeader))\n\t\t\t\t}\n\n\t\t\t\t// Inside the footer window\n\t\t\t\tif clicked && t.footerWindow != nil && t.footerWindow.Enclose(my, mx) {\n\t\t\t\t\tmx -= t.footerWindow.Left() + t.headerIndent(t.footerBorderShape)\n\t\t\t\t\tmy -= t.footerWindow.Top()\n\t\t\t\t\tif mx < 0 {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tt.clickFooterLine = my + 1\n\t\t\t\t\tt.clickFooterColumn = mx + 1\n\t\t\t\t\treturn doActions(actionsFor(tui.ClickFooter))\n\t\t\t\t}\n\n\t\t\t\t// Ignored\n\t\t\t\tif !t.window.Enclose(my, mx) && !barDragging {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Translate coordinates\n\t\t\t\tmx -= t.window.Left()\n\t\t\t\tmy -= t.window.Top()\n\t\t\t\tmin := t.promptLines() + t.visibleHeaderLinesInList()\n\t\t\t\th := t.window.Height()\n\t\t\t\tswitch t.layout {\n\t\t\t\tcase layoutDefault:\n\t\t\t\t\tmy = h - my - 1\n\t\t\t\tcase layoutReverseList:\n\t\t\t\t\tif my < h-min {\n\t\t\t\t\t\tmy += min\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmy = h - my - 1\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Scrollbar dragging\n\t\t\t\tbarDragging = me.Down && (barDragging || click && my >= min && mx == t.window.Width()-1)\n\t\t\t\tif barDragging {\n\t\t\t\t\tbarLength, barStart := t.getScrollbar()\n\t\t\t\t\tif barLength > 0 {\n\t\t\t\t\t\tmaxItems := t.maxItems()\n\t\t\t\t\t\tif newBarStart := util.Constrain(my-min-barLength/2, 0, maxItems-barLength); newBarStart != barStart {\n\t\t\t\t\t\t\ttotal := t.merger.Length()\n\t\t\t\t\t\t\tprevOffset := t.offset\n\t\t\t\t\t\t\t// barStart = (maxItems - barLength) * t.offset / (total - maxItems)\n\t\t\t\t\t\t\tperLine := t.avgNumLines()\n\t\t\t\t\t\t\tt.offset = int(math.Ceil(float64(newBarStart) * float64(total*perLine-maxItems) / float64(maxItems*perLine-barLength)))\n\t\t\t\t\t\t\tt.cy = t.offset + t.cy - prevOffset\n\t\t\t\t\t\t\treq(reqList)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// There can be empty lines after the list in multi-line mode\n\t\t\t\tprevLine := t.prevLines[my]\n\t\t\t\tif prevLine.empty {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Double-click on an item\n\t\t\t\tcy := prevLine.cy\n\t\t\t\tif me.Double && mx < t.window.Width()-1 {\n\t\t\t\t\t// Double-click\n\t\t\t\t\tif my >= min {\n\t\t\t\t\t\tif t.vset(cy) && t.cy < t.merger.Length() {\n\t\t\t\t\t\t\treturn doActions(actionsFor(tui.DoubleClick))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif me.Down {\n\t\t\t\t\tmxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))\n\t\t\t\t\tif !t.inputless && t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 {\n\t\t\t\t\t\t// Prompt\n\t\t\t\t\t\tt.cx = mxCons + t.xoffset\n\t\t\t\t\t} else if my >= min {\n\t\t\t\t\t\tt.vset(cy)\n\t\t\t\t\t\treq(reqList)\n\t\t\t\t\t\tevt := tui.RightClick\n\t\t\t\t\t\tif me.Mod() {\n\t\t\t\t\t\t\tevt = tui.SRightClick\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif me.Left {\n\t\t\t\t\t\t\tevt = tui.LeftClick\n\t\t\t\t\t\t\tif me.Mod() {\n\t\t\t\t\t\t\t\tevt = tui.SLeftClick\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn doActions(actionsFor(evt))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif clicked && t.headerVisible && t.headerWindow == nil {\n\t\t\t\t\t// Header\n\t\t\t\t\tnumLines := t.visibleHeaderLinesInList()\n\t\t\t\t\tlineOffset := 0\n\t\t\t\t\tif !t.inputless && t.inputWindow == nil && !t.headerFirst {\n\t\t\t\t\t\t// offset for info line\n\t\t\t\t\t\tif t.noSeparatorLine() {\n\t\t\t\t\t\t\tlineOffset = 1\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlineOffset = 2\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tmy -= lineOffset\n\t\t\t\t\tmx -= t.pointerLen + t.markerLen\n\t\t\t\t\tif my >= 0 && my < numLines && mx >= 0 {\n\t\t\t\t\t\tif t.layout == layoutReverse {\n\t\t\t\t\t\t\tt.clickHeaderLine = my + 1\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.clickHeaderLine = numLines - my\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.clickHeaderColumn = mx + 1\n\t\t\t\t\t\treturn doActions(actionsFor(tui.ClickHeader))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actReload, actReloadSync:\n\t\t\t\tt.failed = nil\n\n\t\t\t\tvalid, list := t.buildPlusList(a.a, false)\n\t\t\t\tif !valid {\n\t\t\t\t\t// We run the command even when there's no match\n\t\t\t\t\t// 1. If the template doesn't have any slots\n\t\t\t\t\t// 2. If the template has {q}\n\t\t\t\t\tslot, _, _, forceUpdate := hasPreviewFlags(a.a)\n\t\t\t\t\tvalid = !slot || forceUpdate\n\t\t\t\t}\n\t\t\t\tif valid {\n\t\t\t\t\tcommand, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)\n\t\t\t\t\tnewCommand = &commandSpec{command, tempFiles}\n\t\t\t\t\treloadSync = a.t == actReloadSync\n\t\t\t\t\tt.reading = true\n\n\t\t\t\t\tif len(t.idNth) > 0 {\n\t\t\t\t\t\tt.trackSync = reloadSync\n\t\t\t\t\t}\n\t\t\t\t\t// Capture tracking key before reload\n\t\t\t\t\tif !t.track.Disabled() && len(t.idNth) > 0 {\n\t\t\t\t\t\tif item := t.currentItem(); item != nil {\n\t\t\t\t\t\t\tt.trackKey = t.trackKeyFor(item, t.idNth)\n\t\t\t\t\t\t\tt.trackKeyCache = make(map[int32]bool)\n\t\t\t\t\t\t\tt.trackBlocked = true\n\t\t\t\t\t\t\tif !t.inputless {\n\t\t\t\t\t\t\t\tt.tui.HideCursor()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treq(reqPrompt, reqInfo)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actUnbind:\n\t\t\t\tif keys, _, err := parseKeyChords(a.a, \"PANIC\"); err == nil {\n\t\t\t\t\tfor key := range keys {\n\t\t\t\t\t\tdelete(t.keymap, key)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actRebind:\n\t\t\t\tif keys, _, err := parseKeyChords(a.a, \"PANIC\"); err == nil {\n\t\t\t\t\tfor key := range keys {\n\t\t\t\t\t\tif originalAction, found := t.keymapOrg[key]; found {\n\t\t\t\t\t\t\tt.keymap[key] = originalAction\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actToggleBind:\n\t\t\t\tif keys, _, err := parseKeyChords(a.a, \"PANIC\"); err == nil {\n\t\t\t\t\tfor key := range keys {\n\t\t\t\t\t\tif _, bound := t.keymap[key]; bound {\n\t\t\t\t\t\t\tdelete(t.keymap, key)\n\t\t\t\t\t\t} else if originalAction, found := t.keymapOrg[key]; found {\n\t\t\t\t\t\t\tt.keymap[key] = originalAction\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase actChangeGhost, actTransformGhost, actBgTransformGhost:\n\t\t\t\tcapture(true, func(ghost string) {\n\t\t\t\t\tt.ghost = ghost\n\t\t\t\t\tif len(t.input) == 0 {\n\t\t\t\t\t\treq(reqPrompt)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangePointer, actTransformPointer, actBgTransformPointer:\n\t\t\t\tcapture(true, func(pointer string) {\n\t\t\t\t\tlength := uniseg.StringWidth(pointer)\n\t\t\t\t\tif length <= 2 {\n\t\t\t\t\t\tif length != t.pointerLen {\n\t\t\t\t\t\t\tt.forceRerenderList()\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.pointer = pointer\n\t\t\t\t\t\tt.pointerLen = length\n\t\t\t\t\t\tt.pointerEmpty = strings.Repeat(\" \", t.pointerLen)\n\t\t\t\t\t\treq(reqList)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tcase actChangePreview:\n\t\t\t\tif t.previewOpts.command != a.a {\n\t\t\t\t\tt.previewOpts.command = a.a\n\t\t\t\t\tupdatePreviewWindow(false)\n\t\t\t\t\trefreshPreview(t.previewOpts.command)\n\t\t\t\t}\n\t\t\tcase actChangePreviewWindow:\n\t\t\t\t// NOTE: We intentionally use \"previewOpts\" instead of \"activePreviewOpts\" here\n\t\t\t\tcurrentPreviewOpts := t.previewOpts\n\n\t\t\t\t// Reset preview options and apply the additional options\n\t\t\t\tt.previewOpts = t.initialPreviewOpts\n\t\t\t\tt.previewOpts.command = currentPreviewOpts.command\n\n\t\t\t\t// Split window options\n\t\t\t\ttokens := strings.Split(a.a, \"|\")\n\t\t\t\tif len(tokens[0]) > 0 && t.initialPreviewOpts.hidden {\n\t\t\t\t\tt.previewOpts.hidden = false\n\t\t\t\t}\n\t\t\t\tparsePreviewWindow(&t.previewOpts, tokens[0])\n\t\t\t\tif len(tokens) > 1 {\n\t\t\t\t\ta.a = strings.Join(append(tokens[1:], tokens[0]), \"|\")\n\t\t\t\t}\n\n\t\t\t\t// Full redraw\n\t\t\t\tswitch currentPreviewOpts.compare(t.activePreviewOpts, &t.previewOpts) {\n\t\t\t\tcase previewOptsDifferentLayout:\n\t\t\t\t\t// Preview command can be running in the background if the size of\n\t\t\t\t\t// the preview window is 0 but not 'hidden'\n\t\t\t\t\twasHidden := currentPreviewOpts.hidden\n\n\t\t\t\t\t// FIXME: One-time preview window can't reappear once hidden\n\t\t\t\t\t// fzf --bind space:preview:ls --bind 'enter:change-preview-window:down|left|up|hidden|'\n\t\t\t\t\tupdatePreviewWindow(t.hasPreviewWindow() && !t.activePreviewOpts.hidden)\n\t\t\t\t\tif wasHidden && t.hasPreviewWindow() {\n\t\t\t\t\t\t// Restart\n\t\t\t\t\t\trefreshPreview(t.previewOpts.command)\n\t\t\t\t\t} else if t.activePreviewOpts.hidden {\n\t\t\t\t\t\t// Cancel\n\t\t\t\t\t\tt.cancelPreview()\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Refresh\n\t\t\t\t\t\treq(reqPreviewRefresh)\n\t\t\t\t\t}\n\t\t\t\tcase previewOptsDifferentContentLayout:\n\t\t\t\t\tt.previewed.version = 0\n\t\t\t\t\treq(reqPreviewRefresh)\n\t\t\t\t}\n\n\t\t\t\t// Adjust scroll offset\n\t\t\t\tif t.hasPreviewWindow() && currentPreviewOpts.scroll != t.activePreviewOpts.scroll {\n\t\t\t\t\tscrollPreviewTo(t.evaluateScrollOffset())\n\t\t\t\t}\n\n\t\t\t\t// Resume following\n\t\t\t\tt.previewer.following.Force(t.previewOpts.follow)\n\t\t\tcase actNextSelected, actPrevSelected:\n\t\t\t\tif len(t.selected) > 0 {\n\t\t\t\t\ttotal := t.merger.Length()\n\t\t\t\t\tfor i := 1; i < total; i++ {\n\t\t\t\t\t\ty := (t.cy + i) % total\n\t\t\t\t\t\tif t.layout == layoutDefault && a.t == actNextSelected ||\n\t\t\t\t\t\t\tt.layout != layoutDefault && a.t == actPrevSelected {\n\t\t\t\t\t\t\ty = (t.cy - i + total) % total\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif _, found := t.selected[t.merger.Get(y).item.Index()]; found {\n\t\t\t\t\t\t\tt.vset(y)\n\t\t\t\t\t\t\treq(reqList)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !processExecution(a.t) {\n\t\t\t\tt.lastAction = a.t\n\t\t\t}\n\n\t\t\tif t.inputless {\n\t\t\t\t// Always just discard the change\n\t\t\t\tt.input = currentInput\n\t\t\t\tt.cx = len(t.input)\n\t\t\t\tbeof = false\n\t\t\t} else if string(t.input) != string(currentInput) {\n\t\t\t\tt.inputOverride = nil\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\n\t\tif t.jumping == jumpDisabled || len(actions) > 0 {\n\t\t\t// Break out of jump mode if any action is submitted to the server\n\t\t\tif t.jumping != jumpDisabled {\n\t\t\t\tt.jumping = jumpDisabled\n\t\t\t\tif acts, prs := t.keymap[tui.JumpCancel.AsEvent()]; prs && !doActions(acts) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq(reqList)\n\t\t\t}\n\t\t\tif len(actions) == 0 {\n\t\t\t\tactions = t.keymap[event.Comparable()]\n\t\t\t}\n\t\t\tif len(actions) == 0 && event.Type == tui.Rune {\n\t\t\t\tdoAction(&action{t: actChar})\n\t\t\t} else if !doActions(actions) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !t.inputless {\n\t\t\t\tt.truncateQuery()\n\t\t\t}\n\t\t\tqueryChanged = queryChanged || t.pasting == nil && string(previousInput) != string(t.input)\n\t\t\tchanged = changed || queryChanged\n\t\t\tif onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif onMultis, prs := t.keymap[tui.Multi.AsEvent()]; t.version != previousVersion && prs && !doActions(onMultis) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tjumpEvent := tui.JumpCancel\n\t\t\tif event.Type == tui.Rune {\n\t\t\t\tif idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {\n\t\t\t\t\tjumpEvent = tui.Jump\n\t\t\t\t\tt.cy = idx + t.offset\n\t\t\t\t\tif t.jumping == jumpAcceptEnabled {\n\t\t\t\t\t\treq(reqClose)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.jumping = jumpDisabled\n\t\t\tif acts, prs := t.keymap[jumpEvent.AsEvent()]; prs && !doActions(acts) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq(reqList)\n\t\t}\n\n\t\tif queryChanged && t.canPreview() && len(t.previewOpts.command) > 0 {\n\t\t\t_, _, _, forceUpdate := hasPreviewFlags(t.previewOpts.command)\n\t\t\tif forceUpdate {\n\t\t\t\tt.version++\n\t\t\t}\n\t\t}\n\n\t\tif queryChanged || t.cx != previousCx {\n\t\t\treq(reqPrompt)\n\t\t}\n\n\t\treload := changed || newCommand != nil\n\t\tvar reloadRequest *searchRequest\n\t\tif reload {\n\t\t\treloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, withNth: newWithNth, headerLines: newHeaderLines, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}\n\t\t}\n\n\t\t// Dispatch queued background requests\n\t\tt.dispatchAsync()\n\n\t\tif t.pendingReqList {\n\t\t\tt.pendingReqList = false\n\t\t\treq(reqList)\n\t\t}\n\n\t\tt.mutex.Unlock() // Must be unlocked before touching reqBox\n\n\t\tif reload {\n\t\t\tt.eventBox.Set(EvtSearchNew, *reloadRequest)\n\t\t}\n\t\tfor _, event := range events {\n\t\t\tt.reqBox.Set(event, nil)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *Terminal) constrain() {\n\t// count of items to display allowed by filtering\n\tcount := t.merger.Length()\n\tmaxLines := t.maxItems()\n\n\t// May need to try again after adjusting the offset\n\tt.offset = util.Constrain(t.offset, 0, count)\n\tfor range maxLines {\n\t\tnumItems := maxLines\n\t\t// How many items can be fit on screen including the current item?\n\t\tif t.canSpanMultiLines() && t.merger.Length() > 0 {\n\t\t\tnumItemsFound := 0\n\t\t\tlinesSum := 0\n\n\t\t\tadd := func(i int) bool {\n\t\t\t\tlines, overflow := t.numItemLines(t.merger.Get(i).item, numItems-linesSum)\n\t\t\t\tlinesSum += lines\n\t\t\t\tif linesSum >= numItems {\n\t\t\t\t\t/*\n\t\t\t\t\t\t# Should show all 3 items\n\t\t\t\t\t\tprintf \"file1\\0file2\\0file3\\0\" | fzf --height=5 --read0 --bind load:last --reverse\n\n\t\t\t\t\t\t# Should not truncate the last item\n\t\t\t\t\t\tprintf \"file\\n1\\0file\\n2\\0file\\n3\\0\" | fzf --height=5 --read0 --bind load:last --reverse\n\t\t\t\t\t*/\n\t\t\t\t\tif numItemsFound == 0 || !overflow {\n\t\t\t\t\t\tnumItemsFound++\n\t\t\t\t\t}\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tnumItemsFound++\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tfor i := t.offset; i < t.merger.Length(); i++ {\n\t\t\t\tif !add(i) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We can possibly fit more items \"before\" the offset on screen\n\t\t\tif linesSum < numItems {\n\t\t\t\tfor i := t.offset - 1; i >= 0; i-- {\n\t\t\t\t\tif !add(i) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnumItems = numItemsFound\n\t\t}\n\n\t\tt.cy = util.Constrain(t.cy, 0, max(0, count-1))\n\t\tminOffset := max(t.cy-numItems+1, 0)\n\t\tmaxOffset := max(min(count-numItems, t.cy), 0)\n\t\tprevOffset := t.offset\n\t\tt.offset = util.Constrain(t.offset, minOffset, maxOffset)\n\t\tif t.scrollOff > 0 {\n\t\t\tscrollOff := min(maxLines/2, t.scrollOff)\n\t\t\tnewOffset := t.offset\n\t\t\t// 2-phase adjustment to avoid infinite loop of alternating between moving up and down\n\t\t\tfor phase := range 2 {\n\t\t\t\tfor {\n\t\t\t\t\tprevOffset := newOffset\n\t\t\t\t\tnumItems := t.merger.Length()\n\t\t\t\t\titemLines := 1 + t.gap\n\t\t\t\t\tif t.canSpanMultiLines() && t.cy < numItems {\n\t\t\t\t\t\titemLines, _ = t.numItemLines(t.merger.Get(t.cy).item, maxLines)\n\t\t\t\t\t}\n\t\t\t\t\tlinesBefore := t.cy - newOffset\n\t\t\t\t\tif t.canSpanMultiLines() {\n\t\t\t\t\t\tlinesBefore = 0\n\t\t\t\t\t\tfor i := newOffset; i < t.cy && i < numItems; i++ {\n\t\t\t\t\t\t\tlines, _ := t.numItemLines(t.merger.Get(i).item, maxLines-linesBefore-itemLines)\n\t\t\t\t\t\t\tlinesBefore += lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlinesAfter := maxLines - (linesBefore + itemLines)\n\n\t\t\t\t\t// Stuck in the middle, nothing to do\n\t\t\t\t\tif linesBefore < scrollOff && linesAfter < scrollOff {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tif phase == 0 && linesBefore < scrollOff {\n\t\t\t\t\t\tnewOffset = max(minOffset, newOffset-1)\n\t\t\t\t\t} else if phase == 1 && linesAfter < scrollOff {\n\t\t\t\t\t\tnewOffset = min(maxOffset, newOffset+1)\n\t\t\t\t\t}\n\t\t\t\t\tif newOffset == prevOffset {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tt.offset = newOffset\n\t\t\t}\n\t\t}\n\t\tif t.offset == prevOffset {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Returns true if the cursor position is successfully updated\nfunc (t *Terminal) vmove(o int, allowCycle bool) bool {\n\tif t.layout != layoutDefault {\n\t\to *= -1\n\t}\n\tdest := t.cy + o\n\tif t.cycle && allowCycle {\n\t\tmax := t.merger.Length() - 1\n\t\tif dest > max {\n\t\t\tif t.cy == max {\n\t\t\t\tdest = 0\n\t\t\t}\n\t\t} else if dest < 0 {\n\t\t\tif t.cy == 0 {\n\t\t\t\tdest = max\n\t\t\t}\n\t\t}\n\t}\n\treturn t.vset(dest)\n}\n\nfunc (t *Terminal) vset(o int) bool {\n\tt.cy = util.Constrain(o, 0, t.merger.Length()-1)\n\treturn t.cy == o\n}\n\n// Number of prompt lines in the list window\nfunc (t *Terminal) promptLines() int {\n\tif t.inputless {\n\t\treturn 0\n\t}\n\tif t.inputWindow != nil {\n\t\treturn 0\n\t}\n\tif t.noSeparatorLine() {\n\t\treturn 1\n\t}\n\treturn 2\n}\n\n// Number of item lines in the list window\nfunc (t *Terminal) maxItems() int {\n\tmaximum := t.window.Height() - t.visibleHeaderLinesInList() - t.promptLines()\n\treturn max(maximum, 0)\n}\n\nfunc (t *Terminal) dumpItem(i *Item) StatusItem {\n\tif i == nil {\n\t\treturn StatusItem{}\n\t}\n\treturn StatusItem{\n\t\tIndex: int(i.Index()),\n\t\tText:  i.AsString(t.ansi),\n\t}\n}\n\nfunc (t *Terminal) tryLock(timeout time.Duration) bool {\n\tsleepDuration := 10 * time.Millisecond\n\n\tfor {\n\t\tif t.mutex.TryLock() {\n\t\t\treturn true\n\t\t}\n\n\t\ttimeout -= sleepDuration\n\t\tif timeout <= 0 {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(sleepDuration)\n\t}\n\treturn false\n}\n\nfunc (t *Terminal) dumpStatus(params getParams) string {\n\tif !t.tryLock(channelTimeout) {\n\t\treturn \"\"\n\t}\n\tdefer t.mutex.Unlock()\n\n\tselectedItems := t.sortSelected()\n\tselected := make([]StatusItem, max(0, min(params.limit, len(selectedItems)-params.offset)))\n\tfor i := range selected {\n\t\tselected[i] = t.dumpItem(selectedItems[i+params.offset].item)\n\t}\n\n\tmatches := make([]StatusItem, max(0, min(params.limit, t.resultMerger.Length()-params.offset)))\n\tfor i := range matches {\n\t\tmatches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item)\n\t}\n\n\tvar current *StatusItem\n\tcurrentItem := t.currentItem()\n\tif currentItem != nil {\n\t\titem := t.dumpItem(currentItem)\n\t\tcurrent = &item\n\t}\n\n\tdump := Status{\n\t\tReading:    t.reading,\n\t\tProgress:   t.progress,\n\t\tQuery:      string(t.input),\n\t\tPosition:   t.cy,\n\t\tSort:       t.sort,\n\t\tTotalCount: t.count,\n\t\tMatchCount: t.resultMerger.Length(),\n\t\tCurrent:    current,\n\t\tMatches:    matches,\n\t\tSelected:   selected,\n\t}\n\tbytes, _ := json.Marshal(&dump) // TODO: Errors?\n\treturn string(bytes)\n}\n"
  },
  {
    "path": "src/terminal_test.go",
    "content": "package fzf\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"text/template\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems [3][]*Item) string {\n\treplaced, _ := replacePlaceholder(replacePlaceholderParams{\n\t\ttemplate:   template,\n\t\tstripAnsi:  stripAnsi,\n\t\tdelimiter:  delimiter,\n\t\tprintsep:   printsep,\n\t\tforcePlus:  forcePlus,\n\t\tquery:      query,\n\t\tallItems:   allItems,\n\t\tlastAction: actBackwardDeleteCharEof,\n\t\tprompt:     \"prompt\",\n\t\texecutor:   util.NewExecutor(\"\"),\n\t})\n\treturn replaced\n}\n\nfunc TestReplacePlaceholder(t *testing.T) {\n\titem1 := newItem(\"  foo'bar \\x1b[31mbaz\\x1b[m\")\n\titems1 := [3][]*Item{{item1}, {item1}, nil}\n\titems2 := [3][]*Item{\n\t\t{newItem(\"foo'bar \\x1b[31mbaz\\x1b[m\")},\n\t\t{newItem(\"foo'bar \\x1b[31mbaz\\x1b[m\"),\n\t\t\tnewItem(\"FOO'BAR \\x1b[31mBAZ\\x1b[m\")}, nil}\n\n\tdelim := \"'\"\n\tvar regex *regexp.Regexp\n\n\tvar result string\n\tcheck := func(expected string) {\n\t\tif result != expected {\n\t\t\tt.Errorf(\"expected: %s, actual: %s\", expected, result)\n\t\t}\n\t}\n\t// helper function that converts template format into string and carries out the check()\n\tcheckFormat := func(format string) {\n\t\ttype quotes struct{ O, I, S string } // outer, inner quotes, print separator\n\t\tunixStyle := quotes{`'`, `'\\''`, \"\\n\"}\n\t\twindowsStyle := quotes{`^\"`, `'`, \"\\n\"}\n\t\tvar effectiveStyle quotes\n\n\t\tif util.IsWindows() {\n\t\t\teffectiveStyle = windowsStyle\n\t\t} else {\n\t\t\teffectiveStyle = unixStyle\n\t\t}\n\n\t\texpected := templateToString(format, effectiveStyle)\n\t\tcheck(expected)\n\t}\n\tprintsep := \"\\n\"\n\n\t/*\n\t\tTest multiple placeholders and the function parameters.\n\t*/\n\n\t// {}, preserve ansi\n\tresult = replacePlaceholderTest(\"echo {}\", false, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar \\x1b[31mbaz\\x1b[m{{.O}}\")\n\n\t// {}, strip ansi\n\tresult = replacePlaceholderTest(\"echo {}\", true, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar baz{{.O}}\")\n\n\t// {r}, strip ansi\n\tresult = replacePlaceholderTest(\"echo {r}\", true, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo   foo'bar baz\")\n\n\t// {r..}, strip ansi\n\tresult = replacePlaceholderTest(\"echo {r..}\", true, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo foo'bar baz\")\n\n\t// {}, with multiple items\n\tresult = replacePlaceholderTest(\"echo {}\", true, Delimiter{}, printsep, false, \"query\", items2)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar baz{{.O}}\")\n\n\t// {..}, strip leading whitespaces, preserve ansi\n\tresult = replacePlaceholderTest(\"echo {..}\", false, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar \\x1b[31mbaz\\x1b[m{{.O}}\")\n\n\t// {..}, strip leading whitespaces, strip ansi\n\tresult = replacePlaceholderTest(\"echo {..}\", true, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar baz{{.O}}\")\n\n\t// {q}\n\tresult = replacePlaceholderTest(\"echo {} {q}\", true, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}\")\n\n\t// {q}, multiple items\n\tresult = replacePlaceholderTest(\"echo {+}{q}{+}\", true, Delimiter{}, printsep, false, \"query 'string'\", items2)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {}{q}{}\", true, Delimiter{}, printsep, false, \"query 'string'\", items2)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\\\{}/\\\\{1}/\\\\{q}/{3}\", true, Delimiter{}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}  foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\\\{}/\\\\{1}/\\\\{q}/{3}\", true, Delimiter{}, printsep, false, \"query\", items2)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\\\{}/\\\\{1}/\\\\{q}/{+3}\", true, Delimiter{}, printsep, false, \"query\", items2)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}\")\n\n\t// forcePlus\n\tresult = replacePlaceholderTest(\"echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\\\{}/\\\\{1}/\\\\{q}/{3}\", true, Delimiter{}, printsep, true, \"query\", items2)\n\tcheckFormat(\"echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}\")\n\n\t// Whitespace preserving flag with \"'\" delimiter\n\tresult = replacePlaceholderTest(\"echo {s1}\", true, Delimiter{str: &delim}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {s2}\", true, Delimiter{str: &delim}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}bar baz{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {s}\", true, Delimiter{str: &delim}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar baz{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {s..}\", true, Delimiter{str: &delim}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar baz{{.O}}\")\n\n\t// Whitespace preserving flag with regex delimiter\n\tregex = regexp.MustCompile(`\\w+`)\n\n\tresult = replacePlaceholderTest(\"echo {s1}\", true, Delimiter{regex: regex}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  {{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {s2}\", true, Delimiter{regex: regex}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}{{.I}}{{.O}}\")\n\n\tresult = replacePlaceholderTest(\"echo {s3}\", true, Delimiter{regex: regex}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}} {{.O}}\")\n\n\t// No match\n\tresult = replacePlaceholderTest(\"echo {}/{+}\", true, Delimiter{}, printsep, false, \"query\", [3][]*Item{nil, nil, nil})\n\tcheck(\"echo /\")\n\n\t// No match, but with selections\n\tresult = replacePlaceholderTest(\"echo {}/{+}\", true, Delimiter{}, printsep, false, \"query\", [3][]*Item{nil, {item1}, nil})\n\tcheckFormat(\"echo /{{.O}}  foo{{.I}}bar baz{{.O}}\")\n\n\t// String delimiter\n\tresult = replacePlaceholderTest(\"echo {}/{1}/{2}\", true, Delimiter{str: &delim}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}\")\n\n\t// Regex delimiter\n\tregex = regexp.MustCompile(\"[oa]+\")\n\t// foo'bar baz\n\tresult = replacePlaceholderTest(\"echo {}/{1}/{3}/{2..3}\", true, Delimiter{regex: regex}, printsep, false, \"query\", items1)\n\tcheckFormat(\"echo {{.O}}  foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}\")\n\n\t/*\n\t\tTest single placeholders, but focus on the placeholders' parameters (e.g. flags).\n\t\tsee: TestParsePlaceholder\n\t*/\n\titems3 := [3][]*Item{\n\t\t// single line\n\t\t{newItem(\"1a 1b 1c 1d 1e 1f\")},\n\t\t// multi line\n\t\t{newItem(\"1a 1b 1c 1d 1e 1f\"),\n\t\t\tnewItem(\"2a 2b 2c 2d 2e 2f\"),\n\t\t\tnewItem(\"3a 3b 3c 3d 3e 3f\"),\n\t\t\tnewItem(\"4a 4b 4c 4d 4e 4f\"),\n\t\t\tnewItem(\"5a 5b 5c 5d 5e 5f\"),\n\t\t\tnewItem(\"6a 6b 6c 6d 6e 6f\"),\n\t\t\tnewItem(\"7a 7b 7c 7d 7e 7f\")},\n\t\tnil,\n\t}\n\tstripAnsi := false\n\tforcePlus := false\n\tquery := \"sample query\"\n\n\ttemplateToOutput := make(map[string]string)\n\ttemplateToFile := make(map[string]string) // same as above, but the file contents will be matched\n\t// I. item type placeholder\n\ttemplateToOutput[`{}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}}`\n\ttemplateToOutput[`{+}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}} {{.O}}2a 2b 2c 2d 2e 2f{{.O}} {{.O}}3a 3b 3c 3d 3e 3f{{.O}} {{.O}}4a 4b 4c 4d 4e 4f{{.O}} {{.O}}5a 5b 5c 5d 5e 5f{{.O}} {{.O}}6a 6b 6c 6d 6e 6f{{.O}} {{.O}}7a 7b 7c 7d 7e 7f{{.O}}`\n\ttemplateToOutput[`{n}`] = `0`\n\ttemplateToOutput[`{+n}`] = `0 0 0 0 0 0 0`\n\ttemplateToFile[`{f}`] = `1a 1b 1c 1d 1e 1f{{.S}}`\n\ttemplateToFile[`{+f}`] = `1a 1b 1c 1d 1e 1f{{.S}}2a 2b 2c 2d 2e 2f{{.S}}3a 3b 3c 3d 3e 3f{{.S}}4a 4b 4c 4d 4e 4f{{.S}}5a 5b 5c 5d 5e 5f{{.S}}6a 6b 6c 6d 6e 6f{{.S}}7a 7b 7c 7d 7e 7f{{.S}}`\n\ttemplateToFile[`{nf}`] = `0{{.S}}`\n\ttemplateToFile[`{+nf}`] = `0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}`\n\n\t// II. token type placeholders\n\ttemplateToOutput[`{..}`] = templateToOutput[`{}`]\n\ttemplateToOutput[`{1..}`] = templateToOutput[`{}`]\n\ttemplateToOutput[`{..2}`] = `{{.O}}1a 1b{{.O}}`\n\ttemplateToOutput[`{1..2}`] = templateToOutput[`{..2}`]\n\ttemplateToOutput[`{-2..-1}`] = `{{.O}}1e 1f{{.O}}`\n\t// shorthand for x..x range\n\ttemplateToOutput[`{1}`] = `{{.O}}1a{{.O}}`\n\ttemplateToOutput[`{1..1}`] = templateToOutput[`{1}`]\n\ttemplateToOutput[`{-6}`] = templateToOutput[`{1}`]\n\t// multiple ranges\n\ttemplateToOutput[`{1,2}`] = templateToOutput[`{1..2}`]\n\ttemplateToOutput[`{1,2,4}`] = `{{.O}}1a 1b 1d{{.O}}`\n\ttemplateToOutput[`{1,2..4}`] = `{{.O}}1a 1b 1c 1d{{.O}}`\n\ttemplateToOutput[`{1..2,-4..-3}`] = `{{.O}}1a 1b 1c 1d{{.O}}`\n\t// flags\n\ttemplateToOutput[`{+1}`] = `{{.O}}1a{{.O}} {{.O}}2a{{.O}} {{.O}}3a{{.O}} {{.O}}4a{{.O}} {{.O}}5a{{.O}} {{.O}}6a{{.O}} {{.O}}7a{{.O}}`\n\ttemplateToOutput[`{+-1}`] = `{{.O}}1f{{.O}} {{.O}}2f{{.O}} {{.O}}3f{{.O}} {{.O}}4f{{.O}} {{.O}}5f{{.O}} {{.O}}6f{{.O}} {{.O}}7f{{.O}}`\n\ttemplateToOutput[`{s1}`] = `{{.O}}1a {{.O}}`\n\ttemplateToFile[`{f1}`] = `1a{{.S}}`\n\ttemplateToOutput[`{+s1..2}`] = `{{.O}}1a 1b {{.O}} {{.O}}2a 2b {{.O}} {{.O}}3a 3b {{.O}} {{.O}}4a 4b {{.O}} {{.O}}5a 5b {{.O}} {{.O}}6a 6b {{.O}} {{.O}}7a 7b {{.O}}`\n\ttemplateToFile[`{+sf1..2}`] = `1a 1b {{.S}}2a 2b {{.S}}3a 3b {{.S}}4a 4b {{.S}}5a 5b {{.S}}6a 6b {{.S}}7a 7b {{.S}}`\n\n\t// III. query type placeholder\n\t// query flag is not removed after parsing, so it gets doubled\n\t// while the double q is invalid, it is useful here for testing purposes\n\ttemplateToOutput[`{q}`] = \"{{.O}}\" + query + \"{{.O}}\"\n\ttemplateToOutput[`{fzf:query}`] = \"{{.O}}\" + query + \"{{.O}}\"\n\ttemplateToOutput[`{fzf:action} {fzf:prompt}`] = \"backward-delete-char-eof 'prompt'\"\n\n\t// IV. escaping placeholder\n\ttemplateToOutput[`\\{}`] = `{}`\n\ttemplateToOutput[`\\{q}`] = `{q}`\n\ttemplateToOutput[`\\{fzf:query}`] = `{fzf:query}`\n\ttemplateToOutput[`\\{fzf:action}`] = `{fzf:action}`\n\ttemplateToOutput[`\\{++}`] = `{++}`\n\ttemplateToOutput[`{++}`] = templateToOutput[`{+}`]\n\n\tfor giveTemplate, wantOutput := range templateToOutput {\n\t\tresult = replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)\n\t\tcheckFormat(wantOutput)\n\t}\n\tfor giveTemplate, wantOutput := range templateToFile {\n\t\tpath := replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)\n\n\t\tdata, err := readFile(path)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Cannot read the content of the temp file %s.\", path)\n\t\t}\n\t\tresult = string(data)\n\n\t\tcheckFormat(wantOutput)\n\t}\n}\n\nfunc TestQuoteEntry(t *testing.T) {\n\ttype quotes struct{ E, O, SQ, DQ, BS string } // standalone escape, outer, single and double quotes, backslash\n\tunixStyle := quotes{``, `'`, `'\\''`, `\"`, `\\`}\n\twindowsStyle := quotes{`^`, `^\"`, `'`, `\\^\"`, `\\\\`}\n\tvar effectiveStyle quotes\n\texec := util.NewExecutor(\"\")\n\n\tif util.IsWindows() {\n\t\teffectiveStyle = windowsStyle\n\t} else {\n\t\teffectiveStyle = unixStyle\n\t}\n\n\ttests := map[string]string{\n\t\t`'`:     `{{.O}}{{.SQ}}{{.O}}`,\n\t\t`\"`:     `{{.O}}{{.DQ}}{{.O}}`,\n\t\t`\\`:     `{{.O}}{{.BS}}{{.O}}`,\n\t\t`\\\"`:    `{{.O}}{{.BS}}{{.DQ}}{{.O}}`,\n\t\t`\"\\\\\\\"`: `{{.O}}{{.DQ}}{{.BS}}{{.BS}}{{.BS}}{{.DQ}}{{.O}}`,\n\n\t\t`$`:       `{{.O}}${{.O}}`,\n\t\t`$HOME`:   `{{.O}}$HOME{{.O}}`,\n\t\t`'$HOME'`: `{{.O}}{{.SQ}}$HOME{{.SQ}}{{.O}}`,\n\n\t\t`&`:                       `{{.O}}{{.E}}&{{.O}}`,\n\t\t`|`:                       `{{.O}}{{.E}}|{{.O}}`,\n\t\t`<`:                       `{{.O}}{{.E}}<{{.O}}`,\n\t\t`>`:                       `{{.O}}{{.E}}>{{.O}}`,\n\t\t`(`:                       `{{.O}}{{.E}}({{.O}}`,\n\t\t`)`:                       `{{.O}}{{.E}}){{.O}}`,\n\t\t`@`:                       `{{.O}}{{.E}}@{{.O}}`,\n\t\t`^`:                       `{{.O}}{{.E}}^{{.O}}`,\n\t\t`%`:                       `{{.O}}{{.E}}%{{.O}}`,\n\t\t`!`:                       `{{.O}}{{.E}}!{{.O}}`,\n\t\t`%USERPROFILE%`:           `{{.O}}{{.E}}%USERPROFILE{{.E}}%{{.O}}`,\n\t\t`C:\\Program Files (x86)\\`: `{{.O}}C:{{.BS}}Program Files {{.E}}(x86{{.E}}){{.BS}}{{.O}}`,\n\t\t`\"C:\\Program Files\"`:      `{{.O}}{{.DQ}}C:{{.BS}}Program Files{{.DQ}}{{.O}}`,\n\t}\n\n\tfor input, expected := range tests {\n\t\tescaped := exec.QuoteEntry(input)\n\t\texpected = templateToString(expected, effectiveStyle)\n\t\tif escaped != expected {\n\t\t\tt.Errorf(\"Input: %s, expected: %s, actual %s\", input, expected, escaped)\n\t\t}\n\t}\n}\n\n// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Unix\nfunc TestUnixCommands(t *testing.T) {\n\tif util.IsWindows() {\n\t\tt.SkipNow()\n\t}\n\ttests := []testCase{\n\t\t// reference: give{template, query, items}, want{output OR match}\n\n\t\t// 1) working examples\n\n\t\t// paths that does not have to evaluated will work fine, when quoted\n\t\t{give{`grep foo {}`, ``, newItems(`test`)}, want{output: `grep foo 'test'`}},\n\t\t{give{`grep foo {}`, ``, newItems(`/home/user/test`)}, want{output: `grep foo '/home/user/test'`}},\n\t\t{give{`grep foo {}`, ``, newItems(`./test`)}, want{output: `grep foo './test'`}},\n\n\t\t// only placeholders are escaped as data, this will lookup tilde character in a test file in your home directory\n\t\t// quoting the tilde is required (to be treated as string)\n\t\t{give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}},\n\n\t\t// 2) problematic examples\n\t\t// (not necessarily unexpected)\n\n\t\t// paths that need to expand some part of it won't work (special characters and variables)\n\t\t{give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}},\n\t\t{give{`cat {}`, ``, newItems(`$HOME/test`)}, want{output: `cat '$HOME/test'`}},\n\t}\n\ttestCommands(t, tests)\n}\n\n// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows\nfunc TestWindowsCommands(t *testing.T) {\n\t// XXX Deprecated\n\tt.SkipNow()\n\n\ttests := []testCase{\n\t\t// reference: give{template, query, items}, want{output OR match}\n\n\t\t// 1) working examples\n\n\t\t// example of redundantly escaped backslash in the output, besides looking bit ugly, it won't cause any issue\n\t\t{give{`type {}`, ``, newItems(`C:\\test.txt`)}, want{output: `type ^\"C:\\\\test.txt^\"`}},\n\t\t{give{`rg -- \"package\" {}`, ``, newItems(`.\\test.go`)}, want{output: `rg -- \"package\" ^\".\\\\test.go^\"`}},\n\t\t// example of mandatorily escaped backslash in the output, otherwise `rg -- \"C:\\test.txt\"` is matching for tabulator\n\t\t{give{`rg -- {}`, ``, newItems(`C:\\test.txt`)}, want{output: `rg -- ^\"C:\\\\test.txt^\"`}},\n\t\t// example of mandatorily escaped double quote in the output, otherwise `rg -- \"\"C:\\\\test.txt\"\"` is not matching for the double quotes around the path\n\t\t{give{`rg -- {}`, ``, newItems(`\"C:\\test.txt\"`)}, want{output: `rg -- ^\"\\^\"C:\\\\test.txt\\^\"^\"`}},\n\n\t\t// 2) problematic examples\n\t\t// (not necessarily unexpected)\n\n\t\t// notepad++'s parser can't handle `-n\"12\"` generate by fzf, expects `-n12`\n\t\t{give{`notepad++ -n{1} {2}`, ``, newItems(`12\tC:\\Work\\Test Folder\\File.txt`)}, want{output: `notepad++ -n^\"12^\" ^\"C:\\\\Work\\\\Test Folder\\\\File.txt^\"`}},\n\n\t\t// cat is parsing `\\\"` as a part of the file path, double quote is illegal character for paths on Windows\n\t\t// cat: \"C:\\\\test.txt: Invalid argument\n\t\t{give{`cat {}`, ``, newItems(`\"C:\\test.txt\"`)}, want{output: `cat ^\"\\^\"C:\\\\test.txt\\^\"^\"`}},\n\t\t// cat: \"C:\\\\test.txt\": Invalid argument\n\t\t{give{`cmd /c {}`, ``, newItems(`cat \"C:\\test.txt\"`)}, want{output: `cmd /c ^\"cat \\^\"C:\\\\test.txt\\^\"^\"`}},\n\n\t\t// the \"file\" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it\n\t\t// the temp file contains: `cat \"C:\\test.txt\"`\n\t\t// TODO this should actually work\n\t\t{give{`cmd /c {f}`, ``, newItems(`cat \"C:\\test.txt\"`)}, want{match: `^cmd /c .*\\fzf-preview-[0-9]{9}$`}},\n\t}\n\ttestCommands(t, tests)\n}\n\n// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows in Powershell\nfunc TestPowershellCommands(t *testing.T) {\n\tif !util.IsWindows() {\n\t\tt.SkipNow()\n\t}\n\n\ttests := []testCase{\n\t\t// reference: give{template, query, items}, want{output OR match}\n\n\t\t/*\n\t\t\tYou can read each line in the following table as a pipeline that\n\t\t\tconsist of series of parsers that act upon your input (col. 1) and\n\t\t\teach cell represents the output value.\n\n\t\t\tFor example:\n\t\t\t - exec.Command(\"program.exe\", `\\''`)\n\t\t\t   - goes to win32 api which will process it transparently as it contains no special characters, see [CommandLineToArgvW][].\n\t\t\t     - powershell command will receive it as is, that is two arguments: a literal backslash and empty string in single quotes\n\t\t\t     - native command run via/from powershell will receive only one argument: a literal backslash. Because extra parsing rules apply, see [NativeCallsFromPowershell][].\n\t\t\t       - some¹ apps have internal parser, that requires one more level of escaping (yes, this is completely application-specific, but see terminal_test.go#TestWindowsCommands)\n\n\t\t\tCharacter⁰   CommandLineToArgvW   Powershell commands              Native commands from Powershell   Apps requiring escapes¹    | Being tested below\n\t\t\t----------   ------------------   ------------------------------   -------------------------------   -------------------------- | ------------------\n\t\t\t\"            empty string²        missing argument error           ...                               ...                        |\n\t\t\t\\\"           literal \"            unbalanced quote error           ...                               ...                        |\n\t\t\t'\\\"'         literal '\"'          literal \"                        empty string                      empty string (match all)   | yes\n\t\t\t'\\\\\\\"'       literal '\\\"'         literal \\\"                       literal \"                         literal \"                  |\n\t\t\t----------   ------------------   ------------------------------   -------------------------------   -------------------------- | ------------------\n\t\t\t\\            transparent          transparent                      transparent                       regex error                |\n\t\t\t'\\'          transparent          literal \\                        literal \\                         regex error                | yes\n\t\t\t\\\\           transparent          transparent                      transparent                       literal \\                  |\n\t\t\t'\\\\'         transparent          literal \\\\                       literal \\\\                        literal \\                  |\n\t\t\t----------   ------------------   ------------------------------   -------------------------------   -------------------------- | ------------------\n\t\t\t'            transparent          unbalanced quote error           ...                               ...                        |\n\t\t\t\\'           transparent          literal \\ and unb. quote error   ...                               ...                        |\n\t\t\t\\''          transparent          literal \\ and empty string       literal \\                         regex error                | no, but given as example above\n\t\t\t'''          transparent          unbalanced quote error           ...                               ...                        |\n\t\t\t''''         transparent          literal '                        literal '                         literal '                  | yes\n\t\t\t----------   ------------------   ------------------------------   -------------------------------   -------------------------- | ------------------\n\n\t\t\t⁰: charatecter or characters 'x' as an argument to a program in go's call: exec.Command(\"program.exe\", `x`)\n\t\t\t¹: native commands like grep, git grep, ripgrep\n\t\t\t²: interpreted as a grouping quote, affects argument parser and gets removed from the result\n\n\t\t\t[CommandLineToArgvW]: https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks\n\t\t\t[NativeCallsFromPowershell]: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.1#passing-arguments-that-contain-quote-characters\n\t\t*/\n\n\t\t// 1) working examples\n\n\t\t{give{`Get-Content {}`, ``, newItems(`C:\\test.txt`)}, want{output: `Get-Content 'C:\\test.txt'`}},\n\t\t{give{`rg -- \"package\" {}`, ``, newItems(`.\\test.go`)}, want{output: `rg -- \"package\" '.\\test.go'`}},\n\n\t\t// example of escaping single quotes\n\t\t{give{`rg -- {}`, ``, newItems(`'foobar'`)}, want{output: `rg -- '''foobar'''`}},\n\n\t\t// chaining powershells\n\t\t{give{`powershell -NoProfile -Command {}`, ``, newItems(`cat \"C:\\test.txt\"`)}, want{output: `powershell -NoProfile -Command 'cat \\\"C:\\test.txt\\\"'`}},\n\n\t\t// 2) problematic examples\n\t\t// (not necessarily unexpected)\n\n\t\t// looking for a path string will only work with escaped backslashes\n\t\t{give{`rg -- {}`, ``, newItems(`C:\\test.txt`)}, want{output: `rg -- 'C:\\test.txt'`}},\n\t\t// looking for a literal double quote will only work with triple escaped double quotes\n\t\t{give{`rg -- {}`, ``, newItems(`\"C:\\test.txt\"`)}, want{output: `rg -- '\\\"C:\\test.txt\\\"'`}},\n\n\t\t// Get-Content (i.e. cat alias) is parsing `\"` as a part of the file path, returns an error:\n\t\t// Get-Content : Cannot find drive. A drive with the name '\"C:' does not exist.\n\t\t{give{`cat {}`, ``, newItems(`\"C:\\test.txt\"`)}, want{output: `cat '\\\"C:\\test.txt\\\"'`}},\n\n\t\t// the \"file\" flag in the pattern won't create *.ps1 file so the powershell will offload this \"unknown\" filetype\n\t\t// to explorer, which will prompt user to pick editing program for the fzf-preview file\n\t\t// the temp file contains: `cat \"C:\\test.txt\"`\n\t\t// TODO this should actually work\n\t\t{give{`powershell -NoProfile -Command {f}`, ``, newItems(`cat \"C:\\test.txt\"`)}, want{match: `^powershell -NoProfile -Command .*\\fzf-preview-[0-9]{9}$`}},\n\t}\n\n\t// to force powershell-style escaping we temporarily set environment variable that fzf honors\n\tshellBackup := os.Getenv(\"SHELL\")\n\tos.Setenv(\"SHELL\", \"powershell\")\n\ttestCommands(t, tests)\n\tos.Setenv(\"SHELL\", shellBackup)\n}\n\n/*\nTest typical valid placeholders and parsing of them.\n\nAlso since the parser assumes the input is matched with `placeholder` regex,\nthe regex is tested here as well.\n*/\nfunc TestParsePlaceholder(t *testing.T) {\n\t// give, want pairs\n\ttemplates := map[string]string{\n\t\t// I. item type placeholder\n\t\t`{}`:    `{}`,\n\t\t`{+}`:   `{+}`,\n\t\t`{n}`:   `{n}`,\n\t\t`{+n}`:  `{+n}`,\n\t\t`{f}`:   `{f}`,\n\t\t`{+nf}`: `{+nf}`,\n\n\t\t// II. token type placeholders\n\t\t`{..}`:     `{..}`,\n\t\t`{1..}`:    `{1..}`,\n\t\t`{..2}`:    `{..2}`,\n\t\t`{1..2}`:   `{1..2}`,\n\t\t`{-2..-1}`: `{-2..-1}`,\n\t\t// shorthand for x..x range\n\t\t`{1}`:    `{1}`,\n\t\t`{1..1}`: `{1..1}`,\n\t\t`{-6}`:   `{-6}`,\n\t\t// multiple ranges\n\t\t`{1,2}`:         `{1,2}`,\n\t\t`{1,2,4}`:       `{1,2,4}`,\n\t\t`{1,2..4}`:      `{1,2..4}`,\n\t\t`{1..2,-4..-3}`: `{1..2,-4..-3}`,\n\t\t// flags\n\t\t`{+1}`:      `{+1}`,\n\t\t`{+-1}`:     `{+-1}`,\n\t\t`{s1}`:      `{s1}`,\n\t\t`{f1}`:      `{f1}`,\n\t\t`{+s1..2}`:  `{+s1..2}`,\n\t\t`{+sf1..2}`: `{+sf1..2}`,\n\n\t\t// III. query type placeholder\n\t\t// query flag is not removed after parsing, so it gets doubled\n\t\t// while the double q is invalid, it is useful here for testing purposes\n\t\t`{q}`:        `{qq}`,\n\t\t`{q:1}`:      `{qq:1}`,\n\t\t`{q:2..}`:    `{qq:2..}`,\n\t\t`{q:..}`:     `{qq:..}`,\n\t\t`{q:2..-1}`:  `{qq:2..-1}`,\n\t\t`{q:s2..-1}`: `{sqq:2..-1}`, // FIXME\n\n\t\t// IV. escaping placeholder\n\t\t`\\{}`:   `{}`,\n\t\t`\\{++}`: `{++}`,\n\t\t`{++}`:  `{+}`,\n\t}\n\n\tfor giveTemplate, wantTemplate := range templates {\n\t\tif !placeholder.MatchString(giveTemplate) {\n\t\t\tt.Errorf(`given placeholder %s does not match placeholder regex, so attempt to parse it is unexpected`, giveTemplate)\n\t\t\tcontinue\n\t\t}\n\n\t\t_, placeholderWithoutFlags, flags := parsePlaceholder(giveTemplate)\n\t\tgotTemplate := placeholderWithoutFlags[:1] + flags.encodePlaceholder() + placeholderWithoutFlags[1:]\n\n\t\tif gotTemplate != wantTemplate {\n\t\t\tt.Errorf(`parsed placeholder \"%s\" into \"%s\", but want \"%s\"`, giveTemplate, gotTemplate, wantTemplate)\n\t\t}\n\t}\n}\n\nfunc TestExtractPassthroughs(t *testing.T) {\n\tfor _, middle := range []string{\n\t\t\"\\x1bPtmux;\\x1b\\x1bbar\\x1b\\\\\",\n\t\t\"\\x1bPtmux;\\x1b\\x1bbar\\x1bbar\\x1b\\\\\",\n\t\t\"\\x1b]1337;bar\\x1b\\\\\",\n\t\t\"\\x1b]1337;bar\\x1bbar\\x1b\\\\\",\n\t\t\"\\x1b]1337;bar\\a\",\n\t\t\"\\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\\x1b\\\\\",\n\t\t\"\\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\\x1b\\\\\\r\",\n\t\t\"\\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\\x1bbar\\x1b\\\\\\r\",\n\t\t\"\\x1b_Gm=1;AAAAAAAAA=\\x1b\\\\\",\n\t\t\"\\x1b_Gm=1;AAAAAAAAA=\\x1b\\\\\\r\",\n\t\t\"\\x1b_Gm=1;\\x1bAAAAAAAAA=\\x1b\\\\\\r\",\n\t} {\n\t\tline := \"foo\" + middle + \"baz\"\n\t\tloc := findPassThrough(line)\n\t\tif loc == nil || line[0:loc[0]] != \"foo\" || line[loc[1]:] != \"baz\" {\n\t\t\tt.Error(\"failed to find passthrough\")\n\t\t}\n\t\tgarbage := \"\\x1bPtmux;\\x1b]1337;\\x1b_Ga=\\x1b]1337;bar\\x1b.\"\n\t\tline = strings.Repeat(\"foo\"+middle+middle+\"baz\", 3) + garbage\n\t\tpassthroughs, result := extractPassThroughs(line)\n\t\tif result != \"foobazfoobazfoobaz\"+garbage || len(passthroughs) != 6 {\n\t\t\tt.Error(\"failed to extract passthroughs\")\n\t\t}\n\t}\n}\n\n/* utilities section */\n\n// Item represents one line in fzf UI. Usually it is relative path to files and folders.\nfunc newItem(str string) *Item {\n\tbytes := []byte(str)\n\ttrimmed, _, _ := extractColor(str, nil, nil)\n\treturn &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}\n}\n\n// Functions tested in this file require array of items (allItems).\n// This is helper function.\nfunc newItems(str ...string) [3][]*Item {\n\tresult := make([]*Item, len(str))\n\tfor i, s := range str {\n\t\tresult[i] = newItem(s)\n\t}\n\treturn [3][]*Item{result, nil, nil}\n}\n\n// (for logging purposes)\nfunc (item *Item) String() string {\n\treturn item.AsString(true)\n}\n\n// Helper function to parse, execute and convert \"text/template\" to string. Panics on error.\nfunc templateToString(format string, data any) string {\n\tbb := &bytes.Buffer{}\n\n\terr := template.Must(template.New(\"\").Parse(format)).Execute(bb, data)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn bb.String()\n}\n\n// ad hoc types for test cases\ntype give struct {\n\ttemplate string\n\tquery    string\n\tallItems [3][]*Item\n}\ntype want struct {\n\t/*\n\t\tUnix:\n\t\tThe `want.output` string is supposed to be formatted for evaluation by\n\t\t`sh -c command` system call.\n\n\t\tWindows:\n\t\tThe `want.output` string is supposed to be formatted for evaluation by\n\t\t`cmd.exe /s /c \"command\"` system call. The `/s` switch enables so called old\n\t\tbehaviour, which is more favourable for nesting (possibly escaped)\n\t\tspecial characters. This is the relevant section of `help cmd`:\n\n\t\t...old behavior is to see if the first character is\n\t\ta quote character and if so, strip the leading character and\n\t\tremove the last quote character on the command line, preserving\n\t\tany text after the last quote character.\n\t*/\n\toutput string // literal output\n\tmatch  string // output is matched against this regex (when output is empty string)\n}\ntype testCase struct {\n\tgive\n\twant\n}\n\nfunc testCommands(t *testing.T, tests []testCase) {\n\t// common test parameters\n\tdelim := \"\\t\"\n\tdelimiter := Delimiter{str: &delim}\n\tprintsep := \"\"\n\tstripAnsi := false\n\tforcePlus := false\n\n\t// evaluate the test cases\n\tfor idx, test := range tests {\n\t\tgotOutput := replacePlaceholderTest(\n\t\t\ttest.template, stripAnsi, delimiter, printsep, forcePlus,\n\t\t\ttest.query,\n\t\t\ttest.allItems)\n\t\tswitch {\n\t\tcase test.output != \"\":\n\t\t\tif gotOutput != test.output {\n\t\t\t\tt.Errorf(\"tests[%v]:\\ngave{\\n\\ttemplate: '%s',\\n\\tquery: '%s',\\n\\tallItems: %s}\\nand got '%s',\\nbut want '%s'\",\n\t\t\t\t\tidx,\n\t\t\t\t\ttest.template, test.query, test.allItems,\n\t\t\t\t\tgotOutput, test.output)\n\t\t\t}\n\t\tcase test.match != \"\":\n\t\t\twantMatch := strings.ReplaceAll(test.match, `\\`, `\\\\`)\n\t\t\twantRegex := regexp.MustCompile(wantMatch)\n\t\t\tif !wantRegex.MatchString(gotOutput) {\n\t\t\t\tt.Errorf(\"tests[%v]:\\ngave{\\n\\ttemplate: '%s',\\n\\tquery: '%s',\\n\\tallItems: %s}\\nand got '%s',\\nbut want '%s'\",\n\t\t\t\t\tidx,\n\t\t\t\t\ttest.template, test.query, test.allItems,\n\t\t\t\t\tgotOutput, test.match)\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Errorf(\"tests[%v]: test case does not describe 'want' property\", idx)\n\t\t}\n\t}\n}\n\n// naive encoder of placeholder flags\nfunc (flags placeholderFlags) encodePlaceholder() string {\n\tencoded := \"\"\n\tif flags.plus {\n\t\tencoded += \"+\"\n\t}\n\tif flags.preserveSpace {\n\t\tencoded += \"s\"\n\t}\n\tif flags.number {\n\t\tencoded += \"n\"\n\t}\n\tif flags.file {\n\t\tencoded += \"f\"\n\t}\n\tif flags.forceUpdate { // FIXME\n\t\tencoded += \"q\"\n\t}\n\treturn encoded\n}\n\n// can be replaced with os.ReadFile() in go 1.16+\nfunc readFile(path string) ([]byte, error) {\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tdata := make([]byte, 0, 128)\n\tfor {\n\t\tif len(data) >= cap(data) {\n\t\t\td := append(data[:cap(data)], 0)\n\t\t\tdata = d[:len(data)]\n\t\t}\n\n\t\tn, err := file.Read(data[len(data):cap(data)])\n\t\tdata = data[:len(data)+n]\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn data, err\n\t\t}\n\t}\n}\n\nfunc TestWordWrapAnsiLine(t *testing.T) {\n\tterm := &Terminal{}\n\n\t// Simple wrapping\n\tresult := term.wordWrapAnsiLine(\"hello world\", 7, 2)\n\tif len(result) != 2 || result[0] != \"hello\" || result[1] != \"world\" {\n\t\tt.Errorf(\"Simple: %q\", result)\n\t}\n\n\t// No wrapping needed\n\tresult = term.wordWrapAnsiLine(\"hello\", 10, 2)\n\tif len(result) != 1 || result[0] != \"hello\" {\n\t\tt.Errorf(\"No wrap: %q\", result)\n\t}\n\n\t// ANSI codes preserved across split\n\tresult = term.wordWrapAnsiLine(\"\\x1b[31mhello \\x1b[32mworld\", 8, 2)\n\tif len(result) != 2 || result[0] != \"\\x1b[31mhello\" || result[1] != \"\\x1b[32mworld\" {\n\t\tt.Errorf(\"ANSI: %q\", result)\n\t}\n\n\t// Long word (no space) — no break, let character wrapping handle it\n\tresult = term.wordWrapAnsiLine(\"abcdefghij\", 5, 2)\n\tif len(result) != 1 || result[0] != \"abcdefghij\" {\n\t\tt.Errorf(\"Long word: %q\", result)\n\t}\n\n\t// Multiple words with continuation wrapSignWidth\n\tresult = term.wordWrapAnsiLine(\"aa bb cc dd\", 5, 2)\n\t// max=5 for first line, max=3 for continuations (5-2)\n\t// \"aa bb\" (5 wide), split at second space -> \"aa bb\" | \"cc\" | \"dd\"\n\tif len(result) != 3 || result[0] != \"aa bb\" || result[1] != \"cc\" || result[2] != \"dd\" {\n\t\tt.Errorf(\"Multiple words: %q\", result)\n\t}\n\n\t// Empty string\n\tresult = term.wordWrapAnsiLine(\"\", 10, 2)\n\tif len(result) != 1 || result[0] != \"\" {\n\t\tt.Errorf(\"Empty: %q\", result)\n\t}\n\n\t// OSC 8 hyperlink preserved\n\tresult = term.wordWrapAnsiLine(\"\\x1b]8;;http://example.com\\x1b\\\\click here\\x1b]8;;\\x1b\\\\\", 8, 2)\n\tif len(result) != 2 {\n\t\tt.Errorf(\"Hyperlink split count: %d, %q\", len(result), result)\n\t}\n\n\t// Tab handling: tab expands to tabstop-aligned width\n\tterm.tabstop = 8\n\t// \"\\thi there\" — tab at column 0 expands to 8, total \"hi\" starts at 8\n\t// maxWidth=15: \"\\thi\" = 10 wide, \"there\" = 5 wide, total 16 > 15, wrap at space\n\tresult = term.wordWrapAnsiLine(\"\\thi there\", 15, 2)\n\tif len(result) != 2 || result[0] != \"\\thi\" || result[1] != \"there\" {\n\t\tt.Errorf(\"Tab: %q\", result)\n\t}\n\n\t// Tab as word boundary: \"hello\"(5) + tab(3→col8) + \"world\"(5) = 13 total\n\t// maxWidth=13: fits without wrapping\n\tresult = term.wordWrapAnsiLine(\"hello\\tworld\", 13, 2)\n\tif len(result) != 1 || result[0] != \"hello\\tworld\" {\n\t\tt.Errorf(\"Tab no wrap: %q\", result)\n\t}\n\t// maxWidth=12: 13 > 12, wraps at tab\n\tresult = term.wordWrapAnsiLine(\"hello\\tworld\", 12, 2)\n\tif len(result) != 2 || result[0] != \"hello\" || result[1] != \"world\" {\n\t\tt.Errorf(\"Tab wrap: %q\", result)\n\t}\n}\n"
  },
  {
    "path": "src/terminal_unix.go",
    "content": "//go:build !windows\n\npackage fzf\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc notifyOnResize(resizeChan chan<- os.Signal) {\n\tsignal.Notify(resizeChan, syscall.SIGWINCH)\n}\n\nfunc notifyStop(p *os.Process) {\n\tpid := p.Pid\n\tpgid, err := unix.Getpgid(pid)\n\tif err == nil {\n\t\tpid = pgid * -1\n\t}\n\tunix.Kill(pid, syscall.SIGTSTP)\n}\n"
  },
  {
    "path": "src/terminal_windows.go",
    "content": "//go:build windows\n\npackage fzf\n\nimport (\n\t\"os\"\n)\n\nfunc notifyOnResize(resizeChan chan<- os.Signal) {\n\t// TODO\n}\n\nfunc notifyStop(p *os.Process) {\n\t// NOOP\n}\n"
  },
  {
    "path": "src/tmux.go",
    "content": "package fzf\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\n\t\"github.com/junegunn/fzf/src/tui\"\n)\n\nfunc runTmux(args []string, opts *Options) (int, error) {\n\t// Prepare arguments\n\tfzf, rest := args[0], args[1:]\n\targs = []string{\"--bind=ctrl-z:ignore\"}\n\tif !opts.Tmux.border && (opts.BorderShape == tui.BorderUndefined || opts.BorderShape == tui.BorderLine) {\n\t\t// We append --border option at the end, because `--style=full:STYLE`\n\t\t// may have changed the default border style.\n\t\tif tui.DefaultBorderShape == tui.BorderRounded {\n\t\t\trest = append(rest, \"--border=rounded\")\n\t\t} else {\n\t\t\trest = append(rest, \"--border=sharp\")\n\t\t}\n\t}\n\tif opts.Tmux.border && opts.Margin == defaultMargin() {\n\t\targs = append(args, \"--margin=0,1\")\n\t}\n\targStr := escapeSingleQuote(fzf)\n\tfor _, arg := range append(args, rest...) {\n\t\targStr += \" \" + escapeSingleQuote(arg)\n\t}\n\targStr += ` --no-tmux --no-height`\n\n\t// Get current directory\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\tdir = \".\"\n\t}\n\n\t// Set tmux options for popup placement\n\t// C        Both    The centre of the terminal\n\t// R        -x      The right side of the terminal\n\t// P        Both    The bottom left of the pane\n\t// M        Both    The mouse position\n\t// W        Both    The window position on the status line\n\t// S        -y      The line above or below the status line\n\ttmuxArgs := []string{\"display-popup\", \"-E\", \"-d\", dir}\n\tif !opts.Tmux.border {\n\t\ttmuxArgs = append(tmuxArgs, \"-B\")\n\t}\n\tswitch opts.Tmux.position {\n\tcase posUp:\n\t\ttmuxArgs = append(tmuxArgs, \"-xC\", \"-y0\")\n\tcase posDown:\n\t\ttmuxArgs = append(tmuxArgs, \"-xC\", \"-y9999\")\n\tcase posLeft:\n\t\ttmuxArgs = append(tmuxArgs, \"-x0\", \"-yC\")\n\tcase posRight:\n\t\ttmuxArgs = append(tmuxArgs, \"-xR\", \"-yC\")\n\tcase posCenter:\n\t\ttmuxArgs = append(tmuxArgs, \"-xC\", \"-yC\")\n\t}\n\ttmuxArgs = append(tmuxArgs, \"-w\"+opts.Tmux.width.String())\n\ttmuxArgs = append(tmuxArgs, \"-h\"+opts.Tmux.height.String())\n\n\treturn runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {\n\t\tsh, err := sh(needBash)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttmuxArgs = append(tmuxArgs, sh, temp)\n\t\treturn exec.Command(\"tmux\", tmuxArgs...), nil\n\t}, opts, true)\n}\n"
  },
  {
    "path": "src/tokenizer.go",
    "content": "package fzf\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nconst rangeEllipsis = 0\n\n// Range represents nth-expression\ntype Range struct {\n\tbegin int\n\tend   int\n}\n\nfunc (r Range) IsFull() bool {\n\treturn r.begin == rangeEllipsis && r.end == rangeEllipsis\n}\n\nfunc compareRanges(r1 []Range, r2 []Range) bool {\n\tif len(r1) != len(r2) {\n\t\treturn false\n\t}\n\tfor idx := range r1 {\n\t\tif r1[idx] != r2[idx] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc RangesToString(ranges []Range) string {\n\tstrs := []string{}\n\tfor _, r := range ranges {\n\t\ts := \"\"\n\t\tif r.begin == rangeEllipsis && r.end == rangeEllipsis {\n\t\t\ts = \"..\"\n\t\t} else if r.begin == r.end {\n\t\t\ts = strconv.Itoa(r.begin)\n\t\t} else {\n\t\t\tif r.begin != rangeEllipsis {\n\t\t\t\ts += strconv.Itoa(r.begin)\n\t\t\t}\n\n\t\t\tif r.begin != -1 {\n\t\t\t\ts += \"..\"\n\t\t\t\tif r.end != rangeEllipsis {\n\t\t\t\t\ts += strconv.Itoa(r.end)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tstrs = append(strs, s)\n\t}\n\n\treturn strings.Join(strs, \",\")\n}\n\n// Token contains the tokenized part of the strings and its prefix length\ntype Token struct {\n\ttext         *util.Chars\n\tprefixLength int32\n}\n\n// String returns the string representation of a Token.\nfunc (t Token) String() string {\n\treturn fmt.Sprintf(\"Token{text: %s, prefixLength: %d}\", t.text, t.prefixLength)\n}\n\n// Delimiter for tokenizing the input\ntype Delimiter struct {\n\tregex *regexp.Regexp\n\tstr   *string\n}\n\n// IsAwk returns true if the delimiter is an AWK-style delimiter\nfunc (d Delimiter) IsAwk() bool {\n\treturn d.regex == nil && d.str == nil\n}\n\n// String returns the string representation of a Delimiter.\nfunc (d Delimiter) String() string {\n\treturn fmt.Sprintf(\"Delimiter{regex: %v, str: &%q}\", d.regex, *d.str)\n}\n\nfunc newRange(begin int, end int) Range {\n\tif begin == 1 && end != 1 {\n\t\tbegin = rangeEllipsis\n\t}\n\tif end == -1 {\n\t\tend = rangeEllipsis\n\t}\n\treturn Range{begin, end}\n}\n\n// ParseRange parses nth-expression and returns the corresponding Range object\nfunc ParseRange(str *string) (Range, bool) {\n\tif (*str) == \"..\" {\n\t\treturn newRange(rangeEllipsis, rangeEllipsis), true\n\t} else if strings.HasPrefix(*str, \"..\") {\n\t\tend, err := strconv.Atoi((*str)[2:])\n\t\tif err != nil || end == 0 {\n\t\t\treturn Range{}, false\n\t\t}\n\t\treturn newRange(rangeEllipsis, end), true\n\t} else if strings.HasSuffix(*str, \"..\") {\n\t\tbegin, err := strconv.Atoi((*str)[:len(*str)-2])\n\t\tif err != nil || begin == 0 {\n\t\t\treturn Range{}, false\n\t\t}\n\t\treturn newRange(begin, rangeEllipsis), true\n\t} else if strings.Contains(*str, \"..\") {\n\t\tns := strings.Split(*str, \"..\")\n\t\tif len(ns) != 2 {\n\t\t\treturn Range{}, false\n\t\t}\n\t\tbegin, err1 := strconv.Atoi(ns[0])\n\t\tend, err2 := strconv.Atoi(ns[1])\n\t\tif err1 != nil || err2 != nil || begin == 0 || end == 0 || begin < 0 && end > 0 {\n\t\t\treturn Range{}, false\n\t\t}\n\t\treturn newRange(begin, end), true\n\t}\n\n\tn, err := strconv.Atoi(*str)\n\tif err != nil || n == 0 {\n\t\treturn Range{}, false\n\t}\n\treturn newRange(n, n), true\n}\n\nfunc withPrefixLengths(tokens []string, begin int) []Token {\n\tret := make([]Token, len(tokens))\n\n\tprefixLength := begin\n\tfor idx := range tokens {\n\t\tchars := util.ToChars(stringBytes(tokens[idx]))\n\t\tret[idx] = Token{&chars, int32(prefixLength)}\n\t\tprefixLength += chars.Length()\n\t}\n\treturn ret\n}\n\nconst (\n\tawkNil = iota\n\tawkBlack\n\tawkWhite\n)\n\nfunc awkTokenizer(input string) ([]string, int) {\n\t// 9, 32\n\tret := []string{}\n\tprefixLength := 0\n\tstate := awkNil\n\tbegin := 0\n\tend := 0\n\tfor idx := 0; idx < len(input); idx++ {\n\t\tr := input[idx]\n\t\twhite := r == 9 || r == 32 || r == 10\n\t\tswitch state {\n\t\tcase awkNil:\n\t\t\tif white {\n\t\t\t\tprefixLength++\n\t\t\t} else {\n\t\t\t\tstate, begin, end = awkBlack, idx, idx+1\n\t\t\t}\n\t\tcase awkBlack:\n\t\t\tend = idx + 1\n\t\t\tif white {\n\t\t\t\tstate = awkWhite\n\t\t\t}\n\t\tcase awkWhite:\n\t\t\tif white {\n\t\t\t\tend = idx + 1\n\t\t\t} else {\n\t\t\t\tret = append(ret, input[begin:end])\n\t\t\t\tstate, begin, end = awkBlack, idx, idx+1\n\t\t\t}\n\t\t}\n\t}\n\tif begin < end {\n\t\tret = append(ret, input[begin:end])\n\t}\n\treturn ret, prefixLength\n}\n\n// Tokenize tokenizes the given string with the delimiter\nfunc Tokenize(text string, delimiter Delimiter) []Token {\n\tif delimiter.str == nil && delimiter.regex == nil {\n\t\t// AWK-style (\\S+\\s*)\n\t\ttokens, prefixLength := awkTokenizer(text)\n\t\treturn withPrefixLengths(tokens, prefixLength)\n\t}\n\n\tif delimiter.str != nil {\n\t\treturn withPrefixLengths(strings.SplitAfter(text, *delimiter.str), 0)\n\t}\n\n\t// FIXME performance\n\tvar tokens []string\n\tif delimiter.regex != nil {\n\t\tlocs := delimiter.regex.FindAllStringIndex(text, -1)\n\t\tbegin := 0\n\t\ttokens = make([]string, len(locs))\n\t\tfor i, loc := range locs {\n\t\t\ttokens[i] = text[begin:loc[1]]\n\t\t\tbegin = loc[1]\n\t\t}\n\t\tif begin < len(text) {\n\t\t\ttokens = append(tokens, text[begin:])\n\t\t}\n\t}\n\treturn withPrefixLengths(tokens, 0)\n}\n\n// StripLastDelimiter removes the trailing delimiter\nfunc StripLastDelimiter(str string, delimiter Delimiter) string {\n\tif delimiter.str != nil {\n\t\treturn strings.TrimSuffix(str, *delimiter.str)\n\t}\n\tif delimiter.regex != nil {\n\t\tlocs := delimiter.regex.FindAllStringIndex(str, -1)\n\t\tif len(locs) > 0 {\n\t\t\tlastLoc := locs[len(locs)-1]\n\t\t\tif lastLoc[1] == len(str) {\n\t\t\t\tstr = str[:lastLoc[0]]\n\t\t\t}\n\t\t}\n\t\treturn str\n\t}\n\treturn strings.TrimRightFunc(str, unicode.IsSpace)\n}\n\nfunc GetLastDelimiter(str string, delimiter Delimiter) string {\n\tif delimiter.str != nil {\n\t\tif strings.HasSuffix(str, *delimiter.str) {\n\t\t\treturn *delimiter.str\n\t\t}\n\t} else if delimiter.regex != nil {\n\t\tlocs := delimiter.regex.FindAllStringIndex(str, -1)\n\t\tif len(locs) > 0 {\n\t\t\tlastLoc := locs[len(locs)-1]\n\t\t\tif lastLoc[1] == len(str) {\n\t\t\t\treturn str[lastLoc[0]:]\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// JoinTokens concatenates the tokens into a single string\nfunc JoinTokens(tokens []Token) string {\n\tvar output bytes.Buffer\n\tfor _, token := range tokens {\n\t\toutput.WriteString(token.text.ToString())\n\t}\n\treturn output.String()\n}\n\n// Transform is used to transform the input when --with-nth option is given\nfunc Transform(tokens []Token, withNth []Range) []Token {\n\ttransTokens := make([]Token, len(withNth))\n\tnumTokens := len(tokens)\n\tfor idx, r := range withNth {\n\t\tparts := []*util.Chars{}\n\t\tminIdx := 0\n\t\tif r.begin == r.end {\n\t\t\tidx := r.begin\n\t\t\tif idx == rangeEllipsis {\n\t\t\t\tchars := util.ToChars(stringBytes(JoinTokens(tokens)))\n\t\t\t\tparts = append(parts, &chars)\n\t\t\t} else {\n\t\t\t\tif idx < 0 {\n\t\t\t\t\tidx += numTokens + 1\n\t\t\t\t}\n\t\t\t\tif idx >= 1 && idx <= numTokens {\n\t\t\t\t\tminIdx = idx - 1\n\t\t\t\t\tparts = append(parts, tokens[idx-1].text)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tvar begin, end int\n\t\t\tif r.begin == rangeEllipsis { // ..N\n\t\t\t\tbegin, end = 1, r.end\n\t\t\t\tif end < 0 {\n\t\t\t\t\tend += numTokens + 1\n\t\t\t\t}\n\t\t\t} else if r.end == rangeEllipsis { // N..\n\t\t\t\tbegin, end = r.begin, numTokens\n\t\t\t\tif begin < 0 {\n\t\t\t\t\tbegin += numTokens + 1\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbegin, end = r.begin, r.end\n\t\t\t\tif begin < 0 {\n\t\t\t\t\tbegin += numTokens + 1\n\t\t\t\t}\n\t\t\t\tif end < 0 {\n\t\t\t\t\tend += numTokens + 1\n\t\t\t\t}\n\t\t\t}\n\t\t\tminIdx = max(0, begin-1)\n\t\t\tfor idx := begin; idx <= end; idx++ {\n\t\t\t\tif idx >= 1 && idx <= numTokens {\n\t\t\t\t\tparts = append(parts, tokens[idx-1].text)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Merge multiple parts\n\t\tvar merged util.Chars\n\t\tswitch len(parts) {\n\t\tcase 0:\n\t\t\tmerged = util.ToChars([]byte{})\n\t\tcase 1:\n\t\t\tmerged = *parts[0]\n\t\tdefault:\n\t\t\tvar output bytes.Buffer\n\t\t\tfor _, part := range parts {\n\t\t\t\toutput.WriteString(part.ToString())\n\t\t\t}\n\t\t\tmerged = util.ToChars(output.Bytes())\n\t\t}\n\n\t\tvar prefixLength int32\n\t\tif minIdx < numTokens {\n\t\t\tprefixLength = tokens[minIdx].prefixLength\n\t\t} else {\n\t\t\tprefixLength = 0\n\t\t}\n\t\ttransTokens[idx] = Token{&merged, prefixLength}\n\t}\n\treturn transTokens\n}\n"
  },
  {
    "path": "src/tokenizer_test.go",
    "content": "package fzf\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParseRange(t *testing.T) {\n\t{\n\t\ti := \"..\"\n\t\tr, _ := ParseRange(&i)\n\t\tif r.begin != rangeEllipsis || r.end != rangeEllipsis {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n\t{\n\t\ti := \"3..\"\n\t\tr, _ := ParseRange(&i)\n\t\tif r.begin != 3 || r.end != rangeEllipsis {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n\t{\n\t\ti := \"3..5\"\n\t\tr, _ := ParseRange(&i)\n\t\tif r.begin != 3 || r.end != 5 {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n\t{\n\t\ti := \"-3..-5\"\n\t\tr, _ := ParseRange(&i)\n\t\tif r.begin != -3 || r.end != -5 {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n\t{\n\t\ti := \"3\"\n\t\tr, _ := ParseRange(&i)\n\t\tif r.begin != 3 || r.end != 3 {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n\t{\n\t\ti := \"1..3..5\"\n\t\tif r, ok := ParseRange(&i); ok {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n\t{\n\t\ti := \"-3..3\"\n\t\tif r, ok := ParseRange(&i); ok {\n\t\t\tt.Errorf(\"%v\", r)\n\t\t}\n\t}\n}\n\nfunc TestTokenize(t *testing.T) {\n\t// AWK-style\n\tinput := \"  abc: \\n\\t def:  ghi  \"\n\ttokens := Tokenize(input, Delimiter{})\n\tif tokens[0].text.ToString() != \"abc: \\n\\t \" || tokens[0].prefixLength != 2 {\n\t\tt.Errorf(\"%s\", tokens)\n\t}\n\n\t// With delimiter\n\ttokens = Tokenize(input, delimiterRegexp(\":\"))\n\tif tokens[0].text.ToString() != \"  abc:\" || tokens[0].prefixLength != 0 {\n\t\tt.Error(tokens[0].text.ToString(), tokens[0].prefixLength)\n\t}\n\n\t// With delimiter regex\n\ttokens = Tokenize(input, delimiterRegexp(\"\\\\s+\"))\n\tif tokens[0].text.ToString() != \"  \" || tokens[0].prefixLength != 0 ||\n\t\ttokens[1].text.ToString() != \"abc: \\n\\t \" || tokens[1].prefixLength != 2 ||\n\t\ttokens[2].text.ToString() != \"def:  \" || tokens[2].prefixLength != 10 ||\n\t\ttokens[3].text.ToString() != \"ghi  \" || tokens[3].prefixLength != 16 {\n\t\tt.Errorf(\"%s\", tokens)\n\t}\n}\n\nfunc TestTransform(t *testing.T) {\n\tinput := \"  abc:  def:  ghi:  jkl\"\n\t{\n\t\ttokens := Tokenize(input, Delimiter{})\n\t\t{\n\t\t\tranges, _ := splitNth(\"1,2,3\")\n\t\t\ttx := Transform(tokens, ranges)\n\t\t\tif JoinTokens(tx) != \"abc:  def:  ghi:  \" {\n\t\t\t\tt.Errorf(\"%s\", tx)\n\t\t\t}\n\t\t}\n\t\t{\n\t\t\tranges, _ := splitNth(\"1..2,3,2..,1\")\n\t\t\ttx := Transform(tokens, ranges)\n\t\t\tif string(JoinTokens(tx)) != \"abc:  def:  ghi:  def:  ghi:  jklabc:  \" ||\n\t\t\t\tlen(tx) != 4 ||\n\t\t\t\ttx[0].text.ToString() != \"abc:  def:  \" || tx[0].prefixLength != 2 ||\n\t\t\t\ttx[1].text.ToString() != \"ghi:  \" || tx[1].prefixLength != 14 ||\n\t\t\t\ttx[2].text.ToString() != \"def:  ghi:  jkl\" || tx[2].prefixLength != 8 ||\n\t\t\t\ttx[3].text.ToString() != \"abc:  \" || tx[3].prefixLength != 2 {\n\t\t\t\tt.Errorf(\"%s\", tx)\n\t\t\t}\n\t\t}\n\t}\n\t{\n\t\ttokens := Tokenize(input, delimiterRegexp(\":\"))\n\t\t{\n\t\t\tranges, _ := splitNth(\"1..2,3,2..,1\")\n\t\t\ttx := Transform(tokens, ranges)\n\t\t\tif JoinTokens(tx) != \"  abc:  def:  ghi:  def:  ghi:  jkl  abc:\" ||\n\t\t\t\tlen(tx) != 4 ||\n\t\t\t\ttx[0].text.ToString() != \"  abc:  def:\" || tx[0].prefixLength != 0 ||\n\t\t\t\ttx[1].text.ToString() != \"  ghi:\" || tx[1].prefixLength != 12 ||\n\t\t\t\ttx[2].text.ToString() != \"  def:  ghi:  jkl\" || tx[2].prefixLength != 6 ||\n\t\t\t\ttx[3].text.ToString() != \"  abc:\" || tx[3].prefixLength != 0 {\n\t\t\t\tt.Errorf(\"%s\", tx)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTransformIndexOutOfBounds(t *testing.T) {\n\ts, _ := splitNth(\"1\")\n\tTransform([]Token{}, s)\n}\n"
  },
  {
    "path": "src/tui/dummy.go",
    "content": "//go:build !tcell && !windows\n\npackage tui\n\nconst (\n\tBold          = Attr(1)\n\tDim           = Attr(1 << 1)\n\tItalic        = Attr(1 << 2)\n\tUnderline     = Attr(1 << 3)\n\tBlink         = Attr(1 << 4)\n\tBlink2        = Attr(1 << 5)\n\tReverse       = Attr(1 << 6)\n\tStrikeThrough = Attr(1 << 7)\n)\n\nfunc HasFullscreenRenderer() bool {\n\treturn false\n}\n\nvar DefaultBorderShape = BorderRounded\n\nfunc (r *FullscreenRenderer) Init() error                        { return nil }\nfunc (r *FullscreenRenderer) DefaultTheme() *ColorTheme          { return nil }\nfunc (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}\nfunc (r *FullscreenRenderer) Pause(bool)                         {}\nfunc (r *FullscreenRenderer) Resume(bool, bool)                  {}\nfunc (r *FullscreenRenderer) PassThrough(string)                 {}\nfunc (r *FullscreenRenderer) Clear()                             {}\nfunc (r *FullscreenRenderer) NeedScrollbarRedraw() bool          { return false }\nfunc (r *FullscreenRenderer) ShouldEmitResizeEvent() bool        { return false }\nfunc (r *FullscreenRenderer) Bell()                              {}\nfunc (r *FullscreenRenderer) HideCursor()                        {}\nfunc (r *FullscreenRenderer) ShowCursor()                        {}\nfunc (r *FullscreenRenderer) Refresh()                           {}\nfunc (r *FullscreenRenderer) Close()                             {}\nfunc (r *FullscreenRenderer) Size() TermSize                     { return TermSize{} }\nfunc (r *FullscreenRenderer) Top() int                           { return 0 }\nfunc (r *FullscreenRenderer) MaxX() int                          { return 0 }\nfunc (r *FullscreenRenderer) MaxY() int                          { return 0 }\nfunc (r *FullscreenRenderer) GetChar(bool) Event                 { return Event{} }\nfunc (r *FullscreenRenderer) CancelGetChar()                     {}\n\nfunc (r *FullscreenRenderer) RefreshWindows(windows []Window) {}\n\nfunc (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {\n\treturn nil\n}\n"
  },
  {
    "path": "src/tui/eventtype_string.go",
    "content": "// Code generated by \"stringer -type=EventType\"; DO NOT EDIT.\n\npackage tui\n\nimport \"strconv\"\n\nfunc _() {\n\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n\t// Re-run the stringer command to generate them again.\n\tvar x [1]struct{}\n\t_ = x[Rune-0]\n\t_ = x[CtrlA-1]\n\t_ = x[CtrlB-2]\n\t_ = x[CtrlC-3]\n\t_ = x[CtrlD-4]\n\t_ = x[CtrlE-5]\n\t_ = x[CtrlF-6]\n\t_ = x[CtrlG-7]\n\t_ = x[CtrlH-8]\n\t_ = x[Tab-9]\n\t_ = x[CtrlJ-10]\n\t_ = x[CtrlK-11]\n\t_ = x[CtrlL-12]\n\t_ = x[Enter-13]\n\t_ = x[CtrlN-14]\n\t_ = x[CtrlO-15]\n\t_ = x[CtrlP-16]\n\t_ = x[CtrlQ-17]\n\t_ = x[CtrlR-18]\n\t_ = x[CtrlS-19]\n\t_ = x[CtrlT-20]\n\t_ = x[CtrlU-21]\n\t_ = x[CtrlV-22]\n\t_ = x[CtrlW-23]\n\t_ = x[CtrlX-24]\n\t_ = x[CtrlY-25]\n\t_ = x[CtrlZ-26]\n\t_ = x[Esc-27]\n\t_ = x[CtrlSpace-28]\n\t_ = x[CtrlBackSlash-29]\n\t_ = x[CtrlRightBracket-30]\n\t_ = x[CtrlCaret-31]\n\t_ = x[CtrlSlash-32]\n\t_ = x[ShiftTab-33]\n\t_ = x[Backspace-34]\n\t_ = x[Delete-35]\n\t_ = x[PageUp-36]\n\t_ = x[PageDown-37]\n\t_ = x[Up-38]\n\t_ = x[Down-39]\n\t_ = x[Left-40]\n\t_ = x[Right-41]\n\t_ = x[Home-42]\n\t_ = x[End-43]\n\t_ = x[Insert-44]\n\t_ = x[ShiftUp-45]\n\t_ = x[ShiftDown-46]\n\t_ = x[ShiftLeft-47]\n\t_ = x[ShiftRight-48]\n\t_ = x[ShiftDelete-49]\n\t_ = x[ShiftHome-50]\n\t_ = x[ShiftEnd-51]\n\t_ = x[ShiftPageUp-52]\n\t_ = x[ShiftPageDown-53]\n\t_ = x[F1-54]\n\t_ = x[F2-55]\n\t_ = x[F3-56]\n\t_ = x[F4-57]\n\t_ = x[F5-58]\n\t_ = x[F6-59]\n\t_ = x[F7-60]\n\t_ = x[F8-61]\n\t_ = x[F9-62]\n\t_ = x[F10-63]\n\t_ = x[F11-64]\n\t_ = x[F12-65]\n\t_ = x[AltBackspace-66]\n\t_ = x[AltUp-67]\n\t_ = x[AltDown-68]\n\t_ = x[AltLeft-69]\n\t_ = x[AltRight-70]\n\t_ = x[AltDelete-71]\n\t_ = x[AltHome-72]\n\t_ = x[AltEnd-73]\n\t_ = x[AltPageUp-74]\n\t_ = x[AltPageDown-75]\n\t_ = x[AltShiftUp-76]\n\t_ = x[AltShiftDown-77]\n\t_ = x[AltShiftLeft-78]\n\t_ = x[AltShiftRight-79]\n\t_ = x[AltShiftDelete-80]\n\t_ = x[AltShiftHome-81]\n\t_ = x[AltShiftEnd-82]\n\t_ = x[AltShiftPageUp-83]\n\t_ = x[AltShiftPageDown-84]\n\t_ = x[CtrlUp-85]\n\t_ = x[CtrlDown-86]\n\t_ = x[CtrlLeft-87]\n\t_ = x[CtrlRight-88]\n\t_ = x[CtrlHome-89]\n\t_ = x[CtrlEnd-90]\n\t_ = x[CtrlBackspace-91]\n\t_ = x[CtrlDelete-92]\n\t_ = x[CtrlPageUp-93]\n\t_ = x[CtrlPageDown-94]\n\t_ = x[Alt-95]\n\t_ = x[CtrlAlt-96]\n\t_ = x[CtrlAltUp-97]\n\t_ = x[CtrlAltDown-98]\n\t_ = x[CtrlAltLeft-99]\n\t_ = x[CtrlAltRight-100]\n\t_ = x[CtrlAltHome-101]\n\t_ = x[CtrlAltEnd-102]\n\t_ = x[CtrlAltBackspace-103]\n\t_ = x[CtrlAltDelete-104]\n\t_ = x[CtrlAltPageUp-105]\n\t_ = x[CtrlAltPageDown-106]\n\t_ = x[CtrlShiftUp-107]\n\t_ = x[CtrlShiftDown-108]\n\t_ = x[CtrlShiftLeft-109]\n\t_ = x[CtrlShiftRight-110]\n\t_ = x[CtrlShiftHome-111]\n\t_ = x[CtrlShiftEnd-112]\n\t_ = x[CtrlShiftDelete-113]\n\t_ = x[CtrlShiftPageUp-114]\n\t_ = x[CtrlShiftPageDown-115]\n\t_ = x[CtrlAltShiftUp-116]\n\t_ = x[CtrlAltShiftDown-117]\n\t_ = x[CtrlAltShiftLeft-118]\n\t_ = x[CtrlAltShiftRight-119]\n\t_ = x[CtrlAltShiftHome-120]\n\t_ = x[CtrlAltShiftEnd-121]\n\t_ = x[CtrlAltShiftDelete-122]\n\t_ = x[CtrlAltShiftPageUp-123]\n\t_ = x[CtrlAltShiftPageDown-124]\n\t_ = x[Invalid-125]\n\t_ = x[Fatal-126]\n\t_ = x[BracketedPasteBegin-127]\n\t_ = x[BracketedPasteEnd-128]\n\t_ = x[Mouse-129]\n\t_ = x[DoubleClick-130]\n\t_ = x[LeftClick-131]\n\t_ = x[RightClick-132]\n\t_ = x[SLeftClick-133]\n\t_ = x[SRightClick-134]\n\t_ = x[ScrollUp-135]\n\t_ = x[ScrollDown-136]\n\t_ = x[SScrollUp-137]\n\t_ = x[SScrollDown-138]\n\t_ = x[PreviewScrollUp-139]\n\t_ = x[PreviewScrollDown-140]\n\t_ = x[Resize-141]\n\t_ = x[Change-142]\n\t_ = x[BackwardEOF-143]\n\t_ = x[Start-144]\n\t_ = x[Load-145]\n\t_ = x[Focus-146]\n\t_ = x[One-147]\n\t_ = x[Zero-148]\n\t_ = x[Result-149]\n\t_ = x[Jump-150]\n\t_ = x[JumpCancel-151]\n\t_ = x[ClickHeader-152]\n\t_ = x[ClickFooter-153]\n\t_ = x[Multi-154]\n}\n\nconst _EventType_name = \"RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti\"\n\nvar _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 157, 173, 182, 191, 199, 208, 214, 220, 228, 230, 234, 238, 243, 247, 250, 256, 263, 272, 281, 291, 302, 311, 319, 330, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 364, 367, 370, 382, 387, 394, 401, 409, 418, 425, 431, 440, 451, 461, 473, 485, 498, 512, 524, 535, 549, 565, 571, 579, 587, 596, 604, 611, 624, 634, 644, 656, 659, 666, 675, 686, 697, 709, 720, 730, 746, 759, 772, 787, 798, 811, 824, 838, 851, 863, 878, 893, 910, 924, 940, 956, 973, 989, 1004, 1022, 1040, 1060, 1067, 1072, 1091, 1108, 1113, 1124, 1133, 1143, 1153, 1164, 1172, 1182, 1191, 1202, 1217, 1234, 1240, 1246, 1257, 1262, 1266, 1271, 1274, 1278, 1284, 1288, 1298, 1309, 1320, 1325}\n\nfunc (i EventType) String() string {\n\tif i < 0 || i >= EventType(len(_EventType_index)-1) {\n\t\treturn \"EventType(\" + strconv.FormatInt(int64(i), 10) + \")\"\n\t}\n\treturn _EventType_name[_EventType_index[i]:_EventType_index[i+1]]\n}\n"
  },
  {
    "path": "src/tui/light.go",
    "content": "package tui\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n\n\t\"golang.org/x/term\"\n)\n\nconst (\n\tdefaultWidth  = 80\n\tdefaultHeight = 24\n\n\tdefaultEscDelay = 100\n\tescPollInterval = 5\n\toffsetPollTries = 10\n\tmaxInputBuffer  = 1024 * 1024\n\tmaxSelectTries  = 100\n)\n\nconst DefaultTtyDevice string = \"/dev/tty\"\n\nvar offsetRegexp = regexp.MustCompile(\"(.*?)\\x00?\\x1b\\\\[([0-9]+);([0-9]+)R\")\nvar offsetRegexpBegin = regexp.MustCompile(\"^\\x1b\\\\[[0-9]+;[0-9]+R\")\n\nfunc (r *LightRenderer) Bell() {\n\tr.flushRaw(\"\\a\")\n}\n\nfunc (r *LightRenderer) PassThrough(str string) {\n\tr.queued.WriteString(\"\\x1b7\" + str + \"\\x1b8\")\n}\n\nfunc (r *LightRenderer) stderr(str string) {\n\tr.stderrInternal(str, true, \"\")\n}\n\nconst DIM string = \"\\x1b[2m\"\nconst CR string = DIM + \"␍\"\nconst LF string = DIM + \"␊\"\n\ntype getCharResult int\n\nconst (\n\tgetCharSuccess getCharResult = iota\n\tgetCharError\n\tgetCharCancelled\n)\n\nfunc (r getCharResult) ok() bool {\n\treturn r == getCharSuccess\n}\n\nfunc (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {\n\tbytes := []byte(str)\n\trunes := []rune{}\n\tfor len(bytes) > 0 {\n\t\tr, sz := utf8.DecodeRune(bytes)\n\t\tnlcr := r == '\\n' || r == '\\r'\n\t\tif r >= 32 || r == '\\x1b' || nlcr {\n\t\t\tif nlcr && !allowNLCR {\n\t\t\t\tif r == '\\r' {\n\t\t\t\t\trunes = append(runes, []rune(CR+resetCode)...)\n\t\t\t\t} else {\n\t\t\t\t\trunes = append(runes, []rune(LF+resetCode)...)\n\t\t\t\t}\n\t\t\t} else if r != utf8.RuneError {\n\t\t\t\trunes = append(runes, r)\n\t\t\t}\n\t\t}\n\t\tbytes = bytes[sz:]\n\t}\n\tr.queued.WriteString(string(runes))\n}\n\nfunc (r *LightRenderer) csi(code string) string {\n\tfullcode := \"\\x1b[\" + code\n\tr.stderr(fullcode)\n\treturn fullcode\n}\n\nfunc (r *LightRenderer) flush() {\n\tif r.queued.Len() > 0 {\n\t\traw := \"\\x1b[?7l\\x1b[?25l\" + r.queued.String()\n\t\tif r.showCursor {\n\t\t\traw += \"\\x1b[?25h\\x1b[?7h\"\n\t\t} else {\n\t\t\traw += \"\\x1b[?7h\"\n\t\t}\n\t\tr.flushRaw(raw)\n\t\tr.queued.Reset()\n\t}\n}\n\nfunc (r *LightRenderer) flushRaw(sequence string) {\n\tfmt.Fprint(r.ttyout, sequence)\n}\n\n// Light renderer\ntype LightRenderer struct {\n\ttheme         *ColorTheme\n\tmouse         bool\n\tforceBlack    bool\n\tclearOnExit   bool\n\tprevDownTime  time.Time\n\tclicks        [][2]int\n\tttyin         *os.File\n\tttyout        *os.File\n\tcancel        func()\n\tbuffer        []byte\n\torigState     *term.State\n\twidth         int\n\theight        int\n\tyoffset       int\n\ttabstop       int\n\tescDelay      int\n\tfullscreen    bool\n\tupOneLine     bool\n\tqueued        strings.Builder\n\ty             int\n\tx             int\n\tmaxHeightFunc func(int) int\n\tshowCursor    bool\n\tmutex         sync.Mutex\n\n\t// Windows only\n\tttyinChannel    chan byte\n\tinHandle        uintptr\n\toutHandle       uintptr\n\torigStateInput  uint32\n\torigStateOutput uint32\n}\n\ntype LightWindow struct {\n\trenderer      *LightRenderer\n\tcolored       bool\n\twindowType    WindowType\n\tborder        BorderStyle\n\ttop           int\n\tleft          int\n\twidth         int\n\theight        int\n\tposx          int\n\tposy          int\n\ttabstop       int\n\tfg            Color\n\tbg            Color\n\twrapSign      string\n\twrapSignWidth int\n}\n\nfunc NewLightRenderer(ttyDefault string, ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {\n\tout, err := openTtyOut(ttyDefault)\n\tif err != nil {\n\t\tout = os.Stderr\n\t}\n\tr := LightRenderer{\n\t\ttheme:         theme,\n\t\tforceBlack:    forceBlack,\n\t\tmouse:         mouse,\n\t\tclearOnExit:   clearOnExit,\n\t\tttyin:         ttyin,\n\t\tttyout:        out,\n\t\tyoffset:       0,\n\t\ttabstop:       tabstop,\n\t\tfullscreen:    fullscreen,\n\t\tupOneLine:     false,\n\t\tmaxHeightFunc: maxHeightFunc,\n\t\tshowCursor:    true}\n\treturn &r, nil\n}\n\nfunc repeat(r rune, times int) string {\n\tif times > 0 {\n\t\treturn strings.Repeat(string(r), times)\n\t}\n\treturn \"\"\n}\n\nfunc atoi(s string, defaultValue int) int {\n\tvalue, err := strconv.Atoi(s)\n\tif err != nil {\n\t\treturn defaultValue\n\t}\n\treturn value\n}\n\nfunc (r *LightRenderer) Init() error {\n\tr.escDelay = atoi(os.Getenv(\"ESCDELAY\"), defaultEscDelay)\n\n\tif err := r.initPlatform(); err != nil {\n\t\treturn err\n\t}\n\tr.updateTerminalSize()\n\n\tif r.fullscreen {\n\t\tr.smcup()\n\t} else {\n\t\ty, x := r.findOffset()\n\t\tr.mouse = r.mouse && y >= 0\n\t\t// When --no-clear is used for repetitive relaunching, there is a small\n\t\t// time frame between fzf processes where the user keystrokes are not\n\t\t// captured by either of fzf process which can cause x offset to be\n\t\t// increased and we're left with unwanted extra new line.\n\t\tif x > 0 && r.clearOnExit {\n\t\t\tr.upOneLine = true\n\t\t\tr.makeSpace()\n\t\t}\n\t\t// We assume that --no-clear is used for repetitive relaunching of fzf.\n\t\t// So we do not clear the lower bottom of the screen.\n\t\tif r.clearOnExit {\n\t\t\tr.csi(\"J\")\n\t\t}\n\t\tfor i := 1; i < r.MaxY(); i++ {\n\t\t\tr.makeSpace()\n\t\t}\n\t}\n\n\tr.enableModes()\n\tr.csi(fmt.Sprintf(\"%dA\", r.MaxY()-1))\n\tr.csi(\"G\")\n\tr.csi(\"K\")\n\tif !r.clearOnExit && !r.fullscreen {\n\t\tr.csi(\"s\")\n\t}\n\tif !r.fullscreen && r.mouse {\n\t\tr.yoffset, _ = r.findOffset()\n\t}\n\treturn nil\n}\n\nfunc (r *LightRenderer) Resize(maxHeightFunc func(int) int) {\n\tr.maxHeightFunc = maxHeightFunc\n}\n\nfunc (r *LightRenderer) makeSpace() {\n\tr.stderr(\"\\n\")\n\tr.csi(\"G\")\n}\n\nfunc (r *LightRenderer) move(y int, x int) {\n\t// w.csi(\"u\")\n\tif r.y < y {\n\t\tr.csi(fmt.Sprintf(\"%dB\", y-r.y))\n\t} else if r.y > y {\n\t\tr.csi(fmt.Sprintf(\"%dA\", r.y-y))\n\t}\n\tr.stderr(\"\\r\")\n\tif x > 0 {\n\t\tr.csi(fmt.Sprintf(\"%dC\", x))\n\t}\n\tr.y = y\n\tr.x = x\n}\n\nfunc (r *LightRenderer) origin() {\n\tr.move(0, 0)\n}\n\nfunc getEnv(name string, defaultValue int) int {\n\tenv := os.Getenv(name)\n\tif len(env) == 0 {\n\t\treturn defaultValue\n\t}\n\treturn atoi(env, defaultValue)\n}\n\nfunc (r *LightRenderer) getBytes(cancellable bool) ([]byte, getCharResult, error) {\n\treturn r.getBytesInternal(cancellable, r.buffer, false)\n}\n\nfunc (r *LightRenderer) getBytesInternal(cancellable bool, buffer []byte, nonblock bool) ([]byte, getCharResult, error) {\n\tc, result := r.getch(cancellable, nonblock)\n\tif result == getCharCancelled {\n\t\treturn buffer, getCharCancelled, nil\n\t}\n\tif !nonblock && !result.ok() {\n\t\tr.Close()\n\t\treturn nil, getCharError, errors.New(\"failed to read \" + DefaultTtyDevice)\n\t}\n\n\tretries := 0\n\tif c == Esc.Int() || nonblock {\n\t\tretries = r.escDelay / escPollInterval\n\t}\n\tbuffer = append(buffer, byte(c))\n\n\tpc := c\n\tfor {\n\t\tc, result = r.getch(false, true)\n\t\tif !result.ok() {\n\t\t\tif retries > 0 {\n\t\t\t\tretries--\n\t\t\t\ttime.Sleep(escPollInterval * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t} else if c == Esc.Int() && pc != c {\n\t\t\tretries = r.escDelay / escPollInterval\n\t\t} else {\n\t\t\tretries = 0\n\t\t}\n\t\tbuffer = append(buffer, byte(c))\n\t\tpc = c\n\n\t\t// This should never happen under normal conditions,\n\t\t// so terminate fzf immediately.\n\t\tif len(buffer) > maxInputBuffer {\n\t\t\tr.Close()\n\t\t\treturn nil, getCharError, fmt.Errorf(\"input buffer overflow (%d): %v\", len(buffer), buffer)\n\t\t}\n\t}\n\n\treturn buffer, getCharSuccess, nil\n}\n\nfunc (r *LightRenderer) GetChar(cancellable bool) Event {\n\tvar err error\n\tvar result getCharResult\n\tif len(r.buffer) == 0 {\n\t\tr.buffer, result, err = r.getBytes(cancellable)\n\t\tif err != nil {\n\t\t\treturn Event{Fatal, 0, nil}\n\t\t}\n\t\tif result == getCharCancelled {\n\t\t\treturn Event{Invalid, 0, nil}\n\t\t}\n\t}\n\tif len(r.buffer) == 0 {\n\t\treturn Event{Fatal, 0, nil}\n\t}\n\n\tsz := 1\n\tdefer func() {\n\t\tr.buffer = r.buffer[sz:]\n\t}()\n\n\tswitch r.buffer[0] {\n\tcase CtrlC.Byte():\n\t\treturn Event{CtrlC, 0, nil}\n\tcase CtrlG.Byte():\n\t\treturn Event{CtrlG, 0, nil}\n\tcase CtrlQ.Byte():\n\t\treturn Event{CtrlQ, 0, nil}\n\tcase 127:\n\t\treturn Event{Backspace, 0, nil}\n\tcase 8:\n\t\treturn Event{CtrlBackspace, 0, nil}\n\tcase 0:\n\t\treturn Event{CtrlSpace, 0, nil}\n\tcase 28:\n\t\treturn Event{CtrlBackSlash, 0, nil}\n\tcase 29:\n\t\treturn Event{CtrlRightBracket, 0, nil}\n\tcase 30:\n\t\treturn Event{CtrlCaret, 0, nil}\n\tcase 31:\n\t\treturn Event{CtrlSlash, 0, nil}\n\tcase Esc.Byte():\n\t\tev := r.escSequence(&sz)\n\t\t// Second chance\n\t\tif ev.Type == Invalid {\n\t\t\tr.buffer, result, err = r.getBytes(true)\n\t\t\tif err != nil {\n\t\t\t\treturn Event{Fatal, 0, nil}\n\t\t\t}\n\t\t\tif result == getCharCancelled {\n\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t}\n\n\t\t\tev = r.escSequence(&sz)\n\t\t}\n\t\treturn ev\n\t}\n\n\t// CTRL-A ~ CTRL-Z\n\tif r.buffer[0] <= CtrlZ.Byte() {\n\t\treturn Event{EventType(r.buffer[0]), 0, nil}\n\t}\n\tchar, rsz := utf8.DecodeRune(r.buffer)\n\tif char == utf8.RuneError {\n\t\treturn Event{Esc, 0, nil}\n\t}\n\tsz = rsz\n\treturn Event{Rune, char, nil}\n}\n\nfunc (r *LightRenderer) CancelGetChar() {\n\tr.mutex.Lock()\n\tif r.cancel != nil {\n\t\tr.cancel()\n\t\tr.cancel = nil\n\t}\n\tr.mutex.Unlock()\n}\n\nfunc (r *LightRenderer) setCancel(f func()) {\n\tr.mutex.Lock()\n\tr.cancel = f\n\tr.mutex.Unlock()\n}\n\nfunc (r *LightRenderer) escSequence(sz *int) Event {\n\tif len(r.buffer) < 2 {\n\t\treturn Event{Esc, 0, nil}\n\t}\n\n\tloc := offsetRegexpBegin.FindIndex(r.buffer)\n\tif loc != nil && loc[0] == 0 {\n\t\t*sz = loc[1]\n\t\treturn Event{Invalid, 0, nil}\n\t}\n\n\t*sz = 2\n\tif r.buffer[1] == 8 {\n\t\treturn Event{CtrlAltBackspace, 0, nil}\n\t}\n\tif r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {\n\t\treturn CtrlAltKey(rune(r.buffer[1] + 'a' - 1))\n\t}\n\talt := false\n\tif len(r.buffer) > 2 && r.buffer[1] == Esc.Byte() {\n\t\tr.buffer = r.buffer[1:]\n\t\talt = true\n\t}\n\tswitch r.buffer[1] {\n\tcase Esc.Byte():\n\t\treturn Event{Esc, 0, nil}\n\tcase 127:\n\t\treturn Event{AltBackspace, 0, nil}\n\tcase '[', 'O':\n\t\tif len(r.buffer) < 3 {\n\t\t\treturn Event{Invalid, 0, nil}\n\t\t}\n\t\t*sz = 3\n\t\tswitch r.buffer[2] {\n\t\tcase 'D':\n\t\t\tif alt {\n\t\t\t\treturn Event{AltLeft, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Left, 0, nil}\n\t\tcase 'C':\n\t\t\tif alt {\n\t\t\t\t// Ugh..\n\t\t\t\treturn Event{AltRight, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Right, 0, nil}\n\t\tcase 'B':\n\t\t\tif alt {\n\t\t\t\treturn Event{AltDown, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Down, 0, nil}\n\t\tcase 'A':\n\t\t\tif alt {\n\t\t\t\treturn Event{AltUp, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Up, 0, nil}\n\t\tcase 'Z':\n\t\t\treturn Event{ShiftTab, 0, nil}\n\t\tcase 'H':\n\t\t\treturn Event{Home, 0, nil}\n\t\tcase 'F':\n\t\t\treturn Event{End, 0, nil}\n\t\tcase '<':\n\t\t\treturn r.mouseSequence(sz)\n\t\tcase 'P':\n\t\t\treturn Event{F1, 0, nil}\n\t\tcase 'Q':\n\t\t\treturn Event{F2, 0, nil}\n\t\tcase 'R':\n\t\t\treturn Event{F3, 0, nil}\n\t\tcase 'S':\n\t\t\treturn Event{F4, 0, nil}\n\t\tcase '1', '2', '3', '4', '5', '6', '7', '8':\n\t\t\tif len(r.buffer) < 4 {\n\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t}\n\t\t\t*sz = 4\n\t\t\tswitch r.buffer[2] {\n\t\t\tcase '2':\n\t\t\t\tif r.buffer[3] == '~' {\n\t\t\t\t\treturn Event{Insert, 0, nil}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) > 4 && r.buffer[4] == '~' {\n\t\t\t\t\t*sz = 5\n\t\t\t\t\tswitch r.buffer[3] {\n\t\t\t\t\tcase '0':\n\t\t\t\t\t\treturn Event{F9, 0, nil}\n\t\t\t\t\tcase '1':\n\t\t\t\t\t\treturn Event{F10, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{F11, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{F12, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Bracketed paste mode: \\e[200~ ... \\e[201~\n\t\t\t\tif len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {\n\t\t\t\t\t*sz = 6\n\t\t\t\t\tif r.buffer[4] == '0' {\n\t\t\t\t\t\treturn Event{BracketedPasteBegin, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t\treturn Event{BracketedPasteEnd, 0, nil}\n\t\t\t\t}\n\t\t\t\treturn Event{Invalid, 0, nil} // INS\n\t\t\tcase '3':\n\t\t\t\tif r.buffer[3] == '~' {\n\t\t\t\t\treturn Event{Delete, 0, nil}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {\n\t\t\t\t\t*sz = 7\n\t\t\t\t\tswitch r.buffer[5] {\n\t\t\t\t\tcase '0':\n\t\t\t\t\t\treturn Event{AltShiftDelete, 0, nil}\n\t\t\t\t\tcase '1':\n\t\t\t\t\t\treturn Event{AltDelete, 0, nil}\n\t\t\t\t\tcase '2':\n\t\t\t\t\t\treturn Event{AltShiftDelete, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{CtrlAltDelete, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{CtrlAltShiftDelete, 0, nil}\n\t\t\t\t\tcase '5':\n\t\t\t\t\t\treturn Event{CtrlAltDelete, 0, nil}\n\t\t\t\t\tcase '6':\n\t\t\t\t\t\treturn Event{CtrlAltShiftDelete, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) == 6 && r.buffer[5] == '~' {\n\t\t\t\t\t*sz = 6\n\t\t\t\t\tswitch r.buffer[4] {\n\t\t\t\t\tcase '2':\n\t\t\t\t\t\treturn Event{ShiftDelete, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{AltDelete, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{AltShiftDelete, 0, nil}\n\t\t\t\t\tcase '5':\n\t\t\t\t\t\treturn Event{CtrlDelete, 0, nil}\n\t\t\t\t\tcase '6':\n\t\t\t\t\t\treturn Event{CtrlShiftDelete, 0, nil}\n\t\t\t\t\tcase '7':\n\t\t\t\t\t\treturn Event{CtrlAltDelete, 0, nil}\n\t\t\t\t\tcase '8':\n\t\t\t\t\t\treturn Event{CtrlAltShiftDelete, 0, nil}\n\t\t\t\t\tcase '9':\n\t\t\t\t\t\treturn Event{AltDelete, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\tcase '4':\n\t\t\t\treturn Event{End, 0, nil}\n\t\t\tcase '5':\n\t\t\t\tif r.buffer[3] == '~' {\n\t\t\t\t\treturn Event{PageUp, 0, nil}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {\n\t\t\t\t\t*sz = 7\n\t\t\t\t\tswitch r.buffer[5] {\n\t\t\t\t\tcase '0':\n\t\t\t\t\t\treturn Event{AltShiftPageUp, 0, nil}\n\t\t\t\t\tcase '1':\n\t\t\t\t\t\treturn Event{AltPageUp, 0, nil}\n\t\t\t\t\tcase '2':\n\t\t\t\t\t\treturn Event{AltShiftPageUp, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{CtrlAltPageUp, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{CtrlAltShiftPageUp, 0, nil}\n\t\t\t\t\tcase '5':\n\t\t\t\t\t\treturn Event{CtrlAltPageUp, 0, nil}\n\t\t\t\t\tcase '6':\n\t\t\t\t\t\treturn Event{CtrlAltShiftPageUp, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) == 6 && r.buffer[5] == '~' {\n\t\t\t\t\t*sz = 6\n\t\t\t\t\tswitch r.buffer[4] {\n\t\t\t\t\tcase '2':\n\t\t\t\t\t\treturn Event{ShiftPageUp, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{AltPageUp, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{AltShiftPageUp, 0, nil}\n\t\t\t\t\tcase '5':\n\t\t\t\t\t\treturn Event{CtrlPageUp, 0, nil}\n\t\t\t\t\tcase '6':\n\t\t\t\t\t\treturn Event{CtrlShiftPageUp, 0, nil}\n\t\t\t\t\tcase '7':\n\t\t\t\t\t\treturn Event{CtrlAltPageUp, 0, nil}\n\t\t\t\t\tcase '8':\n\t\t\t\t\t\treturn Event{CtrlAltShiftPageUp, 0, nil}\n\t\t\t\t\tcase '9':\n\t\t\t\t\t\treturn Event{AltPageUp, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\tcase '6':\n\t\t\t\tif r.buffer[3] == '~' {\n\t\t\t\t\treturn Event{PageDown, 0, nil}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {\n\t\t\t\t\t*sz = 7\n\t\t\t\t\tswitch r.buffer[5] {\n\t\t\t\t\tcase '0':\n\t\t\t\t\t\treturn Event{AltShiftPageDown, 0, nil}\n\t\t\t\t\tcase '1':\n\t\t\t\t\t\treturn Event{AltPageDown, 0, nil}\n\t\t\t\t\tcase '2':\n\t\t\t\t\t\treturn Event{AltShiftPageDown, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{CtrlAltPageDown, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{CtrlAltShiftPageDown, 0, nil}\n\t\t\t\t\tcase '5':\n\t\t\t\t\t\treturn Event{CtrlAltPageDown, 0, nil}\n\t\t\t\t\tcase '6':\n\t\t\t\t\t\treturn Event{CtrlAltShiftPageDown, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(r.buffer) == 6 && r.buffer[5] == '~' {\n\t\t\t\t\t*sz = 6\n\t\t\t\t\tswitch r.buffer[4] {\n\t\t\t\t\tcase '2':\n\t\t\t\t\t\treturn Event{ShiftPageDown, 0, nil}\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\treturn Event{AltPageDown, 0, nil}\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\treturn Event{AltShiftPageDown, 0, nil}\n\t\t\t\t\tcase '5':\n\t\t\t\t\t\treturn Event{CtrlPageDown, 0, nil}\n\t\t\t\t\tcase '6':\n\t\t\t\t\t\treturn Event{CtrlShiftPageDown, 0, nil}\n\t\t\t\t\tcase '7':\n\t\t\t\t\t\treturn Event{CtrlAltPageDown, 0, nil}\n\t\t\t\t\tcase '8':\n\t\t\t\t\t\treturn Event{CtrlAltShiftPageDown, 0, nil}\n\t\t\t\t\tcase '9':\n\t\t\t\t\t\treturn Event{AltPageDown, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\tcase '7':\n\t\t\t\treturn Event{Home, 0, nil}\n\t\t\tcase '8':\n\t\t\t\treturn Event{End, 0, nil}\n\t\t\tcase '1':\n\t\t\t\tswitch r.buffer[3] {\n\t\t\t\tcase '~':\n\t\t\t\t\treturn Event{Home, 0, nil}\n\t\t\t\tcase '1', '2', '3', '4', '5', '7', '8', '9':\n\t\t\t\t\tif len(r.buffer) == 5 && r.buffer[4] == '~' {\n\t\t\t\t\t\t*sz = 5\n\t\t\t\t\t\tswitch r.buffer[3] {\n\t\t\t\t\t\tcase '1':\n\t\t\t\t\t\t\treturn Event{F1, 0, nil}\n\t\t\t\t\t\tcase '2':\n\t\t\t\t\t\t\treturn Event{F2, 0, nil}\n\t\t\t\t\t\tcase '3':\n\t\t\t\t\t\t\treturn Event{F3, 0, nil}\n\t\t\t\t\t\tcase '4':\n\t\t\t\t\t\t\treturn Event{F4, 0, nil}\n\t\t\t\t\t\tcase '5':\n\t\t\t\t\t\t\treturn Event{F5, 0, nil}\n\t\t\t\t\t\tcase '7':\n\t\t\t\t\t\t\treturn Event{F6, 0, nil}\n\t\t\t\t\t\tcase '8':\n\t\t\t\t\t\t\treturn Event{F7, 0, nil}\n\t\t\t\t\t\tcase '9':\n\t\t\t\t\t\t\treturn Event{F8, 0, nil}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t\tcase ';':\n\t\t\t\t\tif len(r.buffer) < 6 {\n\t\t\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t\t\t}\n\t\t\t\t\t*sz = 6\n\t\t\t\t\tswitch r.buffer[4] {\n\t\t\t\t\tcase '1', '2', '3', '4', '5', '6', '7', '8', '9':\n\t\t\t\t\t\t//                   Kitty      iTerm2     WezTerm\n\t\t\t\t\t\t// SHIFT-ARROW       \"\\e[1;2D\"\n\t\t\t\t\t\t// ALT-SHIFT-ARROW   \"\\e[1;4D\"  \"\\e[1;10D\" \"\\e[1;4D\"\n\t\t\t\t\t\t// CTRL-SHIFT-ARROW  \"\\e[1;6D\"             N/A\n\t\t\t\t\t\t// CMD-SHIFT-ARROW   \"\\e[1;10D\" N/A        N/A (\"\\e[1;2D\")\n\t\t\t\t\t\tctrl := bytes.IndexByte([]byte{'5', '6', '7', '8'}, r.buffer[4]) >= 0\n\t\t\t\t\t\talt := bytes.IndexByte([]byte{'3', '4', '7', '8'}, r.buffer[4]) >= 0\n\t\t\t\t\t\tshift := bytes.IndexByte([]byte{'2', '4', '6', '8'}, r.buffer[4]) >= 0\n\t\t\t\t\t\tchar := r.buffer[5]\n\t\t\t\t\t\tif r.buffer[4] == '9' {\n\t\t\t\t\t\t\tctrl = false\n\t\t\t\t\t\t\talt = true\n\t\t\t\t\t\t\tshift = false\n\t\t\t\t\t\t\tif len(r.buffer) < 6 {\n\t\t\t\t\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t*sz = 6\n\t\t\t\t\t\t\tchar = r.buffer[5]\n\t\t\t\t\t\t} else if r.buffer[4] == '1' && bytes.IndexByte([]byte{'0', '1', '2', '3', '4', '5', '6'}, r.buffer[5]) >= 0 {\n\t\t\t\t\t\t\tctrl = bytes.IndexByte([]byte{'3', '4', '5', '6'}, r.buffer[5]) >= 0\n\t\t\t\t\t\t\talt = true\n\t\t\t\t\t\t\tshift = bytes.IndexByte([]byte{'0', '2', '4', '6'}, r.buffer[5]) >= 0\n\t\t\t\t\t\t\tif len(r.buffer) < 7 {\n\t\t\t\t\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t*sz = 7\n\t\t\t\t\t\t\tchar = r.buffer[6]\n\t\t\t\t\t\t}\n\t\t\t\t\t\tctrlShift := ctrl && shift\n\t\t\t\t\t\tctrlAlt := ctrl && alt\n\t\t\t\t\t\taltShift := alt && shift\n\t\t\t\t\t\tctrlAltShift := ctrl && alt && shift\n\t\t\t\t\t\tswitch char {\n\t\t\t\t\t\tcase 'A':\n\t\t\t\t\t\t\tif ctrlAltShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltShiftUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlAlt {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlShiftUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif altShift {\n\t\t\t\t\t\t\t\treturn Event{AltShiftUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t\t\treturn Event{CtrlUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\treturn Event{AltUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif shift {\n\t\t\t\t\t\t\t\treturn Event{ShiftUp, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'B':\n\t\t\t\t\t\t\tif ctrlAltShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltShiftDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlAlt {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlShiftDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif altShift {\n\t\t\t\t\t\t\t\treturn Event{AltShiftDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t\t\treturn Event{CtrlDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\treturn Event{AltDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif shift {\n\t\t\t\t\t\t\t\treturn Event{ShiftDown, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'C':\n\t\t\t\t\t\t\tif ctrlAltShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltShiftRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlAlt {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlShiftRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif altShift {\n\t\t\t\t\t\t\t\treturn Event{AltShiftRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t\t\treturn Event{CtrlRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif shift {\n\t\t\t\t\t\t\t\treturn Event{ShiftRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\treturn Event{AltRight, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'D':\n\t\t\t\t\t\t\tif ctrlAltShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltShiftLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlAlt {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlShiftLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif altShift {\n\t\t\t\t\t\t\t\treturn Event{AltShiftLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t\t\treturn Event{CtrlLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\treturn Event{AltLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif shift {\n\t\t\t\t\t\t\t\treturn Event{ShiftLeft, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'H':\n\t\t\t\t\t\t\tif ctrlAltShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltShiftHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlAlt {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlShiftHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif altShift {\n\t\t\t\t\t\t\t\treturn Event{AltShiftHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t\t\treturn Event{CtrlHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\treturn Event{AltHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif shift {\n\t\t\t\t\t\t\t\treturn Event{ShiftHome, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 'F':\n\t\t\t\t\t\t\tif ctrlAltShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltShiftEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlAlt {\n\t\t\t\t\t\t\t\treturn Event{CtrlAltEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrlShift {\n\t\t\t\t\t\t\t\treturn Event{CtrlShiftEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif altShift {\n\t\t\t\t\t\t\t\treturn Event{AltShiftEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ctrl {\n\t\t\t\t\t\t\t\treturn Event{CtrlEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif alt {\n\t\t\t\t\t\t\t\treturn Event{AltEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif shift {\n\t\t\t\t\t\t\t\treturn Event{ShiftEnd, 0, nil}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} // r.buffer[4]\n\t\t\t\t} // r.buffer[3]\n\t\t\t} // r.buffer[2]\n\t\t} // r.buffer[2]\n\t} // r.buffer[1]\n\trest := bytes.NewBuffer(r.buffer[1:])\n\tc, size, err := rest.ReadRune()\n\tif err == nil {\n\t\t*sz = 1 + size\n\t\treturn AltKey(c)\n\t}\n\treturn Event{Invalid, 0, nil}\n}\n\n// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking\nfunc (r *LightRenderer) mouseSequence(sz *int) Event {\n\t// \"\\e[<0;0;0M\"\n\tif len(r.buffer) < 9 || !r.mouse {\n\t\treturn Event{Invalid, 0, nil}\n\t}\n\n\trest := r.buffer[*sz:]\n\tend := bytes.IndexAny(rest, \"mM\")\n\tif end == -1 {\n\t\treturn Event{Invalid, 0, nil}\n\t}\n\n\telems := strings.SplitN(string(rest[:end]), \";\", 3)\n\tif len(elems) != 3 {\n\t\treturn Event{Invalid, 0, nil}\n\t}\n\n\tt := atoi(elems[0], -1)\n\tx := atoi(elems[1], -1) - 1\n\ty := atoi(elems[2], -1) - 1 - r.yoffset\n\tif t < 0 || x < 0 {\n\t\treturn Event{Invalid, 0, nil}\n\t}\n\t*sz += end + 1\n\n\tdown := rest[end] == 'M'\n\n\tscroll := 0\n\tif t >= 64 {\n\t\tt -= 64\n\t\tif t&0b1 == 1 {\n\t\t\tscroll = -1\n\t\t} else {\n\t\t\tscroll = 1\n\t\t}\n\t}\n\n\t// middle := t & 0b1\n\tleft := t&0b11 == 0\n\tctrl := t&0b10000 > 0\n\talt := t&0b01000 > 0\n\tshift := t&0b00100 > 0\n\tdrag := t&0b100000 > 0 // 32\n\n\tif scroll != 0 {\n\t\treturn Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, ctrl, alt, shift}}\n\t}\n\n\tdouble := false\n\tif down && !drag {\n\t\tnow := time.Now()\n\t\tif !left { // Right double click is not allowed\n\t\t\tr.clicks = [][2]int{}\n\t\t} else if now.Sub(r.prevDownTime) < doubleClickDuration {\n\t\t\tr.clicks = append(r.clicks, [2]int{x, y})\n\t\t} else {\n\t\t\tr.clicks = [][2]int{{x, y}}\n\t\t}\n\t\tr.prevDownTime = now\n\t} else {\n\t\tn := len(r.clicks)\n\t\tif len(r.clicks) > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1] &&\n\t\t\ttime.Since(r.prevDownTime) < doubleClickDuration {\n\t\t\tdouble = true\n\t\t\tif double {\n\t\t\t\tr.clicks = [][2]int{}\n\t\t\t}\n\t\t}\n\t}\n\treturn Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, ctrl, alt, shift}}\n}\n\nfunc (r *LightRenderer) smcup() {\n\tr.flush()\n\tr.flushRaw(\"\\x1b[?1049h\")\n}\n\nfunc (r *LightRenderer) rmcup() {\n\tr.flush()\n\tr.flushRaw(\"\\x1b[?1049l\")\n}\n\nfunc (r *LightRenderer) Pause(clear bool) {\n\tr.disableModes()\n\tr.restoreTerminal()\n\tif clear {\n\t\tif r.fullscreen {\n\t\t\tr.rmcup()\n\t\t} else {\n\t\t\tr.smcup()\n\t\t\tr.csi(\"H\")\n\t\t}\n\t\tr.flush()\n\t}\n}\n\nfunc (r *LightRenderer) enableModes() {\n\tif r.mouse {\n\t\tr.csi(\"?1000h\")\n\t\tr.csi(\"?1002h\")\n\t\tr.csi(\"?1006h\")\n\t}\n\tr.csi(\"?2004h\") // Enable bracketed paste mode\n}\n\nfunc (r *LightRenderer) disableMouse() {\n\tif r.mouse {\n\t\tr.csi(\"?1000l\")\n\t\tr.csi(\"?1002l\")\n\t\tr.csi(\"?1006l\")\n\t}\n}\n\nfunc (r *LightRenderer) disableModes() {\n\tr.disableMouse()\n\tr.csi(\"?2004l\")\n}\n\nfunc (r *LightRenderer) Resume(clear bool, sigcont bool) {\n\tr.setupTerminal()\n\tif clear {\n\t\tif r.fullscreen {\n\t\t\tr.smcup()\n\t\t} else {\n\t\t\tr.rmcup()\n\t\t}\n\t\tr.enableModes()\n\t\tr.flush()\n\t} else if sigcont && !r.fullscreen && r.mouse {\n\t\t// NOTE: SIGCONT (Coming back from CTRL-Z):\n\t\t// It's highly likely that the offset we obtained at the beginning is\n\t\t// no longer correct, so we simply disable mouse input.\n\t\tr.disableMouse()\n\t\tr.mouse = false\n\t}\n}\n\nfunc (r *LightRenderer) Clear() {\n\tif r.fullscreen {\n\t\tr.csi(\"H\")\n\t}\n\t// r.csi(\"u\")\n\tr.origin()\n\tr.csi(\"J\")\n\tr.flush()\n}\n\nfunc (r *LightRenderer) NeedScrollbarRedraw() bool {\n\treturn false\n}\n\nfunc (r *LightRenderer) ShouldEmitResizeEvent() bool {\n\treturn false\n}\n\nfunc (r *LightRenderer) RefreshWindows(windows []Window) {\n\tr.flush()\n}\n\nfunc (r *LightRenderer) Refresh() {\n\tr.updateTerminalSize()\n}\n\nfunc (r *LightRenderer) Close() {\n\t// r.csi(\"u\")\n\tif r.clearOnExit {\n\t\tif r.fullscreen {\n\t\t\tr.rmcup()\n\t\t} else {\n\t\t\tr.origin()\n\t\t\tif r.upOneLine {\n\t\t\t\tr.csi(\"A\")\n\t\t\t}\n\t\t\tr.csi(\"J\")\n\t\t}\n\t} else if !r.fullscreen {\n\t\tr.csi(\"u\")\n\t}\n\tif !r.showCursor {\n\t\tr.csi(\"?25h\")\n\t}\n\tr.disableModes()\n\tr.flush()\n\tr.restoreTerminal()\n\tr.closePlatform()\n}\n\nfunc (r *LightRenderer) Top() int {\n\treturn r.yoffset\n}\n\nfunc (r *LightRenderer) MaxX() int {\n\treturn r.width\n}\n\nfunc (r *LightRenderer) MaxY() int {\n\tif r.height == 0 {\n\t\tr.updateTerminalSize()\n\t}\n\treturn r.height\n}\n\nfunc (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {\n\twidth = max(0, width)\n\theight = max(0, height)\n\tw := &LightWindow{\n\t\trenderer:   r,\n\t\tcolored:    r.theme.Colored,\n\t\twindowType: windowType,\n\t\tborder:     borderStyle,\n\t\ttop:        top,\n\t\tleft:       left,\n\t\twidth:      width,\n\t\theight:     height,\n\t\ttabstop:    r.tabstop,\n\t\tfg:         colDefault,\n\t\tbg:         colDefault}\n\tswitch windowType {\n\tcase WindowBase:\n\t\tw.fg = r.theme.Fg.Color\n\t\tw.bg = r.theme.Bg.Color\n\tcase WindowList:\n\t\tw.fg = r.theme.ListFg.Color\n\t\tw.bg = r.theme.ListBg.Color\n\tcase WindowInput:\n\t\tw.fg = r.theme.Input.Color\n\t\tw.bg = r.theme.InputBg.Color\n\tcase WindowHeader:\n\t\tw.fg = r.theme.Header.Color\n\t\tw.bg = r.theme.HeaderBg.Color\n\tcase WindowFooter:\n\t\tw.fg = r.theme.Footer.Color\n\t\tw.bg = r.theme.FooterBg.Color\n\tcase WindowPreview:\n\t\tw.fg = r.theme.PreviewFg.Color\n\t\tw.bg = r.theme.PreviewBg.Color\n\t}\n\tif erase && !w.bg.IsDefault() && w.border.shape != BorderNone && w.height > 0 {\n\t\t// fzf --color bg:blue --border --padding 1,2\n\t\tw.Erase()\n\t}\n\tw.drawBorder(false)\n\treturn w\n}\n\nfunc (w *LightWindow) DrawBorder() {\n\tw.drawBorder(false)\n}\n\nfunc (w *LightWindow) DrawHBorder() {\n\tw.drawBorder(true)\n}\n\nfunc (w *LightWindow) drawBorder(onlyHorizontal bool) {\n\tif w.height == 0 {\n\t\treturn\n\t}\n\tswitch w.border.shape {\n\tcase BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:\n\t\tw.drawBorderAround(onlyHorizontal)\n\tcase BorderHorizontal:\n\t\tw.drawBorderHorizontal(true, true)\n\tcase BorderVertical:\n\t\tif onlyHorizontal {\n\t\t\treturn\n\t\t}\n\t\tw.drawBorderVertical(true, true)\n\tcase BorderTop:\n\t\tw.drawBorderHorizontal(true, false)\n\tcase BorderBottom:\n\t\tw.drawBorderHorizontal(false, true)\n\tcase BorderLeft:\n\t\tif onlyHorizontal {\n\t\t\treturn\n\t\t}\n\t\tw.drawBorderVertical(true, false)\n\tcase BorderRight:\n\t\tif onlyHorizontal {\n\t\t\treturn\n\t\t}\n\t\tw.drawBorderVertical(false, true)\n\t}\n}\n\nfunc (w *LightWindow) drawBorderHorizontal(top, bottom bool) {\n\tcolor := ColBorder\n\tswitch w.windowType {\n\tcase WindowList:\n\t\tcolor = ColListBorder\n\tcase WindowInput:\n\t\tcolor = ColInputBorder\n\tcase WindowHeader:\n\t\tcolor = ColHeaderBorder\n\tcase WindowFooter:\n\t\tcolor = ColFooterBorder\n\tcase WindowPreview:\n\t\tcolor = ColPreviewBorder\n\t}\n\thw := runeWidth(w.border.top)\n\tif top {\n\t\tw.Move(0, 0)\n\t\tw.CPrint(color, repeat(w.border.top, w.width/hw))\n\t}\n\n\tif bottom {\n\t\tw.Move(w.height-1, 0)\n\t\tw.CPrint(color, repeat(w.border.bottom, w.width/hw))\n\t}\n}\n\nfunc (w *LightWindow) drawBorderVertical(left, right bool) {\n\tvw := runeWidth(w.border.left)\n\tcolor := ColBorder\n\tswitch w.windowType {\n\tcase WindowList:\n\t\tcolor = ColListBorder\n\tcase WindowInput:\n\t\tcolor = ColInputBorder\n\tcase WindowHeader:\n\t\tcolor = ColHeaderBorder\n\tcase WindowFooter:\n\t\tcolor = ColFooterBorder\n\tcase WindowPreview:\n\t\tcolor = ColPreviewBorder\n\t}\n\tfor y := 0; y < w.height; y++ {\n\t\tif left {\n\t\t\tw.Move(y, 0)\n\t\t\tw.CPrint(color, string(w.border.left))\n\t\t\tw.CPrint(color, \" \") // Margin\n\t\t}\n\t\tif right {\n\t\t\tw.Move(y, w.width-vw-1)\n\t\t\tw.CPrint(color, \" \") // Margin\n\t\t\tw.CPrint(color, string(w.border.right))\n\t\t}\n\t}\n}\n\nfunc (w *LightWindow) drawBorderAround(onlyHorizontal bool) {\n\tw.Move(0, 0)\n\tcolor := ColBorder\n\tswitch w.windowType {\n\tcase WindowList:\n\t\tcolor = ColListBorder\n\tcase WindowInput:\n\t\tcolor = ColInputBorder\n\tcase WindowHeader:\n\t\tcolor = ColHeaderBorder\n\tcase WindowFooter:\n\t\tcolor = ColFooterBorder\n\tcase WindowPreview:\n\t\tcolor = ColPreviewBorder\n\t}\n\thw := runeWidth(w.border.top)\n\ttcw := runeWidth(w.border.topLeft) + runeWidth(w.border.topRight)\n\tbcw := runeWidth(w.border.bottomLeft) + runeWidth(w.border.bottomRight)\n\trem := (w.width - tcw) % hw\n\tw.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))\n\tif !onlyHorizontal {\n\t\tvw := runeWidth(w.border.left)\n\t\tfor y := 1; y < w.height-1; y++ {\n\t\t\tw.Move(y, 0)\n\t\t\tw.CPrint(color, string(w.border.left))\n\t\t\tw.CPrint(color, \" \") // Margin\n\n\t\t\tw.Move(y, w.width-vw-1)\n\t\t\tw.CPrint(color, \" \") // Margin\n\t\t\tw.CPrint(color, string(w.border.right))\n\t\t}\n\t}\n\tw.Move(w.height-1, 0)\n\trem = (w.width - bcw) % hw\n\tw.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.bottom, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))\n}\n\nfunc (w *LightWindow) csi(code string) string {\n\treturn w.renderer.csi(code)\n}\n\nfunc (w *LightWindow) stderrInternal(str string, allowNLCR bool, resetCode string) {\n\tw.renderer.stderrInternal(str, allowNLCR, resetCode)\n}\n\nfunc (w *LightWindow) Top() int {\n\treturn w.top\n}\n\nfunc (w *LightWindow) Left() int {\n\treturn w.left\n}\n\nfunc (w *LightWindow) Width() int {\n\treturn w.width\n}\n\nfunc (w *LightWindow) Height() int {\n\treturn w.height\n}\n\nfunc (w *LightWindow) Refresh() {\n}\n\nfunc (w *LightWindow) X() int {\n\treturn w.posx\n}\n\nfunc (w *LightWindow) Y() int {\n\treturn w.posy\n}\n\nfunc (w *LightWindow) EncloseX(x int) bool {\n\treturn x >= w.left && x < (w.left+w.width)\n}\n\nfunc (w *LightWindow) EncloseY(y int) bool {\n\treturn y >= w.top && y < (w.top+w.height)\n}\n\nfunc (w *LightWindow) Enclose(y int, x int) bool {\n\treturn w.EncloseX(x) && w.EncloseY(y)\n}\n\nfunc (w *LightWindow) Move(y int, x int) {\n\tw.posx = x\n\tw.posy = y\n\n\tw.renderer.move(w.Top()+y, w.Left()+x)\n}\n\nfunc (w *LightWindow) MoveAndClear(y int, x int) {\n\tw.Move(y, x)\n\t// We should not delete preview window on the right\n\t// csi(\"K\")\n\tw.Print(repeat(' ', w.width-x))\n\tw.Move(y, x)\n}\n\nfunc attrCodes(attr Attr) []string {\n\tcodes := []string{}\n\tif (attr & AttrClear) > 0 {\n\t\treturn codes\n\t}\n\tif (attr&Bold) > 0 || (attr&BoldForce) > 0 {\n\t\tcodes = append(codes, \"1\")\n\t}\n\tif (attr & Dim) > 0 {\n\t\tcodes = append(codes, \"2\")\n\t}\n\tif (attr & Italic) > 0 {\n\t\tcodes = append(codes, \"3\")\n\t}\n\tif (attr & Underline) > 0 {\n\t\tswitch attr.UnderlineStyle() {\n\t\tcase UlStyleDouble:\n\t\t\tcodes = append(codes, \"4:2\")\n\t\tcase UlStyleCurly:\n\t\t\tcodes = append(codes, \"4:3\")\n\t\tcase UlStyleDotted:\n\t\t\tcodes = append(codes, \"4:4\")\n\t\tcase UlStyleDashed:\n\t\t\tcodes = append(codes, \"4:5\")\n\t\tdefault:\n\t\t\tcodes = append(codes, \"4\")\n\t\t}\n\t}\n\tif (attr & Blink) > 0 {\n\t\tcodes = append(codes, \"5\")\n\t}\n\tif (attr & Reverse) > 0 {\n\t\tcodes = append(codes, \"7\")\n\t}\n\tif (attr & StrikeThrough) > 0 {\n\t\tcodes = append(codes, \"9\")\n\t}\n\treturn codes\n}\n\nfunc colorCodes(fg Color, bg Color) []string {\n\tcodes := []string{}\n\tappendCode := func(c Color, offset int) {\n\t\tif c == colDefault {\n\t\t\treturn\n\t\t}\n\t\tif c.is24() {\n\t\t\tr := (c >> 16) & 0xff\n\t\t\tg := (c >> 8) & 0xff\n\t\t\tb := (c) & 0xff\n\t\t\tcodes = append(codes, fmt.Sprintf(\"%d;2;%d;%d;%d\", 38+offset, r, g, b))\n\t\t} else if c >= colBlack && c <= colWhite {\n\t\t\tcodes = append(codes, fmt.Sprintf(\"%d\", int(c)+30+offset))\n\t\t} else if c > colWhite && c < 16 {\n\t\t\tcodes = append(codes, fmt.Sprintf(\"%d\", int(c)+90+offset-8))\n\t\t} else if c >= 16 && c < 256 {\n\t\t\tcodes = append(codes, fmt.Sprintf(\"%d;5;%d\", 38+offset, c))\n\t\t}\n\t}\n\tappendCode(fg, 0)\n\tappendCode(bg, 10)\n\treturn codes\n}\n\nfunc ulColorCode(c Color) string {\n\tif c == colDefault {\n\t\treturn \"\"\n\t}\n\tif c.is24() {\n\t\tr := (c >> 16) & 0xff\n\t\tg := (c >> 8) & 0xff\n\t\tb := (c) & 0xff\n\t\treturn fmt.Sprintf(\"58;2;%d;%d;%d\", r, g, b)\n\t}\n\tif c >= 0 && c < 256 {\n\t\treturn fmt.Sprintf(\"58;5;%d\", c)\n\t}\n\treturn \"\"\n}\n\nfunc (w *LightWindow) csiColor(fg Color, bg Color, ul Color, attr Attr) (bool, string) {\n\tcodes := append(attrCodes(attr), colorCodes(fg, bg)...)\n\tif ulCode := ulColorCode(ul); ulCode != \"\" {\n\t\tcodes = append(codes, ulCode)\n\t}\n\tcode := w.csi(\";\" + strings.Join(codes, \";\") + \"m\")\n\treturn len(codes) > 0, code\n}\n\nfunc (w *LightWindow) Print(text string) {\n\tw.cprint2(colDefault, w.bg, AttrRegular, text)\n}\n\nfunc cleanse(str string) string {\n\treturn strings.ReplaceAll(str, \"\\x1b\", \"\")\n}\n\nfunc (w *LightWindow) CPrint(pair ColorPair, text string) {\n\t_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Ul(), pair.Attr())\n\tw.stderrInternal(cleanse(text), false, code)\n\tw.csi(\"0m\")\n}\n\nfunc (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {\n\thasColors, code := w.csiColor(fg, bg, colDefault, attr)\n\tif hasColors {\n\t\tdefer w.csi(\"0m\")\n\t}\n\tw.stderrInternal(cleanse(text), false, code)\n}\n\nfunc (w *LightWindow) fill(str string, resetCode string) FillReturn {\n\tallLines := strings.Split(str, \"\\n\")\n\tfor i, line := range allLines {\n\t\tlines := WrapLine(line, w.posx, w.width, w.tabstop, w.wrapSignWidth)\n\t\tfor j, wl := range lines {\n\t\t\tif w.posx < w.width {\n\t\t\t\tw.stderrInternal(wl.Text, false, resetCode)\n\t\t\t\tw.posx += wl.DisplayWidth\n\t\t\t}\n\n\t\t\t// Wrap line\n\t\t\tif j < len(lines)-1 || i < len(allLines)-1 {\n\t\t\t\tif w.posy+1 >= w.height {\n\t\t\t\t\treturn FillSuspend\n\t\t\t\t}\n\t\t\t\tw.MoveAndClear(w.posy, w.posx)\n\t\t\t\tw.Move(w.posy+1, 0)\n\t\t\t\tw.renderer.stderr(resetCode)\n\t\t\t\tif len(lines) > 1 {\n\t\t\t\t\tsign := w.wrapSign\n\t\t\t\t\twidth := w.wrapSignWidth\n\t\t\t\t\tif width > w.width {\n\t\t\t\t\t\trunes, truncatedWidth := util.Truncate(w.wrapSign, w.width)\n\t\t\t\t\t\tsign = string(runes)\n\t\t\t\t\t\twidth = truncatedWidth\n\t\t\t\t\t}\n\t\t\t\t\tw.stderrInternal(DIM+sign, false, resetCode)\n\t\t\t\t\tw.renderer.stderr(resetCode)\n\t\t\t\t\tw.Move(w.posy, width)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif w.posx >= w.Width() {\n\t\tif w.posy+1 >= w.height {\n\t\t\treturn FillSuspend\n\t\t}\n\t\tw.Move(w.posy+1, 0)\n\t\tw.renderer.stderr(resetCode)\n\t\treturn FillNextLine\n\t}\n\treturn FillContinue\n}\n\nfunc (w *LightWindow) setBg() string {\n\tif w.bg != colDefault {\n\t\t_, code := w.csiColor(colDefault, w.bg, colDefault, AttrRegular)\n\t\treturn code\n\t}\n\t// Should clear dim attribute after ␍ in the preview window\n\t// e.g. printf \"foo\\rbar\" | fzf --ansi --preview 'printf \"foo\\rbar\"'\n\treturn \"\\x1b[m\"\n}\n\nfunc (w *LightWindow) LinkBegin(uri string, params string) {\n\tw.renderer.queued.WriteString(\"\\x1b]8;\" + params + \";\" + uri + \"\\x1b\\\\\")\n}\n\nfunc (w *LightWindow) LinkEnd() {\n\tw.renderer.queued.WriteString(\"\\x1b]8;;\\x1b\\\\\")\n}\n\nfunc (w *LightWindow) Fill(text string) FillReturn {\n\tw.Move(w.posy, w.posx)\n\tcode := w.setBg()\n\treturn w.fill(text, code)\n}\n\nfunc (w *LightWindow) CFill(fg Color, bg Color, ul Color, attr Attr, text string) FillReturn {\n\tw.Move(w.posy, w.posx)\n\tif fg == colDefault {\n\t\tfg = w.fg\n\t}\n\tif bg == colDefault {\n\t\tbg = w.bg\n\t}\n\tif hasColors, resetCode := w.csiColor(fg, bg, ul, attr); hasColors {\n\t\tdefer w.csi(\"0m\")\n\t\treturn w.fill(text, resetCode)\n\t}\n\treturn w.fill(text, w.setBg())\n}\n\nfunc (w *LightWindow) FinishFill() {\n\tif w.posy < w.height {\n\t\tw.MoveAndClear(w.posy, w.posx)\n\t}\n\tfor y := w.posy + 1; y < w.height; y++ {\n\t\tw.MoveAndClear(y, 0)\n\t}\n}\n\nfunc (w *LightWindow) Erase() {\n\tw.DrawBorder()\n\tw.Move(0, 0)\n\tw.FinishFill()\n\tw.Move(0, 0)\n}\n\nfunc (w *LightWindow) EraseMaybe() bool {\n\treturn false\n}\n\nfunc (w *LightWindow) SetWrapSign(sign string, width int) {\n\tw.wrapSign = sign\n\tw.wrapSignWidth = width\n}\n\nfunc (r *LightRenderer) HideCursor() {\n\tr.showCursor = false\n\tr.csi(\"?25l\")\n}\n\nfunc (r *LightRenderer) ShowCursor() {\n\tr.showCursor = true\n\tr.csi(\"?25h\")\n}\n"
  },
  {
    "path": "src/tui/light_test.go",
    "content": "package tui\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"unicode\"\n)\n\nfunc TestLightRenderer(t *testing.T) {\n\ttty_file, _ := os.Open(\"\")\n\trenderer, _ := NewLightRenderer(\n\t\t\"\", tty_file, &ColorTheme{}, true, false, 0, false, true,\n\t\tfunc(h int) int { return h })\n\n\tlight_renderer := renderer.(*LightRenderer)\n\n\tgo func() {\n\t\tfor {\n\t\t\tlight_renderer.mutex.Lock()\n\t\t\tready := light_renderer.cancel != nil\n\t\t\tlight_renderer.mutex.Unlock()\n\n\t\t\tif ready {\n\t\t\t\tlight_renderer.CancelGetChar()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\tevent := light_renderer.GetChar(true)\n\tif event.Type != Invalid {\n\t\tt.Error(\"Not cancelled\")\n\t}\n\n\tassertCharSequence := func(sequence string, name string) {\n\t\tbytes := []byte(sequence)\n\t\tlight_renderer.buffer = bytes\n\t\tevent := light_renderer.GetChar(true)\n\t\tif event.KeyName() != name {\n\t\t\tt.Errorf(\n\t\t\t\t\"sequence: %q | %v | '%s' (%s) != %s\",\n\t\t\t\tstring(bytes), bytes,\n\t\t\t\tevent.KeyName(), event.Type.String(), name)\n\t\t}\n\t}\n\n\tassertEscSequence := func(sequence string, name string) {\n\t\tbytes := []byte(sequence)\n\t\tlight_renderer.buffer = bytes\n\n\t\tsz := 1\n\t\tevent := light_renderer.escSequence(&sz)\n\t\tif fmt.Sprintf(\"!%s\", event.Type.String()) == name {\n\t\t\t// this is fine\n\t\t} else if event.KeyName() != name {\n\t\t\tt.Errorf(\n\t\t\t\t\"sequence: %q | %v | '%s' (%s) != %s\",\n\t\t\t\tstring(bytes), bytes,\n\t\t\t\tevent.KeyName(), event.Type.String(), name)\n\t\t}\n\t}\n\n\t// invalid\n\tassertEscSequence(\"\\x1b[<\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[1;1R\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[1\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[3;3~1\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[13\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[1;3\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[1;10\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[220~\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[5;30~\", \"!Invalid\")\n\tassertEscSequence(\"\\x1b[6;30~\", \"!Invalid\")\n\n\t// general\n\tfor r := 'a'; r < 'z'; r++ {\n\t\tlower_r := fmt.Sprintf(\"%c\", r)\n\t\tupper_r := fmt.Sprintf(\"%c\", unicode.ToUpper(r))\n\t\tassertCharSequence(lower_r, lower_r)\n\t\tassertCharSequence(upper_r, upper_r)\n\t}\n\n\tassertCharSequence(\"\\x01\", \"ctrl-a\")\n\tassertCharSequence(\"\\x02\", \"ctrl-b\")\n\tassertCharSequence(\"\\x03\", \"ctrl-c\")\n\tassertCharSequence(\"\\x04\", \"ctrl-d\")\n\tassertCharSequence(\"\\x05\", \"ctrl-e\")\n\tassertCharSequence(\"\\x06\", \"ctrl-f\")\n\tassertCharSequence(\"\\x07\", \"ctrl-g\")\n\t// ctrl-h is the same as ctrl-backspace\n\t// ctrl-i is the same as tab\n\tassertCharSequence(\"\\n\", \"ctrl-j\")\n\tassertCharSequence(\"\\x0b\", \"ctrl-k\")\n\tassertCharSequence(\"\\x0c\", \"ctrl-l\")\n\tassertCharSequence(\"\\r\", \"enter\") // enter\n\tassertCharSequence(\"\\x0e\", \"ctrl-n\")\n\tassertCharSequence(\"\\x0f\", \"ctrl-o\")\n\tassertCharSequence(\"\\x10\", \"ctrl-p\")\n\tassertCharSequence(\"\\x11\", \"ctrl-q\")\n\tassertCharSequence(\"\\x12\", \"ctrl-r\")\n\tassertCharSequence(\"\\x13\", \"ctrl-s\")\n\tassertCharSequence(\"\\x14\", \"ctrl-t\")\n\tassertCharSequence(\"\\x15\", \"ctrl-u\")\n\tassertCharSequence(\"\\x16\", \"ctrl-v\")\n\tassertCharSequence(\"\\x17\", \"ctrl-w\")\n\tassertCharSequence(\"\\x18\", \"ctrl-x\")\n\tassertCharSequence(\"\\x19\", \"ctrl-y\")\n\tassertCharSequence(\"\\x1a\", \"ctrl-z\")\n\n\tassertCharSequence(\"\\x00\", \"ctrl-space\")\n\tassertCharSequence(\"\\x1c\", \"ctrl-\\\\\")\n\tassertCharSequence(\"\\x1d\", \"ctrl-]\")\n\tassertCharSequence(\"\\x1e\", \"ctrl-^\")\n\tassertCharSequence(\"\\x1f\", \"ctrl-/\")\n\n\tassertEscSequence(\"\\x1ba\", \"alt-a\")\n\tassertEscSequence(\"\\x1bb\", \"alt-b\")\n\tassertEscSequence(\"\\x1bc\", \"alt-c\")\n\tassertEscSequence(\"\\x1bd\", \"alt-d\")\n\tassertEscSequence(\"\\x1be\", \"alt-e\")\n\tassertEscSequence(\"\\x1bf\", \"alt-f\")\n\tassertEscSequence(\"\\x1bg\", \"alt-g\")\n\tassertEscSequence(\"\\x1bh\", \"alt-h\")\n\tassertEscSequence(\"\\x1bi\", \"alt-i\")\n\tassertEscSequence(\"\\x1bj\", \"alt-j\")\n\tassertEscSequence(\"\\x1bk\", \"alt-k\")\n\tassertEscSequence(\"\\x1bl\", \"alt-l\")\n\tassertEscSequence(\"\\x1bm\", \"alt-m\")\n\tassertEscSequence(\"\\x1bn\", \"alt-n\")\n\tassertEscSequence(\"\\x1bo\", \"alt-o\")\n\tassertEscSequence(\"\\x1bp\", \"alt-p\")\n\tassertEscSequence(\"\\x1bq\", \"alt-q\")\n\tassertEscSequence(\"\\x1br\", \"alt-r\")\n\tassertEscSequence(\"\\x1bs\", \"alt-s\")\n\tassertEscSequence(\"\\x1bt\", \"alt-t\")\n\tassertEscSequence(\"\\x1bu\", \"alt-u\")\n\tassertEscSequence(\"\\x1bv\", \"alt-v\")\n\tassertEscSequence(\"\\x1bw\", \"alt-w\")\n\tassertEscSequence(\"\\x1bx\", \"alt-x\")\n\tassertEscSequence(\"\\x1by\", \"alt-y\")\n\tassertEscSequence(\"\\x1bz\", \"alt-z\")\n\n\tassertEscSequence(\"\\x1bOP\", \"f1\")\n\tassertEscSequence(\"\\x1bOQ\", \"f2\")\n\tassertEscSequence(\"\\x1bOR\", \"f3\")\n\tassertEscSequence(\"\\x1bOS\", \"f4\")\n\tassertEscSequence(\"\\x1b[15~\", \"f5\")\n\tassertEscSequence(\"\\x1b[17~\", \"f6\")\n\tassertEscSequence(\"\\x1b[18~\", \"f7\")\n\tassertEscSequence(\"\\x1b[19~\", \"f8\")\n\tassertEscSequence(\"\\x1b[20~\", \"f9\")\n\tassertEscSequence(\"\\x1b[21~\", \"f10\")\n\tassertEscSequence(\"\\x1b[23~\", \"f11\")\n\tassertEscSequence(\"\\x1b[24~\", \"f12\")\n\n\tassertEscSequence(\"\\x1b\", \"esc\")\n\tassertCharSequence(\"\\t\", \"tab\")\n\tassertEscSequence(\"\\x1b[Z\", \"shift-tab\")\n\n\tassertCharSequence(\"\\x7f\", \"backspace\")\n\tassertEscSequence(\"\\x1b\\x7f\", \"alt-backspace\")\n\tassertCharSequence(\"\\b\", \"ctrl-backspace\")\n\tassertEscSequence(\"\\x1b\\b\", \"ctrl-alt-backspace\")\n\n\tassertEscSequence(\"\\x1b[A\", \"up\")\n\tassertEscSequence(\"\\x1b[B\", \"down\")\n\tassertEscSequence(\"\\x1b[C\", \"right\")\n\tassertEscSequence(\"\\x1b[D\", \"left\")\n\tassertEscSequence(\"\\x1b[H\", \"home\")\n\tassertEscSequence(\"\\x1b[F\", \"end\")\n\tassertEscSequence(\"\\x1b[2~\", \"insert\")\n\tassertEscSequence(\"\\x1b[3~\", \"delete\")\n\tassertEscSequence(\"\\x1b[5~\", \"page-up\")\n\tassertEscSequence(\"\\x1b[6~\", \"page-down\")\n\tassertEscSequence(\"\\x1b[7~\", \"home\")\n\tassertEscSequence(\"\\x1b[8~\", \"end\")\n\n\tassertEscSequence(\"\\x1b[1;2A\", \"shift-up\")\n\tassertEscSequence(\"\\x1b[1;2B\", \"shift-down\")\n\tassertEscSequence(\"\\x1b[1;2C\", \"shift-right\")\n\tassertEscSequence(\"\\x1b[1;2D\", \"shift-left\")\n\tassertEscSequence(\"\\x1b[1;2H\", \"shift-home\")\n\tassertEscSequence(\"\\x1b[1;2F\", \"shift-end\")\n\tassertEscSequence(\"\\x1b[3;2~\", \"shift-delete\")\n\tassertEscSequence(\"\\x1b[5;2~\", \"shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;2~\", \"shift-page-down\")\n\n\tassertEscSequence(\"\\x1b\\x1b\", \"esc\")\n\tassertEscSequence(\"\\x1b\\x1b[A\", \"alt-up\")\n\tassertEscSequence(\"\\x1b\\x1b[B\", \"alt-down\")\n\tassertEscSequence(\"\\x1b\\x1b[C\", \"alt-right\")\n\tassertEscSequence(\"\\x1b\\x1b[D\", \"alt-left\")\n\n\tassertEscSequence(\"\\x1b[1;3A\", \"alt-up\")\n\tassertEscSequence(\"\\x1b[1;3B\", \"alt-down\")\n\tassertEscSequence(\"\\x1b[1;3C\", \"alt-right\")\n\tassertEscSequence(\"\\x1b[1;3D\", \"alt-left\")\n\tassertEscSequence(\"\\x1b[1;3H\", \"alt-home\")\n\tassertEscSequence(\"\\x1b[1;3F\", \"alt-end\")\n\tassertEscSequence(\"\\x1b[3;3~\", \"alt-delete\")\n\tassertEscSequence(\"\\x1b[5;3~\", \"alt-page-up\")\n\tassertEscSequence(\"\\x1b[6;3~\", \"alt-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;4A\", \"alt-shift-up\")\n\tassertEscSequence(\"\\x1b[1;4B\", \"alt-shift-down\")\n\tassertEscSequence(\"\\x1b[1;4C\", \"alt-shift-right\")\n\tassertEscSequence(\"\\x1b[1;4D\", \"alt-shift-left\")\n\tassertEscSequence(\"\\x1b[1;4H\", \"alt-shift-home\")\n\tassertEscSequence(\"\\x1b[1;4F\", \"alt-shift-end\")\n\tassertEscSequence(\"\\x1b[3;4~\", \"alt-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;4~\", \"alt-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;4~\", \"alt-shift-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;5A\", \"ctrl-up\")\n\tassertEscSequence(\"\\x1b[1;5B\", \"ctrl-down\")\n\tassertEscSequence(\"\\x1b[1;5C\", \"ctrl-right\")\n\tassertEscSequence(\"\\x1b[1;5D\", \"ctrl-left\")\n\tassertEscSequence(\"\\x1b[1;5H\", \"ctrl-home\")\n\tassertEscSequence(\"\\x1b[1;5F\", \"ctrl-end\")\n\tassertEscSequence(\"\\x1b[3;5~\", \"ctrl-delete\")\n\tassertEscSequence(\"\\x1b[5;5~\", \"ctrl-page-up\")\n\tassertEscSequence(\"\\x1b[6;5~\", \"ctrl-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;7A\", \"ctrl-alt-up\")\n\tassertEscSequence(\"\\x1b[1;7B\", \"ctrl-alt-down\")\n\tassertEscSequence(\"\\x1b[1;7C\", \"ctrl-alt-right\")\n\tassertEscSequence(\"\\x1b[1;7D\", \"ctrl-alt-left\")\n\tassertEscSequence(\"\\x1b[1;7H\", \"ctrl-alt-home\")\n\tassertEscSequence(\"\\x1b[1;7F\", \"ctrl-alt-end\")\n\tassertEscSequence(\"\\x1b[3;7~\", \"ctrl-alt-delete\")\n\tassertEscSequence(\"\\x1b[5;7~\", \"ctrl-alt-page-up\")\n\tassertEscSequence(\"\\x1b[6;7~\", \"ctrl-alt-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;6A\", \"ctrl-shift-up\")\n\tassertEscSequence(\"\\x1b[1;6B\", \"ctrl-shift-down\")\n\tassertEscSequence(\"\\x1b[1;6C\", \"ctrl-shift-right\")\n\tassertEscSequence(\"\\x1b[1;6D\", \"ctrl-shift-left\")\n\tassertEscSequence(\"\\x1b[1;6H\", \"ctrl-shift-home\")\n\tassertEscSequence(\"\\x1b[1;6F\", \"ctrl-shift-end\")\n\tassertEscSequence(\"\\x1b[3;6~\", \"ctrl-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;6~\", \"ctrl-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;6~\", \"ctrl-shift-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;8A\", \"ctrl-alt-shift-up\")\n\tassertEscSequence(\"\\x1b[1;8B\", \"ctrl-alt-shift-down\")\n\tassertEscSequence(\"\\x1b[1;8C\", \"ctrl-alt-shift-right\")\n\tassertEscSequence(\"\\x1b[1;8D\", \"ctrl-alt-shift-left\")\n\tassertEscSequence(\"\\x1b[1;8H\", \"ctrl-alt-shift-home\")\n\tassertEscSequence(\"\\x1b[1;8F\", \"ctrl-alt-shift-end\")\n\tassertEscSequence(\"\\x1b[3;8~\", \"ctrl-alt-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;8~\", \"ctrl-alt-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;8~\", \"ctrl-alt-shift-page-down\")\n\n\t// xterm meta & mac\n\tassertEscSequence(\"\\x1b[1;9A\", \"alt-up\")\n\tassertEscSequence(\"\\x1b[1;9B\", \"alt-down\")\n\tassertEscSequence(\"\\x1b[1;9C\", \"alt-right\")\n\tassertEscSequence(\"\\x1b[1;9D\", \"alt-left\")\n\tassertEscSequence(\"\\x1b[1;9H\", \"alt-home\")\n\tassertEscSequence(\"\\x1b[1;9F\", \"alt-end\")\n\tassertEscSequence(\"\\x1b[3;9~\", \"alt-delete\")\n\tassertEscSequence(\"\\x1b[5;9~\", \"alt-page-up\")\n\tassertEscSequence(\"\\x1b[6;9~\", \"alt-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;10A\", \"alt-shift-up\")\n\tassertEscSequence(\"\\x1b[1;10B\", \"alt-shift-down\")\n\tassertEscSequence(\"\\x1b[1;10C\", \"alt-shift-right\")\n\tassertEscSequence(\"\\x1b[1;10D\", \"alt-shift-left\")\n\tassertEscSequence(\"\\x1b[1;10H\", \"alt-shift-home\")\n\tassertEscSequence(\"\\x1b[1;10F\", \"alt-shift-end\")\n\tassertEscSequence(\"\\x1b[3;10~\", \"alt-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;10~\", \"alt-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;10~\", \"alt-shift-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;11A\", \"alt-up\")\n\tassertEscSequence(\"\\x1b[1;11B\", \"alt-down\")\n\tassertEscSequence(\"\\x1b[1;11C\", \"alt-right\")\n\tassertEscSequence(\"\\x1b[1;11D\", \"alt-left\")\n\tassertEscSequence(\"\\x1b[1;11H\", \"alt-home\")\n\tassertEscSequence(\"\\x1b[1;11F\", \"alt-end\")\n\tassertEscSequence(\"\\x1b[3;11~\", \"alt-delete\")\n\tassertEscSequence(\"\\x1b[5;11~\", \"alt-page-up\")\n\tassertEscSequence(\"\\x1b[6;11~\", \"alt-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;12A\", \"alt-shift-up\")\n\tassertEscSequence(\"\\x1b[1;12B\", \"alt-shift-down\")\n\tassertEscSequence(\"\\x1b[1;12C\", \"alt-shift-right\")\n\tassertEscSequence(\"\\x1b[1;12D\", \"alt-shift-left\")\n\tassertEscSequence(\"\\x1b[1;12H\", \"alt-shift-home\")\n\tassertEscSequence(\"\\x1b[1;12F\", \"alt-shift-end\")\n\tassertEscSequence(\"\\x1b[3;12~\", \"alt-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;12~\", \"alt-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;12~\", \"alt-shift-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;13A\", \"ctrl-alt-up\")\n\tassertEscSequence(\"\\x1b[1;13B\", \"ctrl-alt-down\")\n\tassertEscSequence(\"\\x1b[1;13C\", \"ctrl-alt-right\")\n\tassertEscSequence(\"\\x1b[1;13D\", \"ctrl-alt-left\")\n\tassertEscSequence(\"\\x1b[1;13H\", \"ctrl-alt-home\")\n\tassertEscSequence(\"\\x1b[1;13F\", \"ctrl-alt-end\")\n\tassertEscSequence(\"\\x1b[3;13~\", \"ctrl-alt-delete\")\n\tassertEscSequence(\"\\x1b[5;13~\", \"ctrl-alt-page-up\")\n\tassertEscSequence(\"\\x1b[6;13~\", \"ctrl-alt-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;14A\", \"ctrl-alt-shift-up\")\n\tassertEscSequence(\"\\x1b[1;14B\", \"ctrl-alt-shift-down\")\n\tassertEscSequence(\"\\x1b[1;14C\", \"ctrl-alt-shift-right\")\n\tassertEscSequence(\"\\x1b[1;14D\", \"ctrl-alt-shift-left\")\n\tassertEscSequence(\"\\x1b[1;14H\", \"ctrl-alt-shift-home\")\n\tassertEscSequence(\"\\x1b[1;14F\", \"ctrl-alt-shift-end\")\n\tassertEscSequence(\"\\x1b[3;14~\", \"ctrl-alt-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;14~\", \"ctrl-alt-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;14~\", \"ctrl-alt-shift-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;15A\", \"ctrl-alt-up\")\n\tassertEscSequence(\"\\x1b[1;15B\", \"ctrl-alt-down\")\n\tassertEscSequence(\"\\x1b[1;15C\", \"ctrl-alt-right\")\n\tassertEscSequence(\"\\x1b[1;15D\", \"ctrl-alt-left\")\n\tassertEscSequence(\"\\x1b[1;15H\", \"ctrl-alt-home\")\n\tassertEscSequence(\"\\x1b[1;15F\", \"ctrl-alt-end\")\n\tassertEscSequence(\"\\x1b[3;15~\", \"ctrl-alt-delete\")\n\tassertEscSequence(\"\\x1b[5;15~\", \"ctrl-alt-page-up\")\n\tassertEscSequence(\"\\x1b[6;15~\", \"ctrl-alt-page-down\")\n\n\tassertEscSequence(\"\\x1b[1;16A\", \"ctrl-alt-shift-up\")\n\tassertEscSequence(\"\\x1b[1;16B\", \"ctrl-alt-shift-down\")\n\tassertEscSequence(\"\\x1b[1;16C\", \"ctrl-alt-shift-right\")\n\tassertEscSequence(\"\\x1b[1;16D\", \"ctrl-alt-shift-left\")\n\tassertEscSequence(\"\\x1b[1;16H\", \"ctrl-alt-shift-home\")\n\tassertEscSequence(\"\\x1b[1;16F\", \"ctrl-alt-shift-end\")\n\tassertEscSequence(\"\\x1b[3;16~\", \"ctrl-alt-shift-delete\")\n\tassertEscSequence(\"\\x1b[5;16~\", \"ctrl-alt-shift-page-up\")\n\tassertEscSequence(\"\\x1b[6;16~\", \"ctrl-alt-shift-page-down\")\n\n\t// tmux & emacs\n\tassertEscSequence(\"\\x1bOA\", \"up\")\n\tassertEscSequence(\"\\x1bOB\", \"down\")\n\tassertEscSequence(\"\\x1bOC\", \"right\")\n\tassertEscSequence(\"\\x1bOD\", \"left\")\n\tassertEscSequence(\"\\x1bOH\", \"home\")\n\tassertEscSequence(\"\\x1bOF\", \"end\")\n\n\t// rrvt\n\tassertEscSequence(\"\\x1b[1~\", \"home\")\n\tassertEscSequence(\"\\x1b[4~\", \"end\")\n\tassertEscSequence(\"\\x1b[11~\", \"f1\")\n\tassertEscSequence(\"\\x1b[12~\", \"f2\")\n\tassertEscSequence(\"\\x1b[13~\", \"f3\")\n\tassertEscSequence(\"\\x1b[14~\", \"f4\")\n\n}\n"
  },
  {
    "path": "src/tui/light_unix.go",
    "content": "//go:build !windows\n\npackage tui\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n\t\"golang.org/x/sys/unix\"\n\t\"golang.org/x/term\"\n)\n\nfunc IsLightRendererSupported() bool {\n\treturn true\n}\n\nfunc (r *LightRenderer) DefaultTheme() *ColorTheme {\n\tif strings.Contains(os.Getenv(\"TERM\"), \"256\") {\n\t\treturn Dark256\n\t}\n\tcolors, err := exec.Command(\"tput\", \"colors\").Output()\n\tif err == nil && atoi(strings.TrimSpace(string(colors)), 16) > 16 {\n\t\treturn Dark256\n\t}\n\treturn Default16\n}\n\nfunc (r *LightRenderer) fd() int {\n\treturn int(r.ttyin.Fd())\n}\n\nfunc (r *LightRenderer) initPlatform() (err error) {\n\tr.origState, err = term.MakeRaw(r.fd())\n\treturn err\n}\n\nfunc (r *LightRenderer) closePlatform() {\n\tr.ttyout.Close()\n}\n\nfunc openTty(ttyDefault string, mode int) (*os.File, error) {\n\tvar in *os.File\n\tvar err error\n\tif len(ttyDefault) > 0 {\n\t\tin, err = os.OpenFile(ttyDefault, mode, 0)\n\t}\n\tif in == nil || err != nil || ttyDefault != DefaultTtyDevice && !util.IsTty(in) {\n\t\ttty := ttyname()\n\t\tif len(tty) > 0 {\n\t\t\tif in, err := os.OpenFile(tty, mode, 0); err == nil {\n\t\t\t\treturn in, nil\n\t\t\t}\n\t\t}\n\t\tif ttyDefault != DefaultTtyDevice {\n\t\t\tif in, err = os.OpenFile(DefaultTtyDevice, mode, 0); err == nil {\n\t\t\t\treturn in, nil\n\t\t\t}\n\t\t}\n\t\treturn nil, errors.New(\"failed to open \" + DefaultTtyDevice)\n\t}\n\treturn in, nil\n}\n\nfunc openTtyIn(ttyDefault string) (*os.File, error) {\n\treturn openTty(ttyDefault, syscall.O_RDONLY)\n}\n\nfunc openTtyOut(ttyDefault string) (*os.File, error) {\n\treturn openTty(ttyDefault, syscall.O_WRONLY)\n}\n\nfunc (r *LightRenderer) setupTerminal() {\n\tterm.MakeRaw(r.fd())\n}\n\nfunc (r *LightRenderer) restoreTerminal() {\n\tterm.Restore(r.fd(), r.origState)\n}\n\nfunc (r *LightRenderer) updateTerminalSize() {\n\twidth, height, err := term.GetSize(r.fd())\n\n\tif err == nil {\n\t\tr.width = width\n\t\tr.height = r.maxHeightFunc(height)\n\t} else {\n\t\tr.width = getEnv(\"COLUMNS\", defaultWidth)\n\t\tr.height = r.maxHeightFunc(getEnv(\"LINES\", defaultHeight))\n\t}\n}\n\nfunc (r *LightRenderer) findOffset() (row int, col int) {\n\tr.csi(\"6n\")\n\tr.flush()\n\tvar err error\n\tbytes := []byte{}\n\tfor tries := range offsetPollTries {\n\t\tbytes, _, err = r.getBytesInternal(false, bytes, tries > 0)\n\t\tif err != nil {\n\t\t\treturn -1, -1\n\t\t}\n\n\t\toffsets := offsetRegexp.FindSubmatch(bytes)\n\t\tif len(offsets) > 3 {\n\t\t\t// Add anything we skipped over to the input buffer\n\t\t\tr.buffer = append(r.buffer, offsets[1]...)\n\t\t\treturn atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1\n\t\t}\n\t}\n\treturn -1, -1\n}\n\nfunc (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) {\n\tfd := r.fd()\n\tgetter := func() (int, getCharResult) {\n\t\tb := make([]byte, 1)\n\t\tutil.SetNonblock(r.ttyin, nonblock)\n\t\t_, err := util.Read(fd, b)\n\t\tif err != nil {\n\t\t\treturn 0, getCharError\n\t\t}\n\t\treturn int(b[0]), getCharSuccess\n\t}\n\tif nonblock || !cancellable {\n\t\treturn getter()\n\t}\n\n\trpipe, wpipe, err := os.Pipe()\n\tif err != nil {\n\t\t// Fallback to blocking read without cancellation\n\t\treturn getter()\n\t}\n\tr.setCancel(func() {\n\t\twpipe.Write([]byte{0})\n\t})\n\tdefer func() {\n\t\tr.setCancel(nil)\n\t\trpipe.Close()\n\t\twpipe.Close()\n\t}()\n\n\tcancelFd := int(rpipe.Fd())\n\tfor range maxSelectTries {\n\t\tvar rfds unix.FdSet\n\t\tlimit := len(rfds.Bits) * unix.NFDBITS\n\t\tif fd >= limit || cancelFd >= limit {\n\t\t\treturn getter()\n\t\t}\n\n\t\trfds.Set(fd)\n\t\trfds.Set(cancelFd)\n\t\t_, err := unix.Select(max(fd, cancelFd)+1, &rfds, nil, nil, nil)\n\t\tif err != nil {\n\t\t\tif err == syscall.EINTR {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn 0, getCharError\n\t\t}\n\n\t\tif rfds.IsSet(cancelFd) {\n\t\t\treturn 0, getCharCancelled\n\t\t}\n\n\t\tif rfds.IsSet(fd) {\n\t\t\treturn getter()\n\t\t}\n\t}\n\treturn 0, getCharError\n}\n\nfunc (r *LightRenderer) Size() TermSize {\n\tws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ)\n\tif err != nil {\n\t\treturn TermSize{}\n\t}\n\treturn TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)}\n}\n"
  },
  {
    "path": "src/tui/light_windows.go",
    "content": "//go:build windows\n\npackage tui\n\nimport (\n\t\"os\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n\t\"golang.org/x/sys/windows\"\n)\n\nconst (\n\ttimeoutInterval = 10\n)\n\nvar (\n\tconsoleFlagsInput  = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)\n\tconsoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)\n\tcounter            = uint64(0)\n)\n\n// IsLightRendererSupported checks to see if the Light renderer is supported\nfunc IsLightRendererSupported() bool {\n\tvar oldState uint32\n\t// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)\n\tif windows.GetConsoleMode(windows.Stderr, &oldState) != nil {\n\t\treturn false\n\t}\n\t// attempt to set mode to determine if we support VT 100 codes. This will work on newer Windows 10\n\t// version:\n\tcanSetVt100 := windows.SetConsoleMode(windows.Stderr, oldState|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil\n\tvar checkState uint32\n\tif windows.GetConsoleMode(windows.Stderr, &checkState) != nil ||\n\t\t(checkState&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {\n\t\treturn false\n\t}\n\twindows.SetConsoleMode(windows.Stderr, oldState)\n\treturn canSetVt100\n}\n\nfunc (r *LightRenderer) DefaultTheme() *ColorTheme {\n\t// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:\n\tif !IsLightRendererSupported() || os.Getenv(\"ConEmuPID\") != \"\" || os.Getenv(\"TCELL_TRUECOLOR\") == \"disable\" {\n\t\treturn Default16\n\t}\n\treturn Dark256\n}\n\nfunc (r *LightRenderer) initPlatform() error {\n\t//outHandle := windows.Stdout\n\toutHandle, _ := syscall.Open(\"CONOUT$\", syscall.O_RDWR, 0)\n\t// enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)\n\tif err := windows.GetConsoleMode(windows.Handle(outHandle), &r.origStateOutput); err != nil {\n\t\treturn err\n\t}\n\tr.outHandle = uintptr(outHandle)\n\tinHandle, _ := syscall.Open(\"CONIN$\", syscall.O_RDWR, 0)\n\tif err := windows.GetConsoleMode(windows.Handle(inHandle), &r.origStateInput); err != nil {\n\t\treturn err\n\t}\n\tr.inHandle = uintptr(inHandle)\n\n\t// channel for non-blocking reads. Buffer to make sure\n\t// we get the ESC sets:\n\tr.ttyinChannel = make(chan byte, 1024)\n\n\tr.setupTerminal()\n\n\treturn nil\n}\n\nfunc (r *LightRenderer) closePlatform() {\n\twindows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)\n\twindows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)\n}\n\nfunc openTtyIn(ttyDefault string) (*os.File, error) {\n\t// not used\n\treturn nil, nil\n}\n\nfunc openTtyOut(ttyDefault string) (*os.File, error) {\n\treturn os.Stderr, nil\n}\n\nfunc (r *LightRenderer) setupTerminal() {\n\twindows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput)\n\twindows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)\n\n\t// The following allows for non-blocking IO.\n\t// syscall.SetNonblock() is a NOOP under Windows.\n\tcurrent := counter\n\tgo func() {\n\t\tfd := int(r.inHandle)\n\t\tb := make([]byte, 1)\n\t\tfor {\n\t\t\tif _, err := util.Read(fd, b); err == nil {\n\t\t\t\tr.mutex.Lock()\n\t\t\t\t// This condition prevents the goroutine from running after the renderer\n\t\t\t\t// has been closed or paused.\n\t\t\t\tif current != counter {\n\t\t\t\t\tr.mutex.Unlock()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tr.ttyinChannel <- b[0]\n\t\t\t\t// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.\n\t\t\t\twindows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)\n\t\t\t\tr.mutex.Unlock()\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (r *LightRenderer) restoreTerminal() {\n\tr.mutex.Lock()\n\tcounter++\n\t// We're setting ENABLE_VIRTUAL_TERMINAL_INPUT to allow escape sequences to be read during 'execute'.\n\t// e.g. fzf --bind 'enter:execute:less {}'\n\twindows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput|windows.ENABLE_VIRTUAL_TERMINAL_INPUT)\n\twindows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)\n\tr.mutex.Unlock()\n}\n\nfunc (r *LightRenderer) Size() TermSize {\n\tvar w, h int\n\tvar bufferInfo windows.ConsoleScreenBufferInfo\n\tif err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {\n\t\tw = getEnv(\"COLUMNS\", defaultWidth)\n\t\th = r.maxHeightFunc(getEnv(\"LINES\", defaultHeight))\n\n\t} else {\n\t\tw = int(bufferInfo.Window.Right - bufferInfo.Window.Left)\n\t\th = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))\n\t}\n\treturn TermSize{h, w, 0, 0}\n}\n\nfunc (r *LightRenderer) updateTerminalSize() {\n\tsize := r.Size()\n\tr.width = size.Columns\n\tr.height = size.Lines\n}\n\nfunc (r *LightRenderer) findOffset() (row int, col int) {\n\tvar bufferInfo windows.ConsoleScreenBufferInfo\n\tif err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {\n\t\treturn -1, -1\n\t}\n\treturn int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X)\n}\n\nfunc (r *LightRenderer) getch(cancellable bool, nonblock bool) (int, getCharResult) {\n\tif !nonblock && !cancellable {\n\t\tbc := <-r.ttyinChannel\n\t\treturn int(bc), getCharSuccess\n\t}\n\n\tvar timeout <-chan time.Time\n\tif nonblock {\n\t\ttimeout = time.After(timeoutInterval * time.Millisecond)\n\t}\n\n\tvar cancel chan struct{}\n\tif cancellable {\n\t\tcancel = make(chan struct{})\n\t\tr.setCancel(func() {\n\t\t\tclose(cancel)\n\t\t})\n\t\tdefer r.setCancel(nil)\n\t}\n\n\tselect {\n\tcase bc := <-r.ttyinChannel:\n\t\treturn int(bc), getCharSuccess\n\tcase <-cancel:\n\t\treturn 0, getCharCancelled\n\tcase <-timeout:\n\t\t// NOTE: not really an error\n\t\treturn 0, getCharError\n\t}\n}\n"
  },
  {
    "path": "src/tui/tcell.go",
    "content": "//go:build tcell || windows\n\npackage tui\n\nimport (\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/junegunn/fzf/src/util\"\n\n\t\"github.com/rivo/uniseg\"\n)\n\nfunc HasFullscreenRenderer() bool {\n\treturn true\n}\n\nvar DefaultBorderShape BorderShape = BorderSharp\n\nfunc asTcellColor(color Color) tcell.Color {\n\tif color == colDefault {\n\t\treturn tcell.ColorDefault\n\t}\n\n\tvalue := uint64(tcell.ColorValid) + uint64(color)\n\tif color.is24() {\n\t\tvalue = value | uint64(tcell.ColorIsRGB)\n\t}\n\treturn tcell.Color(value)\n}\n\nfunc (p ColorPair) style() tcell.Style {\n\tstyle := tcell.StyleDefault\n\treturn style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))\n}\n\ntype TcellWindow struct {\n\tcolor         bool\n\twindowType    WindowType\n\ttop           int\n\tleft          int\n\twidth         int\n\theight        int\n\tnormal        ColorPair\n\tlastX         int\n\tlastY         int\n\tmoveCursor    bool\n\tborderStyle   BorderStyle\n\turi           *string\n\tparams        *string\n\tshowCursor    bool\n\twrapSign      string\n\twrapSignWidth int\n\ttabstop       int\n}\n\nfunc (w *TcellWindow) Top() int {\n\treturn w.top\n}\n\nfunc (w *TcellWindow) Left() int {\n\treturn w.left\n}\n\nfunc (w *TcellWindow) Width() int {\n\treturn w.width\n}\n\nfunc (w *TcellWindow) Height() int {\n\treturn w.height\n}\n\nfunc (w *TcellWindow) Refresh() {\n\tif w.moveCursor {\n\t\tif w.showCursor {\n\t\t\t_screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)\n\t\t}\n\t\tw.moveCursor = false\n\t}\n\tw.lastX = 0\n\tw.lastY = 0\n}\n\nfunc (w *TcellWindow) FinishFill() {\n\t// NO-OP\n}\n\nconst (\n\tBold          Attr = Attr(tcell.AttrBold)\n\tDim                = Attr(tcell.AttrDim)\n\tBlink              = Attr(tcell.AttrBlink)\n\tReverse            = Attr(tcell.AttrReverse)\n\tUnderline          = Attr(tcell.AttrUnderline)\n\tStrikeThrough      = Attr(tcell.AttrStrikeThrough)\n\tItalic             = Attr(tcell.AttrItalic)\n)\n\nfunc (r *FullscreenRenderer) Bell() {\n\t_screen.Beep()\n}\n\nfunc (r *FullscreenRenderer) HideCursor() {\n\tr.showCursor = false\n}\n\nfunc (r *FullscreenRenderer) ShowCursor() {\n\tr.showCursor = true\n}\n\nfunc (r *FullscreenRenderer) PassThrough(str string) {\n\t// No-op\n\t// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846\n}\n\nfunc (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}\n\nfunc (r *FullscreenRenderer) DefaultTheme() *ColorTheme {\n\ts, e := r.getScreen()\n\tif e != nil {\n\t\treturn Default16\n\t}\n\tif s.Colors() >= 256 {\n\t\treturn Dark256\n\t}\n\treturn Default16\n}\n\nvar (\n\t_colorToAttribute = []tcell.Color{\n\t\ttcell.ColorBlack,\n\t\ttcell.ColorRed,\n\t\ttcell.ColorGreen,\n\t\ttcell.ColorYellow,\n\t\ttcell.ColorBlue,\n\t\ttcell.ColorDarkMagenta,\n\t\ttcell.ColorLightCyan,\n\t\ttcell.ColorWhite,\n\t}\n)\n\nfunc (c Color) Style() tcell.Color {\n\tif c <= colDefault {\n\t\treturn tcell.ColorDefault\n\t} else if c >= colBlack && c <= colWhite {\n\t\treturn _colorToAttribute[int(c)]\n\t} else {\n\t\treturn tcell.Color(c)\n\t}\n}\n\n// handle the following as private members of FullscreenRenderer instance\n// they are declared here to prevent introducing tcell library in non-windows builds\nvar (\n\t_screen          tcell.Screen\n\t_prevMouseButton tcell.ButtonMask\n\t_initialResize   bool = true\n)\n\nfunc (r *FullscreenRenderer) getScreen() (tcell.Screen, error) {\n\tif _screen == nil {\n\t\ts, e := tcell.NewScreen()\n\t\tif e != nil {\n\t\t\treturn nil, e\n\t\t}\n\t\tif !r.showCursor {\n\t\t\ts.HideCursor()\n\t\t}\n\t\t_screen = s\n\t}\n\treturn _screen, nil\n}\n\nfunc (r *FullscreenRenderer) initScreen() error {\n\ts, e := r.getScreen()\n\tif e != nil {\n\t\treturn e\n\t}\n\tif e = s.Init(); e != nil {\n\t\treturn e\n\t}\n\ts.EnablePaste()\n\tif r.mouse {\n\t\ts.EnableMouse()\n\t} else {\n\t\ts.DisableMouse()\n\t}\n\n\treturn nil\n}\n\nfunc (r *FullscreenRenderer) Init() error {\n\tif os.Getenv(\"TERM\") == \"cygwin\" {\n\t\tos.Setenv(\"TERM\", \"\")\n\t}\n\n\tif err := r.initScreen(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r *FullscreenRenderer) Top() int {\n\treturn 0\n}\n\nfunc (r *FullscreenRenderer) MaxX() int {\n\tncols, _ := _screen.Size()\n\treturn int(ncols)\n}\n\nfunc (r *FullscreenRenderer) MaxY() int {\n\t_, nlines := _screen.Size()\n\treturn int(nlines)\n}\n\nfunc (w *TcellWindow) X() int {\n\treturn w.lastX\n}\n\nfunc (w *TcellWindow) Y() int {\n\treturn w.lastY\n}\n\nfunc (r *FullscreenRenderer) Clear() {\n\t_screen.Sync()\n\t_screen.Clear()\n}\n\nfunc (r *FullscreenRenderer) NeedScrollbarRedraw() bool {\n\treturn true\n}\n\nfunc (r *FullscreenRenderer) ShouldEmitResizeEvent() bool {\n\treturn true\n}\n\nfunc (r *FullscreenRenderer) Refresh() {\n\t// noop\n}\n\n// TODO: Pixel width and height not implemented\nfunc (r *FullscreenRenderer) Size() TermSize {\n\tcols, lines := _screen.Size()\n\treturn TermSize{lines, cols, 0, 0}\n}\n\nfunc (r *FullscreenRenderer) GetChar(cancellable bool) Event {\n\tev := _screen.PollEvent()\n\tswitch ev := ev.(type) {\n\tcase *tcell.EventPaste:\n\t\tif ev.Start() {\n\t\t\treturn Event{BracketedPasteBegin, 0, nil}\n\t\t}\n\t\treturn Event{BracketedPasteEnd, 0, nil}\n\tcase *tcell.EventResize:\n\t\t// Ignore the first resize event\n\t\t// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18\n\t\tif _initialResize {\n\t\t\t_initialResize = false\n\t\t\treturn Event{Invalid, 0, nil}\n\t\t}\n\t\treturn Event{Resize, 0, nil}\n\n\t// process mouse events:\n\tcase *tcell.EventMouse:\n\t\t// mouse down events have zeroed buttons, so we can't use them\n\t\t// mouse up event consists of two events, 1. (main) event with modifier and other metadata, 2. event with zeroed buttons\n\t\t// so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons)\n\t\t// dragging has same structure, it only repeats the middle (main) event appropriately\n\t\tx, y := ev.Position()\n\n\t\tmod := ev.Modifiers()\n\t\tctrl := (mod & tcell.ModCtrl) > 0\n\t\talt := (mod & tcell.ModAlt) > 0\n\t\tshift := (mod & tcell.ModShift) > 0\n\n\t\t// since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton\n\t\tprevButton, button := _prevMouseButton, ev.Buttons()\n\t\t_prevMouseButton = button\n\t\tdrag := prevButton == button\n\n\t\tswitch {\n\t\tcase button&tcell.WheelDown != 0:\n\t\t\treturn Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, ctrl, alt, shift}}\n\t\tcase button&tcell.WheelUp != 0:\n\t\t\treturn Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, ctrl, alt, shift}}\n\t\tcase button&tcell.Button1 != 0:\n\t\t\tdouble := false\n\t\t\tif !drag {\n\t\t\t\t// all potential double click events put their coordinates in the clicks array\n\t\t\t\t// double click event has two conditions, temporal and spatial, the first is checked here\n\t\t\t\tnow := time.Now()\n\t\t\t\tif now.Sub(r.prevDownTime) < doubleClickDuration {\n\t\t\t\t\tr.clicks = append(r.clicks, [2]int{x, y})\n\t\t\t\t} else {\n\t\t\t\t\tr.clicks = [][2]int{{x, y}}\n\t\t\t\t}\n\t\t\t\tr.prevDownTime = now\n\n\t\t\t\t// detect double clicks (also check for spatial condition)\n\t\t\t\tn := len(r.clicks)\n\t\t\t\tdouble = n > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1]\n\t\t\t\tif double {\n\t\t\t\t\t// make sure two consecutive double clicks require four clicks\n\t\t\t\t\tr.clicks = [][2]int{}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// fire single or double click event\n\t\t\treturn Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, ctrl, alt, shift}}\n\t\tcase button&tcell.Button2 != 0:\n\t\t\treturn Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, ctrl, alt, shift}}\n\t\tdefault:\n\t\t\t// double and single taps on Windows don't quite work due to\n\t\t\t// the console acting on the events and not allowing us\n\t\t\t// to consume them.\n\t\t\tleft := button&tcell.Button1 != 0\n\t\t\tdown := left || button&tcell.Button3 != 0\n\t\t\tdouble := false\n\n\t\t\t// No need to report mouse movement events when no button is pressed\n\t\t\tif drag {\n\t\t\t\treturn Event{Invalid, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, ctrl, alt, shift}}\n\t\t}\n\n\t\t// process keyboard:\n\tcase *tcell.EventKey:\n\t\tmods := ev.Modifiers()\n\t\tnone := mods == tcell.ModNone\n\t\talt := (mods & tcell.ModAlt) > 0\n\t\tctrl := (mods & tcell.ModCtrl) > 0\n\t\tshift := (mods & tcell.ModShift) > 0\n\t\tctrlAlt := ctrl && alt\n\t\taltShift := alt && shift\n\t\tctrlShift := ctrl && shift\n\t\tctrlAltShift := ctrl && alt && shift\n\n\t\tkeyfn := func(r rune) Event {\n\t\t\tif alt {\n\t\t\t\treturn CtrlAltKey(r)\n\t\t\t}\n\t\t\treturn EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()\n\t\t}\n\t\tswitch ev.Key() {\n\t\t// section 1: Ctrl+(Alt)+[a-z]\n\t\tcase tcell.KeyCtrlA:\n\t\t\treturn keyfn('a')\n\t\tcase tcell.KeyCtrlB:\n\t\t\treturn keyfn('b')\n\t\tcase tcell.KeyCtrlC:\n\t\t\treturn keyfn('c')\n\t\tcase tcell.KeyCtrlD:\n\t\t\treturn keyfn('d')\n\t\tcase tcell.KeyCtrlE:\n\t\t\treturn keyfn('e')\n\t\tcase tcell.KeyCtrlF:\n\t\t\treturn keyfn('f')\n\t\tcase tcell.KeyCtrlG:\n\t\t\treturn keyfn('g')\n\t\tcase tcell.KeyCtrlH:\n\t\t\tswitch ev.Rune() {\n\t\t\tcase 0:\n\t\t\t\tif ctrlAlt {\n\t\t\t\t\treturn Event{CtrlAltBackspace, 0, nil}\n\t\t\t\t}\n\t\t\t\tif ctrl {\n\t\t\t\t\treturn Event{CtrlBackspace, 0, nil}\n\t\t\t\t}\n\t\t\tcase rune(tcell.KeyCtrlH):\n\t\t\t\tswitch {\n\t\t\t\tcase ctrl:\n\t\t\t\t\treturn keyfn('h')\n\t\t\t\tcase alt:\n\t\t\t\t\treturn Event{AltBackspace, 0, nil}\n\t\t\t\tcase none, shift:\n\t\t\t\t\treturn Event{Backspace, 0, nil}\n\t\t\t\t}\n\t\t\t}\n\t\tcase tcell.KeyCtrlI:\n\t\t\treturn keyfn('i')\n\t\tcase tcell.KeyCtrlJ:\n\t\t\treturn keyfn('j')\n\t\tcase tcell.KeyCtrlK:\n\t\t\treturn keyfn('k')\n\t\tcase tcell.KeyCtrlL:\n\t\t\treturn keyfn('l')\n\t\tcase tcell.KeyCtrlM:\n\t\t\treturn keyfn('m')\n\t\tcase tcell.KeyCtrlN:\n\t\t\treturn keyfn('n')\n\t\tcase tcell.KeyCtrlO:\n\t\t\treturn keyfn('o')\n\t\tcase tcell.KeyCtrlP:\n\t\t\treturn keyfn('p')\n\t\tcase tcell.KeyCtrlQ:\n\t\t\treturn keyfn('q')\n\t\tcase tcell.KeyCtrlR:\n\t\t\treturn keyfn('r')\n\t\tcase tcell.KeyCtrlS:\n\t\t\treturn keyfn('s')\n\t\tcase tcell.KeyCtrlT:\n\t\t\treturn keyfn('t')\n\t\tcase tcell.KeyCtrlU:\n\t\t\treturn keyfn('u')\n\t\tcase tcell.KeyCtrlV:\n\t\t\treturn keyfn('v')\n\t\tcase tcell.KeyCtrlW:\n\t\t\treturn keyfn('w')\n\t\tcase tcell.KeyCtrlX:\n\t\t\treturn keyfn('x')\n\t\tcase tcell.KeyCtrlY:\n\t\t\treturn keyfn('y')\n\t\tcase tcell.KeyCtrlZ:\n\t\t\treturn keyfn('z')\n\t\t// section 2: Ctrl+[ \\]_]\n\t\tcase tcell.KeyCtrlSpace:\n\t\t\treturn Event{CtrlSpace, 0, nil}\n\t\tcase tcell.KeyCtrlBackslash:\n\t\t\treturn Event{CtrlBackSlash, 0, nil}\n\t\tcase tcell.KeyCtrlRightSq:\n\t\t\treturn Event{CtrlRightBracket, 0, nil}\n\t\tcase tcell.KeyCtrlCarat:\n\t\t\treturn Event{CtrlCaret, 0, nil}\n\t\tcase tcell.KeyCtrlUnderscore:\n\t\t\treturn Event{CtrlSlash, 0, nil}\n\t\t// section 3: (Alt)+Backspace2\n\t\tcase tcell.KeyBackspace2:\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlBackspace, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltBackspace, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Backspace, 0, nil}\n\n\t\t// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)\n\t\tcase tcell.KeyUp:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftUp, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltUp, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftUp, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftUp, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlUp, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftUp, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltUp, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Up, 0, nil}\n\t\tcase tcell.KeyDown:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftDown, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltDown, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftDown, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftDown, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlDown, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftDown, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltDown, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Down, 0, nil}\n\t\tcase tcell.KeyLeft:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftLeft, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltLeft, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftLeft, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftLeft, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlLeft, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftLeft, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltLeft, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Left, 0, nil}\n\t\tcase tcell.KeyRight:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftRight, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltRight, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftRight, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftRight, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlRight, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftRight, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltRight, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Right, 0, nil}\n\n\t\t// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)\n\t\tcase tcell.KeyInsert:\n\t\t\treturn Event{Insert, 0, nil}\n\t\tcase tcell.KeyHome:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftHome, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltHome, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftHome, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftHome, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlHome, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftHome, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltHome, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Home, 0, nil}\n\t\tcase tcell.KeyDelete:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftDelete, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltDelete, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftDelete, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftDelete, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlDelete, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltDelete, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftDelete, 0, nil}\n\t\t\t}\n\t\t\treturn Event{Delete, 0, nil}\n\t\tcase tcell.KeyEnd:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftEnd, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltEnd, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftEnd, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftEnd, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlEnd, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftEnd, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltEnd, 0, nil}\n\t\t\t}\n\t\t\treturn Event{End, 0, nil}\n\t\tcase tcell.KeyPgUp:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftPageUp, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltPageUp, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftPageUp, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftPageUp, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlPageUp, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftPageUp, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltPageUp, 0, nil}\n\t\t\t}\n\t\t\treturn Event{PageUp, 0, nil}\n\t\tcase tcell.KeyPgDn:\n\t\t\tif ctrlAltShift {\n\t\t\t\treturn Event{CtrlAltShiftPageDown, 0, nil}\n\t\t\t}\n\t\t\tif ctrlAlt {\n\t\t\t\treturn Event{CtrlAltPageDown, 0, nil}\n\t\t\t}\n\t\t\tif ctrlShift {\n\t\t\t\treturn Event{CtrlShiftPageDown, 0, nil}\n\t\t\t}\n\t\t\tif altShift {\n\t\t\t\treturn Event{AltShiftPageDown, 0, nil}\n\t\t\t}\n\t\t\tif ctrl {\n\t\t\t\treturn Event{CtrlPageDown, 0, nil}\n\t\t\t}\n\t\t\tif shift {\n\t\t\t\treturn Event{ShiftPageDown, 0, nil}\n\t\t\t}\n\t\t\tif alt {\n\t\t\t\treturn Event{AltPageDown, 0, nil}\n\t\t\t}\n\t\t\treturn Event{PageDown, 0, nil}\n\t\tcase tcell.KeyBacktab:\n\t\t\treturn Event{ShiftTab, 0, nil}\n\t\tcase tcell.KeyF1:\n\t\t\treturn Event{F1, 0, nil}\n\t\tcase tcell.KeyF2:\n\t\t\treturn Event{F2, 0, nil}\n\t\tcase tcell.KeyF3:\n\t\t\treturn Event{F3, 0, nil}\n\t\tcase tcell.KeyF4:\n\t\t\treturn Event{F4, 0, nil}\n\t\tcase tcell.KeyF5:\n\t\t\treturn Event{F5, 0, nil}\n\t\tcase tcell.KeyF6:\n\t\t\treturn Event{F6, 0, nil}\n\t\tcase tcell.KeyF7:\n\t\t\treturn Event{F7, 0, nil}\n\t\tcase tcell.KeyF8:\n\t\t\treturn Event{F8, 0, nil}\n\t\tcase tcell.KeyF9:\n\t\t\treturn Event{F9, 0, nil}\n\t\tcase tcell.KeyF10:\n\t\t\treturn Event{F10, 0, nil}\n\t\tcase tcell.KeyF11:\n\t\t\treturn Event{F11, 0, nil}\n\t\tcase tcell.KeyF12:\n\t\t\treturn Event{F12, 0, nil}\n\n\t\t// section 6: (Ctrl+Alt)+'rune'\n\t\tcase tcell.KeyRune:\n\t\t\tr := ev.Rune()\n\n\t\t\tswitch {\n\t\t\t// translate native key events to ascii control characters\n\t\t\tcase r == ' ' && ctrl:\n\t\t\t\treturn Event{CtrlSpace, 0, nil}\n\t\t\t// handle AltGr characters\n\t\t\tcase ctrlAlt:\n\t\t\t\treturn Event{Rune, r, nil} // dropping modifiers\n\t\t\t// simple characters (possibly with modifier)\n\t\t\tcase alt:\n\t\t\t\treturn AltKey(r)\n\t\t\tdefault:\n\t\t\t\treturn Event{Rune, r, nil}\n\t\t\t}\n\n\t\t// section 7: Esc\n\t\tcase tcell.KeyEsc:\n\t\t\treturn Event{Esc, 0, nil}\n\t\t}\n\t}\n\n\t// section 8: Invalid\n\treturn Event{Invalid, 0, nil}\n}\n\nfunc (r *FullscreenRenderer) CancelGetChar() {\n\t// TODO\n}\n\nfunc (r *FullscreenRenderer) Pause(clear bool) {\n\tif clear {\n\t\t_screen.Suspend()\n\t}\n}\n\nfunc (r *FullscreenRenderer) Resume(clear bool, sigcont bool) {\n\tif clear {\n\t\t_screen.Resume()\n\t}\n}\n\nfunc (r *FullscreenRenderer) Close() {\n\t_screen.Fini()\n\t_screen = nil\n}\n\nfunc (r *FullscreenRenderer) RefreshWindows(windows []Window) {\n\t// TODO\n\tfor _, w := range windows {\n\t\tw.Refresh()\n\t}\n\t_screen.Show()\n}\n\nfunc (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {\n\twidth = max(0, width)\n\theight = max(0, height)\n\tnormal := ColBorder\n\tswitch windowType {\n\tcase WindowList:\n\t\tnormal = ColNormal\n\tcase WindowHeader:\n\t\tnormal = ColHeader\n\tcase WindowFooter:\n\t\tnormal = ColFooter\n\tcase WindowInput:\n\t\tnormal = ColInput\n\tcase WindowPreview:\n\t\tnormal = ColPreview\n\t}\n\tw := &TcellWindow{\n\t\tcolor:       r.theme.Colored,\n\t\twindowType:  windowType,\n\t\ttop:         top,\n\t\tleft:        left,\n\t\twidth:       width,\n\t\theight:      height,\n\t\tnormal:      normal,\n\t\tborderStyle: borderStyle,\n\t\tshowCursor:  r.showCursor,\n\t\ttabstop:     r.tabstop}\n\tw.Erase()\n\treturn w\n}\n\nfunc fill(x, y, w, h int, n ColorPair, r rune) {\n\tfor ly := 0; ly <= h; ly++ {\n\t\tfor lx := 0; lx <= w; lx++ {\n\t\t\t_screen.SetContent(x+lx, y+ly, r, nil, n.style())\n\t\t}\n\t}\n}\n\nfunc (w *TcellWindow) Erase() {\n\tfill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')\n\tw.drawBorder(false)\n}\n\nfunc (w *TcellWindow) EraseMaybe() bool {\n\tw.Erase()\n\treturn true\n}\n\nfunc (w *TcellWindow) SetWrapSign(sign string, width int) {\n\tw.wrapSign = sign\n\tw.wrapSignWidth = width\n}\n\nfunc (w *TcellWindow) EncloseX(x int) bool {\n\treturn x >= w.left && x < (w.left+w.width)\n}\n\nfunc (w *TcellWindow) EncloseY(y int) bool {\n\treturn y >= w.top && y < (w.top+w.height)\n}\n\nfunc (w *TcellWindow) Enclose(y int, x int) bool {\n\treturn w.EncloseX(x) && w.EncloseY(y)\n}\n\nfunc (w *TcellWindow) Move(y int, x int) {\n\tw.lastX = x\n\tw.lastY = y\n\tw.moveCursor = true\n}\n\nfunc (w *TcellWindow) MoveAndClear(y int, x int) {\n\tw.Move(y, x)\n\tfor i := w.lastX; i < w.width; i++ {\n\t\t_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())\n\t}\n\tw.lastX = x\n}\n\nfunc (w *TcellWindow) Print(text string) {\n\tw.printString(text, w.normal)\n}\n\nfunc (w *TcellWindow) withUrl(style tcell.Style) tcell.Style {\n\tif w.uri != nil {\n\t\tstyle = style.Url(*w.uri)\n\t\tif md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 {\n\t\t\tstyle = style.UrlId(md[1])\n\t\t}\n\t}\n\treturn style\n}\n\nfunc underlineStyleFromAttr(a Attr) tcell.UnderlineStyle {\n\tswitch a.UnderlineStyle() {\n\tcase UlStyleDouble:\n\t\treturn tcell.UnderlineStyleDouble\n\tcase UlStyleCurly:\n\t\treturn tcell.UnderlineStyleCurly\n\tcase UlStyleDotted:\n\t\treturn tcell.UnderlineStyleDotted\n\tcase UlStyleDashed:\n\t\treturn tcell.UnderlineStyleDashed\n\tdefault:\n\t\treturn tcell.UnderlineStyleSolid\n\t}\n}\n\nfunc (w *TcellWindow) printString(text string, pair ColorPair) {\n\tlx := 0\n\ta := pair.Attr()\n\n\tstyle := pair.style()\n\tif a&AttrClear == 0 {\n\t\tstyle = style.\n\t\t\tReverse(a&Attr(tcell.AttrReverse) != 0).\n\t\t\tStrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).\n\t\t\tItalic(a&Attr(tcell.AttrItalic) != 0).\n\t\t\tBlink(a&Attr(tcell.AttrBlink) != 0).\n\t\t\tDim(a&Attr(tcell.AttrDim) != 0)\n\t\tif a&Attr(tcell.AttrUnderline) != 0 {\n\t\t\tstyle = style.Underline(underlineStyleFromAttr(a))\n\t\t\tif pair.Ul() != colDefault {\n\t\t\t\tstyle = style.Underline(asTcellColor(pair.Ul()))\n\t\t\t}\n\t\t} else {\n\t\t\tstyle = style.Underline(false)\n\t\t}\n\t}\n\tstyle = w.withUrl(style)\n\n\tgr := uniseg.NewGraphemes(text)\n\tfor gr.Next() {\n\t\tst := style\n\t\trs := gr.Runes()\n\n\t\tif len(rs) == 1 {\n\t\t\tr := rs[0]\n\t\t\tif r == '\\r' {\n\t\t\t\tst = style.Dim(true)\n\t\t\t\trs[0] = '␍'\n\t\t\t} else if r == '\\n' {\n\t\t\t\tst = style.Dim(true)\n\t\t\t\trs[0] = '␊'\n\t\t\t} else if r < rune(' ') { // ignore control characters\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tvar xPos = w.left + w.lastX + lx\n\t\tvar yPos = w.top + w.lastY\n\t\tif xPos < (w.left+w.width) && yPos < (w.top+w.height) {\n\t\t\t_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)\n\t\t}\n\t\tlx += util.StringWidth(string(rs))\n\t}\n\tw.lastX += lx\n}\n\nfunc (w *TcellWindow) CPrint(pair ColorPair, text string) {\n\tw.printString(text, pair)\n}\n\nfunc (w *TcellWindow) pairStyle(pair ColorPair) tcell.Style {\n\ta := pair.Attr()\n\tvar style tcell.Style\n\tif w.color {\n\t\tstyle = pair.style()\n\t} else {\n\t\tstyle = w.normal.style()\n\t}\n\tstyle = style.\n\t\tBlink(a&Attr(tcell.AttrBlink) != 0).\n\t\tBold(a&Attr(tcell.AttrBold) != 0 || a&BoldForce != 0).\n\t\tDim(a&Attr(tcell.AttrDim) != 0).\n\t\tReverse(a&Attr(tcell.AttrReverse) != 0).\n\t\tStrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).\n\t\tItalic(a&Attr(tcell.AttrItalic) != 0)\n\tif a&Attr(tcell.AttrUnderline) != 0 {\n\t\tstyle = style.Underline(underlineStyleFromAttr(a))\n\t\tif pair.Ul() != colDefault {\n\t\t\tstyle = style.Underline(asTcellColor(pair.Ul()))\n\t\t}\n\t} else {\n\t\tstyle = style.Underline(false)\n\t}\n\treturn w.withUrl(style)\n}\n\nfunc (w *TcellWindow) renderGraphemes(text string, style tcell.Style) {\n\tgr := uniseg.NewGraphemes(text)\n\tfor gr.Next() {\n\t\tst := style\n\t\trs := gr.Runes()\n\t\tif len(rs) == 1 && rs[0] == '\\r' {\n\t\t\tst = style.Dim(true)\n\t\t\trs[0] = '␍'\n\t\t}\n\n\t\txPos := w.left + w.lastX\n\t\tyPos := w.top + w.lastY\n\t\tif xPos < (w.left+w.width) && yPos < (w.top+w.height) {\n\t\t\t_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)\n\t\t}\n\t\tw.lastX += util.StringWidth(string(rs))\n\t}\n}\n\nfunc (w *TcellWindow) renderWrapSign(style tcell.Style) {\n\tsign := w.wrapSign\n\tif w.wrapSignWidth > w.width {\n\t\trunes, _ := util.Truncate(sign, w.width)\n\t\tsign = string(runes)\n\t}\n\tgr := uniseg.NewGraphemes(sign)\n\tfor gr.Next() {\n\t\trs := gr.Runes()\n\t\t_screen.SetContent(w.left+w.lastX, w.top+w.lastY, rs[0], rs[1:], style.Dim(true))\n\t\tw.lastX += uniseg.StringWidth(string(rs))\n\t}\n}\n\nfunc (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {\n\tstyle := w.pairStyle(pair)\n\n\tfor i, segment := range strings.Split(text, \"\\n\") {\n\t\tfor j, wl := range WrapLine(segment, w.lastX, w.width, w.tabstop, w.wrapSignWidth) {\n\t\t\tif i > 0 || j > 0 {\n\t\t\t\tw.lastY++\n\t\t\t\tif w.lastY >= w.height {\n\t\t\t\t\treturn FillSuspend\n\t\t\t\t}\n\t\t\t\tw.lastX = 0\n\t\t\t\tif j > 0 {\n\t\t\t\t\tw.renderWrapSign(style)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif w.lastX < w.width {\n\t\t\t\tw.renderGraphemes(wl.Text, style)\n\t\t\t}\n\t\t}\n\t}\n\tif w.lastX >= w.width {\n\t\tw.lastY++\n\t\tw.lastX = 0\n\t\treturn FillNextLine\n\t}\n\n\treturn FillContinue\n}\n\nfunc (w *TcellWindow) Fill(str string) FillReturn {\n\treturn w.fillString(str, w.normal)\n}\n\nfunc (w *TcellWindow) LinkBegin(uri string, params string) {\n\tw.uri = &uri\n\tw.params = &params\n}\n\nfunc (w *TcellWindow) LinkEnd() {\n\tw.uri = nil\n\tw.params = nil\n}\n\nfunc (w *TcellWindow) CFill(fg Color, bg Color, ul Color, a Attr, str string) FillReturn {\n\tif fg == colDefault {\n\t\tfg = w.normal.Fg()\n\t}\n\tif bg == colDefault {\n\t\tbg = w.normal.Bg()\n\t}\n\treturn w.fillString(str, NewColorPair(fg, bg, a).WithUl(ul))\n}\n\nfunc (w *TcellWindow) DrawBorder() {\n\tw.drawBorder(false)\n}\n\nfunc (w *TcellWindow) DrawHBorder() {\n\tw.drawBorder(true)\n}\n\nfunc (w *TcellWindow) drawBorder(onlyHorizontal bool) {\n\tif w.height == 0 {\n\t\treturn\n\t}\n\tshape := w.borderStyle.shape\n\tif shape == BorderNone {\n\t\treturn\n\t}\n\n\tleft := w.left\n\tright := left + w.width\n\ttop := w.top\n\tbot := top + w.height\n\n\tvar style tcell.Style\n\tif w.color {\n\t\tswitch w.windowType {\n\t\tcase WindowBase:\n\t\t\tstyle = ColBorder.style()\n\t\tcase WindowList:\n\t\t\tstyle = ColListBorder.style()\n\t\tcase WindowHeader:\n\t\t\tstyle = ColHeaderBorder.style()\n\t\tcase WindowFooter:\n\t\t\tstyle = ColFooterBorder.style()\n\t\tcase WindowInput:\n\t\t\tstyle = ColInputBorder.style()\n\t\tcase WindowPreview:\n\t\t\tstyle = ColPreviewBorder.style()\n\t\t}\n\t} else {\n\t\tstyle = w.normal.style()\n\t}\n\n\thw := runeWidth(w.borderStyle.top)\n\tswitch shape {\n\tcase BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:\n\t\tmax := right - 2*hw\n\t\tif shape == BorderHorizontal || shape == BorderTop {\n\t\t\tmax = right - hw\n\t\t}\n\t\t// tcell has an issue displaying two overlapping wide runes\n\t\t// e.g.  SetContent(  HH  )\n\t\t//       SetContent(   TR )\n\t\t//       ==================\n\t\t//                 (  HH  ) => TR is ignored\n\t\tfor x := left; x <= max; x += hw {\n\t\t\t_screen.SetContent(x, top, w.borderStyle.top, nil, style)\n\t\t}\n\t}\n\tswitch shape {\n\tcase BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:\n\t\tmax := right - 2*hw\n\t\tif shape == BorderHorizontal || shape == BorderBottom {\n\t\t\tmax = right - hw\n\t\t}\n\t\tfor x := left; x <= max; x += hw {\n\t\t\t_screen.SetContent(x, bot-1, w.borderStyle.bottom, nil, style)\n\t\t}\n\t}\n\tif !onlyHorizontal {\n\t\tswitch shape {\n\t\tcase BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:\n\t\t\tfor y := top; y < bot; y++ {\n\t\t\t\t_screen.SetContent(left, y, w.borderStyle.left, nil, style)\n\t\t\t}\n\t\t}\n\t\tswitch shape {\n\t\tcase BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:\n\t\t\tvw := runeWidth(w.borderStyle.right)\n\t\t\tfor y := top; y < bot; y++ {\n\t\t\t\t_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)\n\t\t\t}\n\t\t}\n\t}\n\tswitch shape {\n\tcase BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:\n\t\t_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)\n\t\t_screen.SetContent(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)\n\t\t_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)\n\t\t_screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)\n\t}\n}\n"
  },
  {
    "path": "src/tui/tcell_test.go",
    "content": "//go:build tcell || windows\n\npackage tui\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc assert(t *testing.T, context string, got any, want any) bool {\n\tif got == want {\n\t\treturn true\n\t} else {\n\t\tt.Errorf(\"%s = (%T)%v, want (%T)%v\", context, got, got, want, want)\n\t\treturn false\n\t}\n}\n\n// Test the handling of the tcell keyboard events.\nfunc TestGetCharEventKey(t *testing.T) {\n\tif util.IsTty(os.Stdout) {\n\t\t// This test is skipped when output goes to terminal, because it causes\n\t\t// some glitches:\n\t\t// - output lines may not start at the beginning of a row which makes\n\t\t//   the output unreadable\n\t\t// - terminal may get cleared which prevents you from seeing results of\n\t\t//   previous tests\n\t\t// Good ways to prevent the glitches are piping the output to a pager\n\t\t// or redirecting to a file. I've found `less +G` to be trouble-free.\n\t\tt.Skip(\"Skipped because this test misbehaves in terminal, pipe to a pager or redirect output to a file to run it safely.\")\n\t} else if testing.Verbose() {\n\t\t// I have observed a behaviour when this test outputted more than 8192\n\t\t// bytes (32*256) into the 'less' pager, both the go's test executable\n\t\t// and the pager hanged. The go's executable was blocking on printing.\n\t\t// I was able to create minimal working example of that behaviour, but\n\t\t// that example hanged after 12256 bytes (32*(256+127)).\n\t\tt.Log(\"If you are piping this test to a pager and it hangs, make the pager greedy for input, e.g. 'less +G'.\")\n\t}\n\n\tif !HasFullscreenRenderer() {\n\t\tt.Skip(\"Can't test FullscreenRenderer.\")\n\t}\n\n\t// construct test cases\n\ttype giveKey struct {\n\t\tType tcell.Key\n\t\tChar rune\n\t\tMods tcell.ModMask\n\t}\n\ttype wantKey = Event\n\ttype testCase struct {\n\t\tgiveKey\n\t\twantKey\n\t}\n\t/*\n\t\tSome test cases are marked \"fabricated\". It means that giveKey value\n\t\tis valid, but it is not what you get when you press the keys. For\n\t\texample Ctrl+C will NOT give you tcell.KeyCtrlC, but tcell.KeyETX\n\t\t(End-Of-Text character, causing SIGINT).\n\t\tI was trying to accompany the fabricated test cases with real ones.\n\n\t\tSome test cases are marked \"unhandled\". It means that giveKey.Type\n\t\tis not present in tcell.go source code. It can still be handled via\n\t\timplicit or explicit alias.\n\n\t\tIf not said otherwise, test cases are for US keyboard.\n\n\t\t(tabstop=44)\n\t*/\n\ttests := []testCase{\n\n\t\t// section 1: Ctrl+(Alt)+[a-z]\n\t\t{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl}, wantKey{CtrlA, 0, nil}},\n\t\t{giveKey{tcell.KeyCtrlC, rune(tcell.KeyCtrlC), tcell.ModCtrl}, wantKey{CtrlC, 0, nil}}, // fabricated\n\t\t{giveKey{tcell.KeyETX, rune(tcell.KeyETX), tcell.ModCtrl}, wantKey{CtrlC, 0, nil}},     // this is SIGINT (Ctrl+C)\n\t\t{giveKey{tcell.KeyCtrlZ, rune(tcell.KeyCtrlZ), tcell.ModCtrl}, wantKey{CtrlZ, 0, nil}}, // fabricated\n\t\t// KeyTab is alias for KeyTAB\n\t\t{giveKey{tcell.KeyCtrlI, rune(tcell.KeyCtrlI), tcell.ModCtrl}, wantKey{Tab, 0, nil}}, // fabricated\n\t\t{giveKey{tcell.KeyTab, rune(tcell.KeyTab), tcell.ModNone}, wantKey{Tab, 0, nil}},     // unhandled, actual \"Tab\" keystroke\n\t\t{giveKey{tcell.KeyTAB, rune(tcell.KeyTAB), tcell.ModNone}, wantKey{Tab, 0, nil}},     // fabricated, unhandled\n\t\t// KeyEnter is alias for KeyCR\n\t\t{giveKey{tcell.KeyCtrlM, rune(tcell.KeyCtrlM), tcell.ModNone}, wantKey{Enter, 0, nil}}, // actual \"Enter\" keystroke\n\t\t{giveKey{tcell.KeyCR, rune(tcell.KeyCR), tcell.ModNone}, wantKey{Enter, 0, nil}},       // fabricated, unhandled\n\t\t{giveKey{tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone}, wantKey{Enter, 0, nil}}, // fabricated, unhandled\n\t\t// Ctrl+Alt keys\n\t\t{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'a', nil}},                  // fabricated\n\t\t{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'a', nil}}, // fabricated\n\n\t\t// section 2: Ctrl+[ \\]_]\n\t\t{giveKey{tcell.KeyCtrlSpace, rune(tcell.KeyCtrlSpace), tcell.ModCtrl}, wantKey{CtrlSpace, 0, nil}}, // fabricated\n\t\t{giveKey{tcell.KeyNUL, rune(tcell.KeyNUL), tcell.ModNone}, wantKey{CtrlSpace, 0, nil}},             // fabricated, unhandled\n\t\t{giveKey{tcell.KeyRune, ' ', tcell.ModCtrl}, wantKey{CtrlSpace, 0, nil}},                           // actual Ctrl+' '\n\t\t{giveKey{tcell.KeyCtrlBackslash, rune(tcell.KeyCtrlBackslash), tcell.ModCtrl}, wantKey{CtrlBackSlash, 0, nil}},\n\t\t{giveKey{tcell.KeyCtrlRightSq, rune(tcell.KeyCtrlRightSq), tcell.ModCtrl}, wantKey{CtrlRightBracket, 0, nil}},\n\t\t{giveKey{tcell.KeyCtrlCarat, rune(tcell.KeyCtrlCarat), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlCaret, 0, nil}}, // fabricated\n\t\t{giveKey{tcell.KeyRS, rune(tcell.KeyRS), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlCaret, 0, nil}},               // actual Ctrl+Shift+6 (i.e. Ctrl+^) keystroke\n\t\t{giveKey{tcell.KeyCtrlUnderscore, rune(tcell.KeyCtrlUnderscore), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlSlash, 0, nil}},\n\n\t\t// section 3: (Alt)+Backspace2\n\t\t// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)\n\t\t// KeyDelete = 0x2E (VK_DELETE constant in Windows)\n\t\t// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)\n\t\t{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}},   // fabricated\n\t\t{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // fabricated\n\t\t{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}},          // fabricated, unhandled\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Delete, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}},\n\t\t{giveKey{tcell.KeyBackspace, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}},\n\t\t{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},                                                  // fabricated, unhandled\n\t\t{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},                                                         // fabricated, unhandled\n\t\t{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},                                                      // fabricated, unhandled\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{Backspace, 0, nil}},                                 // actual \"Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}},                               // actual \"Alt+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{CtrlBackspace, 0, nil}},                                 // actual \"Ctrl+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{Backspace, 0, nil}},                                // actual \"Shift+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltBackspace, 0, nil}},                              // actual \"Ctrl+Alt+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlBackspace, 0, nil}},                               // actual \"Ctrl+Shift+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}},              // actual \"Shift+Alt+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAltBackspace, 0, nil}},             // actual \"Ctrl+Shift+Alt+Backspace\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}},                                     // actual \"Ctrl+H\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}},                  // fabricated \"Ctrl+Alt+H\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}},                    // actual \"Ctrl+Shift+H\" keystroke\n\t\t{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'h', nil}}, // fabricated \"Ctrl+Shift+Alt+H\" keystroke\n\n\t\t// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModNone}, wantKey{Down, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModNone}, wantKey{Left, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModNone}, wantKey{Right, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModNone}, wantKey{Down, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModNone}, wantKey{Right, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModNone}, wantKey{Left, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModCtrl}, wantKey{CtrlUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModCtrl}, wantKey{CtrlDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModCtrl}, wantKey{CtrlRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModCtrl}, wantKey{CtrlLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModShift}, wantKey{ShiftUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModShift}, wantKey{ShiftDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModShift}, wantKey{ShiftRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{ShiftLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModAlt}, wantKey{AltUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModAlt}, wantKey{AltRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModAlt}, wantKey{AltLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUp, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftUp, 0, nil}},\n\t\t{giveKey{tcell.KeyDown, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftDown, 0, nil}},\n\t\t{giveKey{tcell.KeyRight, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftRight, 0, nil}},\n\t\t{giveKey{tcell.KeyLeft, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftLeft, 0, nil}},\n\t\t{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},    // fabricated, unhandled\n\t\t{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},   // fabricated, unhandled\n\t\t{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},  // fabricated, unhandled\n\t\t{giveKey{tcell.KeyDownRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled\n\t\t{giveKey{tcell.KeyCenter, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},    // fabricated, unhandled\n\t\t// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)\n\t\t{giveKey{tcell.KeyInsert, 0, tcell.ModNone}, wantKey{Insert, 0, nil}},\n\t\t{giveKey{tcell.KeyF1, 0, tcell.ModNone}, wantKey{F1, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModNone}, wantKey{Home, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModNone}, wantKey{End, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Delete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModNone}, wantKey{PageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModNone}, wantKey{PageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModCtrl}, wantKey{CtrlHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModCtrl}, wantKey{CtrlEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModCtrl}, wantKey{CtrlDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModCtrl}, wantKey{CtrlPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModCtrl}, wantKey{CtrlPageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModShift}, wantKey{ShiftHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModShift}, wantKey{ShiftEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModShift}, wantKey{ShiftDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModShift}, wantKey{ShiftPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModShift}, wantKey{ShiftPageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModAlt}, wantKey{AltHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModAlt}, wantKey{AltEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{AltDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModAlt}, wantKey{AltPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModAlt}, wantKey{AltPageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlShiftPageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAltPageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftPageDown, 0, nil}},\n\t\t{giveKey{tcell.KeyHome, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftHome, 0, nil}},\n\t\t{giveKey{tcell.KeyEnd, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftEnd, 0, nil}},\n\t\t{giveKey{tcell.KeyDelete, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftDelete, 0, nil}},\n\t\t{giveKey{tcell.KeyPgUp, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftPageUp, 0, nil}},\n\t\t{giveKey{tcell.KeyPgDn, 0, tcell.ModCtrl | tcell.ModShift | tcell.ModAlt}, wantKey{CtrlAltShiftPageDown, 0, nil}},\n\t\t// section 6: (Ctrl+Alt)+'rune'\n\t\t{giveKey{tcell.KeyRune, 'a', tcell.ModNone}, wantKey{Rune, 'a', nil}},\n\t\t{giveKey{tcell.KeyRune, 'a', tcell.ModCtrl}, wantKey{Rune, 'a', nil}}, // fabricated\n\t\t{giveKey{tcell.KeyRune, 'a', tcell.ModAlt}, wantKey{Alt, 'a', nil}},\n\t\t{giveKey{tcell.KeyRune, 'A', tcell.ModAlt}, wantKey{Alt, 'A', nil}},\n\t\t{giveKey{tcell.KeyRune, '`', tcell.ModAlt}, wantKey{Alt, '`', nil}},\n\t\t/*\n\t\t\t\"Input method\" in Windows Language options:\n\t\t\tUS: \"US Keyboard\" does not generate any characters (and thus any events) in Ctrl+Alt+[a-z] range\n\t\t\tCS: \"Czech keyboard\"\n\t\t\tDE: \"German keyboard\"\n\n\t\t\tNote that right Alt is not just `tcell.ModAlt` on foreign language keyboards, but it is the AltGr `tcell.ModCtrl|tcell.ModAlt`.\n\t\t*/\n\t\t{giveKey{tcell.KeyRune, '{', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '{', nil}}, // CS: Ctrl+Alt+b = \"{\" // Note that this does not interfere with CtrlB, since the \"b\" is replaced with \"{\" on OS level\n\t\t{giveKey{tcell.KeyRune, '$', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '$', nil}}, // CS: Ctrl+Alt+ů = \"$\"\n\t\t{giveKey{tcell.KeyRune, '~', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '~', nil}}, // CS: Ctrl+Alt++ = \"~\"\n\t\t{giveKey{tcell.KeyRune, '`', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '`', nil}}, // CS: Ctrl+Alt+ý,Space = \"`\" // this is dead key, space is required to emit the char\n\n\t\t{giveKey{tcell.KeyRune, '{', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '{', nil}}, // DE: Ctrl+Alt+7 = \"{\"\n\t\t{giveKey{tcell.KeyRune, '@', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '@', nil}}, // DE: Ctrl+Alt+q = \"@\"\n\t\t{giveKey{tcell.KeyRune, 'µ', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, 'µ', nil}}, // DE: Ctrl+Alt+m = \"µ\"\n\n\t\t// section 7: Esc\n\t\t// KeyEsc and KeyEscape are aliases for KeyESC\n\t\t{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{Esc, 0, nil}},               // fabricated\n\t\t{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{Esc, 0, nil}},               // unhandled\n\t\t{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{Esc, 0, nil}},         // fabricated, unhandled\n\t\t{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{Esc, 0, nil}},               // actual Ctrl+[ keystroke\n\t\t{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // fabricated, unhandled\n\n\t\t// section 8: Invalid\n\t\t{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated\n\t\t{giveKey{tcell.KeyF24, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},\n\t\t{giveKey{tcell.KeyHelp, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},   // fabricated, unhandled\n\t\t{giveKey{tcell.KeyExit, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},   // fabricated, unhandled\n\t\t{giveKey{tcell.KeyClear, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},  // unhandled, actual keystroke Numpad_5 with Numlock OFF\n\t\t{giveKey{tcell.KeyCancel, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled\n\t\t{giveKey{tcell.KeyPrint, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},  // fabricated, unhandled\n\t\t{giveKey{tcell.KeyPause, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},  // unhandled\n\n\t}\n\tr := NewFullscreenRenderer(&ColorTheme{}, false, false, 8)\n\tr.Init()\n\n\t// run and evaluate the tests\n\tinitialResizeAsInvalid := true\n\tfor _, test := range tests {\n\t\t// generate key event\n\t\tgiveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods)\n\t\t_screen.PostEventWait(giveEvent)\n\t\tt.Logf(\"giveEvent = %T{key: %v, ch: %q (%[3]v), mod: %#04b}\\n\", giveEvent, giveEvent.Key(), giveEvent.Rune(), giveEvent.Modifiers())\n\n\t\t// process the event in fzf and evaluate the test\n\t\tgotEvent := r.GetChar(true)\n\t\t// skip Resize events, those are sometimes put in the buffer outside of this test\n\t\tif initialResizeAsInvalid && gotEvent.Type == Invalid {\n\t\t\tt.Logf(\"Resize as Invalid swallowed\")\n\t\t\tinitialResizeAsInvalid = false\n\t\t\tgotEvent = r.GetChar(true)\n\t\t}\n\t\tif gotEvent.Type == Resize {\n\t\t\tt.Logf(\"Resize swallowed\")\n\t\t\tgotEvent = r.GetChar(true)\n\t\t}\n\t\tt.Logf(\"wantEvent = %T{Type: %v, Char: %q (%[3]v)}\\n\", test.wantKey, test.wantKey.Type, test.wantKey.Char)\n\t\tt.Logf(\"gotEvent = %T{Type: %v, Char: %q (%[3]v)}\\n\", gotEvent, gotEvent.Type, gotEvent.Char)\n\n\t\tassert(t, \"r.GetChar(true).Type\", gotEvent.Type, test.wantKey.Type)\n\t\tassert(t, \"r.GetChar(true).Char\", gotEvent.Char, test.wantKey.Char)\n\t}\n\n\tr.Close()\n}\n\n/*\nQuick reference\n---------------\n\n(tabstop=18)\n(this is not mapping table, it merely puts multiple constants ranges in one table)\n\n¹) the two columns are each other implicit alias\n²) explicit aliases here\n\n%v\tsection #\ttcell ctrl key¹\ttcell ctrl char¹\ttcell alias²\ttui constants\ttcell named keys\ttcell mods\n--\t---------\t--------------\t---------------\t-----------\t-------------\t----------------\t----------\n0\t2\tKeyCtrlSpace\tKeyNUL = ^@ \t\tRune\t\tModNone\n1\t1\tKeyCtrlA\tKeySOH = ^A\t\tCtrlA\t\tModShift\n2\t1\tKeyCtrlB\tKeySTX = ^B\t\tCtrlB\t\tModCtrl\n3\t1\tKeyCtrlC\tKeyETX = ^C\t\tCtrlC\n4\t1\tKeyCtrlD\tKeyEOT = ^D\t\tCtrlD\t\tModAlt\n5\t1\tKeyCtrlE\tKeyENQ = ^E\t\tCtrlE\n6\t1\tKeyCtrlF\tKeyACK = ^F\t\tCtrlF\n7\t1\tKeyCtrlG\tKeyBEL = ^G\t\tCtrlG\n8\t1\tKeyCtrlH\tKeyBS = ^H\tKeyBackspace\tCtrlH\t\tModMeta\n9\t1\tKeyCtrlI\tKeyTAB = ^I\tKeyTab\tTab\n10\t1\tKeyCtrlJ\tKeyLF = ^J\t\tCtrlJ\n11\t1\tKeyCtrlK\tKeyVT = ^K\t\tCtrlK\n12\t1\tKeyCtrlL\tKeyFF = ^L\t\tCtrlL\n13\t1\tKeyCtrlM\tKeyCR = ^M\tKeyEnter\tEnter\n14\t1\tKeyCtrlN\tKeySO = ^N\t\tCtrlN\n15\t1\tKeyCtrlO\tKeySI = ^O\t\tCtrlO\n16\t1\tKeyCtrlP\tKeyDLE = ^P\t\tCtrlP\n17\t1\tKeyCtrlQ\tKeyDC1 = ^Q\t\tCtrlQ\n18\t1\tKeyCtrlR\tKeyDC2 = ^R\t\tCtrlR\n19\t1\tKeyCtrlS\tKeyDC3 = ^S\t\tCtrlS\n20\t1\tKeyCtrlT\tKeyDC4 = ^T\t\tCtrlT\n21\t1\tKeyCtrlU\tKeyNAK = ^U\t\tCtrlU\n22\t1\tKeyCtrlV\tKeySYN = ^V\t\tCtrlV\n23\t1\tKeyCtrlW\tKeyETB = ^W\t\tCtrlW\n24\t1\tKeyCtrlX\tKeyCAN = ^X\t\tCtrlX\n25\t1\tKeyCtrlY\tKeyEM = ^Y\t\tCtrlY\n26\t1\tKeyCtrlZ\tKeySUB = ^Z\t\tCtrlZ\n27\t7\tKeyCtrlLeftSq\tKeyESC = ^[\tKeyEsc, KeyEscape\tESC\n28\t2\tKeyCtrlBackslash\tKeyFS = ^\\\t\tCtrlSpace\n29\t2\tKeyCtrlRightSq\tKeyGS = ^]\t\tCtrlBackSlash\n30\t2\tKeyCtrlCarat\tKeyRS = ^^\t\tCtrlRightBracket\n31\t2\tKeyCtrlUnderscore\tKeyUS = ^_\t\tCtrlCaret\n32\t\t\t\t\tCtrlSlash\n33\t\t\t\t\tInvalid\n34\t\t\t\t\tResize\n35\t\t\t\t\tMouse\n36\t\t\t\t\tDoubleClick\n37\t\t\t\t\tLeftClick\n38\t\t\t\t\tRightClick\n39\t\t\t\t\tBTab\n40\t\t\t\t\tBackspace\n41\t\t\t\t\tDel\n42\t\t\t\t\tPgUp\n43\t\t\t\t\tPgDn\n44\t\t\t\t\tUp\n45\t\t\t\t\tDown\n46\t\t\t\t\tLeft\n47\t\t\t\t\tRight\n48\t\t\t\t\tHome\n49\t\t\t\t\tEnd\n50\t\t\t\t\tInsert\n51\t\t\t\t\tSUp\n52\t\t\t\t\tSDown\n53\t\t\t\t\tShiftLeft\n54\t\t\t\t\tSRight\n55\t\t\t\t\tF1\n56\t\t\t\t\tF2\n57\t\t\t\t\tF3\n58\t\t\t\t\tF4\n59\t\t\t\t\tF5\n60\t\t\t\t\tF6\n61\t\t\t\t\tF7\n62\t\t\t\t\tF8\n63\t\t\t\t\tF9\n64\t\t\t\t\tF10\n65\t\t\t\t\tF11\n66\t\t\t\t\tF12\n67\t\t\t\t\tChange\n68\t\t\t\t\tBackwardEOF\n69\t\t\t\t\tAltBackspace\n70\t\t\t\t\tAltUp\n71\t\t\t\t\tAltDown\n72\t\t\t\t\tAltLeft\n73\t\t\t\t\tAltRight\n74\t\t\t\t\tAltSUp\n75\t\t\t\t\tAltSDown\n76\t\t\t\t\tAltShiftLeft\n77\t\t\t\t\tAltShiftRight\n78\t\t\t\t\tAlt\n79\t\t\t\t\tCtrlAlt\n..\n127\t3\t\t  KeyDEL\tKeyBackspace2\n..\n256\t6\t\t\t\t\tKeyRune\n257\t4\t\t\t\t\tKeyUp\n258\t4\t\t\t\t\tKeyDown\n259\t4\t\t\t\t\tKeyRight\n260\t4\t\t\t\t\tKeyLeft\n261\t8\t\t\t\t\tKeyUpLeft\n262\t8\t\t\t\t\tKeyUpRight\n263\t8\t\t\t\t\tKeyDownLeft\n264\t8\t\t\t\t\tKeyDownRight\n265\t8\t\t\t\t\tKeyCenter\n266\t5\t\t\t\t\tKeyPgUp\n267\t5\t\t\t\t\tKeyPgDn\n268\t5\t\t\t\t\tKeyHome\n269\t5\t\t\t\t\tKeyEnd\n270\t5\t\t\t\t\tKeyInsert\n271\t5\t\t\t\t\tKeyDelete\n272\t8\t\t\t\t\tKeyHelp\n273\t8\t\t\t\t\tKeyExit\n274\t8\t\t\t\t\tKeyClear\n275\t8\t\t  \t\t\tKeyCancel\n276\t8\t\t\t\t  \tKeyPrint\n277\t8\t\t\t\t\tKeyPause\n278\t5\t\t\t\t\tKeyBacktab\n279\t5\t\t\t\t\tKeyF1\n280\t5\t\t\t\t\tKeyF2\n281\t5\t\t\t\t\tKeyF3\n282\t5\t\t\t\t\tKeyF4\n283\t5\t\t\t\t\tKeyF5\n284\t5\t\t\t\t\tKeyF6\n285\t5\t\t\t\t\tKeyF7\n286\t5\t\t\t\t\tKeyF8\n287\t5\t\t\t\t\tKeyF9\n288\t5\t\t\t\t\tKeyF10\n289\t5\t\t\t\t\tKeyF11\n290\t5\t\t\t\t\tKeyF12\n291\t8\t\t\t\t\tKeyF13\n292\t8\t\t\t\t\tKeyF14\n293\t8\t\t\t\t\tKeyF15\n294\t8\t\t\t\t\tKeyF16\n295\t8\t\t\t\t\tKeyF17\n296\t8\t\t\t\t\tKeyF18\n297\t8\t\t\t\t\tKeyF19\n298\t8\t\t\t\t\tKeyF20\n299\t8\t\t\t\t\tKeyF21\n300\t8\t\t\t\t\tKeyF22\n301\t8\t\t\t\t\tKeyF23\n302\t8\t\t\t\t\tKeyF24\n303\t8\t\t\t\t\tKeyF25\n304\t8\t\t\t\t\tKeyF26\n305\t8\t\t\t\t\tKeyF27\n306\t8\t\t\t\t\tKeyF28\n307\t8\t\t\t\t\tKeyF29\n308\t8\t\t\t\t\tKeyF30\n309\t8\t\t\t\t\tKeyF31\n310\t8\t\t\t\t\tKeyF32\n311\t8\t\t\t\t\tKeyF33\n312\t8\t\t\t\t\tKeyF34\n313\t8\t\t\t\t\tKeyF35\n314\t8\t\t\t\t\tKeyF36\n315\t8\t\t\t\t\tKeyF37\n316\t8\t\t\t\t\tKeyF38\n317\t8\t\t\t\t\tKeyF39\n318\t8\t\t\t\t\tKeyF40\n319\t8\t\t\t\t\tKeyF41\n320\t8\t\t\t\t\tKeyF42\n321\t8\t\t\t\t\tKeyF43\n322\t8\t\t\t\t\tKeyF44\n323\t8\t\t\t\t\tKeyF45\n324\t8\t\t\t\t\tKeyF46\n325\t8\t\t\t\t\tKeyF47\n326\t8\t\t\t\t\tKeyF48\n327\t8\t\t\t\t\tKeyF49\n328\t8\t\t\t\t\tKeyF50\n329\t8\t\t\t\t\tKeyF51\n330\t8\t\t\t\t\tKeyF52\n331\t8\t\t\t\t\tKeyF53\n332\t8\t\t\t\t\tKeyF54\n333\t8\t\t\t\t\tKeyF55\n334\t8\t\t\t\t\tKeyF56\n335\t8\t\t\t\t\tKeyF57\n336\t8\t\t\t\t\tKeyF58\n337\t8\t\t\t\t\tKeyF59\n338\t8\t\t\t\t\tKeyF60\n339\t8\t\t\t\t\tKeyF61\n340\t8\t\t\t\t\tKeyF62\n341\t8\t\t\t\t\tKeyF63\n342\t8\t\t\t\t\tKeyF64\n--\t---------\t--------------\t---------------\t-----------\t-------------\t----------------\t----------\n%v\tsection #\ttcell ctrl key\ttcell ctrl char\ttcell alias\ttui constants\ttcell named keys\ttcell mods\n*/\n"
  },
  {
    "path": "src/tui/ttyname_unix.go",
    "content": "//go:build !windows\n\npackage tui\n\nimport (\n\t\"os\"\n\t\"sync/atomic\"\n\t\"syscall\"\n)\n\nvar devPrefixes = [...]string{\"/dev/pts/\", \"/dev/\"}\n\nvar tty atomic.Value\n\nfunc ttyname() string {\n\tif cached := tty.Load(); cached != nil {\n\t\treturn cached.(string)\n\t}\n\n\tvar stderr syscall.Stat_t\n\tif syscall.Fstat(2, &stderr) != nil {\n\t\treturn \"\"\n\t}\n\n\tfor _, prefix := range devPrefixes {\n\t\tfiles, err := os.ReadDir(prefix)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, file := range files {\n\t\t\tinfo, err := file.Info()\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {\n\t\t\t\tvalue := prefix + file.Name()\n\t\t\t\ttty.Store(value)\n\t\t\t\treturn value\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// TtyIn returns terminal device to read user input\nfunc TtyIn(ttyDefault string) (*os.File, error) {\n\treturn openTtyIn(ttyDefault)\n}\n\n// TtyOut returns terminal device to write to\nfunc TtyOut(ttyDefault string) (*os.File, error) {\n\treturn openTtyOut(ttyDefault)\n}\n"
  },
  {
    "path": "src/tui/ttyname_windows.go",
    "content": "//go:build windows\n\npackage tui\n\nimport (\n\t\"os\"\n)\n\nfunc ttyname() string {\n\treturn \"\"\n}\n\n// TtyIn on Windows returns os.Stdin\nfunc TtyIn(ttyDefault string) (*os.File, error) {\n\treturn os.Stdin, nil\n}\n\n// TtyOut on Windows returns nil\nfunc TtyOut(ttyDefault string) (*os.File, error) {\n\treturn nil, nil\n}\n"
  },
  {
    "path": "src/tui/tui.go",
    "content": "package tui\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n\t\"github.com/rivo/uniseg\"\n)\n\ntype Attr int32\n\nconst (\n\tAttrUndefined = Attr(0)\n\tAttrRegular   = Attr(1 << 8)\n\tAttrClear     = Attr(1 << 9)\n\tBoldForce     = Attr(1 << 10)\n\tFullBg        = Attr(1 << 11)\n\tStrip         = Attr(1 << 12)\n\n\t// Underline style stored in bits 13-15 (3 bits, values 0-4)\n\t// Only meaningful when the Underline attribute bit is also set.\n\t// 0 = solid (default)\n\tUnderlineStyleShift = 13\n\tUnderlineStyleMask  = Attr(0b111 << UnderlineStyleShift)\n\tUlStyleDouble       = Attr(0b001 << UnderlineStyleShift)\n\tUlStyleCurly        = Attr(0b010 << UnderlineStyleShift)\n\tUlStyleDotted       = Attr(0b011 << UnderlineStyleShift)\n\tUlStyleDashed       = Attr(0b100 << UnderlineStyleShift)\n)\n\nfunc (a Attr) UnderlineStyle() Attr {\n\treturn a & UnderlineStyleMask\n}\n\nfunc (a Attr) Merge(b Attr) Attr {\n\tif b&AttrRegular > 0 {\n\t\t// Only keep bold attribute set by the system\n\t\treturn (b &^ AttrRegular) | (a & BoldForce)\n\t}\n\n\tmerged := (a &^ AttrRegular) | b\n\t// When b sets Underline, use b's underline style instead of OR'ing\n\tif b&Underline > 0 {\n\t\tmerged = (merged &^ UnderlineStyleMask) | (b & UnderlineStyleMask)\n\t}\n\treturn merged\n}\n\n// Types of user action\n//\n//go:generate stringer -type=EventType\ntype EventType int\n\nconst (\n\tRune EventType = iota\n\n\tCtrlA\n\tCtrlB\n\tCtrlC\n\tCtrlD\n\tCtrlE\n\tCtrlF\n\tCtrlG\n\tCtrlH\n\tTab\n\tCtrlJ\n\tCtrlK\n\tCtrlL\n\tEnter\n\tCtrlN\n\tCtrlO\n\tCtrlP\n\tCtrlQ\n\tCtrlR\n\tCtrlS\n\tCtrlT\n\tCtrlU\n\tCtrlV\n\tCtrlW\n\tCtrlX\n\tCtrlY\n\tCtrlZ\n\tEsc\n\tCtrlSpace\n\n\t// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal\n\tCtrlBackSlash\n\tCtrlRightBracket\n\tCtrlCaret\n\tCtrlSlash\n\n\tShiftTab\n\tBackspace\n\n\tDelete\n\tPageUp\n\tPageDown\n\n\tUp\n\tDown\n\tLeft\n\tRight\n\tHome\n\tEnd\n\tInsert\n\n\tShiftUp\n\tShiftDown\n\tShiftLeft\n\tShiftRight\n\tShiftDelete\n\tShiftHome\n\tShiftEnd\n\tShiftPageUp\n\tShiftPageDown\n\n\tF1\n\tF2\n\tF3\n\tF4\n\tF5\n\tF6\n\tF7\n\tF8\n\tF9\n\tF10\n\tF11\n\tF12\n\n\tAltBackspace\n\n\tAltUp\n\tAltDown\n\tAltLeft\n\tAltRight\n\tAltDelete\n\tAltHome\n\tAltEnd\n\tAltPageUp\n\tAltPageDown\n\n\tAltShiftUp\n\tAltShiftDown\n\tAltShiftLeft\n\tAltShiftRight\n\tAltShiftDelete\n\tAltShiftHome\n\tAltShiftEnd\n\tAltShiftPageUp\n\tAltShiftPageDown\n\n\tCtrlUp\n\tCtrlDown\n\tCtrlLeft\n\tCtrlRight\n\tCtrlHome\n\tCtrlEnd\n\tCtrlBackspace\n\tCtrlDelete\n\tCtrlPageUp\n\tCtrlPageDown\n\n\tAlt\n\tCtrlAlt\n\n\tCtrlAltUp\n\tCtrlAltDown\n\tCtrlAltLeft\n\tCtrlAltRight\n\tCtrlAltHome\n\tCtrlAltEnd\n\tCtrlAltBackspace\n\tCtrlAltDelete\n\tCtrlAltPageUp\n\tCtrlAltPageDown\n\n\tCtrlShiftUp\n\tCtrlShiftDown\n\tCtrlShiftLeft\n\tCtrlShiftRight\n\tCtrlShiftHome\n\tCtrlShiftEnd\n\tCtrlShiftDelete\n\tCtrlShiftPageUp\n\tCtrlShiftPageDown\n\n\tCtrlAltShiftUp\n\tCtrlAltShiftDown\n\tCtrlAltShiftLeft\n\tCtrlAltShiftRight\n\tCtrlAltShiftHome\n\tCtrlAltShiftEnd\n\tCtrlAltShiftDelete\n\tCtrlAltShiftPageUp\n\tCtrlAltShiftPageDown\n\n\tInvalid\n\tFatal\n\tBracketedPasteBegin\n\tBracketedPasteEnd\n\n\tMouse\n\tDoubleClick\n\tLeftClick\n\tRightClick\n\tSLeftClick\n\tSRightClick\n\tScrollUp\n\tScrollDown\n\tSScrollUp\n\tSScrollDown\n\tPreviewScrollUp\n\tPreviewScrollDown\n\n\t// Events\n\tResize\n\tChange\n\tBackwardEOF\n\tStart\n\tLoad\n\tFocus\n\tOne\n\tZero\n\tResult\n\tJump\n\tJumpCancel\n\tClickHeader\n\tClickFooter\n\tMulti\n)\n\nfunc (t EventType) AsEvent() Event {\n\treturn Event{t, 0, nil}\n}\n\nfunc (t EventType) Int() int {\n\treturn int(t)\n}\n\nfunc (t EventType) Byte() byte {\n\treturn byte(t)\n}\n\nfunc (e Event) Comparable() Event {\n\t// Ignore MouseEvent pointer\n\treturn Event{e.Type, e.Char, nil}\n}\n\nfunc (e Event) KeyName() string {\n\tif me := e.MouseEvent; me != nil {\n\t\treturn me.Name()\n\t}\n\n\tif e.Type >= Invalid {\n\t\treturn \"\"\n\t}\n\n\tswitch e.Type {\n\tcase Rune:\n\t\tif e.Char == ' ' {\n\t\t\treturn \"space\"\n\t\t}\n\t\treturn string(e.Char)\n\tcase Alt:\n\t\treturn \"alt-\" + string(e.Char)\n\tcase CtrlAlt:\n\t\treturn \"ctrl-alt-\" + string(e.Char)\n\tcase CtrlBackSlash:\n\t\treturn \"ctrl-\\\\\"\n\tcase CtrlRightBracket:\n\t\treturn \"ctrl-]\"\n\tcase CtrlCaret:\n\t\treturn \"ctrl-^\"\n\tcase CtrlSlash:\n\t\treturn \"ctrl-/\"\n\t}\n\n\treturn util.ToKebabCase(e.Type.String())\n}\n\nfunc Key(r rune) Event {\n\treturn Event{Rune, r, nil}\n}\n\nfunc AltKey(r rune) Event {\n\treturn Event{Alt, r, nil}\n}\n\nfunc CtrlAltKey(r rune) Event {\n\treturn Event{CtrlAlt, r, nil}\n}\n\nconst (\n\tdoubleClickDuration = 500 * time.Millisecond\n)\n\ntype Color int32\n\nfunc (c Color) IsDefault() bool {\n\treturn c == colDefault\n}\n\nfunc (c Color) is24() bool {\n\treturn c > 0 && (c&(1<<24)) > 0\n}\n\ntype ColorAttr struct {\n\tColor Color\n\tAttr  Attr\n}\n\nfunc (a ColorAttr) IsColorDefined() bool {\n\treturn a.Color != colUndefined\n}\n\nfunc (a ColorAttr) IsAttrDefined() bool {\n\treturn a.Attr&^BoldForce != AttrUndefined\n}\n\nfunc (a ColorAttr) IsUndefined() bool {\n\treturn !a.IsColorDefined() && !a.IsAttrDefined()\n}\n\nfunc NewColorAttr() ColorAttr {\n\treturn ColorAttr{Color: colUndefined, Attr: AttrUndefined}\n}\n\nfunc (a ColorAttr) Merge(other ColorAttr) ColorAttr {\n\tif other.Color != colUndefined {\n\t\ta.Color = other.Color\n\t}\n\tif other.Attr != AttrUndefined {\n\t\ta.Attr = a.Attr.Merge(other.Attr)\n\t}\n\treturn a\n}\n\nconst (\n\tcolUndefined Color = -2\n\tcolDefault   Color = -1\n)\n\nconst (\n\tcolBlack Color = iota\n\tcolRed\n\tcolGreen\n\tcolYellow\n\tcolBlue\n\tcolMagenta\n\tcolCyan\n\tcolWhite\n\tcolGrey\n\tcolBrightRed\n\tcolBrightGreen\n\tcolBrightYellow\n\tcolBrightBlue\n\tcolBrightMagenta\n\tcolBrightCyan\n\tcolBrightWhite\n)\n\ntype FillReturn int\n\nconst (\n\tFillContinue FillReturn = iota\n\tFillNextLine\n\tFillSuspend\n)\n\ntype ColorPair struct {\n\tfg   Color\n\tbg   Color\n\tul   Color\n\tattr Attr\n}\n\nfunc HexToColor(rrggbb string) Color {\n\tr, _ := strconv.ParseInt(rrggbb[1:3], 16, 0)\n\tg, _ := strconv.ParseInt(rrggbb[3:5], 16, 0)\n\tb, _ := strconv.ParseInt(rrggbb[5:7], 16, 0)\n\treturn Color((1 << 24) + (r << 16) + (g << 8) + b)\n}\n\nfunc NewColorPair(fg Color, bg Color, attr Attr) ColorPair {\n\treturn ColorPair{fg, bg, colDefault, attr}\n}\n\nfunc NoColorPair() ColorPair {\n\treturn ColorPair{-1, -1, -1, 0}\n}\n\nfunc (p ColorPair) Fg() Color {\n\treturn p.fg\n}\n\nfunc (p ColorPair) Bg() Color {\n\treturn p.bg\n}\n\nfunc (p ColorPair) Ul() Color {\n\treturn p.ul\n}\n\nfunc (p ColorPair) WithUl(ul Color) ColorPair {\n\tdup := p\n\tdup.ul = ul\n\treturn dup\n}\n\nfunc (p ColorPair) Attr() Attr {\n\treturn p.attr\n}\n\nfunc (p ColorPair) IsFullBgMarker() bool {\n\treturn p.attr&FullBg > 0\n}\n\nfunc (p ColorPair) ShouldStripColors() bool {\n\treturn p.attr&Strip > 0\n}\n\nfunc (p ColorPair) HasBg() bool {\n\treturn p.attr&Reverse == 0 && p.bg != colDefault ||\n\t\tp.attr&Reverse > 0 && p.fg != colDefault\n}\n\nfunc (p ColorPair) merge(other ColorPair, except Color) ColorPair {\n\tdup := p\n\tdup.attr = dup.attr.Merge(other.attr)\n\tif other.fg != except {\n\t\tdup.fg = other.fg\n\t}\n\tif other.bg != except {\n\t\tdup.bg = other.bg\n\t}\n\tif other.ul != except {\n\t\tdup.ul = other.ul\n\t}\n\treturn dup\n}\n\nfunc (p ColorPair) WithAttr(attr Attr) ColorPair {\n\tdup := p\n\tdup.attr = dup.attr.Merge(attr)\n\treturn dup\n}\n\nfunc (p ColorPair) WithNewAttr(attr Attr) ColorPair {\n\tdup := p\n\tdup.attr = attr\n\treturn dup\n}\n\nfunc (p ColorPair) WithFg(fg ColorAttr) ColorPair {\n\tdup := p\n\tfgPair := ColorPair{fg.Color, colUndefined, colUndefined, fg.Attr}\n\treturn dup.Merge(fgPair)\n}\n\nfunc (p ColorPair) WithBg(bg ColorAttr) ColorPair {\n\tdup := p\n\tbgPair := ColorPair{colUndefined, bg.Color, colUndefined, bg.Attr}\n\treturn dup.Merge(bgPair)\n}\n\nfunc (p ColorPair) MergeAttr(other ColorPair) ColorPair {\n\treturn p.WithAttr(other.attr)\n}\n\nfunc (p ColorPair) Merge(other ColorPair) ColorPair {\n\treturn p.merge(other, colUndefined)\n}\n\nfunc (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {\n\treturn p.merge(other, colDefault)\n}\n\ntype ColorTheme struct {\n\tColored          bool\n\tInput            ColorAttr\n\tGhost            ColorAttr\n\tDisabled         ColorAttr\n\tFg               ColorAttr\n\tBg               ColorAttr\n\tListFg           ColorAttr\n\tListBg           ColorAttr\n\tAltBg            ColorAttr\n\tNth              ColorAttr\n\tNomatch          ColorAttr\n\tSelectedFg       ColorAttr\n\tSelectedBg       ColorAttr\n\tSelectedMatch    ColorAttr\n\tPreviewFg        ColorAttr\n\tPreviewBg        ColorAttr\n\tDarkBg           ColorAttr\n\tGutter           ColorAttr\n\tAltGutter        ColorAttr\n\tPrompt           ColorAttr\n\tInputBg          ColorAttr\n\tInputBorder      ColorAttr\n\tInputLabel       ColorAttr\n\tMatch            ColorAttr\n\tCurrent          ColorAttr\n\tCurrentMatch     ColorAttr\n\tSpinner          ColorAttr\n\tInfo             ColorAttr\n\tCursor           ColorAttr\n\tMarker           ColorAttr\n\tHeader           ColorAttr\n\tHeaderBg         ColorAttr\n\tHeaderBorder     ColorAttr\n\tHeaderLabel      ColorAttr\n\tFooter           ColorAttr\n\tFooterBg         ColorAttr\n\tFooterBorder     ColorAttr\n\tFooterLabel      ColorAttr\n\tSeparator        ColorAttr\n\tScrollbar        ColorAttr\n\tBorder           ColorAttr\n\tPreviewBorder    ColorAttr\n\tPreviewLabel     ColorAttr\n\tPreviewScrollbar ColorAttr\n\tBorderLabel      ColorAttr\n\tListLabel        ColorAttr\n\tListBorder       ColorAttr\n\tGapLine          ColorAttr\n\tNthCurrentAttr   Attr // raw current-fg attr (before fg merge) for nth overlay\n\tNthSelectedAttr  Attr // raw selected-fg attr (before ListFg inherit) for nth overlay\n}\n\ntype Event struct {\n\tType       EventType\n\tChar       rune\n\tMouseEvent *MouseEvent\n}\n\ntype MouseEvent struct {\n\tY      int\n\tX      int\n\tS      int\n\tLeft   bool\n\tDown   bool\n\tDouble bool\n\tCtrl   bool\n\tAlt    bool\n\tShift  bool\n}\n\nfunc (e MouseEvent) Mod() bool {\n\treturn e.Ctrl || e.Alt || e.Shift\n}\n\nfunc (e MouseEvent) Name() string {\n\tname := \"\"\n\tif e.Down {\n\t\treturn name\n\t}\n\n\tif e.Ctrl {\n\t\tname += \"ctrl-\"\n\t}\n\tif e.Alt {\n\t\tname += \"alt-\"\n\t}\n\tif e.Shift {\n\t\tname += \"shift-\"\n\t}\n\tif e.Double {\n\t\tname += \"double-\"\n\t}\n\tif !e.Left {\n\t\tname += \"right-\"\n\t}\n\treturn name + \"click\"\n}\n\ntype BorderShape int\n\nconst (\n\tBorderUndefined BorderShape = iota\n\tBorderLine\n\tBorderNone\n\tBorderPhantom\n\tBorderRounded\n\tBorderSharp\n\tBorderBold\n\tBorderBlock\n\tBorderThinBlock\n\tBorderDouble\n\tBorderHorizontal\n\tBorderVertical\n\tBorderTop\n\tBorderBottom\n\tBorderLeft\n\tBorderRight\n)\n\nfunc (s BorderShape) HasLeft() bool {\n\tswitch s {\n\tcase BorderNone, BorderPhantom, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s BorderShape) HasRight() bool {\n\tswitch s {\n\tcase BorderNone, BorderPhantom, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s BorderShape) HasTop() bool {\n\tswitch s {\n\tcase BorderNone, BorderPhantom, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s BorderShape) HasBottom() bool {\n\tswitch s {\n\tcase BorderNone, BorderPhantom, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s BorderShape) Visible() bool {\n\treturn s != BorderNone\n}\n\ntype BorderStyle struct {\n\tshape       BorderShape\n\ttop         rune\n\tbottom      rune\n\tleft        rune\n\tright       rune\n\ttopLeft     rune\n\ttopRight    rune\n\tbottomLeft  rune\n\tbottomRight rune\n}\n\ntype BorderCharacter int\n\nfunc MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {\n\tif shape == BorderNone || shape == BorderPhantom {\n\t\treturn BorderStyle{\n\t\t\tshape:       BorderNone,\n\t\t\ttop:         ' ',\n\t\t\tbottom:      ' ',\n\t\t\tleft:        ' ',\n\t\t\tright:       ' ',\n\t\t\ttopLeft:     ' ',\n\t\t\ttopRight:    ' ',\n\t\t\tbottomLeft:  ' ',\n\t\t\tbottomRight: ' '}\n\t}\n\tif !unicode {\n\t\treturn BorderStyle{\n\t\t\tshape:       shape,\n\t\t\ttop:         '-',\n\t\t\tbottom:      '-',\n\t\t\tleft:        '|',\n\t\t\tright:       '|',\n\t\t\ttopLeft:     '+',\n\t\t\ttopRight:    '+',\n\t\t\tbottomLeft:  '+',\n\t\t\tbottomRight: '+',\n\t\t}\n\t}\n\tswitch shape {\n\tcase BorderSharp:\n\t\treturn BorderStyle{\n\t\t\tshape:       shape,\n\t\t\ttop:         '─',\n\t\t\tbottom:      '─',\n\t\t\tleft:        '│',\n\t\t\tright:       '│',\n\t\t\ttopLeft:     '┌',\n\t\t\ttopRight:    '┐',\n\t\t\tbottomLeft:  '└',\n\t\t\tbottomRight: '┘',\n\t\t}\n\tcase BorderBold:\n\t\treturn BorderStyle{\n\t\t\tshape:       shape,\n\t\t\ttop:         '━',\n\t\t\tbottom:      '━',\n\t\t\tleft:        '┃',\n\t\t\tright:       '┃',\n\t\t\ttopLeft:     '┏',\n\t\t\ttopRight:    '┓',\n\t\t\tbottomLeft:  '┗',\n\t\t\tbottomRight: '┛',\n\t\t}\n\tcase BorderBlock:\n\t\t// ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜\n\t\t// ▌                  ▐\n\t\t// ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟\n\t\treturn BorderStyle{\n\t\t\tshape:       shape,\n\t\t\ttop:         '▀',\n\t\t\tbottom:      '▄',\n\t\t\tleft:        '▌',\n\t\t\tright:       '▐',\n\t\t\ttopLeft:     '▛',\n\t\t\ttopRight:    '▜',\n\t\t\tbottomLeft:  '▙',\n\t\t\tbottomRight: '▟',\n\t\t}\n\n\tcase BorderThinBlock:\n\t\t// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾\n\t\t// ▏                  ▕\n\t\t// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿\n\t\treturn BorderStyle{\n\t\t\tshape:       shape,\n\t\t\ttop:         '▔',\n\t\t\tbottom:      '▁',\n\t\t\tleft:        '▏',\n\t\t\tright:       '▕',\n\t\t\ttopLeft:     '🭽',\n\t\t\ttopRight:    '🭾',\n\t\t\tbottomLeft:  '🭼',\n\t\t\tbottomRight: '🭿',\n\t\t}\n\n\tcase BorderDouble:\n\t\treturn BorderStyle{\n\t\t\tshape:       shape,\n\t\t\ttop:         '═',\n\t\t\tbottom:      '═',\n\t\t\tleft:        '║',\n\t\t\tright:       '║',\n\t\t\ttopLeft:     '╔',\n\t\t\ttopRight:    '╗',\n\t\t\tbottomLeft:  '╚',\n\t\t\tbottomRight: '╝',\n\t\t}\n\t}\n\treturn BorderStyle{\n\t\tshape:       shape,\n\t\ttop:         '─',\n\t\tbottom:      '─',\n\t\tleft:        '│',\n\t\tright:       '│',\n\t\ttopLeft:     '╭',\n\t\ttopRight:    '╮',\n\t\tbottomLeft:  '╰',\n\t\tbottomRight: '╯',\n\t}\n}\n\ntype TermSize struct {\n\tLines    int\n\tColumns  int\n\tPxWidth  int\n\tPxHeight int\n}\n\ntype WindowType int\n\nconst (\n\tWindowBase WindowType = iota\n\tWindowList\n\tWindowPreview\n\tWindowInput\n\tWindowHeader\n\tWindowFooter\n)\n\ntype Renderer interface {\n\tDefaultTheme() *ColorTheme\n\tInit() error\n\tResize(maxHeightFunc func(int) int)\n\tPause(clear bool)\n\tResume(clear bool, sigcont bool)\n\tClear()\n\tRefreshWindows(windows []Window)\n\tRefresh()\n\tClose()\n\tPassThrough(string)\n\tNeedScrollbarRedraw() bool\n\tShouldEmitResizeEvent() bool\n\tBell()\n\tHideCursor()\n\tShowCursor()\n\n\tGetChar(cancellable bool) Event\n\tCancelGetChar()\n\n\tTop() int\n\tMaxX() int\n\tMaxY() int\n\n\tSize() TermSize\n\n\tNewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window\n}\n\ntype Window interface {\n\tTop() int\n\tLeft() int\n\tWidth() int\n\tHeight() int\n\n\tDrawBorder()\n\tDrawHBorder()\n\tRefresh()\n\tFinishFill()\n\n\tX() int\n\tY() int\n\tEncloseX(x int) bool\n\tEncloseY(y int) bool\n\tEnclose(y int, x int) bool\n\n\tMove(y int, x int)\n\tMoveAndClear(y int, x int)\n\tPrint(text string)\n\tCPrint(color ColorPair, text string)\n\tFill(text string) FillReturn\n\tCFill(fg Color, bg Color, ul Color, attr Attr, text string) FillReturn\n\tLinkBegin(uri string, params string)\n\tLinkEnd()\n\tErase()\n\tEraseMaybe() bool\n\n\tSetWrapSign(string, int)\n}\n\ntype FullscreenRenderer struct {\n\ttheme        *ColorTheme\n\tmouse        bool\n\tforceBlack   bool\n\ttabstop      int\n\tprevDownTime time.Time\n\tclicks       [][2]int\n\tshowCursor   bool\n}\n\nfunc NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int) Renderer {\n\tr := &FullscreenRenderer{\n\t\ttheme:        theme,\n\t\tmouse:        mouse,\n\t\tforceBlack:   forceBlack,\n\t\ttabstop:      tabstop,\n\t\tprevDownTime: time.Unix(0, 0),\n\t\tclicks:       [][2]int{},\n\t\tshowCursor:   true}\n\treturn r\n}\n\nvar (\n\tNoColorTheme *ColorTheme\n\tEmptyTheme   *ColorTheme\n\tDefault16    *ColorTheme\n\tDark256      *ColorTheme\n\tLight256     *ColorTheme\n\n\tColPrompt               ColorPair\n\tColNormal               ColorPair\n\tColInput                ColorPair\n\tColDisabled             ColorPair\n\tColGhost                ColorPair\n\tColMatch                ColorPair\n\tColCursor               ColorPair\n\tColCursorEmpty          ColorPair\n\tColCursorEmptyChar      ColorPair\n\tColAltCursorEmpty       ColorPair\n\tColAltCursorEmptyChar   ColorPair\n\tColMarker               ColorPair\n\tColSelected             ColorPair\n\tColSelectedMatch        ColorPair\n\tColCurrent              ColorPair\n\tColCurrentMatch         ColorPair\n\tColCurrentCursor        ColorPair\n\tColCurrentCursorEmpty   ColorPair\n\tColCurrentMarker        ColorPair\n\tColCurrentSelectedEmpty ColorPair\n\tColSpinner              ColorPair\n\tColInfo                 ColorPair\n\tColHeader               ColorPair\n\tColHeaderBorder         ColorPair\n\tColHeaderLabel          ColorPair\n\tColFooter               ColorPair\n\tColFooterBorder         ColorPair\n\tColFooterLabel          ColorPair\n\tColSeparator            ColorPair\n\tColScrollbar            ColorPair\n\tColGapLine              ColorPair\n\tColBorder               ColorPair\n\tColPreview              ColorPair\n\tColPreviewBorder        ColorPair\n\tColBorderLabel          ColorPair\n\tColPreviewLabel         ColorPair\n\tColPreviewScrollbar     ColorPair\n\tColPreviewSpinner       ColorPair\n\tColListBorder           ColorPair\n\tColListLabel            ColorPair\n\tColInputBorder          ColorPair\n\tColInputLabel           ColorPair\n)\n\nfunc init() {\n\tdefaultColor := ColorAttr{colDefault, AttrUndefined}\n\tundefined := ColorAttr{colUndefined, AttrUndefined}\n\n\tNoColorTheme = &ColorTheme{\n\t\tColored:          false,\n\t\tInput:            defaultColor,\n\t\tFg:               defaultColor,\n\t\tBg:               defaultColor,\n\t\tListFg:           defaultColor,\n\t\tListBg:           defaultColor,\n\t\tAltBg:            undefined,\n\t\tSelectedFg:       defaultColor,\n\t\tSelectedBg:       defaultColor,\n\t\tSelectedMatch:    defaultColor,\n\t\tDarkBg:           defaultColor,\n\t\tPrompt:           defaultColor,\n\t\tMatch:            defaultColor,\n\t\tCurrent:          undefined,\n\t\tCurrentMatch:     undefined,\n\t\tSpinner:          defaultColor,\n\t\tInfo:             defaultColor,\n\t\tCursor:           defaultColor,\n\t\tMarker:           defaultColor,\n\t\tHeader:           defaultColor,\n\t\tBorder:           undefined,\n\t\tBorderLabel:      defaultColor,\n\t\tGhost:            undefined,\n\t\tDisabled:         defaultColor,\n\t\tPreviewFg:        defaultColor,\n\t\tPreviewBg:        defaultColor,\n\t\tGutter:           undefined,\n\t\tAltGutter:        undefined,\n\t\tPreviewBorder:    defaultColor,\n\t\tPreviewScrollbar: defaultColor,\n\t\tPreviewLabel:     defaultColor,\n\t\tListLabel:        defaultColor,\n\t\tListBorder:       defaultColor,\n\t\tSeparator:        defaultColor,\n\t\tScrollbar:        defaultColor,\n\t\tInputBg:          defaultColor,\n\t\tInputBorder:      defaultColor,\n\t\tInputLabel:       defaultColor,\n\t\tHeaderBg:         defaultColor,\n\t\tHeaderBorder:     defaultColor,\n\t\tHeaderLabel:      defaultColor,\n\t\tFooterBg:         defaultColor,\n\t\tFooterBorder:     defaultColor,\n\t\tFooterLabel:      defaultColor,\n\t\tGapLine:          defaultColor,\n\t\tNth:              undefined,\n\t\tNomatch:          undefined,\n\t}\n\n\tEmptyTheme = &ColorTheme{\n\t\tColored:          true,\n\t\tInput:            undefined,\n\t\tFg:               undefined,\n\t\tBg:               undefined,\n\t\tListFg:           undefined,\n\t\tListBg:           undefined,\n\t\tAltBg:            undefined,\n\t\tSelectedFg:       undefined,\n\t\tSelectedBg:       undefined,\n\t\tSelectedMatch:    undefined,\n\t\tDarkBg:           undefined,\n\t\tPrompt:           undefined,\n\t\tMatch:            undefined,\n\t\tCurrent:          undefined,\n\t\tCurrentMatch:     undefined,\n\t\tSpinner:          undefined,\n\t\tInfo:             undefined,\n\t\tCursor:           undefined,\n\t\tMarker:           undefined,\n\t\tHeader:           undefined,\n\t\tFooter:           undefined,\n\t\tBorder:           undefined,\n\t\tBorderLabel:      undefined,\n\t\tListLabel:        undefined,\n\t\tListBorder:       undefined,\n\t\tGhost:            undefined,\n\t\tDisabled:         undefined,\n\t\tPreviewFg:        undefined,\n\t\tPreviewBg:        undefined,\n\t\tGutter:           undefined,\n\t\tAltGutter:        undefined,\n\t\tPreviewBorder:    undefined,\n\t\tPreviewScrollbar: undefined,\n\t\tPreviewLabel:     undefined,\n\t\tSeparator:        undefined,\n\t\tScrollbar:        undefined,\n\t\tInputBg:          undefined,\n\t\tInputBorder:      undefined,\n\t\tInputLabel:       undefined,\n\t\tHeaderBg:         undefined,\n\t\tHeaderBorder:     undefined,\n\t\tHeaderLabel:      undefined,\n\t\tFooterBg:         undefined,\n\t\tFooterBorder:     undefined,\n\t\tFooterLabel:      undefined,\n\t\tGapLine:          undefined,\n\t\tNth:              undefined,\n\t\tNomatch:          undefined,\n\t}\n\n\tDefault16 = &ColorTheme{\n\t\tColored:          true,\n\t\tInput:            defaultColor,\n\t\tFg:               defaultColor,\n\t\tBg:               defaultColor,\n\t\tListFg:           undefined,\n\t\tListBg:           undefined,\n\t\tAltBg:            undefined,\n\t\tSelectedFg:       undefined,\n\t\tSelectedBg:       undefined,\n\t\tSelectedMatch:    undefined,\n\t\tDarkBg:           ColorAttr{colGrey, AttrUndefined},\n\t\tPrompt:           ColorAttr{colBlue, AttrUndefined},\n\t\tMatch:            ColorAttr{colGreen, AttrUndefined},\n\t\tCurrent:          ColorAttr{colBrightWhite, AttrUndefined},\n\t\tCurrentMatch:     ColorAttr{colBrightGreen, AttrUndefined},\n\t\tSpinner:          ColorAttr{colGreen, AttrUndefined},\n\t\tInfo:             ColorAttr{colYellow, AttrUndefined},\n\t\tCursor:           ColorAttr{colRed, AttrUndefined},\n\t\tMarker:           ColorAttr{colMagenta, AttrUndefined},\n\t\tHeader:           ColorAttr{colCyan, AttrUndefined},\n\t\tFooter:           ColorAttr{colCyan, AttrUndefined},\n\t\tBorder:           undefined,\n\t\tBorderLabel:      defaultColor,\n\t\tGhost:            undefined,\n\t\tDisabled:         undefined,\n\t\tPreviewFg:        undefined,\n\t\tPreviewBg:        undefined,\n\t\tGutter:           undefined,\n\t\tAltGutter:        undefined,\n\t\tPreviewBorder:    undefined,\n\t\tPreviewScrollbar: undefined,\n\t\tPreviewLabel:     undefined,\n\t\tListLabel:        undefined,\n\t\tListBorder:       undefined,\n\t\tSeparator:        undefined,\n\t\tScrollbar:        undefined,\n\t\tInputBg:          undefined,\n\t\tInputBorder:      undefined,\n\t\tInputLabel:       undefined,\n\t\tHeaderBg:         undefined,\n\t\tHeaderBorder:     undefined,\n\t\tHeaderLabel:      undefined,\n\t\tFooterBg:         undefined,\n\t\tFooterBorder:     undefined,\n\t\tFooterLabel:      undefined,\n\t\tGapLine:          undefined,\n\t\tNth:              undefined,\n\t\tNomatch:          undefined,\n\t}\n\n\tDark256 = &ColorTheme{\n\t\tColored:          true,\n\t\tInput:            defaultColor,\n\t\tFg:               defaultColor,\n\t\tBg:               defaultColor,\n\t\tListFg:           undefined,\n\t\tListBg:           undefined,\n\t\tAltBg:            undefined,\n\t\tSelectedFg:       undefined,\n\t\tSelectedBg:       undefined,\n\t\tSelectedMatch:    undefined,\n\t\tDarkBg:           ColorAttr{236, AttrUndefined},\n\t\tPrompt:           ColorAttr{110, AttrUndefined},\n\t\tMatch:            ColorAttr{108, AttrUndefined},\n\t\tCurrent:          ColorAttr{254, AttrUndefined},\n\t\tCurrentMatch:     ColorAttr{151, AttrUndefined},\n\t\tSpinner:          ColorAttr{148, AttrUndefined},\n\t\tInfo:             ColorAttr{144, AttrUndefined},\n\t\tCursor:           ColorAttr{161, AttrUndefined},\n\t\tMarker:           ColorAttr{168, AttrUndefined},\n\t\tHeader:           ColorAttr{109, AttrUndefined},\n\t\tFooter:           ColorAttr{109, AttrUndefined},\n\t\tBorder:           ColorAttr{59, AttrUndefined},\n\t\tBorderLabel:      ColorAttr{145, AttrUndefined},\n\t\tGhost:            undefined,\n\t\tDisabled:         undefined,\n\t\tPreviewFg:        undefined,\n\t\tPreviewBg:        undefined,\n\t\tGutter:           undefined,\n\t\tAltGutter:        undefined,\n\t\tPreviewBorder:    undefined,\n\t\tPreviewScrollbar: undefined,\n\t\tPreviewLabel:     undefined,\n\t\tListLabel:        undefined,\n\t\tListBorder:       undefined,\n\t\tSeparator:        undefined,\n\t\tScrollbar:        undefined,\n\t\tInputBg:          undefined,\n\t\tInputBorder:      undefined,\n\t\tInputLabel:       undefined,\n\t\tHeaderBg:         undefined,\n\t\tHeaderBorder:     undefined,\n\t\tHeaderLabel:      undefined,\n\t\tFooterBg:         undefined,\n\t\tFooterBorder:     undefined,\n\t\tFooterLabel:      undefined,\n\t\tGapLine:          undefined,\n\t\tNth:              undefined,\n\t\tNomatch:          undefined,\n\t}\n\n\tLight256 = &ColorTheme{\n\t\tColored:          true,\n\t\tInput:            defaultColor,\n\t\tFg:               defaultColor,\n\t\tBg:               defaultColor,\n\t\tListFg:           undefined,\n\t\tListBg:           undefined,\n\t\tAltBg:            undefined,\n\t\tSelectedFg:       undefined,\n\t\tSelectedBg:       undefined,\n\t\tSelectedMatch:    undefined,\n\t\tDarkBg:           ColorAttr{251, AttrUndefined},\n\t\tPrompt:           ColorAttr{25, AttrUndefined},\n\t\tMatch:            ColorAttr{66, AttrUndefined},\n\t\tCurrent:          ColorAttr{237, AttrUndefined},\n\t\tCurrentMatch:     ColorAttr{23, AttrUndefined},\n\t\tSpinner:          ColorAttr{65, AttrUndefined},\n\t\tInfo:             ColorAttr{101, AttrUndefined},\n\t\tCursor:           ColorAttr{161, AttrUndefined},\n\t\tMarker:           ColorAttr{168, AttrUndefined},\n\t\tHeader:           ColorAttr{31, AttrUndefined},\n\t\tFooter:           ColorAttr{31, AttrUndefined},\n\t\tBorder:           ColorAttr{145, AttrUndefined},\n\t\tBorderLabel:      ColorAttr{59, AttrUndefined},\n\t\tGhost:            undefined,\n\t\tDisabled:         undefined,\n\t\tPreviewFg:        undefined,\n\t\tPreviewBg:        undefined,\n\t\tGutter:           undefined,\n\t\tAltGutter:        undefined,\n\t\tPreviewBorder:    undefined,\n\t\tPreviewScrollbar: undefined,\n\t\tPreviewLabel:     undefined,\n\t\tListLabel:        undefined,\n\t\tListBorder:       undefined,\n\t\tSeparator:        undefined,\n\t\tScrollbar:        undefined,\n\t\tInputBg:          undefined,\n\t\tInputBorder:      undefined,\n\t\tInputLabel:       undefined,\n\t\tHeaderBg:         undefined,\n\t\tHeaderBorder:     undefined,\n\t\tHeaderLabel:      undefined,\n\t\tFooterBg:         undefined,\n\t\tFooterBorder:     undefined,\n\t\tFooterLabel:      undefined,\n\t\tGapLine:          undefined,\n\t\tNth:              undefined,\n\t\tNomatch:          undefined,\n\t}\n}\n\nfunc InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) {\n\tif forceBlack {\n\t\ttheme.Bg = ColorAttr{colBlack, AttrUndefined}\n\t}\n\n\tif boldify {\n\t\tboldify := func(c ColorAttr) ColorAttr {\n\t\t\tdup := c\n\t\t\tif (c.Attr & AttrRegular) == 0 {\n\t\t\t\tdup.Attr |= BoldForce\n\t\t\t}\n\t\t\treturn dup\n\t\t}\n\t\ttheme.Current = boldify(theme.Current)\n\t\ttheme.CurrentMatch = boldify(theme.CurrentMatch)\n\t\ttheme.Prompt = boldify(theme.Prompt)\n\t\ttheme.Input = boldify(theme.Input)\n\t\ttheme.Cursor = boldify(theme.Cursor)\n\t\ttheme.Spinner = boldify(theme.Spinner)\n\t}\n\n\to := func(a ColorAttr, b ColorAttr) ColorAttr {\n\t\tc := a\n\t\tif b.Color != colUndefined {\n\t\t\tc.Color = b.Color\n\t\t}\n\t\tif b.Attr != AttrUndefined {\n\t\t\tc.Attr = b.Attr\n\t\t}\n\t\treturn c\n\t}\n\ttheme.Input = o(baseTheme.Input, theme.Input)\n\ttheme.Fg = o(baseTheme.Fg, theme.Fg)\n\ttheme.Bg = o(baseTheme.Bg, theme.Bg)\n\ttheme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)\n\ttheme.Prompt = o(baseTheme.Prompt, theme.Prompt)\n\tmatch := theme.Match\n\tif !baseTheme.Colored && match.IsUndefined() {\n\t\tmatch.Attr = Underline\n\t}\n\ttheme.Match = o(baseTheme.Match, match)\n\t// These colors are not defined in the base themes.\n\t// Resolve ListFg/ListBg early so Current and Selected can inherit from them.\n\ttheme.ListFg = o(theme.Fg, theme.ListFg)\n\ttheme.ListBg = o(theme.Bg, theme.ListBg)\n\t// Inherit from 'list-fg', so that we don't have to write 'current-fg:dim'\n\t// e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular\n\tcurrent := theme.Current\n\tif !baseTheme.Colored && current.IsUndefined() {\n\t\tcurrent.Attr |= Reverse\n\t}\n\tresolvedCurrent := o(baseTheme.Current, current)\n\ttheme.NthCurrentAttr = resolvedCurrent.Attr\n\ttheme.Current = theme.ListFg.Merge(resolvedCurrent)\n\tcurrentMatch := theme.CurrentMatch\n\tif !baseTheme.Colored && currentMatch.IsUndefined() {\n\t\tcurrentMatch.Attr |= Reverse | Underline\n\t}\n\ttheme.CurrentMatch = o(baseTheme.CurrentMatch, currentMatch)\n\ttheme.Spinner = o(baseTheme.Spinner, theme.Spinner)\n\ttheme.Info = o(baseTheme.Info, theme.Info)\n\ttheme.Cursor = o(baseTheme.Cursor, theme.Cursor)\n\ttheme.Marker = o(baseTheme.Marker, theme.Marker)\n\ttheme.Header = o(baseTheme.Header, theme.Header)\n\ttheme.Footer = o(baseTheme.Footer, theme.Footer)\n\n\t// If border color is undefined, set it to default color with dim attribute.\n\tborder := theme.Border\n\tif baseTheme.Border.IsUndefined() && border.IsUndefined() {\n\t\tborder.Attr = Dim\n\t}\n\ttheme.Border = o(baseTheme.Border, border)\n\ttheme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)\n\n\tundefined := NewColorAttr()\n\tscrollbarDefined := theme.Scrollbar != undefined\n\tpreviewBorderDefined := theme.PreviewBorder != undefined\n\n\ttheme.NthSelectedAttr = theme.SelectedFg.Attr\n\ttheme.SelectedFg = theme.ListFg.Merge(theme.SelectedFg)\n\ttheme.SelectedBg = o(theme.ListBg, theme.SelectedBg)\n\ttheme.SelectedMatch = o(theme.Match, theme.SelectedMatch)\n\n\tghost := theme.Ghost\n\tif ghost.IsUndefined() {\n\t\tghost.Attr = Dim\n\t} else if ghost.IsColorDefined() && !ghost.IsAttrDefined() {\n\t\t// Don't want to inherit 'bold' from 'input'\n\t\tghost.Attr = AttrRegular\n\t}\n\ttheme.Ghost = o(theme.Input, ghost)\n\ttheme.Disabled = o(theme.Input, theme.Disabled)\n\n\t// Use dim gutter on non-colored themes if undefined\n\tgutter := theme.Gutter\n\tif !baseTheme.Colored && gutter.IsUndefined() {\n\t\tgutter.Attr = Dim\n\t}\n\ttheme.Gutter = o(theme.DarkBg, gutter)\n\ttheme.AltGutter = o(theme.Gutter, theme.AltGutter)\n\ttheme.PreviewFg = o(theme.Fg, theme.PreviewFg)\n\ttheme.PreviewBg = o(theme.Bg, theme.PreviewBg)\n\ttheme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)\n\ttheme.PreviewBorder = o(theme.Border, theme.PreviewBorder)\n\ttheme.ListLabel = o(theme.BorderLabel, theme.ListLabel)\n\ttheme.ListBorder = o(theme.Border, theme.ListBorder)\n\ttheme.Separator = o(theme.ListBorder, theme.Separator)\n\ttheme.Scrollbar = o(theme.ListBorder, theme.Scrollbar)\n\ttheme.GapLine = o(theme.ListBorder, theme.GapLine)\n\t/*\n\t\t--color list-border:green\n\t\t--color scrollbar:red\n\t\t--color scrollbar:red,list-border:green\n\t\t--color scrollbar:red,preview-border:green\n\t*/\n\tif scrollbarDefined && !previewBorderDefined {\n\t\ttheme.PreviewScrollbar = o(theme.Scrollbar, theme.PreviewScrollbar)\n\t} else {\n\t\ttheme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)\n\t}\n\tif hasInputWindow {\n\t\ttheme.InputBg = o(theme.Bg, theme.InputBg)\n\t} else {\n\t\t// We shouldn't use input-bg if there's no separate input window\n\t\t// e.g. fzf --color 'list-bg:green,input-bg:red' --no-input-border\n\t\ttheme.InputBg = o(theme.Bg, theme.ListBg)\n\t}\n\ttheme.InputBorder = o(theme.Border, theme.InputBorder)\n\ttheme.InputLabel = o(theme.BorderLabel, theme.InputLabel)\n\tif hasHeaderWindow {\n\t\ttheme.HeaderBg = o(theme.Bg, theme.HeaderBg)\n\t} else {\n\t\ttheme.HeaderBg = o(theme.Bg, theme.ListBg)\n\t}\n\ttheme.HeaderBorder = o(theme.Border, theme.HeaderBorder)\n\ttheme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)\n\n\ttheme.FooterBg = o(theme.Bg, theme.FooterBg)\n\ttheme.FooterBorder = o(theme.Border, theme.FooterBorder)\n\ttheme.FooterLabel = o(theme.BorderLabel, theme.FooterLabel)\n\n\tif theme.Nomatch.IsUndefined() {\n\t\ttheme.Nomatch.Attr = Dim\n\t}\n\n\tinitPalette(theme)\n}\n\nfunc initPalette(theme *ColorTheme) {\n\tpair := func(fg, bg ColorAttr) ColorPair {\n\t\tif fg.Color == colDefault && (fg.Attr&Reverse) > 0 {\n\t\t\tbg.Color = colDefault\n\t\t}\n\t\treturn ColorPair{fg.Color, bg.Color, colDefault, fg.Attr}\n\t}\n\tblank := theme.ListFg\n\tblank.Attr = AttrRegular\n\n\tColPrompt = pair(theme.Prompt, theme.InputBg)\n\tColNormal = pair(theme.ListFg, theme.ListBg)\n\tColSelected = pair(theme.SelectedFg, theme.SelectedBg)\n\tColInput = pair(theme.Input, theme.InputBg)\n\tColGhost = pair(theme.Ghost, theme.InputBg)\n\tColDisabled = pair(theme.Disabled, theme.InputBg)\n\tColMatch = pair(theme.Match, theme.ListBg)\n\tColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)\n\tColCursor = pair(theme.Cursor, theme.Gutter)\n\tColCursorEmpty = pair(blank, theme.Gutter)\n\tColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)\n\tColAltCursorEmpty = pair(blank, theme.AltGutter)\n\tColAltCursorEmptyChar = pair(theme.AltGutter, theme.ListBg)\n\tif theme.SelectedBg.Color != theme.ListBg.Color {\n\t\tColMarker = pair(theme.Marker, theme.SelectedBg)\n\t} else {\n\t\tColMarker = pair(theme.Marker, theme.ListBg)\n\t}\n\tColCurrent = pair(theme.Current, theme.DarkBg)\n\tColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)\n\tColCurrentCursor = pair(theme.Cursor, theme.DarkBg)\n\tColCurrentCursorEmpty = pair(blank, theme.DarkBg)\n\tColCurrentMarker = pair(theme.Marker, theme.DarkBg)\n\tColCurrentSelectedEmpty = pair(blank, theme.DarkBg)\n\tColSpinner = pair(theme.Spinner, theme.InputBg)\n\tColInfo = pair(theme.Info, theme.InputBg)\n\tColSeparator = pair(theme.Separator, theme.InputBg)\n\tColScrollbar = pair(theme.Scrollbar, theme.ListBg)\n\tColGapLine = pair(theme.GapLine, theme.ListBg)\n\tColBorder = pair(theme.Border, theme.Bg)\n\tColBorderLabel = pair(theme.BorderLabel, theme.Bg)\n\tColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)\n\tColPreview = pair(theme.PreviewFg, theme.PreviewBg)\n\tColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)\n\tColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)\n\tColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)\n\tColListLabel = pair(theme.ListLabel, theme.ListBg)\n\tColListBorder = pair(theme.ListBorder, theme.ListBg)\n\tColInputBorder = pair(theme.InputBorder, theme.InputBg)\n\tColInputLabel = pair(theme.InputLabel, theme.InputBg)\n\tColHeader = pair(theme.Header, theme.HeaderBg)\n\tColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)\n\tColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)\n\tColFooter = pair(theme.Footer, theme.FooterBg)\n\tColFooterBorder = pair(theme.FooterBorder, theme.FooterBg)\n\tColFooterLabel = pair(theme.FooterLabel, theme.FooterBg)\n}\n\nfunc runeWidth(r rune) int {\n\treturn uniseg.StringWidth(string(r))\n}\n\n// WrappedLine represents a single visual line after character-level wrapping.\ntype WrappedLine struct {\n\tText         string\n\tDisplayWidth int\n}\n\n// WrapLine splits a single line (no embedded \\n) into visual lines\n// that fit within initialMax columns. Character-level wrapping only.\nfunc WrapLine(input string, prefixLength int, initialMax int, tabstop int, wrapSignWidth int) []WrappedLine {\n\tlines := []WrappedLine{}\n\twidth := 0\n\tline := \"\"\n\tgr := uniseg.NewGraphemes(input)\n\tmaxWidth := initialMax\n\tcontMax := max(1, initialMax-wrapSignWidth)\n\tfor gr.Next() {\n\t\trs := gr.Runes()\n\t\tstr := string(rs)\n\t\tvar w int\n\t\tif len(rs) == 1 && rs[0] == '\\t' {\n\t\t\tw = tabstop - (prefixLength+width)%tabstop\n\t\t\tstr = strings.Repeat(\" \", w)\n\t\t} else if rs[0] == '\\r' {\n\t\t\tw++\n\t\t} else {\n\t\t\tw = uniseg.StringWidth(str)\n\t\t}\n\t\twidth += w\n\n\t\tif prefixLength+width <= maxWidth {\n\t\t\tline += str\n\t\t} else {\n\t\t\tlines = append(lines, WrappedLine{string(line), width - w})\n\t\t\tline = str\n\t\t\tprefixLength = 0\n\t\t\twidth = w\n\t\t\tmaxWidth = contMax\n\t\t}\n\t}\n\tlines = append(lines, WrappedLine{string(line), width})\n\treturn lines\n}\n"
  },
  {
    "path": "src/tui/tui_test.go",
    "content": "package tui\n\nimport \"testing\"\n\nfunc TestWrapLine(t *testing.T) {\n\t// Basic wrapping\n\tlines := WrapLine(\"hello world\", 0, 7, 8, 2)\n\tif len(lines) != 2 || lines[0].Text != \"hello w\" || lines[1].Text != \"orld\" {\n\t\tt.Errorf(\"Basic wrap: %v\", lines)\n\t}\n\n\t// Exact fit — no wrapping needed\n\tlines = WrapLine(\"hello\", 0, 5, 8, 2)\n\tif len(lines) != 1 || lines[0].Text != \"hello\" || lines[0].DisplayWidth != 5 {\n\t\tt.Errorf(\"Exact fit: %v\", lines)\n\t}\n\n\t// With prefix length\n\tlines = WrapLine(\"hello\", 3, 5, 8, 2)\n\tif len(lines) != 2 || lines[0].Text != \"he\" || lines[1].Text != \"llo\" {\n\t\tt.Errorf(\"Prefix length: %v\", lines)\n\t}\n\n\t// Empty string\n\tlines = WrapLine(\"\", 0, 10, 8, 2)\n\tif len(lines) != 1 || lines[0].Text != \"\" || lines[0].DisplayWidth != 0 {\n\t\tt.Errorf(\"Empty string: %v\", lines)\n\t}\n\n\t// Continuation lines account for wrapSignWidth\n\tlines = WrapLine(\"abcdefghij\", 0, 5, 8, 2)\n\t// First line: \"abcde\" (5 chars fit in width 5)\n\t// Continuation max: 5-2=3, so \"fgh\" then \"ij\"\n\tif len(lines) != 3 || lines[0].Text != \"abcde\" || lines[1].Text != \"fgh\" || lines[2].Text != \"ij\" {\n\t\tt.Errorf(\"Continuation: %v\", lines)\n\t}\n\n\t// Tab expansion\n\tlines = WrapLine(\"\\there\", 0, 10, 4, 2)\n\tif len(lines) != 1 || lines[0].DisplayWidth != 8 {\n\t\tt.Errorf(\"Tab: %v\", lines)\n\t}\n}\n\nfunc TestHexToColor(t *testing.T) {\n\tassert := func(expr string, r, g, b int) {\n\t\tcolor := HexToColor(expr)\n\t\tif !color.is24() ||\n\t\t\tint((color>>16)&0xff) != r ||\n\t\t\tint((color>>8)&0xff) != g ||\n\t\t\tint((color)&0xff) != b {\n\t\t\tt.Fail()\n\t\t}\n\t}\n\n\tassert(\"#ff0000\", 255, 0, 0)\n\tassert(\"#010203\", 1, 2, 3)\n\tassert(\"#102030\", 16, 32, 48)\n\tassert(\"#ffffff\", 255, 255, 255)\n}\n"
  },
  {
    "path": "src/util/atexit.go",
    "content": "package util\n\nimport (\n\t\"sync\"\n)\n\nvar atExitFuncs []func()\n\n// AtExit registers the function fn to be called on program termination.\n// The functions will be called in reverse order they were registered.\nfunc AtExit(fn func()) {\n\tif fn == nil {\n\t\tpanic(\"AtExit called with nil func\")\n\t}\n\tonce := &sync.Once{}\n\tatExitFuncs = append(atExitFuncs, func() {\n\t\tonce.Do(fn)\n\t})\n}\n\n// RunAtExitFuncs runs any functions registered with AtExit().\nfunc RunAtExitFuncs() {\n\tfns := atExitFuncs\n\tfor i := len(fns) - 1; i >= 0; i-- {\n\t\tfns[i]()\n\t}\n\tatExitFuncs = nil\n}\n"
  },
  {
    "path": "src/util/atexit_test.go",
    "content": "package util\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestAtExit(t *testing.T) {\n\twant := []int{3, 2, 1, 0}\n\tvar called []int\n\tfor i := range 4 {\n\t\tn := i\n\t\tAtExit(func() { called = append(called, n) })\n\t}\n\tRunAtExitFuncs()\n\tif !reflect.DeepEqual(called, want) {\n\t\tt.Errorf(\"AtExit: want call order: %v got: %v\", want, called)\n\t}\n\n\tRunAtExitFuncs()\n\tif !reflect.DeepEqual(called, want) {\n\t\tt.Error(\"AtExit: should only call exit funcs once\")\n\t}\n}\n"
  },
  {
    "path": "src/util/atomicbool.go",
    "content": "package util\n\nimport (\n\t\"sync/atomic\"\n)\n\nfunc convertBoolToInt32(b bool) int32 {\n\tif b {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\n// AtomicBool is a boxed-class that provides synchronized access to the\n// underlying boolean value\ntype AtomicBool struct {\n\tstate int32 // \"1\" is true, \"0\" is false\n}\n\n// NewAtomicBool returns a new AtomicBool\nfunc NewAtomicBool(initialState bool) *AtomicBool {\n\treturn &AtomicBool{state: convertBoolToInt32(initialState)}\n}\n\n// Get returns the current boolean value synchronously\nfunc (a *AtomicBool) Get() bool {\n\treturn atomic.LoadInt32(&a.state) == 1\n}\n\n// Set updates the boolean value synchronously\nfunc (a *AtomicBool) Set(newState bool) bool {\n\tatomic.StoreInt32(&a.state, convertBoolToInt32(newState))\n\treturn newState\n}\n"
  },
  {
    "path": "src/util/atomicbool_test.go",
    "content": "package util\n\nimport \"testing\"\n\nfunc TestAtomicBool(t *testing.T) {\n\tif !NewAtomicBool(true).Get() || NewAtomicBool(false).Get() {\n\t\tt.Error(\"Invalid initial value\")\n\t}\n\n\tab := NewAtomicBool(true)\n\tif ab.Set(false) {\n\t\tt.Error(\"Invalid return value\")\n\t}\n\tif ab.Get() {\n\t\tt.Error(\"Invalid state\")\n\t}\n}\n"
  },
  {
    "path": "src/util/chars.go",
    "content": "package util\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\t\"unsafe\"\n)\n\nconst (\n\toverflow64 uint64 = 0x8080808080808080\n\toverflow32 uint32 = 0x80808080\n)\n\ntype Chars struct {\n\tslice           []byte // or []rune\n\tinBytes         bool\n\ttrimLengthKnown bool\n\ttrimLength      uint16\n\n\t// XXX Piggybacking item index here is a horrible idea. But I'm trying to\n\t// minimize the memory footprint by not wasting padded spaces.\n\tIndex int32\n}\n\nfunc checkAscii(bytes []byte) (bool, int) {\n\ti := 0\n\tfor ; i <= len(bytes)-8; i += 8 {\n\t\tif (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {\n\t\t\treturn false, i\n\t\t}\n\t}\n\tfor ; i <= len(bytes)-4; i += 4 {\n\t\tif (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {\n\t\t\treturn false, i\n\t\t}\n\t}\n\tfor ; i < len(bytes); i++ {\n\t\tif bytes[i] >= utf8.RuneSelf {\n\t\t\treturn false, i\n\t\t}\n\t}\n\treturn true, 0\n}\n\n// ToChars converts byte array into rune array\nfunc ToChars(bytes []byte) Chars {\n\tinBytes, bytesUntil := checkAscii(bytes)\n\tif inBytes {\n\t\treturn Chars{slice: bytes, inBytes: inBytes}\n\t}\n\n\trunes := make([]rune, bytesUntil, len(bytes))\n\tfor i := range bytesUntil {\n\t\trunes[i] = rune(bytes[i])\n\t}\n\tfor i := bytesUntil; i < len(bytes); {\n\t\tr, sz := utf8.DecodeRune(bytes[i:])\n\t\ti += sz\n\t\trunes = append(runes, r)\n\t}\n\treturn RunesToChars(runes)\n}\n\nfunc RunesToChars(runes []rune) Chars {\n\treturn Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}\n}\n\nfunc (chars *Chars) IsBytes() bool {\n\treturn chars.inBytes\n}\n\nfunc (chars *Chars) Bytes() []byte {\n\treturn chars.slice\n}\n\nfunc (chars *Chars) NumLines(atMost int) (int, bool) {\n\tlines := 1\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\tfor _, r := range runes {\n\t\t\tif r == '\\n' {\n\t\t\t\tlines++\n\t\t\t}\n\t\t\tif lines > atMost {\n\t\t\t\treturn atMost, true\n\t\t\t}\n\t\t}\n\t\treturn lines, false\n\t}\n\n\tfor idx := 0; idx < len(chars.slice); idx++ {\n\t\tfound := bytes.IndexByte(chars.slice[idx:], '\\n')\n\t\tif found < 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tidx += found\n\t\tlines++\n\t\tif lines > atMost {\n\t\t\treturn atMost, true\n\t\t}\n\t}\n\treturn lines, false\n}\n\nfunc (chars *Chars) optionalRunes() []rune {\n\tif chars.inBytes {\n\t\treturn nil\n\t}\n\treturn *(*[]rune)(unsafe.Pointer(&chars.slice))\n}\n\nfunc (chars *Chars) Get(i int) rune {\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\treturn runes[i]\n\t}\n\treturn rune(chars.slice[i])\n}\n\nfunc (chars *Chars) Length() int {\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\treturn len(runes)\n\t}\n\treturn len(chars.slice)\n}\n\n// String returns the string representation of a Chars object.\nfunc (chars *Chars) String() string {\n\treturn fmt.Sprintf(\"Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}\", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)\n}\n\n// TrimLength returns the length after trimming leading and trailing whitespaces\nfunc (chars *Chars) TrimLength() uint16 {\n\tif chars.trimLengthKnown {\n\t\treturn chars.trimLength\n\t}\n\tchars.trimLengthKnown = true\n\tvar i int\n\tlen := chars.Length()\n\tfor i = len - 1; i >= 0; i-- {\n\t\tchar := chars.Get(i)\n\t\tif !unicode.IsSpace(char) {\n\t\t\tbreak\n\t\t}\n\t}\n\t// Completely empty\n\tif i < 0 {\n\t\treturn 0\n\t}\n\n\tvar j int\n\tfor j = 0; j < len; j++ {\n\t\tchar := chars.Get(j)\n\t\tif !unicode.IsSpace(char) {\n\t\t\tbreak\n\t\t}\n\t}\n\tchars.trimLength = AsUint16(i - j + 1)\n\treturn chars.trimLength\n}\n\nfunc (chars *Chars) LeadingWhitespaces() int {\n\twhitespaces := 0\n\tfor i := 0; i < chars.Length(); i++ {\n\t\tchar := chars.Get(i)\n\t\tif !unicode.IsSpace(char) {\n\t\t\tbreak\n\t\t}\n\t\twhitespaces++\n\t}\n\treturn whitespaces\n}\n\nfunc (chars *Chars) TrailingWhitespaces() int {\n\twhitespaces := 0\n\tfor i := chars.Length() - 1; i >= 0; i-- {\n\t\tchar := chars.Get(i)\n\t\tif !unicode.IsSpace(char) {\n\t\t\tbreak\n\t\t}\n\t\twhitespaces++\n\t}\n\treturn whitespaces\n}\n\nfunc (chars *Chars) TrimTrailingWhitespaces(maxIndex int) {\n\twhitespaces := chars.TrailingWhitespaces()\n\tend := len(chars.slice) - whitespaces\n\tchars.slice = chars.slice[0:max(end, maxIndex)]\n}\n\nfunc (chars *Chars) TrimSuffix(runes []rune) {\n\tlastIdx := len(chars.slice)\n\tfirstIdx := lastIdx - len(runes)\n\tif firstIdx < 0 {\n\t\treturn\n\t}\n\n\tfor i := firstIdx; i < lastIdx; i++ {\n\t\tchar := chars.Get(i)\n\t\tif char != runes[i-firstIdx] {\n\t\t\treturn\n\t\t}\n\t}\n\n\tchars.slice = chars.slice[0:firstIdx]\n}\n\nfunc (chars *Chars) SliceRight(last int) {\n\tchars.slice = chars.slice[:last]\n}\n\nfunc (chars *Chars) ToString() string {\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\treturn string(runes)\n\t}\n\treturn unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))\n}\n\nfunc (chars *Chars) ToRunes() []rune {\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\treturn runes\n\t}\n\tbytes := chars.slice\n\trunes := make([]rune, len(bytes))\n\tfor idx, b := range bytes {\n\t\trunes[idx] = rune(b)\n\t}\n\treturn runes\n}\n\nfunc (chars *Chars) CopyRunes(dest []rune, from int) {\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\tcopy(dest, runes[from:])\n\t\treturn\n\t}\n\tfor idx, b := range chars.slice[from:][:len(dest)] {\n\t\tdest[idx] = rune(b)\n\t}\n}\n\nfunc (chars *Chars) Prepend(prefix string) {\n\tif runes := chars.optionalRunes(); runes != nil {\n\t\trunes = append([]rune(prefix), runes...)\n\t\tchars.slice = *(*[]byte)(unsafe.Pointer(&runes))\n\t} else {\n\t\tchars.slice = append([]byte(prefix), chars.slice...)\n\t}\n}\n\nfunc (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, wrapWord bool) ([][]rune, bool) {\n\ttext := make([]rune, chars.Length())\n\tcopy(text, chars.ToRunes())\n\n\tlines := [][]rune{}\n\toverflow := false\n\tif !multiLine {\n\t\tlines = append(lines, text)\n\t} else {\n\t\tfrom := 0\n\t\tfor off := range text {\n\t\t\tif text[off] == '\\n' {\n\t\t\t\tlines = append(lines, text[from:off+1]) // Include '\\n'\n\t\t\t\tfrom = off + 1\n\t\t\t\tif len(lines) >= maxLines {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvar lastLine []rune\n\t\tif from < len(text) {\n\t\t\tlastLine = text[from:]\n\t\t}\n\n\t\toverflow = false\n\t\tif len(lines) >= maxLines {\n\t\t\toverflow = true\n\t\t} else {\n\t\t\tlines = append(lines, lastLine)\n\t\t}\n\t}\n\n\t// If wrapping is disabled, we're done\n\tif wrapCols == 0 {\n\t\treturn lines, overflow\n\t}\n\n\twrapped := [][]rune{}\n\tfor _, line := range lines {\n\t\t// Remove trailing '\\n' and remember if it was there\n\t\tnewline := len(line) > 0 && line[len(line)-1] == '\\n'\n\t\tif newline {\n\t\t\tline = line[:len(line)-1]\n\t\t}\n\n\t\thasWrapSign := false\n\t\tfor {\n\t\t\tcols := wrapCols\n\t\t\tif hasWrapSign {\n\t\t\t\tcols -= wrapSignWidth\n\t\t\t}\n\t\t\t_, overflowIdx := RunesWidth(line, 0, tabstop, cols)\n\t\t\tif overflowIdx >= 0 {\n\t\t\t\t// Might be a wide character\n\t\t\t\tif overflowIdx == 0 {\n\t\t\t\t\toverflowIdx = 1\n\t\t\t\t}\n\t\t\t\tif wrapWord {\n\t\t\t\t\t// Find last space/tab at or before overflowIdx\n\t\t\t\t\tbreakIdx := -1\n\t\t\t\t\tfor k := overflowIdx; k > 0; k-- {\n\t\t\t\t\t\tif line[k-1] == ' ' || line[k-1] == '\\t' {\n\t\t\t\t\t\t\tbreakIdx = k\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif breakIdx > 0 {\n\t\t\t\t\t\toverflowIdx = breakIdx\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(wrapped) >= maxLines {\n\t\t\t\t\treturn wrapped, true\n\t\t\t\t}\n\t\t\t\twrapped = append(wrapped, line[:overflowIdx])\n\t\t\t\thasWrapSign = true\n\t\t\t\tline = line[overflowIdx:]\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\thasWrapSign = false\n\n\t\t\t// Restore trailing '\\n'\n\t\t\tif newline {\n\t\t\t\tline = append(line, '\\n')\n\t\t\t}\n\n\t\t\tif len(wrapped) >= maxLines {\n\t\t\t\treturn wrapped, true\n\t\t\t}\n\n\t\t\twrapped = append(wrapped, line)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn wrapped, overflow\n}\n"
  },
  {
    "path": "src/util/chars_test.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestToCharsAscii(t *testing.T) {\n\tchars := ToChars([]byte(\"foobar\"))\n\tif !chars.inBytes || chars.ToString() != \"foobar\" || !chars.inBytes {\n\t\tt.Error()\n\t}\n}\n\nfunc TestCharsLength(t *testing.T) {\n\tchars := ToChars([]byte(\"\\tabc한글  \"))\n\tif chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {\n\t\tt.Error()\n\t}\n}\n\nfunc TestCharsToString(t *testing.T) {\n\ttext := \"\\tabc한글  \"\n\tchars := ToChars([]byte(text))\n\tif chars.ToString() != text {\n\t\tt.Error()\n\t}\n}\n\nfunc TestTrimLength(t *testing.T) {\n\tcheck := func(str string, exp uint16) {\n\t\tchars := ToChars([]byte(str))\n\t\ttrimmed := chars.TrimLength()\n\t\tif trimmed != exp {\n\t\t\tt.Errorf(\"Invalid TrimLength result for '%s': %d (expected %d)\",\n\t\t\t\tstr, trimmed, exp)\n\t\t}\n\t}\n\tcheck(\"hello\", 5)\n\tcheck(\"hello \", 5)\n\tcheck(\"hello  \", 5)\n\tcheck(\" hello\", 5)\n\tcheck(\"  hello\", 5)\n\tcheck(\" hello \", 5)\n\tcheck(\"  hello  \", 5)\n\tcheck(\"h   o\", 5)\n\tcheck(\"  h   o  \", 5)\n\tcheck(\"         \", 0)\n}\n\nfunc TestCharsLines(t *testing.T) {\n\tchars := ToChars([]byte(\"abcdef\\n가나다\\n\\tdef\"))\n\tcheck := func(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, expectedNumLines int, expectedOverflow bool) {\n\t\tlines, overflow := chars.Lines(multiLine, maxLines, wrapCols, wrapSignWidth, tabstop, false)\n\t\tfmt.Println(lines, overflow)\n\t\tif len(lines) != expectedNumLines || overflow != expectedOverflow {\n\t\t\tt.Errorf(\"Invalid result: %d %v (expected %d %v)\", len(lines), overflow, expectedNumLines, expectedOverflow)\n\t\t}\n\t}\n\n\t// No wrap\n\tcheck(true, 1, 0, 0, 8, 1, true)\n\tcheck(true, 2, 0, 0, 8, 2, true)\n\tcheck(true, 3, 0, 0, 8, 3, false)\n\n\t// Wrap (2)\n\tcheck(true, 4, 2, 0, 8, 4, true)\n\tcheck(true, 5, 2, 0, 8, 5, true)\n\tcheck(true, 6, 2, 0, 8, 6, true)\n\tcheck(true, 7, 2, 0, 8, 7, true)\n\tcheck(true, 8, 2, 0, 8, 8, true)\n\tcheck(true, 9, 2, 0, 8, 9, false)\n\tcheck(true, 9, 2, 0, 1, 8, false) // Smaller tab size\n\n\t// With wrap sign (3 + 1)\n\tcheck(true, 100, 3, 1, 1, 8, false)\n\n\t// With wrap sign (3 + 2)\n\tcheck(true, 100, 3, 2, 1, 10, false)\n\n\t// With wrap sign (3 + 2) and no multi-line\n\tcheck(false, 100, 3, 2, 1, 13, false)\n}\n\nfunc TestCharsLinesWrapWord(t *testing.T) {\n\t// \"hello world foo bar\" with width 12 should break at word boundaries\n\tchars := ToChars([]byte(\"hello world foo bar\"))\n\tlines, overflow := chars.Lines(false, 100, 12, 0, 8, true)\n\t// \"hello world \" (12) | \"foo bar\" (7)\n\tif len(lines) != 2 || overflow {\n\t\tt.Errorf(\"Expected 2 lines, got %d (overflow: %v): %v\", len(lines), overflow, lines)\n\t}\n\tif string(lines[0]) != \"hello world \" {\n\t\tt.Errorf(\"Expected first line 'hello world ', got %q\", string(lines[0]))\n\t}\n\tif string(lines[1]) != \"foo bar\" {\n\t\tt.Errorf(\"Expected second line 'foo bar', got %q\", string(lines[1]))\n\t}\n\n\t// No word boundary: a single long word falls back to character wrap\n\tchars2 := ToChars([]byte(\"abcdefghijklmnop\"))\n\tlines2, _ := chars2.Lines(false, 100, 10, 0, 8, true)\n\tif len(lines2) != 2 {\n\t\tt.Errorf(\"Expected 2 lines for long word, got %d: %v\", len(lines2), lines2)\n\t}\n\tif string(lines2[0]) != \"abcdefghij\" {\n\t\tt.Errorf(\"Expected first line 'abcdefghij', got %q\", string(lines2[0]))\n\t}\n\n\t// Tab as word boundary\n\tchars3 := ToChars([]byte(\"hello\\tworld\"))\n\tlines3, _ := chars3.Lines(false, 100, 7, 0, 8, true)\n\t// \"hello\\t\" should break at tab (width of tab at pos 5 with tabstop 8 = 3, total width = 8 > 7)\n\t// Actually RunesWidth: 'h'=1,'e'=1,'l'=1,'l'=1,'o'=1,'\\t'=3 = 8 > 7, overflowIdx=5\n\t// Then word-wrap scans back and finds no space/tab before idx 5 (tab IS at idx 5 but we check line[k-1])\n\t// Wait - let me think: overflowIdx=5, we check k=5 -> line[4]='o', k=4 -> line[3]='l'... no space/tab found\n\t// Falls back to character wrap: \"hello\" | \"\\tworld\"\n\tif len(lines3) < 2 {\n\t\tt.Errorf(\"Expected at least 2 lines for tab test, got %d: %v\", len(lines3), lines3)\n\t}\n\n\t// wrapWord=false still character-wraps\n\tchars4 := ToChars([]byte(\"hello world\"))\n\tlines4, _ := chars4.Lines(false, 100, 8, 0, 8, false)\n\tif len(lines4) != 2 {\n\t\tt.Errorf(\"Expected 2 lines with wrapWord=false, got %d: %v\", len(lines4), lines4)\n\t}\n\tif string(lines4[0]) != \"hello wo\" {\n\t\tt.Errorf(\"Expected first line 'hello wo', got %q\", string(lines4[0]))\n\t}\n}\n"
  },
  {
    "path": "src/util/concurrent_set.go",
    "content": "package util\n\nimport \"sync\"\n\n// ConcurrentSet is a thread-safe set implementation.\ntype ConcurrentSet[T comparable] struct {\n\tlock  sync.RWMutex\n\titems map[T]struct{}\n}\n\n// NewConcurrentSet creates a new ConcurrentSet.\nfunc NewConcurrentSet[T comparable]() *ConcurrentSet[T] {\n\treturn &ConcurrentSet[T]{\n\t\titems: make(map[T]struct{}),\n\t}\n}\n\n// Add adds an item to the set.\nfunc (s *ConcurrentSet[T]) Add(item T) {\n\ts.lock.Lock()\n\tdefer s.lock.Unlock()\n\ts.items[item] = struct{}{}\n}\n\n// Remove removes an item from the set.\nfunc (s *ConcurrentSet[T]) Remove(item T) {\n\ts.lock.Lock()\n\tdefer s.lock.Unlock()\n\tdelete(s.items, item)\n}\n\n// ForEach iterates over each item in the set and applies the provided function.\nfunc (s *ConcurrentSet[T]) ForEach(fn func(item T)) {\n\ts.lock.RLock()\n\tdefer s.lock.RUnlock()\n\tfor item := range s.items {\n\t\tfn(item)\n\t}\n}\n"
  },
  {
    "path": "src/util/eventbox.go",
    "content": "package util\n\nimport \"sync\"\n\n// EventType is the type for fzf events\ntype EventType int\n\n// Events is a type that associates EventType to any data\ntype Events map[EventType]any\n\n// EventBox is used for coordinating events\ntype EventBox struct {\n\tevents Events\n\tcond   *sync.Cond\n\tignore map[EventType]bool\n}\n\n// NewEventBox returns a new EventBox\nfunc NewEventBox() *EventBox {\n\treturn &EventBox{\n\t\tevents: make(Events),\n\t\tcond:   sync.NewCond(&sync.Mutex{}),\n\t\tignore: make(map[EventType]bool)}\n}\n\n// Wait blocks the goroutine until signaled\nfunc (b *EventBox) Wait(callback func(*Events)) {\n\tb.cond.L.Lock()\n\n\tif len(b.events) == 0 {\n\t\tb.cond.Wait()\n\t}\n\n\tcallback(&b.events)\n\tb.cond.L.Unlock()\n}\n\n// Set turns on the event type on the box\nfunc (b *EventBox) Set(event EventType, value any) {\n\tb.cond.L.Lock()\n\tb.events[event] = value\n\tif _, found := b.ignore[event]; !found {\n\t\tb.cond.Broadcast()\n\t}\n\tb.cond.L.Unlock()\n}\n\n// Clear clears the events\n// Unsynchronized; should be called within Wait routine\nfunc (events *Events) Clear() {\n\tfor event := range *events {\n\t\tdelete(*events, event)\n\t}\n}\n\n// Peek peeks at the event box if the given event is set\nfunc (b *EventBox) Peek(event EventType) bool {\n\tb.cond.L.Lock()\n\t_, ok := b.events[event]\n\tb.cond.L.Unlock()\n\treturn ok\n}\n\n// Watch deletes the events from the ignore list\nfunc (b *EventBox) Watch(events ...EventType) {\n\tb.cond.L.Lock()\n\tfor _, event := range events {\n\t\tdelete(b.ignore, event)\n\t}\n\tb.cond.L.Unlock()\n}\n\n// Unwatch adds the events to the ignore list\nfunc (b *EventBox) Unwatch(events ...EventType) {\n\tb.cond.L.Lock()\n\tfor _, event := range events {\n\t\tb.ignore[event] = true\n\t}\n\tb.cond.L.Unlock()\n}\n\n// WaitFor blocks the execution until the event is received\nfunc (b *EventBox) WaitFor(event EventType) {\n\tlooping := true\n\tfor looping {\n\t\tb.Wait(func(events *Events) {\n\t\t\tfor evt := range *events {\n\t\t\t\tswitch evt {\n\t\t\t\tcase event:\n\t\t\t\t\tlooping = false\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/util/eventbox_test.go",
    "content": "package util\n\nimport \"testing\"\n\n// fzf events\nconst (\n\tEvtReadNew EventType = iota\n\tEvtReadFin\n\tEvtSearchNew\n\tEvtSearchProgress\n\tEvtSearchFin\n)\n\nfunc TestEventBox(t *testing.T) {\n\teb := NewEventBox()\n\n\t// Wait should return immediately\n\tch := make(chan bool)\n\n\tgo func() {\n\t\teb.Set(EvtReadNew, 10)\n\t\tch <- true\n\t\t<-ch\n\t\teb.Set(EvtSearchNew, 10)\n\t\teb.Set(EvtSearchNew, 15)\n\t\teb.Set(EvtSearchNew, 20)\n\t\teb.Set(EvtSearchProgress, 30)\n\t\tch <- true\n\t\t<-ch\n\t\teb.Set(EvtSearchFin, 40)\n\t\tch <- true\n\t\t<-ch\n\t}()\n\n\tcount := 0\n\tsum := 0\n\tlooping := true\n\tfor looping {\n\t\t<-ch\n\t\teb.Wait(func(events *Events) {\n\t\t\tfor _, value := range *events {\n\t\t\t\tswitch val := value.(type) {\n\t\t\t\tcase int:\n\t\t\t\t\tsum += val\n\t\t\t\t\tlooping = sum < 100\n\t\t\t\t}\n\t\t\t}\n\t\t\tevents.Clear()\n\t\t})\n\t\tch <- true\n\t\tcount++\n\t}\n\n\tif count != 3 {\n\t\tt.Error(\"Invalid number of events\", count)\n\t}\n\tif sum != 100 {\n\t\tt.Error(\"Invalid sum\", sum)\n\t}\n}\n"
  },
  {
    "path": "src/util/slab.go",
    "content": "package util\n\ntype Slab struct {\n\tI16 []int16\n\tI32 []int32\n}\n\nfunc MakeSlab(size16 int, size32 int) *Slab {\n\treturn &Slab{\n\t\tI16: make([]int16, size16),\n\t\tI32: make([]int32, size32)}\n}\n"
  },
  {
    "path": "src/util/util.go",
    "content": "package util\n\nimport (\n\t\"cmp\"\n\t\"math\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/mattn/go-isatty\"\n\t\"github.com/rivo/uniseg\"\n)\n\n// StringWidth returns string width where each CR/LF character takes 1 column\nfunc StringWidth(s string) int {\n\treturn uniseg.StringWidth(s) + strings.Count(s, \"\\n\") + strings.Count(s, \"\\r\")\n}\n\n// RunesWidth returns runes width\nfunc RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {\n\treturn StringsWidth(string(runes), prefixWidth, tabstop, limit)\n}\n\n// StringsWidth returns the width of the string\nfunc StringsWidth(str string, prefixWidth int, tabstop int, limit int) (int, int) {\n\twidth := 0\n\tgr := uniseg.NewGraphemes(str)\n\tidx := 0\n\tfor gr.Next() {\n\t\trs := gr.Runes()\n\t\tvar w int\n\t\tif len(rs) == 1 && rs[0] == '\\t' {\n\t\t\tw = tabstop - (prefixWidth+width)%tabstop\n\t\t} else {\n\t\t\tw = StringWidth(string(rs))\n\t\t}\n\t\twidth += w\n\t\tif width > limit {\n\t\t\treturn width, idx\n\t\t}\n\t\tidx += len(rs)\n\t}\n\treturn width, -1\n}\n\n// Truncate returns the truncated runes and its width\nfunc Truncate(input string, limit int) ([]rune, int) {\n\trunes := []rune{}\n\twidth := 0\n\tgr := uniseg.NewGraphemes(input)\n\tfor gr.Next() {\n\t\trs := gr.Runes()\n\t\tw := StringWidth(string(rs))\n\t\tif width+w > limit {\n\t\t\treturn runes, width\n\t\t}\n\t\twidth += w\n\t\trunes = append(runes, rs...)\n\t}\n\treturn runes, width\n}\n\nfunc Constrain[T cmp.Ordered](val, minimum, maximum T) T {\n\treturn max(min(val, maximum), minimum)\n}\n\nfunc AsUint16(val int) uint16 {\n\tif val > math.MaxUint16 {\n\t\treturn math.MaxUint16\n\t} else if val < 0 {\n\t\treturn 0\n\t}\n\treturn uint16(val)\n}\n\n// IsTty returns true if the file is a terminal\nfunc IsTty(file *os.File) bool {\n\tfd := file.Fd()\n\treturn isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)\n}\n\n// RunOnce runs the given function only once\nfunc RunOnce(f func()) func() {\n\tonce := Once(true)\n\treturn func() {\n\t\tif once() {\n\t\t\tf()\n\t\t}\n\t}\n}\n\n// Once returns a function that returns the specified boolean value only once\nfunc Once(nextResponse bool) func() bool {\n\tstate := nextResponse\n\treturn func() bool {\n\t\tprevState := state\n\t\tstate = !nextResponse\n\t\treturn prevState\n\t}\n}\n\n// RepeatToFill repeats the given string to fill the given width\nfunc RepeatToFill(str string, length int, limit int) string {\n\ttimes := limit / length\n\trest := limit % length\n\toutput := strings.Repeat(str, times)\n\tif rest > 0 {\n\t\tfor _, r := range str {\n\t\t\trest -= uniseg.StringWidth(string(r))\n\t\t\tif rest < 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toutput += string(r)\n\t\t\tif rest == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn output\n}\n\n// ToKebabCase converts the given CamelCase string to kebab-case\nfunc ToKebabCase(s string) string {\n\tname := \"\"\n\tfor i, r := range s {\n\t\tif i > 0 && r >= 'A' && r <= 'Z' {\n\t\t\tname += \"-\"\n\t\t}\n\t\tname += string(r)\n\t}\n\treturn strings.ToLower(name)\n}\n\n// CompareVersions compares two version strings\nfunc CompareVersions(v1, v2 string) int {\n\tparts1 := strings.Split(v1, \".\")\n\tparts2 := strings.Split(v2, \".\")\n\n\tatoi := func(s string) int {\n\t\tn, e := strconv.Atoi(s)\n\t\tif e != nil {\n\t\t\treturn 0\n\t\t}\n\t\treturn n\n\t}\n\n\tfor i := 0; i < max(len(parts1), len(parts2)); i++ {\n\t\tvar p1, p2 int\n\t\tif i < len(parts1) {\n\t\t\tp1 = atoi(parts1[i])\n\t\t}\n\t\tif i < len(parts2) {\n\t\t\tp2 = atoi(parts2[i])\n\t\t}\n\n\t\tif p1 > p2 {\n\t\t\treturn 1\n\t\t} else if p1 < p2 {\n\t\t\treturn -1\n\t\t}\n\t}\n\treturn 0\n}\n"
  },
  {
    "path": "src/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"math\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestConstrain(t *testing.T) {\n\tif Constrain(-3, -1, 3) != -1 {\n\t\tt.Error(\"Expected\", -1)\n\t}\n\tif Constrain(2, -1, 3) != 2 {\n\t\tt.Error(\"Expected\", 2)\n\t}\n\n\tif Constrain(5, -1, 3) != 3 {\n\t\tt.Error(\"Expected\", 3)\n\t}\n}\n\nfunc TestAsUint16(t *testing.T) {\n\tif AsUint16(5) != 5 {\n\t\tt.Error(\"Expected\", 5)\n\t}\n\tif AsUint16(-10) != 0 {\n\t\tt.Error(\"Expected\", 0)\n\t}\n\tif AsUint16(math.MaxUint16) != math.MaxUint16 {\n\t\tt.Error(\"Expected\", math.MaxUint16)\n\t}\n\tif AsUint16(math.MinInt32) != 0 {\n\t\tt.Error(\"Expected\", 0)\n\t}\n\tif AsUint16(math.MinInt16) != 0 {\n\t\tt.Error(\"Expected\", 0)\n\t}\n\tif AsUint16(math.MaxUint16+1) != math.MaxUint16 {\n\t\tt.Error(\"Expected\", math.MaxUint16)\n\t}\n}\n\nfunc TestOnce(t *testing.T) {\n\to := Once(false)\n\tif o() {\n\t\tt.Error(\"Expected: false\")\n\t}\n\tif !o() {\n\t\tt.Error(\"Expected: true\")\n\t}\n\tif !o() {\n\t\tt.Error(\"Expected: true\")\n\t}\n\n\to = Once(true)\n\tif !o() {\n\t\tt.Error(\"Expected: true\")\n\t}\n\tif o() {\n\t\tt.Error(\"Expected: false\")\n\t}\n\tif o() {\n\t\tt.Error(\"Expected: false\")\n\t}\n}\n\nfunc TestRunesWidth(t *testing.T) {\n\tfor _, args := range [][]int{\n\t\t{100, 5, -1},\n\t\t{3, 4, 3},\n\t\t{0, 1, 0},\n\t} {\n\t\twidth, overflowIdx := RunesWidth([]rune(\"hello\"), 0, 0, args[0])\n\t\tif width != args[1] {\n\t\t\tt.Errorf(\"Expected width: %d, actual: %d\", args[1], width)\n\t\t}\n\t\tif overflowIdx != args[2] {\n\t\t\tt.Errorf(\"Expected overflow index: %d, actual: %d\", args[2], overflowIdx)\n\t\t}\n\t}\n\tfor _, input := range []struct {\n\t\ts string\n\t\tw int\n\t}{\n\t\t{\"▶\", 1},\n\t\t{\"▶️\", 2},\n\t} {\n\t\twidth, _ := RunesWidth([]rune(input.s), 0, 0, 100)\n\t\tif width != input.w {\n\t\t\tt.Errorf(\"Expected width of %s: %d, actual: %d\", input.s, input.w, width)\n\t\t}\n\t}\n}\n\nfunc TestTruncate(t *testing.T) {\n\ttruncated, width := Truncate(\"가나다라마\", 7)\n\tif string(truncated) != \"가나다\" {\n\t\tt.Errorf(\"Expected: 가나다, actual: %s\", string(truncated))\n\t}\n\tif width != 6 {\n\t\tt.Errorf(\"Expected: 6, actual: %d\", width)\n\t}\n}\n\nfunc TestRepeatToFill(t *testing.T) {\n\tif RepeatToFill(\"abcde\", 10, 50) != strings.Repeat(\"abcde\", 5) {\n\t\tt.Error(\"Expected:\", strings.Repeat(\"abcde\", 5))\n\t}\n\tif RepeatToFill(\"abcde\", 10, 42) != strings.Repeat(\"abcde\", 4)+\"abcde\"[:2] {\n\t\tt.Error(\"Expected:\", strings.Repeat(\"abcde\", 4)+\"abcde\"[:2])\n\t}\n}\n\nfunc TestStringWidth(t *testing.T) {\n\tw := StringWidth(\"─\")\n\tif w != 1 {\n\t\tt.Errorf(\"Expected: %d, Actual: %d\", 1, w)\n\t}\n}\n\nfunc TestCompareVersions(t *testing.T) {\n\tassert := func(a, b string, expected int) {\n\t\tif result := CompareVersions(a, b); result != expected {\n\t\t\tt.Errorf(\"Expected: %d, Actual: %d\", expected, result)\n\t\t}\n\t}\n\n\tassert(\"2\", \"1\", 1)\n\tassert(\"2\", \"2\", 0)\n\tassert(\"2\", \"10\", -1)\n\n\tassert(\"2.1\", \"2.2\", -1)\n\tassert(\"2.1\", \"2.1.1\", -1)\n\n\tassert(\"1.2.3\", \"1.2.2\", 1)\n\tassert(\"1.2.3\", \"1.2.3\", 0)\n\tassert(\"1.2.3\", \"1.2.3.0\", 0)\n\tassert(\"1.2.3\", \"1.2.4\", -1)\n\n\t// Different number of parts\n\tassert(\"1.0.0\", \"1\", 0)\n\tassert(\"1.0.0\", \"1.0\", 0)\n\tassert(\"1.0.0\", \"1.0.0\", 0)\n\tassert(\"1.0\", \"1.0.0\", 0)\n\tassert(\"1\", \"1.0.0\", 0)\n\tassert(\"1.0.0\", \"1.0.0.1\", -1)\n\tassert(\"1.0.0.1.0\", \"1.0.0.1\", 0)\n\n\tassert(\"\", \"3.4.5\", -1)\n}\n"
  },
  {
    "path": "src/util/util_unix.go",
    "content": "//go:build !windows\n\npackage util\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/junegunn/go-shellwords\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype Executor struct {\n\tshell   string\n\targs    []string\n\tescaper *strings.Replacer\n}\n\nfunc NewExecutor(withShell string) *Executor {\n\tshell := os.Getenv(\"SHELL\")\n\targs, err := shellwords.Parse(withShell)\n\tif err == nil && len(args) > 0 {\n\t\tshell = args[0]\n\t\targs = args[1:]\n\t} else {\n\t\tif len(shell) == 0 {\n\t\t\tshell = \"sh\"\n\t\t}\n\t\targs = []string{\"-c\"}\n\t}\n\n\tvar escaper *strings.Replacer\n\ttokens := strings.Split(shell, \"/\")\n\tif tokens[len(tokens)-1] == \"fish\" {\n\t\t// https://fishshell.com/docs/current/language.html#quotes\n\t\t// > The only meaningful escape sequences in single quotes are \\', which\n\t\t// > escapes a single quote and \\\\, which escapes the backslash symbol.\n\t\tescaper = strings.NewReplacer(\"\\\\\", \"\\\\\\\\\", \"'\", \"\\\\'\")\n\t} else {\n\t\tescaper = strings.NewReplacer(\"'\", \"'\\\\''\")\n\t}\n\treturn &Executor{shell, args, escaper}\n}\n\n// ExecCommand executes the given command with $SHELL\nfunc (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {\n\tcmd := exec.Command(x.shell, append(x.args, command)...)\n\tif setpgid {\n\t\tcmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}\n\t}\n\treturn cmd\n}\n\nfunc (x *Executor) QuoteEntry(entry string) string {\n\treturn \"'\" + x.escaper.Replace(entry) + \"'\"\n}\n\nfunc (x *Executor) Become(stdin *os.File, environ []string, command string) {\n\tshellPath, err := exec.LookPath(x.shell)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"fzf (become): %s\\n\", err.Error())\n\t\tos.Exit(127)\n\t}\n\targs := append([]string{shellPath}, append(x.args, command)...)\n\tSetStdin(stdin)\n\tsyscall.Exec(shellPath, args, environ)\n}\n\n// KillCommand kills the process for the given command\nfunc KillCommand(cmd *exec.Cmd) error {\n\treturn syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)\n}\n\n// IsWindows returns true on Windows\nfunc IsWindows() bool {\n\treturn false\n}\n\n// SetNonblock executes syscall.SetNonblock on file descriptor\nfunc SetNonblock(file *os.File, nonblock bool) {\n\tsyscall.SetNonblock(int(file.Fd()), nonblock)\n}\n\n// Read executes syscall.Read on file descriptor\nfunc Read(fd int, b []byte) (int, error) {\n\treturn syscall.Read(int(fd), b)\n}\n\nfunc SetStdin(file *os.File) {\n\tunix.Dup2(int(file.Fd()), 0)\n}\n"
  },
  {
    "path": "src/util/util_windows.go",
    "content": "//go:build windows\n\npackage util\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"syscall\"\n)\n\ntype shellType int\n\nconst (\n\tshellTypeUnknown shellType = iota\n\tshellTypeCmd\n\tshellTypePowerShell\n)\n\nvar escapeRegex = regexp.MustCompile(`[&|<>()^%!\"]`)\n\ntype Executor struct {\n\tshell     string\n\tshellType shellType\n\targs      []string\n\tshellPath atomic.Value\n}\n\nfunc NewExecutor(withShell string) *Executor {\n\tshell := os.Getenv(\"SHELL\")\n\targs := strings.Fields(withShell)\n\tif len(args) > 0 {\n\t\tshell = args[0]\n\t} else if len(shell) == 0 {\n\t\tshell = \"cmd\"\n\t}\n\n\tshellType := shellTypeUnknown\n\tbasename := filepath.Base(shell)\n\tif len(args) > 0 {\n\t\targs = args[1:]\n\t} else if strings.HasPrefix(basename, \"cmd\") {\n\t\tshellType = shellTypeCmd\n\t\targs = []string{\"/s/c\"}\n\t} else if strings.HasPrefix(basename, \"pwsh\") || strings.HasPrefix(basename, \"powershell\") {\n\t\tshellType = shellTypePowerShell\n\t\targs = []string{\"-NoProfile\", \"-Command\"}\n\t} else {\n\t\targs = []string{\"-c\"}\n\t}\n\treturn &Executor{shell: shell, shellType: shellType, args: args}\n}\n\n// ExecCommand executes the given command with $SHELL\n// FIXME: setpgid is unused. We set it in the Unix implementation so that we\n// can kill preview process with its child processes at once.\n// NOTE: For \"powershell\", we should ideally set output encoding to UTF8,\n// but it is left as is now because no adverse effect has been observed.\nfunc (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {\n\tshell := x.shell\n\tif cached := x.shellPath.Load(); cached != nil {\n\t\tshell = cached.(string)\n\t} else {\n\t\tif strings.Contains(shell, \"/\") {\n\t\t\tout, err := exec.Command(\"cygpath\", \"-w\", shell).Output()\n\t\t\tif err == nil {\n\t\t\t\tshell = strings.Trim(string(out), \"\\n\")\n\t\t\t}\n\t\t}\n\t\tx.shellPath.Store(shell)\n\t}\n\tvar cmd *exec.Cmd\n\tif x.shellType == shellTypeCmd {\n\t\tcmd = exec.Command(shell)\n\t\tcmd.SysProcAttr = &syscall.SysProcAttr{\n\t\t\tHideWindow:    false,\n\t\t\tCmdLine:       fmt.Sprintf(`%s \"%s\"`, strings.Join(x.args, \" \"), command),\n\t\t\tCreationFlags: 0,\n\t\t}\n\t} else {\n\t\tcmd = exec.Command(shell, append(x.args, command)...)\n\t\tcmd.SysProcAttr = &syscall.SysProcAttr{\n\t\t\tHideWindow:    false,\n\t\t\tCreationFlags: 0,\n\t\t}\n\t}\n\treturn cmd\n}\n\nfunc (x *Executor) Become(stdin *os.File, environ []string, command string) {\n\tcmd := x.ExecCommand(command, false)\n\tcmd.Stdin = stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tcmd.Env = environ\n\terr := cmd.Start()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"fzf (become): %s\\n\", err.Error())\n\t\tos.Exit(127)\n\t}\n\terr = cmd.Wait()\n\tif err != nil {\n\t\tif exitError, ok := err.(*exec.ExitError); ok {\n\t\t\tos.Exit(exitError.ExitCode())\n\t\t}\n\t}\n\tos.Exit(0)\n}\n\nfunc escapeArg(s string) string {\n\tb := make([]byte, 0, len(s)+2)\n\tb = append(b, '\"')\n\tslashes := 0\n\tfor i := 0; i < len(s); i++ {\n\t\tc := s[i]\n\t\tswitch c {\n\t\tdefault:\n\t\t\tslashes = 0\n\t\tcase '\\\\':\n\t\t\tslashes++\n\t\tcase '\"':\n\t\t\tfor ; slashes > 0; slashes-- {\n\t\t\t\tb = append(b, '\\\\')\n\t\t\t}\n\t\t\tb = append(b, '\\\\')\n\t\t}\n\t\tb = append(b, c)\n\t}\n\tfor ; slashes > 0; slashes-- {\n\t\tb = append(b, '\\\\')\n\t}\n\tb = append(b, '\"')\n\treturn escapeRegex.ReplaceAllStringFunc(string(b), func(match string) string {\n\t\treturn \"^\" + match\n\t})\n}\n\nfunc (x *Executor) QuoteEntry(entry string) string {\n\tswitch x.shellType {\n\tcase shellTypeCmd:\n\t\t/* Manually tested with the following commands:\n\t\t   fzf --preview \"echo {}\"\n\t\t   fzf --preview \"type {}\"\n\t\t   echo .git\\refs\\| fzf --preview \"dir {}\"\n\t\t   echo .git\\refs\\\\| fzf --preview \"dir {}\"\n\t\t   echo .git\\refs\\\\\\| fzf --preview \"dir {}\"\n\t\t   reg query HKCU | fzf --reverse --bind \"enter:reload(reg query {})\"\n\t\t   fzf --disabled --preview \"echo {q} {n} {}\" --query \"&|<>()@^%!\"\n\t\t   fd -H --no-ignore -td -d 4 | fzf --preview \"dir {}\"\n\t\t   fd -H --no-ignore -td -d 4 | fzf --preview \"eza {}\" --preview-window up\n\t\t   fd -H --no-ignore -td -d 4 | fzf --preview \"eza --color=always --tree --level=3 --icons=always {}\"\n\t\t   fd -H --no-ignore -td -d 4 | fzf --preview \".\\eza.exe --color=always --tree --level=3 --icons=always {}\" --with-shell \"powershell -NoProfile -Command\"\n\t\t*/\n\t\treturn escapeArg(entry)\n\tcase shellTypePowerShell:\n\t\tescaped := strings.ReplaceAll(entry, `\"`, `\\\"`)\n\t\treturn \"'\" + strings.ReplaceAll(escaped, \"'\", \"''\") + \"'\"\n\tdefault:\n\t\treturn \"'\" + strings.ReplaceAll(entry, \"'\", \"'\\\\''\") + \"'\"\n\t}\n}\n\n// KillCommand kills the process for the given command\nfunc KillCommand(cmd *exec.Cmd) error {\n\treturn cmd.Process.Kill()\n}\n\n// IsWindows returns true on Windows\nfunc IsWindows() bool {\n\treturn true\n}\n\n// SetNonblock executes syscall.SetNonblock on file descriptor\nfunc SetNonblock(file *os.File, nonblock bool) {\n\tsyscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)\n}\n\n// Read executes syscall.Read on file descriptor\nfunc Read(fd int, b []byte) (int, error) {\n\treturn syscall.Read(syscall.Handle(fd), b)\n}\n\nfunc SetStdin(file *os.File) {\n\t// No-op\n}\n"
  },
  {
    "path": "src/winpty.go",
    "content": "//go:build !windows\n\npackage fzf\n\nimport \"errors\"\n\nfunc needWinpty(_ *Options) bool {\n\treturn false\n}\n\nfunc runWinpty(_ []string, _ *Options) (int, error) {\n\treturn ExitError, errors.New(\"Not supported\")\n}\n"
  },
  {
    "path": "src/winpty_windows.go",
    "content": "//go:build windows\n\npackage fzf\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/junegunn/fzf/src/util\"\n)\n\nfunc isMintty345() bool {\n\treturn util.CompareVersions(os.Getenv(\"TERM_PROGRAM_VERSION\"), \"3.4.5\") >= 0\n}\n\nfunc needWinpty(opts *Options) bool {\n\tif os.Getenv(\"TERM_PROGRAM\") != \"mintty\" {\n\t\treturn false\n\t}\n\tif isMintty345() {\n\t\t/*\n\t\t See: https://github.com/junegunn/fzf/issues/3809\n\n\t\t \"MSYS=enable_pcon\" allows fzf to run properly on mintty 3.4.5 or later.\n\t\t*/\n\t\tif strings.Contains(os.Getenv(\"MSYS\"), \"enable_pcon\") {\n\t\t\treturn false\n\t\t}\n\n\t\t// Setting the environment variable here unfortunately doesn't help,\n\t\t// so we need to start a child process with \"MSYS=enable_pcon\"\n\t\t//   os.Setenv(\"MSYS\", \"enable_pcon\")\n\t\treturn true\n\t}\n\tif opts.NoWinpty {\n\t\treturn false\n\t}\n\tif _, err := exec.LookPath(\"winpty\"); err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc runWinpty(args []string, opts *Options) (int, error) {\n\targStr := escapeSingleQuote(args[0])\n\tfor _, arg := range args[1:] {\n\t\targStr += \" \" + escapeSingleQuote(arg)\n\t}\n\targStr += ` --no-winpty`\n\n\tif isMintty345() {\n\t\treturn runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {\n\t\t\tsh, err := sh(needBash)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tcmd := exec.Command(sh, temp)\n\t\t\tcmd.Env = append(os.Environ(), \"MSYS=enable_pcon\")\n\t\t\tcmd.Stdin = os.Stdin\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stderr = os.Stderr\n\t\t\treturn cmd, nil\n\t\t}, opts, false)\n\t}\n\n\treturn runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {\n\t\tsh, err := sh(needBash)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcmd := exec.Command(sh, \"-c\", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Stderr = os.Stderr\n\t\treturn cmd, nil\n\t}, opts, false)\n}\n"
  },
  {
    "path": "test/lib/common.fish",
    "content": "# Unset fzf variables\nset -e FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_DEFAULT_OPTS_FILE FZF_TMUX FZF_TMUX_OPTS\nset -e FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS\nset -e FZF_API_KEY\n# Unset completion-specific variables\nset -e FZF_COMPLETION_TRIGGER FZF_COMPLETION_OPTS\n\nset -gx FZF_DEFAULT_OPTS \"--no-scrollbar --pointer '>' --marker '>'\"\nset -gx FZF_COMPLETION_TRIGGER '++'\nset -gx fish_history fzf_test\n\n# Add fzf to PATH\nfish_add_path <%= BASE %>/bin\n\n# Source key bindings and completion\nsource \"<%= BASE %>/shell/key-bindings.fish\"\nsource \"<%= BASE %>/shell/completion.fish\"\n"
  },
  {
    "path": "test/lib/common.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/setup'\nrequire 'minitest/autorun'\nrequire 'fileutils'\nrequire 'English'\nrequire 'shellwords'\nrequire 'erb'\nrequire 'tempfile'\nrequire 'net/http'\nrequire 'json'\n\nTEMPLATE = File.read(File.expand_path('common.sh', __dir__))\nFISH_TEMPLATE = File.read(File.expand_path('common.fish', __dir__))\nUNSETS = %w[\n  FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS\n  FZF_TMUX FZF_TMUX_OPTS\n  FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS\n  FZF_ALT_C_COMMAND\n  FZF_ALT_C_OPTS FZF_CTRL_R_OPTS\n  FZF_API_KEY\n].freeze\nDEFAULT_TIMEOUT = 10\n\nFILE = File.expand_path(__FILE__)\nBASE = File.expand_path('../..', __dir__)\nDir.chdir(BASE)\nFZF = %(FZF_DEFAULT_OPTS=\"--no-scrollbar --gutter ' ' --pointer '>' --marker '>'\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf).freeze\n\ndef wait(timeout = DEFAULT_TIMEOUT)\n  since = Time.now\n  begin\n    yield or raise Minitest::Assertion, 'Assertion failure'\n  rescue Minitest::Assertion\n    raise if Time.now - since > timeout\n\n    sleep(0.05)\n    retry\n  end\nend\n\nclass Shell\n  class << self\n    def bash\n      @bash ||=\n        begin\n          bashrc = '/tmp/fzf.bash'\n          File.open(bashrc, 'w') do |f|\n            f.puts ERB.new(TEMPLATE).result(binding)\n          end\n\n          \"bash --rcfile #{bashrc}\"\n        end\n    end\n\n    def zsh\n      @zsh ||=\n        begin\n          zdotdir = '/tmp/fzf-zsh'\n          FileUtils.rm_rf(zdotdir)\n          FileUtils.mkdir_p(zdotdir)\n          File.open(\"#{zdotdir}/.zshrc\", 'w') do |f|\n            f.puts ERB.new(TEMPLATE).result(binding)\n          end\n          \"ZDOTDIR=#{zdotdir} zsh\"\n        end\n    end\n\n    def fish\n      @fish ||=\n        begin\n          confdir = '/tmp/fzf-fish'\n          FileUtils.rm_rf(confdir)\n          FileUtils.mkdir_p(\"#{confdir}/fish/conf.d\")\n          File.open(\"#{confdir}/fish/conf.d/fzf.fish\", 'w') do |f|\n            f.puts ERB.new(FISH_TEMPLATE).result(binding)\n          end\n          \"rm -f ~/.local/share/fish/fzf_test_history; XDG_CONFIG_HOME=#{confdir} fish\"\n        end\n    end\n  end\nend\n\nclass Tmux\n  attr_reader :win\n\n  def initialize(shell = :bash)\n    @win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first\n    go(%W[set-window-option -t #{@win} pane-base-index 0])\n    return unless shell == :fish\n\n    send_keys 'function fish_prompt; end; clear', :Enter\n    self.until(&:empty?)\n  end\n\n  def kill\n    go(%W[kill-window -t #{win}])\n  end\n\n  def focus\n    go(%W[select-window -t #{win}])\n  end\n\n  def send_keys(*args)\n    go(%W[send-keys -t #{win}] + args.map(&:to_s))\n  end\n\n  def paste(str)\n    system('tmux', 'setb', str, ';', 'pasteb', '-t', win, ';', 'send-keys', '-t', win, 'Enter')\n  end\n\n  def capture\n    go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse\n  end\n\n  def until(refresh = false, timeout: DEFAULT_TIMEOUT)\n    lines = nil\n    begin\n      wait(timeout) do\n        lines = capture\n        class << lines\n          def counts\n            lazy\n              .map { |l| l.scan(%r{^. ([0-9]+)/([0-9]+)( \\(([0-9]+)\\))?}) }\n              .reject(&:empty?)\n              .first&.first&.map(&:to_i)&.values_at(0, 1, 3) || [0, 0, 0]\n          end\n\n          def match_count\n            counts[0]\n          end\n\n          def item_count\n            counts[1]\n          end\n\n          def select_count\n            counts[2]\n          end\n\n          def any_include?(val)\n            method = val.is_a?(Regexp) ? :match : :include?\n            find { |line| line.send(method, val) }\n          end\n        end\n        yield(lines).tap do |ok|\n          send_keys 'C-l' if refresh && !ok\n        end\n      end\n    rescue Minitest::Assertion\n      puts $ERROR_INFO.backtrace\n      puts '>' * 80\n      puts lines\n      puts '<' * 80\n      raise\n    end\n    lines\n  end\n\n  def prepare\n    tries = 0\n    begin\n      self.until(true) do |lines|\n        message = \"Prepare[#{tries}]\"\n        send_keys ' ', 'C-u', :Enter, message, :Left, :Right\n        sleep(0.15)\n        lines[-1] == message\n      end\n    rescue Minitest::Assertion\n      (tries += 1) < 5 ? retry : raise\n    end\n    send_keys 'C-u', 'C-l'\n  end\n\n  private\n\n  def go(args)\n    IO.popen(%w[tmux] + args) { |io| io.readlines(chomp: true) }\n  end\nend\n\nclass TestBase < Minitest::Test\n  TEMPNAME = Dir::Tmpname.create(%w[fzf]) {}\n  FIFONAME = Dir::Tmpname.create(%w[fzf-fifo]) {}\n\n  def writelines(lines)\n    File.write(TEMPNAME, lines.join(\"\\n\"))\n  end\n\n  def tempname\n    TEMPNAME\n  end\n\n  def fzf_output\n    @thread.join.value.chomp.tap { @thread = nil }\n  end\n\n  def fzf_output_lines\n    fzf_output.lines(chomp: true)\n  end\n\n  def setup\n    File.mkfifo(FIFONAME)\n  end\n\n  def teardown\n    FileUtils.rm_f([TEMPNAME, FIFONAME])\n  end\n\n  alias assert_equal_org assert_equal\n  def assert_equal(expected, actual)\n    # Ignore info separator\n    actual = actual&.sub(/\\s*─+$/, '') if actual.is_a?(String) && actual&.match?(%r{\\d+/\\d+})\n    assert_equal_org(expected, actual)\n  end\n\n  # Run fzf with its output piped to a fifo\n  def fzf(*opts)\n    raise 'fzf_output not taken' if @thread\n\n    @thread = Thread.new { File.read(FIFONAME) }\n    fzf!(*opts) + \" > #{FIFONAME.shellescape}\"\n  end\n\n  def fzf!(*opts)\n    opts = opts.filter_map do |o|\n      case o\n      when Symbol\n        o = o.to_s\n        o.length > 1 ? \"--#{o.tr('_', '-')}\" : \"-#{o}\"\n      when String, Numeric\n        o.to_s\n      end\n    end\n    \"#{FZF} #{opts.join(' ')}\"\n  end\nend\n\nclass TestInteractive < TestBase\n  attr_reader :tmux\n\n  def setup\n    super\n    @tmux = Tmux.new\n  end\n\n  def teardown\n    super\n    @tmux.kill\n  end\nend\n"
  },
  {
    "path": "test/lib/common.sh",
    "content": "set -u\nPS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100\nunset <%= UNSETS.join(' ') %>\nunset $(env | sed -n /^_fzf_orig/s/=.*//p)\nunset $(declare -F | sed -n \"/_fzf/s/.*-f //p\")\n\nexport FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\"\n\n# Setup fzf\n# ---------\nif [[ ! \"$PATH\" == *<%= BASE %>/bin* ]]; then\n  export PATH=\"${PATH:+${PATH}:}<%= BASE %>/bin\"\nfi\n\n# Auto-completion\n# ---------------\n[[ $- == *i* ]] && source \"<%= BASE %>/shell/completion.<%= __method__ %>\" 2> /dev/null\n\n# Key bindings\n# ------------\nsource \"<%= BASE %>/shell/key-bindings.<%= __method__ %>\"\n\n# Old API\n_fzf_complete_f() {\n  _fzf_complete \"+m --multi --prompt \\\"prompt-f> \\\"\" \"$@\" < <(\n    echo foo\n    echo bar\n  )\n}\n\n# New API\n_fzf_complete_g() {\n  _fzf_complete +m --multi --prompt \"prompt-g> \" -- \"$@\" < <(\n    echo foo\n    echo bar\n  )\n}\n\n_fzf_complete_f_post() {\n  awk '{print \"f\" $0 $0}'\n}\n\n_fzf_complete_g_post() {\n  awk '{print \"g\" $0 $0}'\n}\n\n[ -n \"${BASH-}\" ] && complete -F _fzf_complete_f -o default -o bashdefault f\n[ -n \"${BASH-}\" ] && complete -F _fzf_complete_g -o default -o bashdefault g\n\n_comprun() {\n  local command=$1\n  shift\n\n  case \"$command\" in\n    f) fzf \"$@\" --preview 'echo preview-f-{}' ;;\n    g) fzf \"$@\" --preview 'echo preview-g-{}' ;;\n    *) fzf \"$@\" ;;\n  esac\n}\n"
  },
  {
    "path": "test/runner.rb",
    "content": "# frozen_string_literal: true\n\nDir[File.join(__dir__, 'test_*.rb')].each { require it }\n\nrequire 'minitest/autorun'\n"
  },
  {
    "path": "test/test_core.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Testing basic features of fzf\nclass TestCore < TestInteractive\n  def test_fzf_default_command\n    tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', \"FZF_DEFAULT_COMMAND='echo hello'\"), :Enter\n    tmux.until { |lines| assert_equal '> hello', lines[-3] }\n\n    tmux.send_keys :Enter\n    assert_equal 'hello', fzf_output\n  end\n\n  def test_fzf_default_command_failure\n    tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter\n    tmux.until { |lines| assert_includes lines[-2], '  [Command failed: false] ─' }\n    tmux.send_keys :Enter\n  end\n\n  def test_key_bindings\n    tmux.send_keys \"#{FZF} -q 'foo bar foo-bar'\", :Enter\n    tmux.until { |lines| assert_equal '> foo bar foo-bar', lines.last }\n\n    # CTRL-A\n    tmux.send_keys 'C-A', '('\n    tmux.until { |lines| assert_equal '> (foo bar foo-bar', lines.last }\n\n    # META-F\n    tmux.send_keys :Escape, :f, ')'\n    tmux.until { |lines| assert_equal '> (foo) bar foo-bar', lines.last }\n\n    # CTRL-B\n    tmux.send_keys 'C-B', 'var'\n    tmux.until { |lines| assert_equal '> (foovar) bar foo-bar', lines.last }\n\n    # Left, CTRL-D\n    tmux.send_keys :Left, :Left, 'C-D'\n    tmux.until { |lines| assert_equal '> (foovr) bar foo-bar', lines.last }\n\n    # META-BS\n    tmux.send_keys :Escape, :BSpace\n    tmux.until { |lines| assert_equal '> (r) bar foo-bar', lines.last }\n\n    # CTRL-Y\n    tmux.send_keys 'C-Y', 'C-Y'\n    tmux.until { |lines| assert_equal '> (foovfoovr) bar foo-bar', lines.last }\n\n    # META-B\n    tmux.send_keys :Escape, :b, :Space, :Space\n    tmux.until { |lines| assert_equal '> (  foovfoovr) bar foo-bar', lines.last }\n\n    # CTRL-F / Right\n    tmux.send_keys 'C-F', :Right, '/'\n    tmux.until { |lines| assert_equal '> (  fo/ovfoovr) bar foo-bar', lines.last }\n\n    # CTRL-H / BS\n    tmux.send_keys 'C-H', :BSpace\n    tmux.until { |lines| assert_equal '> (  fovfoovr) bar foo-bar', lines.last }\n\n    # CTRL-E\n    tmux.send_keys 'C-E', 'baz'\n    tmux.until { |lines| assert_equal '> (  fovfoovr) bar foo-barbaz', lines.last }\n\n    # CTRL-U\n    tmux.send_keys 'C-U'\n    tmux.until { |lines| assert_equal '>', lines.last }\n\n    # CTRL-Y\n    tmux.send_keys 'C-Y'\n    tmux.until { |lines| assert_equal '> (  fovfoovr) bar foo-barbaz', lines.last }\n\n    # CTRL-W\n    tmux.send_keys 'C-W', 'bar-foo'\n    tmux.until { |lines| assert_equal '> (  fovfoovr) bar bar-foo', lines.last }\n\n    # META-D\n    tmux.send_keys :Escape, :b, :Escape, :b, :Escape, :d, 'C-A', 'C-Y'\n    tmux.until { |lines| assert_equal '> bar(  fovfoovr) bar -foo', lines.last }\n\n    # CTRL-M\n    tmux.send_keys 'C-M'\n    tmux.until { |lines| refute_equal '>', lines.last }\n  end\n\n  def test_file_word\n    tmux.send_keys \"#{FZF} -q '--/foo bar/foo-bar/baz' --filepath-word\", :Enter\n    tmux.until { |lines| assert_equal '> --/foo bar/foo-bar/baz', lines.last }\n\n    tmux.send_keys :Escape, :b\n    tmux.send_keys :Escape, :b\n    tmux.send_keys :Escape, :b\n    tmux.send_keys :Escape, :d\n    tmux.send_keys :Escape, :f\n    tmux.send_keys :Escape, :BSpace\n    tmux.until { |lines| assert_equal '> --///baz', lines.last }\n  end\n\n  def test_multi_order\n    tmux.send_keys \"seq 1 10 | #{fzf(:multi)}\", :Enter\n    tmux.until { |lines| assert_equal '>', lines.last }\n\n    tmux.send_keys :Tab, :Up, :Up, :Tab, :Tab, :Tab, # 3, 2\n                   'C-K', 'C-K', 'C-K', 'C-K', :BTab, :BTab, # 5, 6\n                   :PgUp, 'C-J', :Down, :Tab, :Tab # 8, 7\n    tmux.until { |lines| assert_equal '  10/10 (6)', lines[-2] }\n    tmux.send_keys 'C-M'\n    assert_equal %w[3 2 5 6 8 7], fzf_output_lines\n  end\n\n  def test_subword_forward\n    tmux.send_keys \"#{FZF} --bind K:kill-subword,F:forward-subword -q 'foo bar foo-bar fooFooBar'\", :Enter, :Home\n    tmux.until { |lines| assert_equal '> foo bar foo-bar fooFooBar', lines.last }\n\n    tmux.send_keys 'F', :Delete\n    tmux.until { |lines| assert_equal '> foobar foo-bar fooFooBar', lines.last }\n\n    tmux.send_keys 'K'\n    tmux.until { |lines| assert_equal '> foo foo-bar fooFooBar', lines.last }\n\n    tmux.send_keys 'F', 'K'\n    tmux.until { |lines| assert_equal '> foo foo fooFooBar', lines.last }\n\n    tmux.send_keys 'F', 'F', 'K'\n    tmux.until { |lines| assert_equal '> foo foo fooFoo', lines.last }\n  end\n\n  def test_subword_backward\n    tmux.send_keys \"#{FZF} --bind K:backward-kill-subword,B:backward-subword -q 'foo bar foo-bar fooBar'\", :Enter\n    tmux.until { |lines| assert_equal '> foo bar foo-bar fooBar', lines.last }\n\n    tmux.send_keys 'B', :BSpace\n    tmux.until { |lines| assert_equal '> foo bar foo-bar foBar', lines.last }\n\n    tmux.send_keys 'K'\n    tmux.until { |lines| assert_equal '> foo bar foo-bar Bar', lines.last }\n\n    tmux.send_keys 'B', :BSpace\n    tmux.until { |lines| assert_equal '> foo bar foobar Bar', lines.last }\n\n    tmux.send_keys 'B', 'B', :BSpace\n    tmux.until { |lines| assert_equal '> foobar foobar Bar', lines.last }\n  end\n\n  def test_multi_max\n    tmux.send_keys \"seq 1 10 | #{FZF} -m 3 --bind A:select-all,T:toggle-all --preview 'echo [{+}]/{}'\", :Enter\n\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n\n    tmux.send_keys '1'\n    tmux.until do |lines|\n      assert_includes lines[1], ' [1]/1 '\n      assert lines[-2]&.start_with?('  2/10 ')\n    end\n\n    tmux.send_keys 'A'\n    tmux.until do |lines|\n      assert_includes lines[1], ' [1 10]/1 '\n      assert lines[-2]&.start_with?('  2/10 (2/3)')\n    end\n\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 (2/3)') }\n\n    tmux.send_keys 'T'\n    tmux.until do |lines|\n      assert_includes lines[1], ' [2 3 4]/1 '\n      assert lines[-2]&.start_with?('  10/10 (3/3)')\n    end\n\n    %w[T A].each do |key|\n      tmux.send_keys key\n      tmux.until do |lines|\n        assert_includes lines[1], ' [1 5 6]/1 '\n        assert lines[-2]&.start_with?('  10/10 (3/3)')\n      end\n    end\n\n    tmux.send_keys :BTab\n    tmux.until do |lines|\n      assert_includes lines[1], ' [5 6]/2 '\n      assert lines[-2]&.start_with?('  10/10 (2/3)')\n    end\n\n    [:BTab, :BTab, 'A'].each do |key|\n      tmux.send_keys key\n      tmux.until do |lines|\n        assert_includes lines[1], ' [5 6 2]/3 '\n        assert lines[-2]&.start_with?('  10/10 (3/3)')\n      end\n    end\n\n    tmux.send_keys '2'\n    tmux.until { |lines| assert lines[-2]&.start_with?('  1/10 (3/3)') }\n\n    tmux.send_keys 'T'\n    tmux.until do |lines|\n      assert_includes lines[1], ' [5 6]/2 '\n      assert lines[-2]&.start_with?('  1/10 (2/3)')\n    end\n\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 (2/3)') }\n\n    tmux.send_keys 'A'\n    tmux.until do |lines|\n      assert_includes lines[1], ' [5 6 1]/1 '\n      assert lines[-2]&.start_with?('  10/10 (3/3)')\n    end\n  end\n\n  def test_multi_action\n    tmux.send_keys \"seq 10 | #{FZF} --bind 'a:change-multi,b:change-multi(3),c:change-multi(xxx),d:change-multi(0)'\", :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 ') }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 (0)') }\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 (0/3)') }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 (1/3)') }\n    tmux.send_keys 'c'\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert lines[-2]&.start_with?('  10/10 (2/3)') }\n    tmux.send_keys 'd'\n    tmux.until do |lines|\n      assert lines[-2]&.start_with?('  10/10 ') && !lines[-2]&.include?('(')\n    end\n  end\n\n  def test_with_nth\n    [true, false].each do |multi|\n      tmux.send_keys \"(echo '  1st 2nd 3rd/';\n                       echo '  first second third/') |\n                       #{fzf(multi && :multi, :x, :nth, 2, :with_nth, '2,-1,1')}\",\n                     :Enter\n      tmux.until { |lines| assert_equal multi ? '  2/2 (0)' : '  2/2', lines[-2] }\n\n      # Transformed list\n      lines = tmux.capture\n      assert_equal '  second third/first', lines[-4]\n      assert_equal '> 2nd 3rd/1st',        lines[-3]\n\n      # However, the output must not be transformed\n      if multi\n        tmux.send_keys :BTab, :BTab\n        tmux.until { |lines| assert_equal '  2/2 (2)', lines[-2] }\n        tmux.send_keys :Enter\n        assert_equal ['  1st 2nd 3rd/', '  first second third/'], fzf_output_lines\n      else\n        tmux.send_keys '^', '3'\n        tmux.until { |lines| assert_equal '  1/2', lines[-2] }\n        tmux.send_keys :Enter\n        assert_equal ['  1st 2nd 3rd/'], fzf_output_lines\n      end\n    end\n  end\n\n  def test_scroll\n    [true, false].each do |rev|\n      tmux.send_keys \"seq 1 100 | #{fzf(rev && :reverse)}\", :Enter\n      tmux.until { |lines| assert_equal '  100/100', lines[rev ? 1 : -2] }\n      tmux.send_keys(*Array.new(110) { rev ? :Down : :Up })\n      tmux.until { |lines| assert_includes lines, '> 100' }\n      tmux.send_keys :Enter\n      assert_equal '100', fzf_output\n    end\n  end\n\n  def test_select_1\n    tmux.send_keys \"seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1')}\", :Enter\n    assert_equal %w[5555 55], fzf_output_lines\n  end\n\n  def test_select_1_accept_nth\n    tmux.send_keys \"seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1', :accept_nth, '\"{1} // {1}\"')}\", :Enter\n    assert_equal ['5555', '55 // 55'], fzf_output_lines\n  end\n\n  def test_exit_0\n    tmux.send_keys \"seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}\", :Enter\n    assert_equal %w[555555], fzf_output_lines\n  end\n\n  def test_select_1_exit_0_fail\n    [:'0', :'1', %i[1 0]].each do |opt|\n      tmux.send_keys \"seq 1 100 | #{fzf(:print_query, :multi, :q, 5, *opt)}\", :Enter\n      tmux.until { |lines| assert_equal '> 5', lines.last }\n      tmux.send_keys :BTab, :BTab, :BTab\n      tmux.until { |lines| assert_equal '  19/100 (3)', lines[-2] }\n      tmux.send_keys :Enter\n      assert_equal %w[5 5 50 51], fzf_output_lines\n    end\n  end\n\n  def test_query_unicode\n    tmux.paste \"(echo abc; echo $'\\\\352\\\\260\\\\200\\\\353\\\\202\\\\230\\\\353\\\\213\\\\244') | #{fzf(:query, \"$'\\\\352\\\\260\\\\200\\\\353\\\\213\\\\244'\")}\"\n    tmux.until { |lines| assert_equal '  1/2', lines[-2] }\n    tmux.send_keys :Enter\n    assert_equal %w[가나다], fzf_output_lines\n  end\n\n  def test_sync\n    tmux.send_keys \"seq 1 100 | #{FZF} --multi | awk '{print $1 $1}' | #{fzf(:sync)}\", :Enter\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys 9\n    tmux.until { |lines| assert_equal '  19/100 (0)', lines[-2] }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal '  19/100 (3)', lines[-2] }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys 'C-K', :Enter\n    assert_equal %w[9090], fzf_output_lines\n  end\n\n  def test_tac\n    tmux.send_keys \"seq 1 1000 | #{fzf(:tac, :multi)}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal '  1000/1000 (3)', lines[-2] }\n    tmux.send_keys :Enter\n    assert_equal %w[1000 999 998], fzf_output_lines\n  end\n\n  def test_tac_sort\n    tmux.send_keys \"seq 1 1000 | #{fzf(:tac, :multi)}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys '99'\n    tmux.until { |lines| assert_equal '  28/1000 (0)', lines[-2] }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal '  28/1000 (3)', lines[-2] }\n    tmux.send_keys :Enter\n    assert_equal %w[99 999 998], fzf_output_lines\n  end\n\n  def test_tac_nosort\n    tmux.send_keys \"seq 1 1000 | #{fzf(:tac, :no_sort, :multi)}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys '00'\n    tmux.until { |lines| assert_equal '  10/1000 (0)', lines[-2] }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal '  10/1000 (3)', lines[-2] }\n    tmux.send_keys :Enter\n    assert_equal %w[1000 900 800], fzf_output_lines\n  end\n\n  def test_expect\n    test = lambda do |key, feed, expected = key|\n      tmux.send_keys \"seq 1 100 | #{fzf(:expect, key, :prompt, \"[#{key}]\")}\", :Enter\n      tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n      tmux.send_keys '55'\n      tmux.until { |lines| assert_equal '  1/100', lines[-2] }\n      tmux.send_keys(*feed)\n      tmux.prepare\n      assert_equal [expected, '55'], fzf_output_lines\n    end\n    test.call('ctrl-t', 'C-T')\n    test.call('ctrl-t', 'Enter', '')\n    test.call('alt-c', %i[Escape c])\n    test.call('f1', 'f1')\n    test.call('f2', 'f2')\n    test.call('f3', 'f3')\n    test.call('f2,f4', 'f2', 'f2')\n    test.call('f2,f4', 'f4', 'f4')\n    test.call('alt-/', %i[Escape /])\n    %w[f5 f6 f7 f8 f9 f10].each do |key|\n      test.call('f5,f6,f7,f8,f9,f10', key, key)\n    end\n    test.call('@', '@')\n  end\n\n  def test_expect_with_bound_actions\n    tmux.send_keys \"seq 1 100 | #{fzf('--query 1 --print-query --expect z --bind z:up+up')}\", :Enter\n    tmux.until { |lines| assert_equal 20, lines.match_count }\n    tmux.send_keys('z')\n    assert_equal %w[1 z 1], fzf_output_lines\n  end\n\n  def test_expect_print_query\n    tmux.send_keys \"seq 1 100 | #{fzf('--expect=alt-z', :print_query)}\", :Enter\n    tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n    tmux.send_keys '55'\n    tmux.until { |lines| assert_equal '  1/100', lines[-2] }\n    tmux.send_keys :Escape, :z\n    assert_equal %w[55 alt-z 55], fzf_output_lines\n  end\n\n  def test_expect_printable_character_print_query\n    tmux.send_keys \"seq 1 100 | #{fzf('--expect=z --print-query')}\", :Enter\n    tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n    tmux.send_keys '55'\n    tmux.until { |lines| assert_equal '  1/100', lines[-2] }\n    tmux.send_keys 'z'\n    assert_equal %w[55 z 55], fzf_output_lines\n  end\n\n  def test_expect_print_query_select_1\n    tmux.send_keys \"seq 1 100 | #{fzf('-q55 -1 --expect=alt-z --print-query')}\", :Enter\n    assert_equal ['55', '', '55'], fzf_output_lines\n  end\n\n  def test_toggle_sort\n    ['--toggle-sort=ctrl-r', '--bind=ctrl-r:toggle-sort'].each do |opt|\n      tmux.send_keys \"seq 1 111 | #{fzf(\"-m +s --tac #{opt} -q11\")}\", :Enter\n      tmux.until { |lines| assert_equal '> 111', lines[-3] }\n      tmux.send_keys :Tab\n      tmux.until { |lines| assert_equal '  4/111 (1) -S', lines[-2] }\n      tmux.send_keys 'C-R'\n      tmux.until { |lines| assert_equal '> 11', lines[-3] }\n      tmux.send_keys :Tab\n      tmux.until { |lines| assert_equal '  4/111 (2) +S', lines[-2] }\n      tmux.send_keys :Enter\n      assert_equal %w[111 11], fzf_output_lines\n    end\n  end\n\n  def test_invalid_cache\n    tmux.send_keys \"(echo d; echo D; echo x) | #{fzf('-q d')}\", :Enter\n    tmux.until { |lines| assert_equal '  2/3', lines[-2] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '  3/3', lines[-2] }\n    tmux.send_keys :D\n    tmux.until { |lines| assert_equal '  1/3', lines[-2] }\n    tmux.send_keys :Enter\n  end\n\n  def test_invalid_cache_query_type\n    command = %[(echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo \"foo'1-2\"; seq 100) | #{FZF}]\n\n    # Suffix match\n    tmux.send_keys command, :Enter\n    tmux.until { |lines| assert_equal 104, lines.match_count }\n    tmux.send_keys 'foo$'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys 'bar'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n\n    # Prefix match\n    tmux.prepare\n    tmux.send_keys command, :Enter\n    tmux.until { |lines| assert_equal 104, lines.match_count }\n    tmux.send_keys '^bar'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys 'C-a', 'foo'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n\n    # Exact match\n    tmux.prepare\n    tmux.send_keys command, :Enter\n    tmux.until { |lines| assert_equal 104, lines.match_count }\n    tmux.send_keys \"'12\"\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys 'C-a', 'foo'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n  end\n\n  def test_bind\n    tmux.send_keys \"seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u,:,U:up,X,,,Z:toggle-up,t:toggle')}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys 'uU:', 'X,Z', 'tt', 'uu', 'ttt', 'C-j'\n    assert_equal %w[4 5 6 9], fzf_output_lines\n  end\n\n  def test_bind_print_query\n    tmux.send_keys \"seq 1 1000 | #{fzf('-m --bind=ctrl-j:print-query')}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys 'print-my-query', 'C-j'\n    assert_equal %w[print-my-query], fzf_output_lines\n  end\n\n  def test_bind_replace_query\n    tmux.send_keys \"seq 1 1000 | #{fzf('--print-query --bind=ctrl-j:replace-query')}\", :Enter\n    tmux.send_keys '1'\n    tmux.until { |lines| assert_equal '  272/1000', lines[-2] }\n    tmux.send_keys 'C-k', 'C-j'\n    tmux.until { |lines| assert_equal '  29/1000', lines[-2] }\n    tmux.until { |lines| assert_equal '> 10', lines[-1] }\n  end\n\n  def test_select_all_deselect_all_toggle_all\n    tmux.send_keys \"seq 100 | #{fzf('--bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all --multi')}\", :Enter\n    tmux.until { |lines| assert_equal '  100/100 (0)', lines[-2] }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal '  100/100 (3)', lines[-2] }\n    tmux.send_keys 'C-t'\n    tmux.until { |lines| assert_equal '  100/100 (97)', lines[-2] }\n    tmux.send_keys 'C-a'\n    tmux.until { |lines| assert_equal '  100/100 (100)', lines[-2] }\n    tmux.send_keys :Tab, :Tab\n    tmux.until { |lines| assert_equal '  100/100 (98)', lines[-2] }\n    tmux.send_keys '100'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys 'C-d'\n    tmux.until { |lines| assert_equal '  1/100 (97)', lines[-2] }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 'C-d'\n    tmux.until { |lines| assert_equal '  100/100 (0)', lines[-2] }\n    tmux.send_keys :BTab, :BTab\n    tmux.until { |lines| assert_equal '  100/100 (2)', lines[-2] }\n    tmux.send_keys 0\n    tmux.until { |lines| assert_equal '  10/100 (2)', lines[-2] }\n    tmux.send_keys 'C-a'\n    tmux.until { |lines| assert_equal '  10/100 (12)', lines[-2] }\n    tmux.send_keys :Enter\n    assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],\n                 fzf_output_lines\n  end\n\n  def test_history\n    history_file = '/tmp/fzf-test-history'\n\n    # History with limited number of entries\n    FileUtils.rm_f(history_file)\n    opts = \"--history=#{history_file} --history-size=4\"\n    input = %w[00 11 22 33 44]\n    input.each do |keys|\n      tmux.prepare\n      tmux.send_keys \"seq 100 | #{FZF} #{opts}\", :Enter\n      tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n      tmux.send_keys keys\n      tmux.until { |lines| assert_equal '  1/100', lines[-2] }\n      tmux.send_keys :Enter\n    end\n    wait do\n      assert_path_exists history_file\n      assert_equal input[1..], File.readlines(history_file, chomp: true)\n    end\n\n    # Update history entries (not changed on disk)\n    tmux.send_keys \"seq 100 | #{FZF} #{opts}\", :Enter\n    tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n    tmux.send_keys 'C-p'\n    tmux.until { |lines| assert_equal '> 44', lines[-1] }\n    tmux.send_keys 'C-p'\n    tmux.until { |lines| assert_equal '> 33', lines[-1] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '> 3', lines[-1] }\n    tmux.send_keys 1\n    tmux.until { |lines| assert_equal '> 31', lines[-1] }\n    tmux.send_keys 'C-p'\n    tmux.until { |lines| assert_equal '> 22', lines[-1] }\n    tmux.send_keys 'C-n'\n    tmux.until { |lines| assert_equal '> 31', lines[-1] }\n    tmux.send_keys 0\n    tmux.until { |lines| assert_equal '> 310', lines[-1] }\n    tmux.send_keys :Enter\n    wait do\n      assert_path_exists history_file\n      assert_equal %w[22 33 44 310], File.readlines(history_file, chomp: true)\n    end\n\n    # Respect --bind option\n    tmux.send_keys \"seq 100 | #{FZF} #{opts} --bind ctrl-p:next-history,ctrl-n:previous-history\", :Enter\n    tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n    tmux.send_keys 'C-n', 'C-n', 'C-n', 'C-n', 'C-p'\n    tmux.until { |lines| assert_equal '> 33', lines[-1] }\n    tmux.send_keys :Enter\n  ensure\n    FileUtils.rm_f(history_file)\n  end\n\n  def test_cycle\n    tmux.send_keys \"seq 8 | #{FZF} --cycle\", :Enter\n    tmux.until { |lines| assert_equal '  8/8', lines[-2] }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal '> 8', lines[-10] }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal '> 7', lines[-9] }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal '> 8', lines[-10] }\n    tmux.send_keys :PgUp\n    tmux.until { |lines| assert_equal '> 8', lines[-10] }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal '> 1', lines[-3] }\n    tmux.send_keys :PgDn\n    tmux.until { |lines| assert_equal '> 1', lines[-3] }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal '> 8', lines[-10] }\n  end\n\n  def test_header_lines\n    tmux.send_keys \"seq 100 | #{fzf('--header-lines=10 -q 5')}\", :Enter\n    2.times do\n      tmux.until do |lines|\n        assert_equal '  18/90', lines[-2]\n        assert_equal '  1', lines[-3]\n        assert_equal '  2', lines[-4]\n        assert_equal '> 50', lines[-13]\n      end\n      tmux.send_keys :Down\n    end\n    tmux.send_keys :Enter\n    assert_equal '50', fzf_output\n  end\n\n  def test_header_lines_reverse\n    tmux.send_keys \"seq 100 | #{fzf('--header-lines=10 -q 5 --reverse')}\", :Enter\n    2.times do\n      tmux.until do |lines|\n        assert_equal '  18/90', lines[1]\n        assert_equal '  1', lines[2]\n        assert_equal '  2', lines[3]\n        assert_equal '> 50', lines[12]\n      end\n      tmux.send_keys :Up\n    end\n    tmux.send_keys :Enter\n    assert_equal '50', fzf_output\n  end\n\n  def test_header_lines_reverse_list\n    tmux.send_keys \"seq 100 | #{fzf('--header-lines=10 -q 5 --layout=reverse-list')}\", :Enter\n    2.times do\n      tmux.until do |lines|\n        assert_equal '  9', lines[8]\n        assert_equal '  10', lines[9]\n        assert_equal '> 50', lines[10]\n        assert_equal '  18/90', lines[-2]\n      end\n      tmux.send_keys :Up\n    end\n    tmux.send_keys :Enter\n    assert_equal '50', fzf_output\n  end\n\n  def test_header_lines_overflow\n    tmux.send_keys \"seq 100 | #{fzf('--header-lines=200')}\", :Enter\n    tmux.until do |lines|\n      assert_equal '  0/0', lines[-2]\n      assert_equal '  1', lines[-3]\n    end\n    tmux.send_keys :Enter\n    assert_equal '', fzf_output\n  end\n\n  def test_header_lines_with_nth\n    tmux.send_keys \"seq 100 | #{fzf('--header-lines 5 --with-nth 1,1,1,1,1')}\", :Enter\n    tmux.until do |lines|\n      assert_equal '  95/95', lines[-2]\n      assert_equal '  11111', lines[-3]\n      assert_equal '  55555', lines[-7]\n      assert_equal '> 66666', lines[-8]\n    end\n    tmux.send_keys :Enter\n    assert_equal '6', fzf_output\n  end\n\n  def test_header\n    tmux.send_keys %[seq 100 | #{FZF} --header \"$(head -5 #{FILE})\"], :Enter\n    header = File.readlines(FILE, chomp: true).take(5)\n    tmux.until do |lines|\n      assert_equal '  100/100', lines[-2]\n      assert_equal header.map { |line| \"  #{line}\".rstrip }, lines[-7..-3]\n      assert_equal '> 1', lines[-8]\n    end\n  end\n\n  def test_header_reverse\n    tmux.send_keys %[seq 100 | #{FZF} --header \"$(head -5 #{FILE})\" --reverse], :Enter\n    header = File.readlines(FILE, chomp: true).take(5)\n    tmux.until do |lines|\n      assert_equal '  100/100', lines[1]\n      assert_equal header.map { |line| \"  #{line}\".rstrip }, lines[2..6]\n      assert_equal '> 1', lines[7]\n    end\n  end\n\n  def test_header_reverse_list\n    tmux.send_keys %[seq 100 | #{FZF} --header \"$(head -5 #{FILE})\" --layout=reverse-list], :Enter\n    header = File.readlines(FILE, chomp: true).take(5)\n    tmux.until do |lines|\n      assert_equal '  100/100', lines[-2]\n      assert_equal header.map { |line| \"  #{line}\".rstrip }, lines[-7..-3]\n      assert_equal '> 1', lines[0]\n    end\n  end\n\n  def test_header_and_header_lines\n    tmux.send_keys %[seq 100 | #{FZF} --header-lines 10 --header \"$(head -5 #{FILE})\"], :Enter\n    header = File.readlines(FILE, chomp: true).take(5)\n    tmux.until do |lines|\n      assert_equal '  90/90', lines[-2]\n      assert_equal header.map { |line| \"  #{line}\".rstrip }, lines[-7...-2]\n      assert_equal ('  1'..'  10').to_a.reverse, lines[-17...-7]\n    end\n  end\n\n  def test_header_and_header_lines_reverse\n    tmux.send_keys %[seq 100 | #{FZF} --reverse --header-lines 10 --header \"$(head -5 #{FILE})\"], :Enter\n    header = File.readlines(FILE, chomp: true).take(5)\n    tmux.until do |lines|\n      assert_equal '  90/90', lines[1]\n      assert_equal header.map { |line| \"  #{line}\".rstrip }, lines[2...7]\n      assert_equal ('  1'..'  10').to_a, lines[7...17]\n    end\n  end\n\n  def test_header_and_header_lines_reverse_list\n    tmux.send_keys %[seq 100 | #{FZF} --layout=reverse-list --header-lines 10 --header \"$(head -5 #{FILE})\"], :Enter\n    header = File.readlines(FILE, chomp: true).take(5)\n    tmux.until do |lines|\n      assert_equal '  90/90', lines[-2]\n      assert_equal header.map { |line| \"  #{line}\".rstrip }, lines[-7...-2]\n      assert_equal ('  1'..'  10').to_a, lines.take(10)\n    end\n  end\n\n  def test_cancel\n    tmux.send_keys \"seq 10 | #{FZF} --bind 2:cancel\", :Enter\n    tmux.until { |lines| assert_equal '  10/10', lines[-2] }\n    tmux.send_keys '123'\n    tmux.until do |lines|\n      assert_equal '> 3', lines[-1]\n      assert_equal '  1/10', lines[-2]\n    end\n    tmux.send_keys 'C-y', 'C-y'\n    tmux.until { |lines| assert_equal '> 311', lines[-1] }\n    tmux.send_keys 2\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys 2\n    tmux.prepare\n  end\n\n  def test_margin\n    tmux.send_keys \"yes | head -1000 | #{FZF} --margin 5,3\", :Enter\n    tmux.until do |lines|\n      assert_equal '', lines[4]\n      assert_equal '     y', lines[5]\n    end\n    tmux.send_keys :Enter\n  end\n\n  def test_margin_reverse\n    tmux.send_keys \"seq 1000 | #{FZF} --margin 7,5 --reverse\", :Enter\n    tmux.until { |lines| assert_equal '       1000/1000', lines[1 + 7] }\n    tmux.send_keys :Enter\n  end\n\n  def test_margin_reverse_list\n    tmux.send_keys \"yes | head -1000 | #{FZF} --margin 5,3 --layout=reverse-list\", :Enter\n    tmux.until do |lines|\n      assert_equal '', lines[4]\n      assert_equal '   > y', lines[5]\n    end\n    tmux.send_keys :Enter\n  end\n\n  def test_tabstop\n    writelines(%W[f\\too\\tba\\tr\\tbaz\\tbarfooq\\tux])\n    {\n      1 => '> f oo ba r baz barfooq ux',\n      2 => '> f oo  ba  r baz barfooq ux',\n      3 => '> f  oo ba r  baz   barfooq  ux',\n      4 => '> f   oo  ba  r   baz barfooq ux',\n      5 => '> f    oo   ba   r    baz  barfooq   ux',\n      6 => '> f     oo    ba    r     baz   barfooq     ux',\n      7 => '> f      oo     ba     r      baz    barfooq       ux',\n      8 => '> f       oo      ba      r       baz     barfooq ux',\n      9 => '> f        oo       ba       r        baz      barfooq  ux'\n    }.each do |ts, exp|\n      tmux.prepare\n      tmux.send_keys %(cat #{tempname} | fzf --tabstop=#{ts}), :Enter\n      tmux.until(true) do |lines|\n        assert_equal exp, lines[-3]\n      end\n      tmux.send_keys :Enter\n    end\n  end\n\n  def test_exit_0_exit_code\n    `echo foo | #{FZF} -q bar -0`\n    assert_equal 1, $CHILD_STATUS.exitstatus\n  end\n\n  def test_invalid_option\n    lines = `#{FZF} --foobar 2>&1`\n    assert_equal 2, $CHILD_STATUS.exitstatus\n    assert_includes lines, 'unknown option: --foobar'\n  end\n\n  def test_exitstatus_empty\n    { '99' => '0', '999' => '1' }.each do |query, status|\n      tmux.send_keys \"seq 100 | #{FZF} -q #{query}; echo --$?--\", :Enter\n      tmux.until { |lines| assert_match %r{ [10]/100}, lines[-2] }\n      tmux.send_keys :Enter\n      tmux.until { |lines| assert_equal \"--#{status}--\", lines.last }\n    end\n  end\n\n  def test_hscroll_off\n    writelines([('=' * 10_000) + '0123456789'])\n    [0, 3, 6].each do |off|\n      tmux.prepare\n      tmux.send_keys \"#{FZF} --hscroll-off=#{off} -q 0 --bind space:toggle-hscroll < #{tempname}\", :Enter\n      tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }\n      tmux.send_keys '9'\n      tmux.until { |lines| assert lines[-3]&.end_with?('789') }\n      tmux.send_keys :Space\n      tmux.until { |lines| assert lines[-3]&.end_with?('=··') }\n      tmux.send_keys :Space\n      tmux.until { |lines| assert lines[-3]&.end_with?('789') }\n      tmux.send_keys :Enter\n    end\n  end\n\n  def test_partial_caching\n    tmux.send_keys 'seq 1000 | fzf -e', :Enter\n    tmux.until { |lines| assert_equal '  1000/1000', lines[-2] }\n    tmux.send_keys 11\n    tmux.until { |lines| assert_equal '  19/1000', lines[-2] }\n    tmux.send_keys 'C-a', \"'\"\n    tmux.until { |lines| assert_equal '  28/1000', lines[-2] }\n    tmux.send_keys :Enter\n  end\n\n  def test_jump\n    tmux.send_keys \"seq 1000 | #{fzf(\"--multi --jump-labels 12345 --bind 'ctrl-j:jump'\")}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_equal '5 5', lines[-7] }\n    tmux.until { |lines| assert_equal '  6', lines[-8] }\n    tmux.send_keys '5'\n    tmux.until { |lines| assert_equal '> 5', lines[-7] }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal ' >5', lines[-7] }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_equal '5>5', lines[-7] }\n    tmux.send_keys '2'\n    tmux.until { |lines| assert_equal '> 2', lines[-4] }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal ' >2', lines[-4] }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_equal '5>5', lines[-7] }\n\n    # Press any key other than jump labels to cancel jump\n    tmux.send_keys '6'\n    tmux.until { |lines| assert_equal '> 1', lines[-3] }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal '>>1', lines[-3] }\n    tmux.send_keys :Enter\n    assert_equal %w[5 2 1], fzf_output_lines\n  end\n\n  def test_jump_accept\n    tmux.send_keys \"seq 1000 | #{fzf(\"--multi --jump-labels 12345 --bind 'ctrl-j:jump-accept'\")}\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_equal '5 5', lines[-7] }\n    tmux.send_keys '3'\n    assert_equal '3', fzf_output\n  end\n\n  def test_jump_events\n    tmux.send_keys \"seq 1000 | #{FZF} --multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000 (0)', lines[-2] }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_includes lines[-7], '5 5' }\n    tmux.send_keys '3'\n    tmux.until { |lines| assert(lines.any? { it.include?('jumped to 3') }) }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_includes lines[-7], '5 5' }\n    tmux.send_keys 'C-c'\n    tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }\n  end\n\n  def test_jump_no_pointer\n    tmux.send_keys \"seq 100 | #{FZF} --pointer= --jump-labels 12345 --bind ctrl-j:jump\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_equal '5 5', lines[-7] }\n    tmux.send_keys 'C-c'\n    tmux.until { |lines| assert_equal ' 5', lines[-7] }\n  end\n\n  def test_jump_no_pointer_no_marker\n    tmux.send_keys \"seq 100 | #{FZF} --pointer= --marker= --jump-labels 12345 --bind ctrl-j:jump\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 'C-j'\n    tmux.until { |lines| assert_equal '55', lines[-7] }\n    tmux.send_keys 'C-c'\n    tmux.until { |lines| assert_equal '5', lines[-7] }\n  end\n\n  def test_pointer\n    tmux.send_keys \"seq 10 | #{fzf(\"--pointer '>>'\")}\", :Enter\n    # Assert that specified pointer is displayed\n    tmux.until { |lines| assert_equal '>> 1', lines[-3] }\n  end\n\n  def test_pointer_with_jump\n    tmux.send_keys \"seq 10 | #{FZF} --multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '>>'\", :Enter\n    tmux.until { |lines| assert_equal '  10/10 (0)', lines[-2] }\n    tmux.send_keys 'C-j'\n    # Correctly padded jump label should appear\n    tmux.until { |lines| assert_equal '5  5', lines[-7] }\n    tmux.until { |lines| assert_equal '   6', lines[-8] }\n    tmux.send_keys '5'\n    # Assert that specified pointer is displayed\n    tmux.until { |lines| assert_equal '>> 5', lines[-7] }\n  end\n\n  def test_marker\n    tmux.send_keys \"seq 10 | #{FZF} --multi --marker '>>'\", :Enter\n    tmux.until { |lines| assert_equal '  10/10 (0)', lines[-2] }\n    tmux.send_keys :BTab\n    # Assert that specified marker is displayed\n    tmux.until { |lines| assert_equal ' >>1', lines[-3] }\n  end\n\n  def test_no_clear\n    tmux.send_keys \"seq 10 | #{fzf('--no-clear --inline-info --height 5')}\", :Enter\n    prompt = '>   < 10/10'\n    tmux.until { |lines| assert_equal prompt, lines[-1] }\n    tmux.send_keys :Enter\n    assert_equal %w[1], fzf_output_lines\n    tmux.until { |lines| assert_equal prompt, lines[-1] }\n  end\n\n  def test_info_hidden\n    tmux.send_keys 'seq 10 | fzf --info=hidden --no-separator', :Enter\n    tmux.until { |lines| assert_equal '> 1', lines[-2] }\n  end\n\n  def test_info_inline_separator\n    tmux.send_keys 'seq 10 | fzf --info=inline:___ --no-separator', :Enter\n    tmux.until { |lines| assert_equal '>  ___10/10', lines[-1] }\n  end\n\n  def test_change_first_last\n    tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal '> 2', lines[-4] }\n    tmux.send_keys 1\n    tmux.until { |lines| assert_equal '> 1', lines[-3] }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal '> 10', lines[-4] }\n    tmux.send_keys 1\n    tmux.until { |lines| assert_equal '> 11', lines[-3] }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_equal '> 1', lines[-3] }\n    tmux.send_keys :Escape, 'Z'\n    tmux.until { |lines| assert_equal '> 1000', lines[0] }\n    tmux.send_keys :Enter\n  end\n\n  def test_pos\n    tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:pos(3),b:pos(-3),c:pos(1),d:pos(-1),e:pos(0)' --preview 'echo {}/{}'), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys :a\n    tmux.until { |lines| assert_includes lines[1], ' 3/3' }\n    tmux.send_keys :b\n    tmux.until { |lines| assert_includes lines[1], ' 998/998' }\n    tmux.send_keys :c\n    tmux.until { |lines| assert_includes lines[1], ' 1/1' }\n    tmux.send_keys :d\n    tmux.until { |lines| assert_includes lines[1], ' 1000/1000' }\n    tmux.send_keys :e\n    tmux.until { |lines| assert_includes lines[1], ' 1/1' }\n  end\n\n  def test_put\n    tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:put+put,b:put+put(ravo)' --preview 'echo {q}/{q}'), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys :a\n    tmux.until { |lines| assert_includes lines[1], ' aa/aa' }\n    tmux.send_keys :b\n    tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' }\n  end\n\n  def test_accept_non_empty\n    tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys 'foo'\n    tmux.until { |lines| assert_equal '  0/1000', lines[-2] }\n    # fzf doesn't exit since there's no selection\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal '  0/1000', lines[-2] }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_equal '  1000/1000', lines[-2] }\n    tmux.send_keys '999'\n    tmux.until { |lines| assert_equal '  1/1000', lines[-2] }\n    tmux.send_keys :Enter\n    assert_equal %w[999 999], fzf_output_lines\n  end\n\n  def test_accept_non_empty_with_multi_selection\n    tmux.send_keys %(seq 1000 | #{fzf('-m --print-query --bind enter:accept-non-empty')}), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal '  1000/1000 (1)', lines[-2] }\n    tmux.send_keys 'foo'\n    tmux.until { |lines| assert_equal '  0/1000 (1)', lines[-2] }\n    # fzf will exit in this case even though there's no match for the current query\n    tmux.send_keys :Enter\n    assert_equal %w[foo 1], fzf_output_lines\n  end\n\n  def test_accept_non_empty_with_empty_list\n    tmux.send_keys %(: | #{fzf('-q foo --print-query --bind enter:accept-non-empty')}), :Enter\n    tmux.until { |lines| assert_equal '  0/0', lines[-2] }\n    tmux.send_keys :Enter\n    # fzf will exit anyway since input list is empty\n    assert_equal %w[foo], fzf_output_lines\n  end\n\n  def test_accept_or_print_query_without_match\n    tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query')}), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys 99_999\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.send_keys :Enter\n    assert_equal %w[99999], fzf_output_lines\n  end\n\n  def test_accept_or_print_query_with_match\n    tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query')}), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys '^99$'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    assert_equal %w[99], fzf_output_lines\n  end\n\n  def test_accept_or_print_query_with_multi_selection\n    tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query --multi')}), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal 3, lines.select_count }\n    tmux.send_keys 99_999\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.send_keys :Enter\n    assert_equal %w[1 2 3], fzf_output_lines\n  end\n\n  def test_inverse_only_search_should_not_sort_the_result\n    # Filter\n    assert_equal %w[aaaaa b ccc],\n                 `printf '%s\\n' aaaaa b ccc BAD | #{FZF} -f '!bad'`.lines(chomp: true)\n\n    # Interactive\n    tmux.send_keys %(printf '%s\\n' aaaaa b ccc BAD | #{FZF} -q '!bad'), :Enter\n    tmux.until do |lines|\n      assert_equal 4, lines.item_count\n      assert_equal 3, lines.match_count\n    end\n    tmux.until { |lines| assert_equal '> aaaaa', lines[-3] }\n    tmux.until { |lines| assert_equal '  b', lines[-4] }\n    tmux.until { |lines| assert_equal '  ccc', lines[-5] }\n  end\n\n  def test_disabled\n    tmux.send_keys %(seq 1000 | #{FZF} --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}'), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }\n    tmux.send_keys 'foo'\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }\n\n    # Already disabled, no change\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n\n    # Enable search\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.send_keys :BSpace, :BSpace, :BSpace\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' 333 333 ' }\n\n    # Toggle search -> disabled again, but retains the previous result\n    tmux.send_keys 'c'\n    tmux.send_keys 'foo'\n    tmux.until { |lines| assert_includes lines[1], ' 333 333foo ' }\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n\n    # Enabled, no match\n    tmux.send_keys 'c'\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' 333foo ' }\n  end\n\n  def test_clear_query\n    tmux.send_keys %(: | #{FZF} --query foo --bind space:clear-query), :Enter\n    tmux.until { |lines| assert_equal 0, lines.item_count }\n    tmux.until { |lines| assert_equal '> foo', lines.last }\n    tmux.send_keys 'C-a', 'bar'\n    tmux.until { |lines| assert_equal '> barfoo', lines.last }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal '>', lines.last }\n  end\n\n  def test_change_query\n    tmux.send_keys %(: | #{FZF} --query foo --bind space:change-query:foobar), :Enter\n    tmux.until { |lines| assert_equal 0, lines.item_count }\n    tmux.until { |lines| assert_equal '> foo', lines.last }\n    tmux.send_keys :Space, 'baz'\n    tmux.until { |lines| assert_equal '> foobarbaz', lines.last }\n  end\n\n  def test_transform_query\n    tmux.send_keys %{#{FZF} --bind 'ctrl-r:transform-query(rev <<< {q}),ctrl-u:transform-query: tr \"[:lower:]\" \"[:upper:]\" <<< {q}' --query bar}, :Enter\n    tmux.until { |lines| assert_equal '> bar', lines[-1] }\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_equal '> rab', lines[-1] }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_equal '> RAB', lines[-1] }\n  end\n\n  def test_transform_prompt\n    tmux.send_keys %{#{FZF} --bind 'ctrl-r:transform-query(rev <<< {q}),ctrl-u:transform-query: tr \"[:lower:]\" \"[:upper:]\" <<< {q}' --query bar}, :Enter\n    tmux.until { |lines| assert_equal '> bar', lines[-1] }\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_equal '> rab', lines[-1] }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_equal '> RAB', lines[-1] }\n  end\n\n  def test_transform\n    tmux.send_keys %{#{FZF} --bind 'focus:transform:echo \"change-prompt({fzf:action})\"'}, :Enter\n    tmux.until { |lines| assert_equal 'start', lines[-1] }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal 'up', lines[-1] }\n  end\n\n  def test_search\n    tmux.send_keys %(seq 100 | #{FZF} --query 0 --bind space:search:1), :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal 20, lines.match_count }\n    tmux.send_keys '0'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n  end\n\n  def test_transform_search\n    tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:transform-search:echo {q}{q}'), :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys '1'\n    tmux.until { |lines| assert_equal 28, lines.match_count }\n    tmux.send_keys :BSpace, '0'\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n  end\n\n  def test_clear_selection\n    tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal '  100/100 (1)', lines[-2] }\n    tmux.send_keys 'foo'\n    tmux.until { |lines| assert_equal '  0/100 (1)', lines[-2] }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal '  0/100 (0)', lines[-2] }\n  end\n\n  def test_backward_delete_char_eof\n    tmux.send_keys \"seq 1000 | #{FZF} --bind 'bs:backward-delete-char/eof'\", :Enter\n    tmux.until { |lines| assert_equal '  1000/1000', lines[-2] }\n    tmux.send_keys '11'\n    tmux.until { |lines| assert_equal '> 11', lines[-1] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '> 1', lines[-1] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys :BSpace\n    tmux.prepare\n  end\n\n  def test_strip_xterm_osc_sequence\n    %W[\\x07 \\x1b\\\\].each do |esc|\n      writelines([%(printf $1\"\\e]4;3;rgb:aa/bb/cc#{esc} \"$2)])\n      File.chmod(0o755, tempname)\n      tmux.prepare\n      tmux.send_keys \\\n        %(echo foo bar | #{FZF} --preview '#{tempname} {2} {1}'), :Enter\n\n      tmux.until { |lines| assert lines.any_include?('bar foo') }\n      tmux.send_keys :Enter\n    end\n  end\n\n  def test_keep_right\n    tmux.send_keys \"seq 10000 | #{FZF} --read0 --keep-right --no-multi-line --bind space:toggle-multi-line\", :Enter\n    tmux.until { |lines| assert lines.any_include?('9999␊10000') }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('> 1') }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('9999␊10000') }\n  end\n\n  def test_freeze_left_tabstop\n    writelines(%W[1\\t2\\t3])\n    # With --freeze-left 1 and --tabstop=2:\n    # Frozen left: \"1\" (width 1)\n    # Middle starts with \"\\t\" at prefix width 1, tabstop 2 → 1 space\n    # Then \"2\" at column 2, next \"\\t\" at column 3 → 1 space, then \"3\"\n    tmux.send_keys %(cat #{tempname} | #{FZF} --tabstop=2 --freeze-left 1), :Enter\n    tmux.until { |lines| assert_equal '> 1 2 3', lines[-3] }\n  end\n\n  def test_freeze_left_keep_right\n    tmux.send_keys %(seq 10000 | #{FZF} --read0 --delimiter \"\\n\" --freeze-left 3 --keep-right --ellipsis XX --no-multi-line --bind space:toggle-multi-line), :Enter\n    tmux.until { |lines| assert_match(/^> 1␊2␊3XX.*10000␊$/, lines[-3]) }\n    tmux.send_keys '5'\n    tmux.until { |lines| assert_match(/^> 1␊2␊3␊4␊5␊.*XX$/, lines[-3]) }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('> 1') }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('1␊2␊3␊4␊5␊') }\n  end\n\n  def test_freeze_left_and_right\n    tmux.send_keys %(seq 10000 | tr \"\\n\" ' ' | #{FZF} --freeze-left 3 --freeze-right 3 --ellipsis XX), :Enter\n    tmux.until { |lines| assert_match(/XX9998 9999 10000$/, lines[-3]) }\n    tmux.send_keys \"'1000\"\n    tmux.until { |lines| assert_match(/^> 1 2 3XX.*XX9998 9999 10000$/, lines[-3]) }\n  end\n\n  def test_freeze_left_and_right_delimiter\n    tmux.send_keys %(seq 10000 | tr \"\\n\" ' ' | sed 's/ / , /g' | #{FZF} --freeze-left 3 --freeze-right 3 --ellipsis XX --delimiter ' , '), :Enter\n    tmux.until { |lines| assert_match(/XX, 9999 , 10000 ,$/, lines[-3]) }\n    tmux.send_keys \"'1000\"\n    tmux.until { |lines| assert_match(/^> 1 , 2 , 3 ,XX.*XX, 9999 , 10000 ,$/, lines[-3]) }\n  end\n\n  def test_freeze_right_exceed_range\n    tmux.send_keys %(seq 10000 | tr \"\\n\" ' ' | #{FZF} --freeze-right 100000 --ellipsis XX), :Enter\n    ['', \"'1000\"].each do |query|\n      tmux.send_keys query\n      tmux.until { |lines| assert lines.any_include?(\"> #{query}\".strip) }\n      tmux.until do |lines|\n        assert_match(/ 9998 9999 10000$/, lines[-3])\n        assert_equal(1, lines[-3].scan('XX').size)\n      end\n    end\n  end\n\n  def test_freeze_right_exceed_range_with_freeze_left\n    tmux.send_keys %(seq 10000 | tr \"\\n\" ' ' | #{FZF} --freeze-left 3  --freeze-right 100000 --ellipsis XX), :Enter\n    tmux.until do |lines|\n      assert_match(/^> 1 2 3XX.*9998 9999 10000$/, lines[-3])\n      assert_equal(1, lines[-3].scan('XX').size)\n    end\n  end\n\n  def test_freeze_right_with_ellipsis_and_scrolling\n    tmux.send_keys \"{ seq 6; ruby -e 'print \\\"g\\\"*1000, \\\"\\\\n\\\"'; seq 8 100; } | #{FZF} --ellipsis='777' --freeze-right 1 --scroll-off 0 --bind a:offset-up\", :Enter\n    tmux.until { |lines| assert_equal '  100/100', lines[-2] }\n    tmux.send_keys(*Array.new(6) { :a })\n    tmux.until do |lines|\n      assert_match(/> 777g+$/, lines[-3])\n      assert_equal(1, lines.count { |l| l.end_with?('g') })\n    end\n  end\n\n  def test_backward_eof\n    tmux.send_keys \"echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'\", :Enter\n    tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }\n    tmux.send_keys 'x'\n    tmux.until { |lines| lines.item_count == 1 && lines.match_count == 0 }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| lines.item_count == 100 && lines.match_count == 100 }\n  end\n\n  def test_change_prompt\n    tmux.send_keys \"#{FZF} --bind 'a:change-prompt(a> ),b:change-prompt:b> ' --query foo\", :Enter\n    tmux.until { |lines| assert_equal '> foo', lines[-1] }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_equal 'a> foo', lines[-1] }\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_equal 'b> foo', lines[-1] }\n  end\n\n  def test_select_deselect\n    tmux.send_keys \"seq 3 | #{FZF} --multi --bind up:deselect+up,down:select+down\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal 0, lines.select_count }\n    tmux.send_keys :Down, :Down\n    tmux.until { |lines| assert_equal 2, lines.select_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n    tmux.send_keys :Down, :Down\n    tmux.until { |lines| assert_equal 2, lines.select_count }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal 2, lines.select_count }\n  end\n\n  def test_unbind_rebind_toggle_bind\n    tmux.send_keys \"seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d),e:rebind(c,d),f:toggle-bind(c)'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 'ab'\n    tmux.until { |lines| assert_equal '> ab', lines[-1] }\n    tmux.send_keys 'c'\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys 'dabcd'\n    tmux.until { |lines| assert_equal '> abcd', lines[-1] }\n    tmux.send_keys 'ecabddc'\n    tmux.until { |lines| assert_equal '> abdc', lines[-1] }\n    tmux.send_keys 'fcabfc'\n    tmux.until { |lines| assert_equal '> abc', lines[-1] }\n    tmux.send_keys 'fc'\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n  end\n\n  def test_scroll_off\n    tmux.send_keys \"seq 1000 | #{FZF} --scroll-off=3 --bind l:last\", :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    height = tmux.until { |lines| lines }.first.to_i\n    tmux.send_keys :PgUp\n    tmux.until do |lines|\n      assert_equal height + 3, lines.first.to_i\n      assert_equal \"> #{height}\", lines[3].strip\n    end\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal \"> #{height + 1}\", lines[3].strip }\n    tmux.send_keys 'l'\n    tmux.until { |lines| assert_equal '> 1000', lines.first.strip }\n    tmux.send_keys :PgDn\n    tmux.until { |lines| assert_equal \"> #{1000 - height + 1}\", lines.reverse[5].strip }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal \"> #{1000 - height}\", lines.reverse[5].strip }\n  end\n\n  def test_scroll_off_large\n    tmux.send_keys \"seq 1000 | #{FZF} --scroll-off=9999\", :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    height = tmux.until { |lines| lines }.first.to_i\n    tmux.send_keys :PgUp\n    tmux.until { |lines| assert_equal \"> #{height}\", lines[height / 2].strip }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal \"> #{height + 1}\", lines[height / 2].strip }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal \"> #{height + 2}\", lines[height / 2].strip }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_equal \"> #{height + 1}\", lines[height / 2].strip }\n  end\n\n  def test_ellipsis\n    tmux.send_keys 'seq 1000 | tr \"\\n\" , | fzf --ellipsis=SNIPSNIP -e -q500', :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }\n  end\n\n  def test_start_event\n    tmux.send_keys 'seq 100 | fzf --multi --sync --preview-window hidden:border-none --bind \"start:select-all+last+preview(echo welcome)\"', :Enter\n    tmux.until do |lines|\n      assert_match(/>100.*welcome/, lines[0])\n      assert_includes(lines[-2], '100/100 (100)')\n    end\n  end\n\n  def test_focus_event\n    tmux.send_keys 'seq 100 | fzf --bind \"focus:transform-prompt(echo [[{}]]),?:unbind(focus)\"', :Enter\n    tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }\n    tmux.send_keys :X\n    tmux.until { |lines| assert_includes(lines[-1], '[[]]') }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }\n    tmux.send_keys :X\n    tmux.until { |lines| assert_includes(lines[-1], '[[]]') }\n    tmux.send_keys '?'\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.until { |lines| refute_includes(lines[-1], '[[1]]') }\n  end\n\n  def test_result_event\n    tmux.send_keys '(echo 0; seq 10) | fzf --bind \"result:pos(2)\"', :Enter\n    tmux.until { |lines| assert_equal 11, lines.match_count }\n    tmux.until { |lines| assert_includes lines, '> 1' }\n    tmux.send_keys '9'\n    tmux.until { |lines| assert_includes lines, '> 9' }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_includes lines, '> 1' }\n  end\n\n  def test_labels_center\n    tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind \"space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)\"', :Enter\n    tmux.until do\n      assert_includes(it[0], '─foobar─')\n      assert_includes(it[1], '─barfoo─')\n    end\n    tmux.send_keys :space\n    tmux.until do\n      assert_includes(it[0], '─foobarfoo─')\n      assert_includes(it[1], '─barfoobar─')\n    end\n    tmux.send_keys :Enter\n    tmux.until do\n      assert_includes(it[0], '─fooxfoo─')\n      assert_includes(it[1], '─barxbar─')\n    end\n  end\n\n  def test_labels_left\n    tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter\n    tmux.until do\n      assert_includes(it[0], '╭foobar─')\n      assert_includes(it[1], '╭barfoo─')\n    end\n  end\n\n  def test_labels_right\n    tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter\n    tmux.until do\n      assert_includes(it[0], '─foobar╮')\n      assert_includes(it[1], '─barfoo╮')\n    end\n  end\n\n  def test_labels_bottom\n    tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter\n    tmux.until do\n      assert_includes(it[-1], '╰foobar─')\n      assert_includes(it[-2], '─barfoo╯')\n    end\n  end\n\n  def test_labels_variables\n    tmux.send_keys ': | fzf --border --border-label foobar --preview \"echo \\$FZF_BORDER_LABEL // \\$FZF_PREVIEW_LABEL\" --preview-label barfoo --bind \"space:change-border-label(barbaz)+change-preview-label(bazbar)+refresh-preview,enter:transform-border-label(echo 123)+transform-preview-label(echo 456)+refresh-preview\"', :Enter\n    tmux.until do\n      assert_includes(it[0], '─foobar─')\n      assert_includes(it[1], '─barfoo─')\n      assert_includes(it[2], ' foobar // barfoo ')\n    end\n    tmux.send_keys :Space\n    tmux.until do\n      assert_includes(it[0], '─barbaz─')\n      assert_includes(it[1], '─bazbar─')\n      assert_includes(it[2], ' barbaz // bazbar ')\n    end\n    tmux.send_keys :Enter\n    tmux.until do\n      assert_includes(it[0], '─123─')\n      assert_includes(it[1], '─456─')\n      assert_includes(it[2], ' 123 // 456 ')\n    end\n  end\n\n  def test_info_separator_unicode\n    tmux.send_keys 'seq 100 | fzf -q55', :Enter\n    tmux.until { assert_includes(it[-2], '  1/100 ─') }\n  end\n\n  def test_info_separator_no_unicode\n    tmux.send_keys 'seq 100 | fzf -q55 --no-unicode', :Enter\n    tmux.until { assert_includes(it[-2], '  1/100 -') }\n  end\n\n  def test_info_separator_repeat\n    tmux.send_keys 'seq 100 | fzf -q55 --separator _-', :Enter\n    tmux.until { assert_includes(it[-2], '  1/100 _-_-') }\n  end\n\n  def test_info_separator_ansi_colors_and_tabs\n    tmux.send_keys \"seq 100 | fzf -q55 --tabstop 4 --separator $'\\\\x1b[33ma\\\\tb'\", :Enter\n    tmux.until { assert_includes(it[-2], '  1/100 a   ba   ba') }\n  end\n\n  def test_info_no_separator\n    tmux.send_keys 'seq 100 | fzf -q55 --no-separator', :Enter\n    tmux.until { assert_operator(it[-2], :==, '  1/100') }\n  end\n\n  def test_info_right\n    tmux.send_keys \"#{FZF} --info=right --separator x --bind 'start:reload:seq 100; sleep 10'\", :Enter\n    tmux.until { assert_match(%r{xxx [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, it[-2]) }\n  end\n\n  def test_info_inline_right\n    tmux.send_keys \"#{FZF} --info=inline-right --bind 'start:reload:seq 100; sleep 10'\", :Enter\n    tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, it[-1]) }\n  end\n\n  def test_info_inline_right_clearance\n    tmux.send_keys \"seq 100000 | #{FZF} --info inline-right\", :Enter\n    tmux.until { assert_match(%r{100000/100000}, it[-1]) }\n    tmux.send_keys 'x'\n    tmux.until { assert_match(%r{     0/100000}, it[-1]) }\n  end\n\n  def test_info_command\n    tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e \"--\\\\x1b[33m$FZF_POS\\\\x1b[m/$FZF_INFO--\"'), :Enter)\n    tmux.until { assert_match(%r{^  --1/10000/10000-- xx}, it[-2]) }\n    tmux.send_keys :Up\n    tmux.until { assert_match(%r{^  --2/10000/10000-- xx}, it[-2]) }\n  end\n\n  def test_info_command_inline\n    tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e \"--\\\\x1b[33m$FZF_POS\\\\x1b[m/$FZF_INFO--\"' --info inline:xx), :Enter)\n    tmux.until { assert_match(%r{^>  xx--1/10000/10000-- xx}, it[-1]) }\n  end\n\n  def test_info_command_right\n    tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e \"--\\\\x1b[33m$FZF_POS\\\\x1b[m/$FZF_INFO--\"' --info right), :Enter)\n    tmux.until { assert_match(%r{xx --1/10000/10000-- *$}, it[-2]) }\n  end\n\n  def test_info_command_inline_right\n    tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e \"--\\\\x1b[33m$FZF_POS\\\\x1b[m/$FZF_INFO--\"' --info inline-right), :Enter)\n    tmux.until { assert_match(%r{   --1/10000/10000-- *$}, it[-1]) }\n  end\n\n  def test_info_command_inline_right_no_ansi\n    tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e \"--$FZF_POS/$FZF_INFO--\"' --info inline-right), :Enter)\n    tmux.until { assert_match(%r{   --1/10000/10000-- *$}, it[-1]) }\n  end\n\n  def test_info_command_and_focus\n    tmux.send_keys(%(seq 100 | #{FZF} --separator x --info-command 'echo $FZF_POS' --bind focus:clear-query), :Enter)\n    tmux.until { assert_match(/^  1 xx/, it[-2]) }\n    tmux.send_keys :Up\n    tmux.until { assert_match(/^  2 xx/, it[-2]) }\n  end\n\n  def test_prev_next_selected\n    tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.send_keys :BTab, :BTab, :Up, :BTab\n    tmux.until { |lines| assert_equal 3, lines.select_count }\n    tmux.send_keys 'C-n'\n    tmux.until { |lines| assert_includes lines, '>>4' }\n    tmux.send_keys 'C-n'\n    tmux.until { |lines| assert_includes lines, '>>2' }\n    tmux.send_keys 'C-n'\n    tmux.until { |lines| assert_includes lines, '>>1' }\n    tmux.send_keys 'C-n'\n    tmux.until { |lines| assert_includes lines, '>>4' }\n    tmux.send_keys 'C-p'\n    tmux.until { |lines| assert_includes lines, '>>1' }\n    tmux.send_keys 'C-p'\n    tmux.until { |lines| assert_includes lines, '>>2' }\n  end\n\n  def test_track\n    tmux.send_keys \"seq 1000 | #{FZF} --query 555 --track --bind t:toggle-track\", :Enter\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> 555'\n    end\n    tmux.send_keys :BSpace\n    index = tmux.until do |lines|\n      assert_equal 28, lines.match_count\n      assert_includes lines, '> 555'\n    end.index('> 555')\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 271, lines.match_count\n      assert_equal '> 555', lines[index]\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_equal '> 555', lines[index]\n    end\n    tmux.send_keys '555'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> 555'\n      assert_includes lines[-2], '+T'\n    end\n    tmux.send_keys 't'\n    tmux.until do |lines|\n      refute_includes lines[-2], '+T'\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 28, lines.match_count\n      assert_includes lines, '> 55'\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 271, lines.match_count\n      assert_includes lines, '> 5'\n    end\n    tmux.send_keys 't'\n    tmux.until do |lines|\n      assert_includes lines[-2], '+T'\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 5'\n    end\n  end\n\n  def test_track_action\n    tmux.send_keys \"seq 1000 | #{FZF} --pointer x --query 555 --bind t:track,T:up+track\", :Enter\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, 'x 555'\n      assert_includes lines, '> 555'\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 28, lines.match_count\n      assert_includes lines, 'x 55'\n      assert_includes lines, '> 55'\n    end\n    tmux.send_keys :t\n    tmux.until do |lines|\n      assert_includes lines[-2], '+t'\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 271, lines.match_count\n      assert_includes lines, 'x 55'\n      assert_includes lines, '> 5'\n    end\n\n    # Automatically disabled when the tracking item is no longer visible\n    tmux.send_keys '4'\n    tmux.until do |lines|\n      assert_equal 28, lines.match_count\n      refute_includes lines[-2], '+t'\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 271, lines.match_count\n      assert_includes lines, 'x 52'\n      assert_includes lines, '> 5'\n    end\n    tmux.send_keys :t\n    tmux.until do |lines|\n      assert_includes lines[-2], '+t'\n    end\n\n    # Automatically disabled when the focus has moved\n    tmux.send_keys :Up\n    tmux.until do |lines|\n      assert_includes lines, 'x 53'\n      refute_includes lines[-2], '+t'\n    end\n\n    # Should work even when combined with a focus moving actions\n    tmux.send_keys 'T'\n    tmux.until do |lines|\n      assert_includes lines, 'x 54'\n      assert_includes lines[-2], '+t'\n    end\n\n    tmux.send_keys 'T'\n    tmux.until do |lines|\n      assert_includes lines, 'x 55'\n      assert_includes lines[-2], '+t'\n    end\n  end\n\n  def test_track_nth_reload_whole_line\n    # --track --id-nth .. should track by entire line across reloads\n    tmux.send_keys \"seq 1000 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:seq 1000 | sort -R'\", :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n\n    # Move to item 555\n    tmux.send_keys '555'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> 555'\n    end\n    tmux.send_keys :BSpace, :BSpace, :BSpace\n\n    # Reload with shuffled order — cursor should track \"555\"\n    tmux.send_keys 'C-r'\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 555'\n      assert_includes lines[-2], '+T'\n      refute_includes lines[-2], '+T*'\n    end\n  end\n\n  def test_track_nth_reload_field\n    # --track --id-nth 1 should track by first field across reloads\n    tmux.send_keys \"printf '1 apple\\\\n2 banana\\\\n3 cherry\\\\n' | #{FZF} --track --id-nth 1 --bind 'ctrl-r:reload:printf \\\"1 apricot\\\\n2 blueberry\\\\n3 cranberry\\\\n\\\"'\", :Enter\n    tmux.until do |lines|\n      assert_equal 3, lines.match_count\n      assert_includes lines, '> 1 apple'\n    end\n\n    # Move up to \"2 banana\"\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, '> 2 banana' }\n\n    # Reload — the second field changes, but first field \"2\" stays\n    tmux.send_keys 'C-r'\n    tmux.until do |lines|\n      assert_equal 3, lines.match_count\n      assert_includes lines, '> 2 blueberry'\n    end\n  end\n\n  def test_track_nth_reload_no_match\n    # When tracked item is not found after reload, cursor stays at current position\n    tmux.send_keys \"printf 'alpha\\\\nbeta\\\\ngamma\\\\n' | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:printf \\\"delta\\\\nepsilon\\\\nzeta\\\\n\\\"'\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, '> beta' }\n\n    # Reload with completely different items — no match for \"beta\"\n    # Cursor stays at the same position (second item)\n    tmux.send_keys 'C-r'\n    tmux.until do |lines|\n      assert_equal 3, lines.match_count\n      assert_includes lines, '> epsilon'\n      refute_includes lines[-2], '+T*'\n    end\n  end\n\n  def test_track_nth_blocked_indicator\n    # +T* should appear during reload and disappear when match is found\n    tmux.send_keys \"seq 100 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; seq 100 | sort -R'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines[-2], '+T'\n    end\n\n    # Trigger slow reload — should show +T* while blocked\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_includes lines[-2], '+T*' }\n\n    # After reload completes, +T* should clear back to +T\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines[-2], '+T'\n      refute_includes lines[-2], '+T*'\n    end\n  end\n\n  def test_track_nth_abort_unblocks\n    # Escape during track-blocked state should unblock, not quit\n    tmux.send_keys \"seq 100 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 3; seq 100'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines[-2], '+T'\n    end\n\n    # Trigger slow reload\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_includes lines[-2], '+T*' }\n\n    # Escape should unblock, not quit fzf\n    tmux.send_keys :Escape\n    tmux.until do |lines|\n      assert_includes lines[-2], '+T'\n      refute_includes lines[-2], '+T*'\n    end\n  end\n\n  def test_track_nth_reload_async_unblocks_early\n    # With async reload, +T* should clear as soon as the match streams in,\n    # even while loading is still in progress.\n    # sleep 1 first so +T* is observable, then the match arrives, then more items after a delay.\n    tmux.send_keys \"seq 5 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; echo 1; sleep 2; seq 2 10'\", :Enter\n    tmux.until do |lines|\n      assert_equal 5, lines.match_count\n      assert_includes lines, '> 1'\n    end\n\n    # Trigger reload — blocked during initial sleep\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_includes lines[-2], '+T*' }\n    # Match \"1\" arrives, unblocks before the remaining items load\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> 1'\n      assert_includes lines[-2], '+T'\n      refute_includes lines[-2], '+T*'\n    end\n  end\n\n  def test_track_nth_reload_sync_blocks_until_complete\n    # With reload-sync, +T* should stay until the entire stream is complete,\n    # even though the match arrives early in the stream.\n    tmux.send_keys \"seq 5 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload-sync:sleep 1; echo 1; sleep 2; seq 2 10'\", :Enter\n    tmux.until do |lines|\n      assert_equal 5, lines.match_count\n      assert_includes lines, '> 1'\n    end\n\n    # Trigger reload-sync — every observable state must be either:\n    # 1. +T* (still blocked), or\n    # 2. final state (count=10, +T without *)\n    # Any other combination (e.g. unblocked while count < 10) is a bug.\n    tmux.send_keys 'C-r'\n    tmux.until do |lines|\n      info = lines[-2]\n      blocked = info&.include?('+T*')\n      unless blocked\n        raise \"Unblocked before stream complete (count: #{lines.match_count})\" if lines.match_count != 10\n\n        assert_includes info, '+T'\n        assert_includes lines, '> 1'\n      end\n      !blocked\n    end\n  end\n\n  def test_track_nth_toggle_track_unblocks\n    # toggle-track during track-blocked state should unblock and disable tracking\n    tmux.send_keys \"seq 100 | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 5; seq 100' --bind 'ctrl-t:toggle-track'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines[-2], '+T'\n    end\n\n    # Trigger slow reload\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_includes lines[-2], '+T*' }\n\n    # toggle-track should unblock and disable tracking before reload completes\n    tmux.send_keys 'C-t'\n    tmux.until(timeout: 3) do |lines|\n      refute_includes lines[-2], '+T'\n    end\n  end\n\n  def test_track_nth_reload_async_no_match\n    # With async reload, when tracked item is not found, cursor stays at\n    # current position after stream completes\n    tmux.send_keys \"printf 'alpha\\\\nbeta\\\\ngamma\\\\n' | #{FZF} --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; printf \\\"delta\\\\nepsilon\\\\nzeta\\\\n\\\"'\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, '> beta' }\n\n    # Reload with completely different items — no match for \"beta\"\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_includes lines[-2], '+T*' }\n    # After stream completes, unblocks with cursor at same position (second item)\n    tmux.until do |lines|\n      assert_equal 3, lines.match_count\n      assert_includes lines, '> epsilon'\n      refute_includes lines[-2], '+T*'\n    end\n  end\n\n  def test_track_action_with_id_nth\n    # track-current with --id-nth should track by specified field\n    tmux.send_keys \"printf '1 apple\\\\n2 banana\\\\n3 cherry\\\\n' | #{FZF} --id-nth 1 --bind 'ctrl-t:track-current,ctrl-r:reload:printf \\\"1 apricot\\\\n2 blueberry\\\\n3 cranberry\\\\n\\\"'\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n\n    # Move to \"2 banana\" and activate tracking\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, '> 2 banana' }\n    tmux.send_keys 'C-t'\n    tmux.until { |lines| assert_includes lines[-2], '+t' }\n\n    # Reload — should track by field \"2\"\n    tmux.send_keys 'C-r'\n    tmux.until do |lines|\n      assert_equal 3, lines.match_count\n      assert_includes lines, '> 2 blueberry'\n    end\n  end\n\n  def test_id_nth_preserve_multi_selection\n    # --id-nth with --multi should preserve selections across reload-sync\n    File.write(tempname, \"1 apricot\\n2 blueberry\\n3 cranberry\\n\")\n    tmux.send_keys \"printf '1 apple\\\\n2 banana\\\\n3 cherry\\\\n' | #{fzf(\"--multi --id-nth 1 --bind 'ctrl-r:reload-sync:cat #{tempname}'\")}\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n\n    # Select first item (1 apple) and third item (3 cherry)\n    tmux.send_keys :Tab\n    tmux.send_keys :Up, :Up, :Tab\n    tmux.until { |lines| assert_includes lines[-2], '(2)' }\n\n    # Reload — selections should be preserved by id-nth key\n    tmux.send_keys 'C-r'\n    tmux.until do |lines|\n      assert_equal 3, lines.match_count\n      assert_includes lines[-2], '(2)'\n      assert(lines.any? { |l| l.include?('apricot') })\n    end\n\n    # Accept and verify the correct items were preserved\n    tmux.send_keys :Enter\n    assert_equal ['1 apricot', '3 cranberry'], fzf_output_lines\n  end\n\n  def test_one_and_zero\n    tmux.send_keys \"seq 10 | #{FZF} --bind 'zero:preview(echo no match),one:preview(echo {} is the only match)'\", :Enter\n    tmux.send_keys '1'\n    tmux.until do |lines|\n      assert_equal 2, lines.match_count\n      refute(lines.any? { it.include?('only match') })\n      refute(lines.any? { it.include?('no match') })\n    end\n    tmux.send_keys '0'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert(lines.any? { it.include?('only match') })\n    end\n    tmux.send_keys '0'\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n      assert(lines.any? { it.include?('no match') })\n    end\n  end\n\n  def test_height_range_with_exit_0\n    tmux.send_keys \"seq 10 | #{FZF} --height ~10% --exit-0\", :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.send_keys :c\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n  end\n\n  def test_delete_with_modifiers\n    if ENV['GITHUB_ACTION']\n      # Expected: \"[3]\"\n      # Actual: \"[]3;5~\"\n      skip('CTRL-DELETE is not properly handled in GitHub Actions environment')\n    end\n    tmux.send_keys \"seq 100 | #{FZF} --bind 'ctrl-delete:up+up,shift-delete:down,focus:transform-prompt:echo [{}]'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 'C-Delete'\n    tmux.until { |lines| assert_equal '[3]', lines[-1] }\n    tmux.send_keys 'S-Delete'\n    tmux.until { |lines| assert_equal '[2]', lines[-1] }\n  end\n\n  def test_fzf_pos\n    tmux.send_keys \"seq 100 | #{FZF} --preview 'echo $FZF_POS / $FZF_MATCH_COUNT'\", :Enter\n    tmux.until { |lines| assert(lines.any? { |line| line.include?('1 / 100') }) }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert(lines.any? { |line| line.include?('2 / 100') }) }\n    tmux.send_keys '99'\n    tmux.until { |lines| assert(lines.any? { |line| line.include?('1 / 1') }) }\n    tmux.send_keys '99'\n    tmux.until { |lines| assert(lines.any? { |line| line.include?('0 / 0') }) }\n  end\n\n  def test_change_nth\n    input = [\n      *[''] * 1000,\n      'foo bar bar bar bar',\n      'foo foo bar bar bar',\n      'foo foo foo bar bar',\n      'foo foo foo foo bar',\n      *[''] * 1000\n    ]\n    writelines(input)\n    nths = '1,2..4,-1,-3..,..2'\n    tmux.send_keys %(#{FZF} -qfoo -n#{nths} --bind 'space:change-nth(2|3|4|5|),result:transform-prompt:echo \"[$FZF_NTH] \"' < #{tempname}), :Enter\n\n    tmux.until do |lines|\n      assert lines.any_include?(\"[#{nths}] foo\")\n      assert_equal 4, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[2] foo')\n      assert_equal 3, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[3] foo')\n      assert_equal 2, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[4] foo')\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[5] foo')\n      assert_equal 0, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?(\"[#{nths}] foo\")\n      assert_equal 4, lines.match_count\n    end\n  end\n\n  def test_change_with_nth\n    input = [\n      'foo bar baz',\n      'aaa bbb ccc',\n      'xxx yyy zzz'\n    ]\n    writelines(input)\n    # Start with field 1 only, cycle through fields, verify $FZF_WITH_NTH via prompt\n    tmux.send_keys %(#{FZF} --with-nth 1 --bind 'space:change-with-nth(2|3|1),result:transform-prompt:echo \"[$FZF_WITH_NTH]> \"' < #{tempname}), :Enter\n    tmux.until do |lines|\n      assert_equal 3, lines.item_count\n      assert lines.any_include?('[1]>')\n      assert lines.any_include?('foo')\n      refute lines.any_include?('bar')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[2]>')\n      assert lines.any_include?('bar')\n      refute lines.any_include?('foo')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[3]>')\n      assert lines.any_include?('baz')\n      refute lines.any_include?('bar')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('[1]>')\n      assert lines.any_include?('foo')\n      refute lines.any_include?('bar')\n    end\n  end\n\n  def test_change_with_nth_default\n    # Empty value restores the default --with-nth\n    tmux.send_keys %(echo -e 'a b c\\nd e f' | #{FZF} --with-nth 1 --bind 'space:change-with-nth(2|)'), :Enter\n    tmux.until do |lines|\n      assert_equal 2, lines.item_count\n      assert lines.any_include?('a')\n      refute lines.any_include?('b')\n    end\n    # Switch to field 2\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('b')\n      refute lines.any_include?('a')\n    end\n    # Empty restores default (field 1)\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('a')\n      refute lines.any_include?('b')\n    end\n  end\n\n  def test_transform_with_nth_search\n    input = [\n      'alpha bravo charlie',\n      'delta echo foxtrot',\n      'golf hotel india'\n    ]\n    writelines(input)\n    tmux.send_keys %(#{FZF} --with-nth 1 --bind 'space:transform-with-nth(echo 2)' -q '^bravo$' < #{tempname}), :Enter\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n    end\n  end\n\n  def test_bg_transform_with_nth_output\n    tmux.send_keys %(echo -e 'a b c\\nd e f' | #{FZF} --with-nth 2 --bind 'space:bg-transform-with-nth(echo 3)'), :Enter\n    tmux.until do |lines|\n      assert_equal 2, lines.item_count\n      assert lines.any_include?('b')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('c')\n      refute lines.any_include?('b')\n    end\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert lines.any_include?('a b c') || lines.any_include?('d e f') }\n  end\n\n  def test_change_with_nth_search\n    input = [\n      'alpha bravo charlie',\n      'delta echo foxtrot',\n      'golf hotel india'\n    ]\n    writelines(input)\n    tmux.send_keys %(#{FZF} --with-nth 1 --bind 'space:change-with-nth(2)' -q '^bravo$' < #{tempname}), :Enter\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n    end\n  end\n\n  def test_change_with_nth_output\n    tmux.send_keys %(echo -e 'a b c\\nd e f' | #{FZF} --with-nth 2 --bind 'space:change-with-nth(3)'), :Enter\n    tmux.until do |lines|\n      assert_equal 2, lines.item_count\n      assert lines.any_include?('b')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('c')\n      refute lines.any_include?('b')\n    end\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert lines.any_include?('a b c') || lines.any_include?('d e f') }\n  end\n\n  def test_change_with_nth_selection\n    # Items: field1 has unique values, field2 has 'match' or 'miss'\n    input = [\n      'one match x',\n      'two miss y',\n      'three match z'\n    ]\n    writelines(input)\n    # Start showing field 2 (match/miss), query 'match', select all matches, then switch to field 3\n    tmux.send_keys %(#{FZF} --with-nth 2 --multi --bind 'ctrl-a:select-all,space:change-with-nth(3)' -q match < #{tempname}), :Enter\n    tmux.until do |lines|\n      assert_equal 2, lines.match_count\n    end\n    # Select all matching items\n    tmux.send_keys 'C-a'\n    tmux.until do |lines|\n      assert lines.any_include?('(2)')\n    end\n    # Now change with-nth to field 3; 'x' and 'z' don't contain 'match'\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n      # Selections of non-matching items should be cleared\n      assert lines.any_include?('(0)')\n    end\n  end\n\n  def test_change_with_nth_multiline\n    # Each item has 3 lines: \"N-a\\nN-b\\nN-c\"\n    # --with-nth 1 shows 1 line per item, --with-nth 1..3 shows 3 lines per item\n    tmux.send_keys %(seq 20 | xargs -I{} printf '{}-a\\\\n{}-b\\\\n{}-c\\\\0' | #{FZF} --read0 --delimiter \"\\n\" --with-nth 1 --bind 'space:change-with-nth(1..3|1)' --no-sort), :Enter\n    tmux.until do |lines|\n      assert_equal 20, lines.item_count\n      assert lines.any_include?('1-a')\n      refute lines.any_include?('1-b')\n    end\n    # Expand to 3 lines per item\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('1-a')\n      assert lines.any_include?('1-b')\n      assert lines.any_include?('1-c')\n    end\n    # Scroll down a few items\n    5.times { tmux.send_keys :Down }\n    tmux.until do |lines|\n      assert lines.any_include?('6-a')\n      assert lines.any_include?('6-b')\n      assert lines.any_include?('6-c')\n    end\n    # Collapse back to 1 line per item\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines.any_include?('6-a')\n      refute lines.any_include?('6-b')\n    end\n    # Scroll down more after collapse\n    5.times { tmux.send_keys :Down }\n    tmux.until do |lines|\n      assert lines.any_include?('11-a')\n      refute lines.any_include?('11-b')\n    end\n  end\n\n  def test_env_vars\n    def env_vars\n      return {} unless File.exist?(tempname)\n\n      File.readlines(tempname).select { it.start_with?('FZF_') }.to_h do\n        key, val = it.chomp.split('=', 2)\n        [key.to_sym, val]\n      end\n    end\n\n    tmux.send_keys %({ echo foo; seq 100; } | #{FZF} --header-lines 1 --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{tempname}' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter\n    expected = {\n      FZF_DIRECTION: 'down',\n      FZF_TOTAL_COUNT: '100',\n      FZF_MATCH_COUNT: '100',\n      FZF_SELECT_COUNT: '0',\n      FZF_ACTION: 'start',\n      FZF_KEY: '',\n      FZF_POS: '1',\n      FZF_QUERY: '',\n      FZF_POINTER: '>',\n      FZF_PROMPT: '> ',\n      FZF_INPUT_STATE: 'hidden'\n    }\n    tmux.until do\n      assert_equal expected, env_vars.slice(*expected.keys)\n    end\n    tmux.send_keys :Enter\n    tmux.until do\n      expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter')\n      assert_equal expected, env_vars.slice(*expected.keys)\n    end\n    tmux.send_keys :Tab, :Tab\n    tmux.until do\n      expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2')\n      assert_equal expected, env_vars.slice(*expected.keys)\n    end\n    tmux.send_keys '99'\n    tmux.until do\n      expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1')\n      assert_equal expected, env_vars.slice(*expected.keys)\n    end\n    tmux.send_keys :Space\n    tmux.until do\n      expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space')\n      assert_equal expected, env_vars.slice(*expected.keys)\n    end\n  end\n\n  def test_abort_action_chain\n    tmux.send_keys %(seq 100 | #{FZF} --bind 'load:accept+up+up' > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      assert_equal '1', File.read(tempname).chomp\n    end\n    tmux.send_keys %(seq 100 | #{FZF} --bind 'load:abort+become(echo {})' > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      assert_equal '', File.read(tempname).chomp\n    end\n  end\n\n  def test_exclude_multi\n    tmux.send_keys %(seq 1000 | #{FZF} --multi --bind 'a:exclude-multi,b:reload(seq 1000),c:reload-sync(seq 1000)'), :Enter\n\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 1'\n    end\n    tmux.send_keys :a\n    tmux.until do |lines|\n      assert_includes lines, '> 2'\n      assert_equal 999, lines.match_count\n    end\n    tmux.send_keys :Up, :BTab, :BTab, :BTab, :a\n    tmux.until do |lines|\n      assert_equal 996, lines.match_count\n      assert_includes lines, '> 9'\n    end\n    tmux.send_keys :b\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 5'\n    end\n    tmux.send_keys :Tab, :Tab, :Tab, :a\n    tmux.until do |lines|\n      assert_equal 997, lines.match_count\n      assert_includes lines, '> 2'\n    end\n    tmux.send_keys :c\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 2'\n    end\n\n    # TODO: We should also check the behavior of 'exclude' during reloads\n  end\n\n  def test_exclude\n    tmux.send_keys %(seq 1000 | #{FZF} --multi --bind 'a:exclude,b:reload(seq 1000),c:reload-sync(seq 1000)'), :Enter\n\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 1'\n    end\n    tmux.send_keys :a\n    tmux.until do |lines|\n      assert_includes lines, '> 2'\n      assert_equal 999, lines.match_count\n    end\n    tmux.send_keys :Up, :BTab, :BTab, :BTab, :a\n    tmux.until do |lines|\n      assert_equal 998, lines.match_count\n      assert_equal 3, lines.select_count\n      assert_includes lines, '> 7'\n    end\n    tmux.send_keys :b\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_equal 0, lines.select_count\n      assert_includes lines, '> 5'\n    end\n    tmux.send_keys :Tab, :Tab, :Tab, :a\n    tmux.until do |lines|\n      assert_equal 999, lines.match_count\n      assert_equal 3, lines.select_count\n      assert_includes lines, '>>3'\n    end\n    tmux.send_keys :a\n    tmux.until do |lines|\n      assert_equal 998, lines.match_count\n      assert_equal 2, lines.select_count\n      assert_includes lines, '>>4'\n    end\n    tmux.send_keys :c\n    tmux.until do |lines|\n      assert_equal 1000, lines.match_count\n      assert_includes lines, '> 2'\n    end\n\n    # TODO: We should also check the behavior of 'exclude' during reloads\n  end\n\n  def test_accept_nth\n    tmux.send_keys %((echo \"foo  bar  baz\"; echo \"bar baz  foo\") | #{FZF} --multi --accept-nth 2,2 --sync --bind start:select-all+accept > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      assert_equal ['bar  bar', 'baz  baz'], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_accept_nth_string_delimiter\n    tmux.send_keys %(echo \"foo  ,bar,baz\" | #{FZF} -d, --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      # Last delimiter is removed\n      assert_equal ['bar,bar,foo  ,bazfoo  '], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_accept_nth_regex_delimiter\n    tmux.send_keys %(echo \"foo  :,:bar,baz\" | #{FZF} --delimiter=' *[:,]+ *' --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      # Last delimiter and the whitespaces are removed\n      assert_equal ['bar,bar,foo  :,:bazfoo'], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_accept_nth_regex_delimiter_strip_last\n    tmux.send_keys %((echo \"foo:,bar:,baz\"; echo \"foo:,bar:,baz:,qux:,\") | #{FZF} --multi --delimiter='[:,]+' --accept-nth 2.. --sync --bind 'load:select-all+accept' > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      # Last delimiter and the whitespaces are removed\n      assert_equal ['bar:,baz', 'bar:,baz:,qux'], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_accept_nth_template\n    tmux.send_keys %(echo \"foo  ,bar,baz\" | #{FZF} -d \" *, *\" --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter\n    wait do\n      assert_path_exists tempname\n      # Last delimiter and the whitespaces are removed\n      assert_equal ['[0] 1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_ghost\n    tmux.send_keys %(seq 100 | #{FZF} --prompt 'X ' --ghost 'Type in query ...' --bind 'space:change-ghost:Y Z' --bind 'enter:transform-ghost:echo Z Y'), :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines, 'X Type in query ...'\n    end\n    tmux.send_keys '100'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, 'X 100'\n    end\n    tmux.send_keys 'C-u'\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines, 'X Type in query ...'\n    end\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines, 'X Y Z' }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_includes lines, 'X Z Y' }\n  end\n\n  def test_ghost_inline\n    tmux.send_keys %(seq 100 | #{FZF} --info 'inline: Y' --no-separator --prompt 'X ' --ghost 'Type in query ...'), :Enter\n    tmux.until do |lines|\n      assert_includes lines, 'X Type in query ... Y100/100'\n    end\n    tmux.send_keys '100'\n    tmux.until do |lines|\n      assert_includes lines, 'X 100  Y1/100'\n    end\n    tmux.send_keys 'C-u'\n    tmux.until do |lines|\n      assert_includes lines, 'X Type in query ... Y100/100'\n    end\n  end\n\n  def test_offset_middle\n    tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --reverse --height 5 --scroll-off 0 --bind space:offset-middle), :Enter\n    line = nil\n    tmux.until { |lines| line = lines.index('> 1') }\n    tmux.send_keys :PgDn\n    tmux.until { |lines| assert_includes lines[line + 4], '> 5' }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines[line + 2], '> 5' }\n  end\n\n  def test_no_input_query\n    tmux.send_keys %(seq 1000 | #{FZF} --no-input --query 555 --bind space:toggle-input), :Enter\n    tmux.until { |lines| assert_includes lines, '> 555' }\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> 555'\n    end\n  end\n\n  def test_no_input_change_query\n    tmux.send_keys %(seq 1000 | #{FZF} --multi --query 999 --no-input --bind 'enter:show-input+change-query(555)+hide-input,space:change-query(555)+select'), :Enter\n    tmux.until { |lines| assert_includes lines, '> 999' }\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_includes lines, '>>999'\n      refute_includes lines, '> 555'\n    end\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      refute_includes lines, '>>999'\n      assert_includes lines, '> 555'\n    end\n  end\n\n  def test_search_override_query_in_no_input_mode\n    tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --bind 'enter:show-input+change-query(555)+hide-input+search(999),space:search(111)+show-input+change-query(777)'), :Enter\n    tmux.until { |lines| assert_includes lines, '> 1' }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_includes lines, '> 999' }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines, '> 777' }\n  end\n\n  def test_change_pointer\n    tmux.send_keys %(seq 2 | #{FZF} --bind 'a:change-pointer(a),b:change-pointer(bb),c:change-pointer(),d:change-pointer(ddd)'), :Enter\n    tmux.until { |lines| assert_includes lines, '> 1' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines, 'a 1' }\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_includes lines, 'bb 1' }\n    tmux.send_keys 'c'\n    tmux.until { |lines| assert_includes lines, ' 1' }\n    tmux.send_keys 'd'\n    tmux.until { |lines| refute_includes lines, 'ddd 1' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, ' 2' }\n  end\n\n  def test_transform_pointer\n    tmux.send_keys %(seq 2 | #{FZF} --bind 'a:transform-pointer(echo a),b:transform-pointer(echo bb),c:transform-pointer(),d:transform-pointer(echo ddd)'), :Enter\n    tmux.until { |lines| assert_includes lines, '> 1' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines, 'a 1' }\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_includes lines, 'bb 1' }\n    tmux.send_keys 'c'\n    tmux.until { |lines| assert_includes lines, ' 1' }\n    tmux.send_keys 'd'\n    tmux.until { |lines| refute_includes lines, 'ddd 1' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, ' 2' }\n  end\n\n  def test_change_header_on_header_window\n    tmux.send_keys %(seq 100 | #{FZF} --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)'), :Enter\n    tmux.until do |lines|\n      assert lines.any_include?('100/100')\n      assert lines.any_include?('foo')\n    end\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('bar') }\n  end\n\n  def test_trailing_new_line\n    tmux.send_keys %(echo -en \"foo\\n\" | fzf --read0 --no-multi-line), :Enter\n    tmux.until { |lines| assert_includes lines, '> foo␊' }\n  end\n\n  def test_async_transform\n    time = Time.now\n    tmux.send_keys %(\n      seq 100 | #{FZF} --style full --border --preview : \\\n          --bind 'focus:bg-transform-header(sleep 0.5; echo th.)' \\\n          --bind 'focus:+bg-transform-footer(sleep 0.5; echo tf.)' \\\n          --bind 'focus:+bg-transform-border-label(sleep 0.5; echo tbl.)' \\\n          --bind \"focus:+bg-transform-preview-label(sleep 0.5; echo tpl.)\" \\\n          --bind 'focus:+bg-transform-input-label(sleep 0.5; echo til.)' \\\n          --bind 'focus:+bg-transform-list-label(sleep 0.5; echo tll.)' \\\n          --bind 'focus:+bg-transform-header-label(sleep 0.5; echo thl.)' \\\n          --bind 'focus:+bg-transform-footer-label(sleep 0.5; echo tfl.)' \\\n          --bind 'focus:+bg-transform-prompt(sleep 0.5; echo tp.)' \\\n          --bind 'focus:+bg-transform-ghost(sleep 0.5; echo tg.)'\n    ).strip, :Enter\n    tmux.until do |lines|\n      assert lines.any_include?('100/100')\n      %w[th tf tbl tpl til tll thl tfl tp tg].each do\n        assert lines.any_include?(\"#{it}.\")\n      end\n    end\n    elapsed = Time.now - time\n    assert_operator elapsed, :<, 2\n  end\n\n  def test_bg_cancel\n    tmux.send_keys %(seq 0 1 | #{FZF} --bind 'space:bg-cancel+bg-transform-header(sleep {}; echo [{}])'), :Enter\n    tmux.until { assert_equal 2, it.match_count }\n    tmux.send_keys '1'\n    tmux.until { assert_equal 1, it.match_count }\n    tmux.send_keys :Space\n    tmux.send_keys :BSpace\n    tmux.until { assert_equal 2, it.match_count }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('[0]') }\n    sleep(2)\n    tmux.until do |lines|\n      assert lines.any_include?('[0]')\n      refute lines.any_include?('[1]')\n    end\n  end\n\n  def test_render_order\n    tmux.send_keys %(seq 100 | #{FZF} --bind='focus:preview(echo boom)+change-footer(bam)'), :Enter\n    tmux.until { assert_equal 100, it.match_count }\n    tmux.until { assert it.any_include?('boom') }\n    tmux.until { assert it.any_include?('bam') }\n  end\n\n  def test_multi_event\n    tmux.send_keys %(seq 100 | #{FZF} --multi --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo \"Selected $FZF_SELECT_COUNT item(s)\"'), :Enter\n    tmux.until { assert_equal 100, it.match_count }\n    tmux.send_keys :Tab\n    tmux.until { assert_equal 1, it.select_count }\n    tmux.until { assert it.any_include?('Selected 1 item(s)') }\n    tmux.send_keys :Tab\n    tmux.until { assert_equal 0, it.select_count }\n    tmux.until { refute it.any_include?('Selected') }\n  end\n\n  def test_preserve_selection_on_revision_bump\n    tmux.send_keys %(seq 100 | #{FZF} --multi --sync --query \"'1\" --bind 'a:select-all+change-header(pressed a),b:change-header(pressed b)+change-nth(1),c:exclude'), :Enter\n    tmux.until do\n      assert_equal 20, it.match_count\n      assert_equal 0, it.select_count\n    end\n    tmux.send_keys :a\n    tmux.until do\n      assert_equal 20, it.match_count\n      assert_equal 20, it.select_count\n      assert it.any_include?('pressed a')\n    end\n    tmux.send_keys :b\n    tmux.until do\n      assert_equal 20, it.match_count\n      assert_equal 20, it.select_count\n      refute it.any_include?('pressed a')\n      assert it.any_include?('pressed b')\n    end\n    tmux.send_keys :a\n    tmux.until do\n      assert_equal 20, it.match_count\n      assert_equal 20, it.select_count\n      assert it.any_include?('pressed a')\n      refute it.any_include?('pressed b')\n    end\n    tmux.send_keys :c\n    tmux.until do\n      assert_equal 19, it.match_count\n      assert_equal 19, it.select_count\n    end\n  end\n\n  def test_trigger\n    tmux.send_keys %(seq 100 | #{FZF} --bind 'a:up+trigger(a),b:trigger(a,a,b,a)'), :Enter\n    tmux.until { assert_equal 100, it.match_count }\n    tmux.until { |lines| assert_includes lines, '> 1' }\n    tmux.send_keys :a\n    tmux.until { |lines| assert_includes lines, '> 3' }\n    tmux.send_keys :b\n    tmux.until { |lines| assert_includes lines, '> 9' }\n  end\n\n  def test_change_nth_unset_default\n    tmux.send_keys %(echo foo bar | #{FZF} --nth 2 --query fb --bind space:change-nth:), :Enter\n    tmux.until do\n      assert_equal 1, it.item_count\n      assert_equal 0, it.match_count\n    end\n\n    tmux.send_keys :Space\n\n    tmux.until do\n      assert_equal 1, it.item_count\n      assert_equal 1, it.match_count\n    end\n  end\n\n  def test_change_header_lines\n    tmux.send_keys %(seq 10 | #{FZF} --header-lines 3 --bind 'space:change-header-lines(5),enter:transform-header-lines(echo 1)'), :Enter\n    tmux.until do |lines|\n      assert_equal 7, lines.item_count\n      assert lines.any_include?('> 4')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 5, lines.item_count\n      assert lines.any_include?('> 6')\n    end\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      assert_equal 9, lines.item_count\n      assert lines.any_include?('> 6')\n    end\n  end\n\n  def test_change_header_lines_to_zero\n    tmux.send_keys %(seq 5 | #{FZF} --header-lines 3 --bind 'space:bg-transform-header-lines(echo 0)'), :Enter\n    tmux.until do |lines|\n      assert_equal 2, lines.item_count\n      assert lines.any_include?('> 4')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 5, lines.item_count\n      # All items are now in the list, cursor stays on item 4\n      assert lines.any_include?('> 4')\n    end\n  end\n\n  def test_change_header_lines_deselect\n    # Selected items that become part of the header should be deselected\n    tmux.send_keys %(seq 10 | #{FZF} --multi --header-lines 0 --bind 'space:change-header-lines(3),enter:change-header-lines(1)'), :Enter\n    tmux.until do |lines|\n      assert_equal 10, lines.item_count\n      assert lines.any_include?('> 1')\n    end\n    # Select items 1, 2, 3 (these will become header lines)\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal 3, lines.select_count }\n    # Also select item 4 (this should remain selected)\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_equal 4, lines.select_count }\n    # Change header-lines to 3: items 1, 2, 3 become headers and should be deselected\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 7, lines.item_count\n      assert_equal 1, lines.select_count\n      assert lines.any_include?('> 5')\n    end\n    # Change header-lines to 1\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      assert_equal 9, lines.item_count\n      assert_equal 1, lines.select_count\n      assert lines.any_include?('> 5')\n    end\n  end\n\n  def test_change_header_lines_reverse\n    tmux.send_keys %(seq 10 | #{FZF} --header-lines 2 --reverse --bind 'space:change-header-lines(4)'), :Enter\n    tmux.until do |lines|\n      assert_equal 8, lines.item_count\n      assert lines.any_include?('> 3')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 6, lines.item_count\n      assert lines.any_include?('> 5')\n    end\n  end\n\n  def test_zero_width_characters\n    tmux.send_keys %(for i in {1..1000}; do string+=\"a̱$i\"; printf '\\\\e[43m%s\\\\e[0m\\\\n' \"$string\"; done | #{FZF} --ansi --query a500 --ellipsis XX), :Enter\n    tmux.until do |lines|\n      assert_equal 981, lines.match_count\n      assert_match(/^> XX.*a̱500/, lines[-3])\n      assert(lines.reverse.drop(5).all? { it.match?(/^  XX.*a̱500.*XX/) })\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_exec.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Process execution: execute, become, reload\nclass TestExec < TestInteractive\n  def test_execute\n    output = '/tmp/fzf-test-execute'\n    opts = %[--bind \"alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)\"]\n    writelines(%w[foo'bar foo\"bar foo$bar])\n    tmux.send_keys \"cat #{tempname} | #{FZF} #{opts}\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n\n    ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }\n    tmux.send_keys :Escape, :a\n    ready.call('alt-a')\n    tmux.send_keys :Escape, :b\n    ready.call('alt-b')\n\n    tmux.send_keys :Up\n    tmux.send_keys :Escape, :a\n    ready.call('alt-a')\n    tmux.send_keys :Escape, :b\n    ready.call('alt-b')\n\n    tmux.send_keys :Up\n    tmux.send_keys :C\n    ready.call('C')\n\n    tmux.send_keys 'barfoo'\n    tmux.until { |lines| assert_equal '  0/3', lines[-2] }\n\n    tmux.send_keys :Escape, :a\n    ready.call('alt-a')\n    tmux.send_keys :Escape, :b\n    ready.call('alt-b')\n\n    wait do\n      assert_path_exists output\n      assert_equal %w[\n        /foo'bar/ /foo'barfoo'bar/\n        /foo\"bar/ /foo\"barfoo\"bar/\n        /foo$barfoo$barfoo$bar/\n      ], File.readlines(output, chomp: true)\n    end\n  ensure\n    FileUtils.rm_f(output)\n  end\n\n  def test_execute_multi\n    output = '/tmp/fzf-test-execute-multi'\n    opts = %[--multi --bind \"alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)\"]\n    writelines(%w[foo'bar foo\"bar foo$bar foobar])\n    tmux.send_keys \"cat #{tempname} | #{FZF} #{opts}\", :Enter\n    ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }\n\n    tmux.until { |lines| assert_equal '  4/4 (0)', lines[-2] }\n    tmux.send_keys :Escape, :a\n    ready.call('alt-a')\n    tmux.send_keys :Escape, :b\n    ready.call('alt-b')\n\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_equal '  4/4 (3)', lines[-2] }\n    tmux.send_keys :Escape, :a\n    ready.call('alt-a')\n    tmux.send_keys :Escape, :b\n    ready.call('alt-b')\n\n    tmux.send_keys :Tab, :Tab\n    tmux.until { |lines| assert_equal '  4/4 (3)', lines[-2] }\n    tmux.send_keys :Escape, :a\n    ready.call('alt-a')\n    wait do\n      assert_path_exists output\n      assert_equal [\n        %(foo'bar/foo'bar),\n        %(foo'bar foo\"bar foo$bar/foo'bar foo\"bar foo$bar),\n        %(foo'bar foo\"bar foobar/foo'bar foo\"bar foobar)\n      ], File.readlines(output, chomp: true)\n    end\n  ensure\n    FileUtils.rm_f(output)\n  end\n\n  def test_execute_plus_flag\n    output = tempname + '.tmp'\n    FileUtils.rm_f(output)\n    writelines(['foo bar', '123 456'])\n\n    tmux.send_keys \"cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'\", :Enter\n\n    tmux.until { |lines| assert_equal '  2/2 (0)', lines[-2] }\n    tmux.send_keys 'xy'\n    tmux.until { |lines| assert_equal '  0/2 (0)', lines[-2] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '  2/2 (0)', lines[-2] }\n\n    tmux.send_keys :Up\n    tmux.send_keys :Tab\n    tmux.send_keys 'xy'\n    tmux.until { |lines| assert_equal '  0/2 (1)', lines[-2] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '  2/2 (1)', lines[-2] }\n\n    tmux.send_keys :Tab\n    tmux.send_keys 'xy'\n    tmux.until { |lines| assert_equal '  0/2 (2)', lines[-2] }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal '  2/2 (2)', lines[-2] }\n\n    wait do\n      assert_path_exists output\n      assert_equal [\n        %(foo bar/foo bar/bar/bar),\n        %(123 456/foo bar/456/bar),\n        %(123 456 foo bar/foo bar/456 bar/bar)\n      ], File.readlines(output, chomp: true)\n    end\n  rescue StandardError\n    FileUtils.rm_f(output)\n  end\n\n  def test_execute_shell\n    # Custom script to use as $SHELL\n    output = tempname + '.out'\n    FileUtils.rm_f(output)\n    writelines(['#!/usr/bin/env bash', \"echo $1 / $2 > #{output}\"])\n    system(\"chmod +x #{tempname}\")\n\n    tmux.send_keys \"echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'\", :Enter\n    tmux.until { |lines| assert_equal '  1/1', lines[-2] }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal '  1/1', lines[-2] }\n    wait do\n      assert_path_exists output\n      assert_equal [\"-c / 'foo'bar\"], File.readlines(output, chomp: true)\n    end\n  ensure\n    FileUtils.rm_f(output)\n  end\n\n  def test_interrupt_execute\n    tmux.send_keys \"seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 'C-l'\n    tmux.until { |lines| assert lines.any_include?('executing 1') }\n    tmux.send_keys 'C-c'\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 99\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n  end\n\n  def test_kill_default_command_on_abort\n    writelines(['#!/usr/bin/env bash',\n                \"echo 'Started'\",\n                'while :; do sleep 1; done'])\n    system(\"chmod +x #{tempname}\")\n\n    tmux.send_keys FZF.sub('FZF_DEFAULT_COMMAND=', \"FZF_DEFAULT_COMMAND=#{tempname}\"), :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys 'C-c'\n    tmux.send_keys 'C-l', 'closed'\n    tmux.until { |lines| assert_includes lines[0], 'closed' }\n    wait { refute system(\"pgrep -f #{tempname}\") }\n  ensure\n    system(\"pkill -9 -f #{tempname}\")\n  end\n\n  def test_kill_default_command_on_accept\n    writelines(['#!/usr/bin/env bash',\n                \"echo 'Started'\",\n                'while :; do sleep 1; done'])\n    system(\"chmod +x #{tempname}\")\n\n    tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', \"FZF_DEFAULT_COMMAND=#{tempname}\"), :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    assert_equal 'Started', fzf_output\n    wait { refute system(\"pgrep -f #{tempname}\") }\n  ensure\n    system(\"pkill -9 -f #{tempname}\")\n  end\n\n  def test_kill_reload_command_on_abort\n    writelines(['#!/usr/bin/env bash',\n                \"echo 'Started'\",\n                'while :; do sleep 1; done'])\n    system(\"chmod +x #{tempname}\")\n\n    tmux.send_keys \"seq 1 3 | #{FZF} --bind 'ctrl-r:reload(#{tempname})'\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys 'C-c'\n    tmux.send_keys 'C-l', 'closed'\n    tmux.until { |lines| assert_includes lines[0], 'closed' }\n    wait { refute system(\"pgrep -f #{tempname}\") }\n  ensure\n    system(\"pkill -9 -f #{tempname}\")\n  end\n\n  def test_kill_reload_command_on_accept\n    writelines(['#!/usr/bin/env bash',\n                \"echo 'Started'\",\n                'while :; do sleep 1; done'])\n    system(\"chmod +x #{tempname}\")\n\n    tmux.send_keys \"seq 1 3 | #{fzf(\"--bind 'ctrl-r:reload(#{tempname})'\")}\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    assert_equal 'Started', fzf_output\n    wait { refute system(\"pgrep -f #{tempname}\") }\n  ensure\n    system(\"pkill -9 -f #{tempname}\")\n  end\n\n  def test_reload\n    tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq $FZF_QUERY),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter\n    tmux.until { |lines| assert_equal 998, lines.match_count }\n    tmux.send_keys 'a'\n    tmux.until do |lines|\n      assert_equal 98, lines.item_count\n      assert_equal 98, lines.match_count\n    end\n    tmux.send_keys 'b'\n    tmux.until do |lines|\n      assert_equal 198, lines.item_count\n      assert_equal 198, lines.match_count\n    end\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal '  198/198 (1/2)', lines[-2] }\n    tmux.send_keys '555'\n    tmux.until { |lines| assert_equal '  1/553 (0/2)', lines[-2] }\n  end\n\n  def test_reload_even_when_theres_no_match\n    tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter\n    tmux.until { |lines| assert_equal 0, lines.item_count }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal 10, lines.item_count }\n  end\n\n  def test_reload_should_terminate_standard_input_stream\n    tmux.send_keys %(ruby -e \"STDOUT.sync = true; loop { puts 1; sleep 0.1 }\" | fzf --bind 'start:reload(seq 100)'), :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n  end\n\n  def test_clear_list_when_header_lines_changed_due_to_reload\n    tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter\n    tmux.until { |lines| assert_includes lines, '  9' }\n    tmux.send_keys :Space\n    tmux.until { |lines| refute_includes lines, '  9' }\n  end\n\n  def test_item_index_reset_on_reload\n    tmux.send_keys \"seq 10 | #{FZF} --preview 'echo [[{n}]]' --bind 'up:last,down:first,space:reload:seq 100'\", :Enter\n    tmux.until { |lines| assert_includes lines[1], '[[0]]' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[1], '[[9]]' }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_includes lines[1], '[[0]]' }\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_includes lines[1], '[[0]]'\n    end\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[1], '[[99]]' }\n  end\n\n  def test_reload_should_update_preview\n    tmux.send_keys \"seq 3 | #{FZF} --bind 'ctrl-t:reload:echo 4' --preview 'echo {}' --preview-window 'nohidden'\", :Enter\n    tmux.until { |lines| assert_includes lines[1], '1' }\n    tmux.send_keys 'C-t'\n    tmux.until { |lines| assert_includes lines[1], '4' }\n  end\n\n  def test_reload_and_change_preview_should_update_preview\n    tmux.send_keys \"seq 3 | #{FZF} --bind 'ctrl-t:reload(echo 4)+change-preview(echo {})'\", :Enter\n    tmux.until { |lines| assert_equal 3, lines.match_count }\n    tmux.until { |lines| refute_includes lines[1], '1' }\n    tmux.send_keys 'C-t'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], '4' }\n  end\n\n  def test_reload_sync\n    tmux.send_keys \"seq 100 | #{FZF} --bind 'load:reload-sync(sleep 1; seq 1000)+unbind(load)'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys '00'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    # After 1 second\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n  end\n\n  def test_reload_disabled_case1\n    tmux.send_keys \"seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.item_count\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n  end\n\n  def test_reload_disabled_case2\n    tmux.send_keys \"seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.item_count\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n  end\n\n  def test_reload_disabled_case3\n    tmux.send_keys \"seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.item_count\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n  end\n\n  def test_reload_disabled_case4\n    tmux.send_keys \"seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.item_count\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n  end\n\n  def test_reload_disabled_case5\n    tmux.send_keys \"seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'\", :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.item_count\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert_equal 1, lines.item_count\n      assert_equal 1, lines.match_count\n    end\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 1001, lines.match_count }\n  end\n\n  def test_reload_disabled_case6\n    tmux.send_keys \"seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'\", :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.send_keys '9'\n    tmux.until { |lines| assert_equal 9, lines.match_count }\n    tmux.send_keys '9'\n    tmux.until { |lines| assert_equal 99, lines.match_count }\n\n    # TODO: How do we verify if an intermediate empty list is not shown?\n  end\n\n  def test_reload_and_change\n    tmux.send_keys \"(echo foo; echo bar) | #{FZF} --bind 'load:reload-sync(sleep 60)+change-query(bar)'\", :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n  end\n\n  def test_become_tty\n    tmux.send_keys \"sleep 0.5 | #{FZF} --bind 'start:reload:ls' --bind 'load:become:tty'\", :Enter\n    tmux.until { |lines| assert_includes lines, '/dev/tty' }\n  end\n\n  def test_disabled_preview_update\n    tmux.send_keys \"echo bar | #{FZF} --disabled --bind 'change:reload:echo foo' --preview 'echo [{q}-{}]'\", :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until { |lines| assert(lines.any? { |line| line.include?('[-bar]') }) }\n    tmux.send_keys :x\n    tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }\n  end\n\n  def test_start_on_reload\n    tmux.send_keys %(echo foo | #{FZF} --header Loading --header-lines 1 --bind 'start:reload:sleep 2; echo bar' --bind 'load:change-header:Loaded' --bind space:change-header:), :Enter\n    tmux.until(timeout: 1) { |lines| assert_includes lines[-3], 'Loading' }\n    tmux.until(timeout: 1) { |lines| refute_includes lines[-4], 'foo' }\n    tmux.until { |lines| assert_includes lines[-3], 'Loaded' }\n    tmux.until { |lines| assert_includes lines[-4], 'bar' }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines[-3], 'bar' }\n  end\n\n  def test_become\n    tmux.send_keys \"seq 100 | fzf --bind 'enter:become:seq {} | fzf'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys 999\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal 99, lines.item_count }\n  end\nend\n"
  },
  {
    "path": "test/test_filter.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Non-interactive tests\nclass TestFilter < TestBase\n  def test_default_extended\n    assert_equal '100', `seq 100 | #{FZF} -f \"1 00$\"`.chomp\n    assert_equal '', `seq 100 | #{FZF} -f \"1 00$\" +x`.chomp\n  end\n\n  def test_exact\n    assert_equal 4, `seq 123 | #{FZF} -f 13`.lines.length\n    assert_equal 2, `seq 123 | #{FZF} -f 13 -e`.lines.length\n    assert_equal 4, `seq 123 | #{FZF} -f 13 +e`.lines.length\n  end\n\n  def test_or_operator\n    assert_equal %w[1 5 10], `seq 10 | #{FZF} -f \"1 | 5\"`.lines(chomp: true)\n    assert_equal %w[1 10 2 3 4 5 6 7 8 9],\n                 `seq 10 | #{FZF} -f '1 | !1'`.lines(chomp: true)\n  end\n\n  def test_smart_case_for_each_term\n    assert_equal 1, `echo Foo bar | #{FZF} -x -f \"foo Fbar\" | wc -l`.to_i\n  end\n\n  def test_filter_exitstatus\n    # filter / streaming filter\n    ['', '--no-sort'].each do |opts|\n      assert_includes `echo foo | #{FZF} -f foo #{opts}`, 'foo'\n      assert_equal 0, $CHILD_STATUS.exitstatus\n\n      assert_empty `echo foo | #{FZF} -f bar #{opts}`\n      assert_equal 1, $CHILD_STATUS.exitstatus\n    end\n  end\n\n  def test_long_line\n    data = '.' * 256 * 1024\n    File.open(tempname, 'w') do |f|\n      f << data\n    end\n    assert_equal data, `#{FZF} -f . < #{tempname}`.chomp\n  end\n\n  def test_read0\n    lines = `find .`.lines(chomp: true)\n    assert_equal lines.last, `find . | #{FZF} -e -f \"^#{lines.last}$\"`.chomp\n    assert_equal \\\n      lines.last,\n      `find . -print0 | #{FZF} --read0 -e -f \"^#{lines.last}$\"`.chomp\n  end\n\n  def test_nth_suffix_match\n    assert_equal \\\n      'foo,bar,baz',\n      `echo foo,bar,baz | #{FZF} -d, -f'bar$' -n2`.chomp\n  end\n\n  def test_with_nth_basic\n    writelines(['hello world ', 'byebye'])\n    assert_equal \\\n      'hello world ',\n      `#{FZF} -f\"^he hehe\" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp\n  end\n\n  def test_with_nth_template\n    writelines(['hello world ', 'byebye'])\n    assert_equal \\\n      'hello world ',\n      `#{FZF} -f\"^he he.he.\" -x -n 2.. --with-nth '{2} {1}. {1}.' < #{tempname}`.chomp\n  end\n\n  def test_with_nth_ansi\n    writelines([\"\\x1b[33mhello \\x1b[34;1mworld\\x1b[m \", 'byebye'])\n    assert_equal \\\n      'hello world ',\n      `#{FZF} -f\"^he hehe\" -x -n 2.. --with-nth 2,1,1 --ansi < #{tempname}`.chomp\n  end\n\n  def test_with_nth_no_ansi\n    src = \"\\x1b[33mhello \\x1b[34;1mworld\\x1b[m \"\n    writelines([src, 'byebye'])\n    assert_equal \\\n      src,\n      `#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp\n  end\n\n  def test_escaped_meta_characters\n    input = [\n      'foo^bar',\n      'foo$bar',\n      'foo!bar',\n      \"foo'bar\",\n      'foo bar',\n      'bar foo'\n    ]\n    writelines(input)\n\n    assert_equal input.length, `#{FZF} -f'foo bar' < #{tempname}`.lines.length\n    assert_equal input.length - 1, `#{FZF} -f'^foo bar$' < #{tempname}`.lines.length\n    assert_equal ['foo bar'], `#{FZF} -f'foo\\\\ bar' < #{tempname}`.lines(chomp: true)\n    assert_equal ['foo bar'], `#{FZF} -f'^foo\\\\ bar$' < #{tempname}`.lines(chomp: true)\n    assert_equal input.length - 1, `#{FZF} -f'!^foo\\\\ bar$' < #{tempname}`.lines.length\n  end\n\n  def test_normalized_match\n    echoes = '(echo a; echo á; echo A; echo Á;)'\n    assert_equal %w[a á A Á], `#{echoes} | #{FZF} -f a`.lines.map(&:chomp)\n    assert_equal %w[á Á], `#{echoes} | #{FZF} -f á`.lines.map(&:chomp)\n    assert_equal %w[A Á], `#{echoes} | #{FZF} -f A`.lines.map(&:chomp)\n    assert_equal %w[Á], `#{echoes} | #{FZF} -f Á`.lines.map(&:chomp)\n  end\n\n  def test_unicode_case\n    writelines(%w[строКА1 СТРОКА2 строка3 Строка4])\n    assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.lines(chomp: true)\n    assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_tiebreak\n    input = %w[\n      --foobar--------\n      -----foobar---\n      ----foobar--\n      -------foobar-\n    ]\n    writelines(input)\n\n    assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.lines(chomp: true)\n\n    by_length = %w[\n      ----foobar--\n      -----foobar---\n      -------foobar-\n      --foobar--------\n    ]\n    assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.lines(chomp: true)\n    assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.lines(chomp: true)\n\n    by_begin = %w[\n      --foobar--------\n      ----foobar--\n      -----foobar---\n      -------foobar-\n    ]\n    assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.lines(chomp: true)\n    assert_equal by_begin, `#{FZF} -f\"!z foobar\" -x --tiebreak begin < #{tempname}`.lines(chomp: true)\n\n    assert_equal %w[\n      -------foobar-\n      ----foobar--\n      -----foobar---\n      --foobar--------\n    ], `#{FZF} -ffoobar --tiebreak end < #{tempname}`.lines(chomp: true)\n\n    assert_equal input, `#{FZF} -f\"!z\" -x --tiebreak end < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_tiebreak_index_begin\n    writelines([\n                 'xoxxxxxoxx',\n                 'xoxxxxxox',\n                 'xxoxxxoxx',\n                 'xxxoxoxxx',\n                 'xxxxoxox',\n                 '  xxoxoxxx'\n               ])\n\n    assert_equal [\n      'xxxxoxox',\n      '  xxoxoxxx',\n      'xxxoxoxxx',\n      'xxoxxxoxx',\n      'xoxxxxxox',\n      'xoxxxxxoxx'\n    ], `#{FZF} -foo < #{tempname}`.lines(chomp: true)\n\n    assert_equal [\n      'xxxoxoxxx',\n      'xxxxoxox',\n      '  xxoxoxxx',\n      'xxoxxxoxx',\n      'xoxxxxxoxx',\n      'xoxxxxxox'\n    ], `#{FZF} -foo --tiebreak=index < #{tempname}`.lines(chomp: true)\n\n    # Note that --tiebreak=begin is now based on the first occurrence of the\n    # first character on the pattern\n    assert_equal [\n      '  xxoxoxxx',\n      'xxxoxoxxx',\n      'xxxxoxox',\n      'xxoxxxoxx',\n      'xoxxxxxoxx',\n      'xoxxxxxox'\n    ], `#{FZF} -foo --tiebreak=begin < #{tempname}`.lines(chomp: true)\n\n    assert_equal [\n      '  xxoxoxxx',\n      'xxxoxoxxx',\n      'xxxxoxox',\n      'xxoxxxoxx',\n      'xoxxxxxox',\n      'xoxxxxxoxx'\n    ], `#{FZF} -foo --tiebreak=begin,length < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_tiebreak_begin_algo_v2\n    writelines(['baz foo bar',\n                'foo bar baz'])\n    assert_equal [\n      'foo bar baz',\n      'baz foo bar'\n    ], `#{FZF} -fbar --tiebreak=begin --algo=v2 < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_tiebreak_end\n    writelines(['xoxxxxxxxx',\n                'xxoxxxxxxx',\n                'xxxoxxxxxx',\n                'xxxxoxxxx',\n                'xxxxxoxxx',\n                '  xxxxoxxx'])\n\n    assert_equal [\n      '  xxxxoxxx',\n      'xxxxoxxxx',\n      'xxxxxoxxx',\n      'xoxxxxxxxx',\n      'xxoxxxxxxx',\n      'xxxoxxxxxx'\n    ], `#{FZF} -fo < #{tempname}`.lines(chomp: true)\n\n    assert_equal [\n      'xxxxxoxxx',\n      '  xxxxoxxx',\n      'xxxxoxxxx',\n      'xxxoxxxxxx',\n      'xxoxxxxxxx',\n      'xoxxxxxxxx'\n    ], `#{FZF} -fo --tiebreak=end < #{tempname}`.lines(chomp: true)\n\n    assert_equal [\n      'xxxxxoxxx',\n      '  xxxxoxxx',\n      'xxxxoxxxx',\n      'xxxoxxxxxx',\n      'xxoxxxxxxx',\n      'xoxxxxxxxx'\n    ], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)\n\n    writelines(['/bar/baz', '/foo/bar/baz'])\n    assert_equal [\n      '/foo/bar/baz',\n      '/bar/baz'\n    ], `#{FZF} -fbaz --tiebreak=end < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_tiebreak_length_with_nth\n    input = %w[\n      1:hell\n      123:hello\n      12345:he\n      1234567:h\n    ]\n    writelines(input)\n\n    output = %w[\n      1:hell\n      12345:he\n      123:hello\n      1234567:h\n    ]\n    assert_equal output, `#{FZF} -fh < #{tempname}`.lines(chomp: true)\n\n    # Since 0.16.8, --nth doesn't affect --tiebreak\n    assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_tiebreak_chunk\n    writelines(['1 foobarbaz ba',\n                '2 foobar baz',\n                '3 foo barbaz'])\n\n    assert_equal [\n      '3 foo barbaz',\n      '2 foobar baz',\n      '1 foobarbaz ba'\n    ], `#{FZF} -fo --tiebreak=chunk < #{tempname}`.lines(chomp: true)\n\n    assert_equal [\n      '1 foobarbaz ba',\n      '2 foobar baz',\n      '3 foo barbaz'\n    ], `#{FZF} -fba --tiebreak=chunk < #{tempname}`.lines(chomp: true)\n\n    assert_equal [\n      '3 foo barbaz'\n    ], `#{FZF} -f'!foobar' --tiebreak=chunk < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_boundary_match\n    # Underscore boundaries should be ranked lower\n    {\n      default: [' xyz '] + %w[/xyz/ [xyz] -xyz- -xyz_ _xyz- _xyz_],\n      path: ['/xyz/', ' xyz '] + %w[[xyz] -xyz- -xyz_ _xyz- _xyz_],\n      history: ['[xyz]', '-xyz-', ' xyz '] + %w[/xyz/ -xyz_ _xyz- _xyz_]\n    }.each do |scheme, expected|\n      result = `printf -- 'xxyzx\\n-xxyz\\nxyzx-\\n_xyz_\\n_xyz-\\n-xyz_\\n[xyz]\\n-xyz-\\n xyz \\n/xyz/\\n' | #{FZF} -f\"'xyz'\" --scheme=#{scheme}`.lines(chomp: true)\n      assert_equal expected, result\n    end\n  end\n\n  def test_accept_nth\n    # Single field selection\n    assert_equal 'three', `echo 'one two three' | #{FZF} -d' ' --with-nth 1 --accept-nth -1 -f one`.chomp\n\n    # Multiple field selection\n    writelines(['ID001:John:Developer', 'ID002:Jane:Manager', 'ID003:Bob:Designer'])\n    assert_equal 'ID001', `#{FZF} -d: --with-nth 2 --accept-nth 1 -f John < #{tempname}`.chomp\n    assert_equal 'ID002:Manager', `#{FZF} -d: --with-nth 2 --accept-nth 1,3 -f Jane < #{tempname}`.chomp\n\n    # Test with different delimiters\n    writelines(['emp001 Alice Engineering', 'emp002 Bob Marketing'])\n    assert_equal 'emp001', `#{FZF} -d' ' --with-nth 2 --accept-nth 1 -f Alice < #{tempname}`.chomp\n  end\n\n  def test_header_lines_filter\n    assert_equal %w[4 5 6 7 8 9 10],\n                 `seq 10 | #{FZF} --header-lines 3 -f \"\"`.lines(chomp: true)\n    assert_equal %w[5],\n                 `seq 10 | #{FZF} --header-lines 3 -f 5`.lines(chomp: true)\n    # Header items should not be matched\n    assert_empty `seq 10 | #{FZF} --header-lines 3 -f \"^1$\"`.lines(chomp: true)\n  end\n\n  def test_header_lines_filter_with_nth\n    writelines(%w[a:1 b:2 c:3 d:4 e:5])\n    assert_equal %w[c:3 d:4 e:5],\n                 `#{FZF} --header-lines 2 -d: --with-nth 2 -f \"\" < #{tempname}`.lines(chomp: true)\n    assert_equal %w[d:4],\n                 `#{FZF} --header-lines 2 -d: --with-nth 2 -f 4 < #{tempname}`.lines(chomp: true)\n  end\n\n  def test_header_lines_all_headers\n    # When all lines are header lines, no results\n    assert_empty `seq 3 | #{FZF} --header-lines 10 -f \"\"`.chomp\n    assert_equal 1, $CHILD_STATUS.exitstatus\n  end\nend\n"
  },
  {
    "path": "test/test_layout.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Test cases that mainly use assert_block to verify the layout of fzf\nclass TestLayout < TestInteractive\n  def assert_block(expected, lines)\n    cols = expected.lines.map { it.chomp.length }.max\n    top = lines.take(expected.lines.length).map { it[0, cols].rstrip + \"\\n\" }.join.chomp\n    bottom = lines.reverse.take(expected.lines.length).reverse.map { it[0, cols].rstrip + \"\\n\" }.join.chomp\n    assert_includes [top, bottom], expected.chomp\n  end\n\n  def test_vanilla\n    tmux.send_keys \"seq 1 100000 | #{fzf}\", :Enter\n    block = <<~BLOCK\n        2\n      > 1\n        100000/100000\n      >\n    BLOCK\n    tmux.until { assert_block(block, it) }\n\n    # Testing basic key bindings\n    tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'\n    block = <<~BLOCK\n      > 3910\n        391\n        856/100000\n      > 391\n    BLOCK\n    tmux.until { assert_block(block, it) }\n\n    tmux.send_keys :Enter\n    assert_equal '3910', fzf_output\n  end\n\n  def test_header_first\n    tmux.send_keys \"seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first\", :Enter\n    block = <<~OUTPUT\n      > 4\n        3\n        2\n        1\n        997/997\n      >\n        foobar\n    OUTPUT\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_header_first_reverse\n    tmux.send_keys \"seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info\", :Enter\n    block = <<~OUTPUT\n        foobar\n      >   < 997/997\n        1\n        2\n        3\n      > 4\n    OUTPUT\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_change_and_transform_header\n    [\n      'space:change-header:$(seq 4)',\n      'space:transform-header:seq 4'\n    ].each_with_index do |binding, i|\n      tmux.send_keys %(seq 3 | #{FZF} --header-lines 2 --header bar --bind \"#{binding}\"), :Enter\n      expected = <<~OUTPUT\n        > 3\n          2\n          1\n          bar\n          1/1\n        >\n      OUTPUT\n      tmux.until { assert_block(expected, it) }\n      tmux.send_keys :Space\n      expected = <<~OUTPUT\n        > 3\n          2\n          1\n          1\n          2\n          3\n          4\n          1/1\n        >\n      OUTPUT\n      tmux.until { assert_block(expected, it) }\n      next unless i.zero?\n\n      teardown\n      setup\n    end\n  end\n\n  def test_change_header\n    tmux.send_keys %(seq 3 | #{FZF} --header-lines 2 --header bar --bind \"space:change-header:$(seq 4)\"), :Enter\n    expected = <<~OUTPUT\n      > 3\n        2\n        1\n        bar\n        1/1\n      >\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n    tmux.send_keys :Space\n    expected = <<~OUTPUT\n      > 3\n        2\n        1\n        1\n        2\n        3\n        4\n        1/1\n      >\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_reload_and_change_cache\n    tmux.send_keys \"echo bar | #{FZF} --bind 'zero:change-header(foo)+reload(echo foo)+clear-query'\", :Enter\n    expected = <<~OUTPUT\n      > bar\n        1/1\n      >\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n    tmux.send_keys :z\n    expected = <<~OUTPUT\n      > foo\n        foo\n        1/1\n      >\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_toggle_header\n    tmux.send_keys \"seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border rounded\", :Enter\n    before = <<~OUTPUT\n      ╭───────\n      │\n      │   4\n      │ > 3\n      │   2\n      │   1\n      │   2/2\n      │ >\n      │   foo\n      ╰───────\n    OUTPUT\n    tmux.until { assert_block(before, it) }\n    tmux.send_keys :Space\n    after = <<~OUTPUT\n      ╭───────\n      │\n      │\n      │\n      │\n      │   4\n      │ > 3\n      │   2/2\n      │ >\n      ╰───────\n    OUTPUT\n    tmux.until { assert_block(after, it) }\n    tmux.send_keys :Space\n    tmux.until { assert_block(before, it) }\n  end\n\n  def test_height_range_fit\n    tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter\n    expected = <<~OUTPUT\n      ╭──────────\n      │ ▌ 3\n      │ ▌ 2\n      │ > 1\n      │ >   < 3/3\n      ╰──────────\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_height_range_fit_preview_above\n    tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded --preview-window border-rounded --preview \"seq {}\" --preview-window up,60%', :Enter\n    expected = <<~OUTPUT\n      ╭──────────\n      │ ╭────────\n      │ │ 1\n      │ │\n      │ │\n      │ │\n      │ ╰────────\n      │ ▌ 3\n      │ ▌ 2\n      │ > 1\n      │ >   < 3/3\n      ╰──────────\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_height_range_fit_preview_above_alternative\n    tmux.send_keys 'seq 3 | fzf --height ~100% --border=sharp --preview \"seq {}\" --preview-window up,40%,border-bottom --padding 1 --exit-0 --header hello --header-lines=2', :Enter\n    expected = <<~OUTPUT\n      ┌─────────\n      │\n      │  1\n      │  2\n      │  3\n      │  ───────\n      │  > 3\n      │    2\n      │    1\n      │    hello\n      │    1/1 ─\n      │  >\n      │\n      └─────────\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_height_range_fit_preview_left\n    tmux.send_keys \"seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\\\nworld' --header-lines=2\", :Enter\n    expected = <<~OUTPUT\n      │\n      │  1     │ > 3\n      │  2     │   2\n      │  3     │   1\n      │        │   hello\n      │        │   world\n      │        │   1/1 ─\n      │        │ >\n      │\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_height_range_overflow\n    tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter\n    expected = <<~OUTPUT\n      ╭──────────────\n      │ ▌ 2\n      │ > 1\n      │ >   < 100/100\n      ╰──────────────\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_no_extra_newline_issue_3209\n    tmux.send_keys(%(seq 100 | #{FZF} --height 10 --preview-window up,wrap,border-rounded --preview 'printf \"─%.0s\" $(seq 1 \"$((FZF_PREVIEW_COLUMNS - 5))\"); printf $\"\\\\e[7m%s\\\\e[0m\" title; echo; echo something'), :Enter)\n    expected = <<~OUTPUT\n      ╭──────────\n      │ ─────────\n      │ something\n      │\n      ╰──────────\n        3\n        2\n      > 1\n        100/100 ─\n      >\n    OUTPUT\n    tmux.until { assert_block(expected, it) }\n  end\n\n  def test_fzf_multi_line\n    tmux.send_keys %[(echo -en '0\\\\0'; echo -en '1\\\\n2\\\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded], :Enter\n    block = <<~BLOCK\n      │ ▌┃998\n      │ ▌┃999\n      │ ▌┃1000\n      │ ▌╹\n      │ ▌╻1\n      │ ▌╹2\n      │ >>0\n      │   3/3 (3)\n      │ >\n      ╰───────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n    tmux.send_keys :Up, :Up\n    block = <<~BLOCK\n      ╭───────\n      │ >╻1\n      │ >┃2\n      │ >┃3\n    BLOCK\n    tmux.until { assert_block(block, it) }\n\n    block = <<~BLOCK\n      │ >┃\n      │\n      │ >\n      ╰───\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_fzf_multi_line_reverse\n    tmux.send_keys %[(echo -en '0\\\\0'; echo -en '1\\\\n2\\\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse], :Enter\n    block = <<~BLOCK\n      ╭───────────\n      │ >\n      │   3/3 (3)\n      │ >>0\n      │ ▌╻1\n      │ ▌╹2\n      │ ▌╻1\n      │ ▌┃2\n      │ ▌┃3\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_fzf_multi_line_no_pointer_and_marker\n    tmux.send_keys %[(echo -en '0\\\\0'; echo -en '1\\\\n2\\\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse --pointer '' --marker '' --marker-multi-line ''], :Enter\n    block = <<~BLOCK\n      ╭───────────\n      │ >\n      │   3/3 (3)\n      │ 0\n      │ 1\n      │ 2\n      │ 1\n      │ 2\n      │ 3\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_gap\n    tmux.send_keys %(seq 100 | #{FZF} --gap --border rounded --reverse), :Enter\n    block = <<~BLOCK\n      ╭─────────────────\n      │ >\n      │   100/100 ──────\n      │ > 1\n      │   ┈┈┈┈┈┈┈┈┈┈┈┈┈┈\n      │   2\n      │   ┈┈┈┈┈┈┈┈┈┈┈┈┈┈\n      │   3\n      │   ┈┈┈┈┈┈┈┈┈┈┈┈┈┈\n      │   4\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_gap_2\n    tmux.send_keys %(seq 100 | #{FZF} --gap=2 --gap-line xyz --border rounded --reverse), :Enter\n    block = <<~BLOCK\n      ╭─────────────────\n      │ >\n      │   100/100 ──────\n      │ > 1\n      │\n      │   xyzxyzxyzxyzxy\n      │   2\n      │\n      │   xyzxyzxyzxyzxy\n      │   3\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_list_border_and_label\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --query 1 --padding 1,2), :Enter\n    block = <<~BLOCK\n      │   ║   11\n      │   ║ > 10\n      │   ║   3\n      │   ║   2\n      │   ║   1\n      │   ║   19/97 ─\n      │   ║ > 1\n      │   ╚list══════\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_input_border_and_label\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2), :Enter\n    block = <<~BLOCK\n      │     11\n      │   > 10\n      │     3\n      │     2\n      │     1\n      │   ┏input━━━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗━━━━━━━━━━\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_input_border_and_label_header_first\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 --header-first), :Enter\n    block = <<~BLOCK\n      │     11\n      │   > 10\n      │   ┏input━━━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗━━━━━━━━━━\n      │     3\n      │     2\n      │     1\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_list_input_border_and_label\n    tmux.send_keys %(\n      seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \\\n      --bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \\\n      --bind 'space:change-input-label( input )+change-list-label( list )'\n    ).strip, :Enter\n    block = <<~BLOCK\n      │   ║   11\n      │   ║ > 10\n      │   ╚LIST══════\n      │       3\n      │       2\n      │       1\n      │   ┏INPUT━━━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗━━━━━━━━━━\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n    tmux.send_keys :Space\n    block = <<~BLOCK\n      │   ║   11\n      │   ║ > 10\n      │   ╚ list ════\n      │       3\n      │       2\n      │       1\n      │   ┏ input ━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗━━━━━━━━━━\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_list_input_border_and_label_header_first\n    tmux.send_keys %(\n      seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \\\n      --bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \\\n      --bind 'space:change-input-label( input )+change-list-label( list )' --header-first\n    ).strip, :Enter\n    block = <<~BLOCK\n      │   ║   11\n      │   ║ > 10\n      │   ╚LIST══════\n      │   ┏INPUT━━━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗━━━━━━━━━━\n      │       3\n      │       2\n      │       1\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n    tmux.send_keys :Space\n    block = <<~BLOCK\n      │   ║   11\n      │   ║ > 10\n      │   ╚ list ════\n      │   ┏ input ━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗━━━━━━━━━━\n      │       3\n      │       2\n      │       1\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_header_border_and_label\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter\n    block = <<~BLOCK\n      │     12\n      │     11\n      │   > 10\n      │   ┌────────\n      │   │ 3\n      │   │ 2\n      │   │ 1\n      │   └header──\n      │     19/97 ─\n      │   > 1\n      │\n      ╰────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_header_border_toggle\n    tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()'), :Enter\n    block1 = <<~BLOCK\n      │   5\n      │   4\n      │   3\n      │   2\n      │ > 1\n      │   100/100 ─\n      │ >\n      ╰────────────\n    BLOCK\n    tmux.until { assert_block(block1, it) }\n\n    tmux.send_keys :Space\n    block2 = <<~BLOCK\n      │   3\n      │   2\n      │ > 1\n      ╰────────────\n      ╭────────────\n      │   hello\n      ╰────────────\n          100/100 ─\n        >\n    BLOCK\n    tmux.until { assert_block(block2, it) }\n\n    tmux.send_keys :Enter\n    tmux.until { assert_block(block1, it) }\n  end\n\n  def test_header_border_toggle_with_header_lines\n    tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2), :Enter\n    block1 = <<~BLOCK\n      │   5\n      │   4\n      │ > 3\n      ╰──────────\n      ╭──────────\n      │   2\n      │   1\n      ╰──────────\n          98/98 ─\n        >\n    BLOCK\n    tmux.until { assert_block(block1, it) }\n\n    tmux.send_keys :Space\n    block2 = <<~BLOCK\n      │   4\n      │ > 3\n      ╰──────────\n      ╭──────────\n      │   2\n      │   1\n      │   hello\n      ╰──────────\n          98/98 ─\n        >\n    BLOCK\n    tmux.until { assert_block(block2, it) }\n\n    tmux.send_keys :Enter\n    tmux.until { assert_block(block1, it) }\n  end\n\n  def test_header_border_toggle_with_header_lines_header_first\n    tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2 --header-first), :Enter\n    block1 = <<~BLOCK\n      │   5\n      │   4\n      │ > 3\n      ╰──────────\n          98/98 ─\n        >\n      ╭──────────\n      │   2\n      │   1\n      ╰──────────\n    BLOCK\n    tmux.until { assert_block(block1, it) }\n\n    tmux.send_keys :Space\n    block2 = <<~BLOCK\n      │   4\n      │ > 3\n      ╰──────────\n          2\n          1\n          98/98 ─\n        >\n      ╭──────────\n      │   hello\n      ╰──────────\n    BLOCK\n    tmux.until { assert_block(block2, it) }\n\n    tmux.send_keys :Enter\n    tmux.until { assert_block(block1, it) }\n  end\n\n  def test_header_border_toggle_with_header_lines_header_lines_border\n    tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2 --header-lines-border double), :Enter\n    block1 = <<~BLOCK\n      │   5\n      │   4\n      │ > 3\n      ╰──────────\n      ╔══════════\n      ║   2\n      ║   1\n      ╚══════════\n          98/98 ─\n        >\n    BLOCK\n    tmux.until { assert_block(block1, it) }\n\n    tmux.send_keys :Space\n    block2 = <<~BLOCK\n      │ > 3\n      ╰──────────\n      ╔══════════\n      ║   2\n      ║   1\n      ╚══════════\n      ╭──────────\n      │   hello\n      ╰──────────\n          98/98 ─\n        >\n    BLOCK\n    tmux.until { assert_block(block2, it) }\n\n    tmux.send_keys :Enter\n    tmux.until { assert_block(block1, it) }\n  end\n\n  def test_header_border_toggle_with_header_lines_header_first_header_lines_border\n    tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2 --header-first --header-lines-border double), :Enter\n    block1 = <<~BLOCK\n      │   5\n      │   4\n      │ > 3\n      ╰──────────\n          98/98 ─\n        >\n      ╔══════════\n      ║   2\n      ║   1\n      ╚══════════\n    BLOCK\n    tmux.until { assert_block(block1, it) }\n\n    tmux.send_keys :Space\n    block2 = <<~BLOCK\n      │ > 3\n      ╰──────────\n      ╔══════════\n      ║   2\n      ║   1\n      ╚══════════\n          98/98 ─\n        >\n      ╭──────────\n      │   hello\n      ╰──────────\n    BLOCK\n    tmux.until { assert_block(block2, it) }\n\n    tmux.send_keys :Enter\n    tmux.until { assert_block(block1, it) }\n  end\n\n  def test_header_border_and_label_header_first\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter\n    block = <<~BLOCK\n      │     12\n      │     11\n      │   > 10\n      │     19/97 ─\n      │   > 1\n      │   ┌────────\n      │   │ 3\n      │   │ 2\n      │   │ 1\n      │   └header──\n      │\n      ╰────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_header_border_and_label_with_list_border\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter\n    block = <<~BLOCK\n      │   ║   12\n      │   ║   11\n      │   ║ > 10\n      │   ╚list══════\n      │   ┌──────────\n      │   │   3\n      │   │   2\n      │   │   1\n      │   └header────\n      │       19/97 ─\n      │     > 1\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_header_border_and_label_with_list_border_header_first\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter\n    block = <<~BLOCK\n      │   ║   12\n      │   ║   11\n      │   ║ > 10\n      │   ╚list══════\n      │       19/97 ─\n      │     > 1\n      │   ┌──────────\n      │   │   3\n      │   │   2\n      │   │   1\n      │   └header────\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_all_borders\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom), :Enter\n    block = <<~BLOCK\n      │   ║   12\n      │   ║   11\n      │   ║ > 10\n      │   ╚list══════\n      │   ┌──────────\n      │   │   3\n      │   │   2\n      │   │   1\n      │   └header────\n      │   ┏━━━━━━━━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗input━━━━━\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_all_borders_header_first\n    tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom --header-first), :Enter\n    block = <<~BLOCK\n      │   ║   12\n      │   ║   11\n      │   ║ > 10\n      │   ╚list══════\n      │   ┏━━━━━━━━━━\n      │   ┃   19/97\n      │   ┃ > 1\n      │   ┗input━━━━━\n      │   ┌──────────\n      │   │   3\n      │   │   2\n      │   │   1\n      │   └header────\n      │\n      ╰──────────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_style_full_adaptive_height\n    tmux.send_keys %(seq 1| #{FZF} --style=full:rounded --height=~100% --header-lines=1 --info=default), :Enter\n    block = <<~BLOCK\n      ╭────────\n      ╰────────\n      ╭────────\n      │   1\n      ╰────────\n      ╭────────\n      │   0/0\n      │ >\n      ╰────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_style_full_adaptive_height_double\n    tmux.send_keys %(seq 1| #{FZF} --style=full:double --border --height=~100% --header-lines=1 --info=default), :Enter\n    block = <<~BLOCK\n      ╔══════════\n      ║ ╔════════\n      ║ ╚════════\n      ║ ╔════════\n      ║ ║   1\n      ║ ╚════════\n      ║ ╔════════\n      ║ ║   0/0\n      ║ ║ >\n      ║ ╚════════\n      ╚══════════\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_preview_window_noinfo\n    # │ 1        ││\n    tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter\n    tmux.until do |lines|\n      assert lines[1]&.start_with?('│ 1')\n      assert lines[1]&.end_with?('  ││')\n    end\n    tmux.send_keys :Space\n    tmux.until do |lines|\n      assert lines[1]&.start_with?('│ 1')\n      assert lines[1]&.end_with?('1000││')\n    end\n  end\n\n  def test_min_height_no_auto\n    tmux.send_keys %(seq 100 | #{FZF} --border sharp --style full:sharp --height 1% --min-height 5), :Enter\n\n    block = <<~BLOCK\n      ┌───────\n      │ ┌─────\n      │ │ >\n      │ └─────\n      └───────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_min_height_auto\n    tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --height 1% --min-height 5+), :Enter\n\n    block = <<~BLOCK\n      ┌─────────\n      │   5\n      │   4\n      │   3\n      │   2\n      │ > 1\n      └─────────\n      ┌─────────\n      │ >\n      └─────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_min_height_auto_no_input\n    tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --no-input --height 1% --min-height 5+), :Enter\n\n    block = <<~BLOCK\n      ┌─────────\n      │   5\n      │   4\n      │   3\n      │   2\n      │ > 1\n      └─────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_min_height_auto_no_input_reverse_list\n    tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --layout reverse-list --no-input --height 1% --min-height 5+ --bind a:show-input,b:hide-input,c:toggle-input), :Enter\n\n    block = <<~BLOCK\n      ┌─────────\n      │ > 1\n      │   2\n      │   3\n      │   4\n      │   5\n      └─────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n    tmux.send_keys :a\n    block2 = <<~BLOCK\n      ┌─────\n      │ > 1\n      │   2\n      └─────\n      ┌─────\n      │ >\n      └─────\n    BLOCK\n    tmux.until { assert_block(block2, it) }\n    tmux.send_keys :b\n    tmux.until { assert_block(block, it) }\n    tmux.send_keys :c\n    tmux.until { assert_block(block2, it) }\n    tmux.send_keys :c\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_layout_reverse_list\n    prefix = \"seq 5 | #{FZF} --layout reverse-list --no-list-border --height ~100% --border sharp \"\n    suffixes = [\n      %(),\n      %[--header \"$(seq 101 103)\"],\n      %[--header \"$(seq 101 103)\" --header-first],\n      %[--header \"$(seq 101 103)\" --header-lines 3],\n      %[--header \"$(seq 101 103)\" --header-lines 3 --header-first],\n      %[--header \"$(seq 101 103)\" --header-border sharp],\n      %[--header \"$(seq 101 103)\" --header-border sharp --header-first],\n      %[--header \"$(seq 101 103)\" --header-border sharp --header-lines 3],\n      %[--header \"$(seq 101 103)\" --header-border sharp --header-lines 3 --header-lines-border sharp],\n      %[--header \"$(seq 101 103)\" --header-border sharp --header-lines 3 --header-lines-border sharp --header-first],\n      %[--header \"$(seq 101 103)\" --header-border sharp --header-lines 3 --header-lines-border sharp --header-first --input-border sharp],\n      %[--header \"$(seq 101 103)\" --header-border sharp --header-lines 3 --header-lines-border sharp --header-first --no-input],\n      %[--header \"$(seq 101 103)\" --input-border sharp],\n      %[--header \"$(seq 101 103)\" --style full:sharp],\n      %[--header \"$(seq 101 103)\" --style full:sharp --header-first]\n    ]\n    output = <<~BLOCK\n      ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌─────── ┌───────── ┌───────── ┌─────────\n      │ > 1     │ > 1     │ > 1     │   1     │   1     │ > 1     │ > 1     │   1     │ ┌────── │ ┌────── │ ┌─────── │ ┌───── │ > 1      │ ┌─────── │ ┌───────\n      │   2     │   2     │   2     │   2     │   2     │   2     │   2     │   2     │ │ 1     │ │ 1     │ │ 1      │ │ 1    │   2      │ │ > 1    │ │ > 1\n      │   3     │   3     │   3     │   3     │   3     │   3     │   3     │   3     │ │ 2     │ │ 2     │ │ 2      │ │ 2    │   3      │ │   2    │ │   2\n      │   4     │   4     │   4     │ > 4     │ > 4     │   4     │   4     │ > 4     │ │ 3     │ │ 3     │ │ 3      │ │ 3    │   4      │ │   3    │ │   3\n      │   5     │   5     │   5     │   5     │   5     │   5     │   5     │   5     │ └────── │ └────── │ └─────── │ └───── │   5      │ │   4    │ │   4\n      │   5/5 ─ │   101   │   5/5 ─ │   101   │   2/2 ─ │ ┌────── │   5/5 ─ │ ┌────── │ > 4     │ > 4     │ > 4      │ > 4    │   101    │ │   5    │ │   5\n      │ >       │   102   │ >       │   102   │ >       │ │ 101   │ >       │ │ 101   │   5     │   5     │   5      │   5    │   102    │ └─────── │ └───────\n      └──────── │   103   │   101   │   103   │   101   │ │ 102   │ ┌────── │ │ 102   │ ┌────── │   2/2 ─ │ ┌─────── │ ┌───── │   103    │ ┌─────── │ ┌───────\n                │   5/5 ─ │   102   │   2/2 ─ │   102   │ │ 103   │ │ 101   │ │ 103   │ │ 101   │ >       │ │   2/2  │ │ 101  │ ┌─────── │ │   101  │ │ >\n                │ >       │   103   │ >       │   103   │ └────── │ │ 102   │ └────── │ │ 102   │ ┌────── │ │ >      │ │ 102  │ │   5/5  │ │   102  │ └───────\n                └──────── └──────── └──────── └──────── │   5/5 ─ │ │ 103   │   2/2 ─ │ │ 103   │ │ 101   │ └─────── │ │ 103  │ │ >      │ │   103  │ ┌───────\n                                                        │ >       │ └────── │ >       │ └────── │ │ 102   │ ┌─────── │ └───── │ └─────── │ └─────── │ │   101\n                                                        └──────── └──────── └──────── │   2/2 ─ │ │ 103   │ │ 101    └─────── └───────── │ ┌─────── │ │   102\n                                                                                      │ >       │ └────── │ │ 102                        │ │ >      │ │   103\n                                                                                      └──────── └──────── │ │ 103                        │ └─────── │ └───────\n                                                                                                          │ └───────                     └───────── └─────────\n                                                                                                          └─────────\n    BLOCK\n\n    expects = []\n    output.each_line.first.scan(/\\S+/) do\n      offset = Regexp.last_match.offset(0)\n      expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join(\"\\n\")\n    end\n\n    suffixes.zip(expects).each do |suffix, block|\n      tmux.send_keys(prefix + suffix, :Enter)\n      tmux.until { assert_block(block, it) }\n\n      teardown\n      setup\n    end\n  end\n\n  def test_layout_default_with_footer\n    prefix = %[\n      seq 3 | #{FZF} --no-list-border --height ~100% \\\n        --border sharp --footer \"$(seq 201 202)\" --footer-label FOOT --footer-label-pos 3 \\\n        --header-label HEAD --header-label-pos 3:bottom \\\n        --bind 'space:transform-footer-label(echo foot)+change-header-label(head)'\n    ].strip + ' '\n    suffixes = [\n      %(),\n      %[--header \"$(seq 101 102)\"],\n      %[--header \"$(seq 101 102)\" --header-first],\n      %[--header \"$(seq 101 102)\" --header-lines 2],\n      %[--header \"$(seq 101 102)\" --header-lines 2 --header-first],\n      %[--header \"$(seq 101 102)\" --header-border sharp],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-first],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --no-header-lines-border],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border none],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border sharp],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],\n      %[--header \"$(seq 101 102)\" --footer-border sharp --input-border line],\n      %[--header \"$(seq 101 102)\" --style full:sharp --header-first]\n    ]\n    output = <<~BLOCK\n      ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌──────── ┌──────── ┌─────────\n      │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201    │   201   │ ┌─FOOT─ │ ┌─FOOT──\n      │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202    │   202   │ │ 201   │ │   201\n      │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT── │ ──FOOT─ │ │ 202   │ │   202\n      │   3     │   3     │   3     │ > 3     │ > 3     │   3     │   3     │ > 3     │ > 3     │ > 3     │ > 3     │ > 3      │ > 3     │ └────── │ └───────\n      │   2     │   2     │   2     │   2     │   2     │   2     │   2     │ ┌────── │ ┌────── │   2     │ ┌────── │ ┌─────── │ ┌────── │   3     │ ┌───────\n      │ > 1     │ > 1     │ > 1     │   1     │   1     │ > 1     │ > 1     │ │ 2     │ │ 2     │   1     │ │ 2     │ │ 2      │ │ 2     │   2     │ │   3\n      │   3/3 ─ │   101   │   3/3 ─ │   101   │   1/1 ─ │ ┌────── │   3/3 ─ │ │ 1     │ │ 1     │ ┌────── │ │ 1     │ │ 1      │ │ 1     │ > 1     │ │   2\n      │ >       │   102   │ >       │   102   │ >       │ │ 101   │ >       │ │ 101   │ │ 101   │ │ 101   │ └────── │ └─────── │ └────── │   101   │ │ > 1\n      └──────── │   3/3 ─ │   101   │   1/1 ─ │   101   │ │ 102   │ ┌────── │ │ 102   │ │ 102   │ │ 102   │ ┌────── │ ┌─────── │ ┌────── │   102   │ └───────\n                │ >       │   102   │ >       │   102   │ └─HEAD─ │ │ 101   │ └─HEAD─ │ └─HEAD─ │ └─HEAD─ │ │ 101   │ │   1/1  │ │ 101   │ ─────── │ ┌───────\n                └──────── └──────── └──────── └──────── │   3/3 ─ │ │ 102   │   1/1 ─ │   1/1 ─ │   1/1 ─ │ │ 102   │ │ >      │ │ 102   │   3/3   │ │ >\n                                                        │ >       │ └─HEAD─ │ >       │ >       │ >       │ └─HEAD─ │ └─────── │ └─HEAD─ │ >       │ └───────\n                                                        └──────── └──────── └──────── └──────── └──────── │   1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────\n                                                                                                          │ >       │ │ 101                        │ │   101\n                                                                                                          └──────── │ │ 102                        │ │   102\n                                                                                                                    │ └─HEAD──                     │ └─HEAD──\n                                                                                                                    └─────────                     └─────────\n    BLOCK\n\n    expects = []\n    output.each_line.first.scan(/\\S+/) do\n      offset = Regexp.last_match.offset(0)\n      expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join(\"\\n\")\n    end\n\n    suffixes.zip(expects).each do |suffix, block|\n      tmux.send_keys(prefix + suffix, :Enter)\n      tmux.until { assert_block(block, it) }\n      tmux.send_keys :Space\n      tmux.until { assert_block(block.downcase, it) }\n\n      teardown\n      setup\n    end\n  end\n\n  def test_layout_reverse_list_with_footer\n    prefix = %[\n      seq 3 | #{FZF} --layout reverse-list --no-list-border --height ~100% \\\n        --border sharp --footer \"$(seq 201 202)\" --footer-label FOOT --footer-label-pos 3 \\\n        --header-label HEAD --header-label-pos 3:bottom \\\n        --bind 'space:transform-footer-label(echo foot)+change-header-label(head)'\n    ].strip + ' '\n    suffixes = [\n      %(),\n      %[--header \"$(seq 101 102)\"],\n      %[--header \"$(seq 101 102)\" --header-first],\n      %[--header \"$(seq 101 102)\" --header-lines 2],\n      %[--header \"$(seq 101 102)\" --header-lines 2 --header-first],\n      %[--header \"$(seq 101 102)\" --header-border sharp],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-first],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border sharp],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],\n      %[--header \"$(seq 101 102)\" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],\n      %[--header \"$(seq 101 102)\" --footer-border sharp --input-border line],\n      %[--header \"$(seq 101 102)\" --style full:sharp --header-first]\n    ]\n    output = <<~BLOCK\n      ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌──────── ┌──────── ┌─────────\n      │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201   │   201    │   201   │ ┌─FOOT─ │ ┌─FOOT──\n      │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202   │   202    │   202   │ │ 201   │ │   201\n      │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT── │ ──FOOT─ │ │ 202   │ │   202\n      │ > 1     │ > 1     │ > 1     │   1     │   1     │ > 1     │ > 1     │   1     │ ┌────── │ ┌─────── │ ┌────── │ └────── │ └───────\n      │   2     │   2     │   2     │   2     │   2     │   2     │   2     │   2     │ │ 1     │ │ 1      │ │ 1     │ > 1     │ ┌───────\n      │   3     │   3     │   3     │ > 3     │ > 3     │   3     │   3     │ > 3     │ │ 2     │ │ 2      │ │ 2     │   2     │ │ > 1\n      │   3/3 ─ │   101   │   3/3 ─ │   101   │   1/1 ─ │ ┌────── │   3/3 ─ │ ┌────── │ └────── │ └─────── │ └────── │   3     │ │   2\n      │ >       │   102   │ >       │   102   │ >       │ │ 101   │ >       │ │ 101   │ > 3     │ > 3      │ > 3     │   101   │ │   3\n      └──────── │   3/3 ─ │   101   │   1/1 ─ │   101   │ │ 102   │ ┌────── │ │ 102   │ ┌────── │ ┌─────── │ ┌────── │   102   │ └───────\n                │ >       │   102   │ >       │   102   │ └─HEAD─ │ │ 101   │ └─HEAD─ │ │ 101   │ │   1/1  │ │ 101   │ ─────── │ ┌───────\n                └──────── └──────── └──────── └──────── │   3/3 ─ │ │ 102   │   1/1 ─ │ │ 102   │ │ >      │ │ 102   │   3/3   │ │ >\n                                                        │ >       │ └─HEAD─ │ >       │ └─HEAD─ │ └─────── │ └─HEAD─ │ >       │ └───────\n                                                        └──────── └──────── └──────── │   1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────\n                                                                                      │ >       │ │ 101                        │ │   101\n                                                                                      └──────── │ │ 102                        │ │   102\n                                                                                                │ └─HEAD──                     │ └─HEAD──\n                                                                                                └─────────                     └─────────\n    BLOCK\n\n    expects = []\n    output.each_line.first.scan(/\\S+/) do\n      offset = Regexp.last_match.offset(0)\n      expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join(\"\\n\")\n    end\n\n    suffixes.zip(expects).each do |suffix, block|\n      tmux.send_keys(prefix + suffix, :Enter)\n      tmux.until { assert_block(block, it) }\n      tmux.send_keys :Space\n      tmux.until { assert_block(block.downcase, it) }\n\n      teardown\n      setup\n    end\n  end\n\n  def test_change_header_and_label_at_once\n    tmux.send_keys %(seq 10 | #{FZF} --border sharp --header-border sharp --header-label-pos 3 --bind 'focus:change-header(header)+change-header-label(label)'), :Enter\n    block = <<~BLOCK\n      │ ┌─label──\n      │ │ header\n      │ └────────\n      │   10/10 ─\n      │ >\n      └──────────\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_label_truncation\n    command = <<~CMD\n      seq 10 | #{FZF} --style full --border --header-lines=1 --preview ':' \\\\\n        --border-label \"#{'b' * 1000}\" \\\\\n        --preview-label \"#{'p' * 1000}\" \\\\\n        --header-label \"#{'h' * 1000}\" \\\\\n        --header-label \"#{'h' * 1000}\" \\\\\n        --input-label \"#{'i' * 1000}\" \\\\\n        --list-label \"#{'l' * 1000}\"\n    CMD\n    writelines(command.lines.map(&:chomp))\n    tmux.send_keys(\"sh #{tempname}\", :Enter)\n    tmux.until do |lines|\n      text = lines.join\n      assert_includes text, 'b··'\n      assert_includes text, 'l··p'\n      assert_includes text, 'p··'\n      assert_includes text, 'h··'\n      assert_includes text, 'i··'\n    end\n  end\n\n  def test_separator_no_ellipsis\n    tmux.send_keys %(seq 10 | #{FZF} --separator \"$(seq 1000 | tr '\\\\n' ' ')\"), :Enter\n    tmux.until do |lines|\n      assert_equal 10, lines.match_count\n      refute_includes lines.join, '··'\n    end\n  end\n\n  def test_header_border_no_pointer_and_marker\n    tmux.send_keys %(seq 10 | #{FZF} --header-lines 1 --header-border sharp --no-list-border --pointer '' --marker ''), :Enter\n    block = <<~BLOCK\n      ┌──────\n      │ 1\n      └──────\n        9/9 ─\n      >\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_gutter_default\n    tmux.send_keys %(seq 10 | fzf), :Enter\n    block = <<~BLOCK\n      ▌ 3\n      ▌ 2\n      > 1\n        10/10\n      >\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_gutter_default_no_unicode\n    tmux.send_keys %(seq 10 | fzf --no-unicode), :Enter\n    block = <<~BLOCK\n        3\n        2\n      > 1\n        10/10\n      >\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  def test_gutter_custom\n    tmux.send_keys %(seq 10 | fzf --gutter x), :Enter\n    block = <<~BLOCK\n      x 3\n      x 2\n      > 1\n        10/10\n      >\n    BLOCK\n    tmux.until { assert_block(block, it) }\n  end\n\n  # https://github.com/junegunn/fzf/issues/4537\n  def test_no_scrollbar_preview_toggle\n    x = 'x' * 300\n    y = 'y' * 300\n    tmux.send_keys %(yes #{x} | head -1000 | fzf --bind 'tab:toggle-preview' --border --no-scrollbar --preview 'echo #{y}' --preview-window 'border-left'), :Enter\n\n    # │ ▌ xxxxxxxx·· │ yyyyyyyy│\n    tmux.until do |lines|\n      lines.any? { it.match?(/x·· │ y+│$/) }\n    end\n    tmux.send_keys :Tab\n\n    # │ ▌ xxxxxxxx·· │\n    tmux.until do |lines|\n      lines.none? { it.match?(/x··y│$/) }\n    end\n\n    tmux.send_keys :Tab\n    tmux.until do |lines|\n      lines.any? { it.match?(/x·· │ y+│$/) }\n    end\n  end\n\n  def test_header_and_footer_should_not_be_wider_than_list\n    tmux.send_keys %(WIDE=$(printf 'x%.0s' {1..1000}); (echo $WIDE; echo $WIDE) | fzf --header-lines 1 --style full --header-border bottom --header-lines-border top --ellipsis XX --header \"$WIDE\" --footer \"$WIDE\" --no-footer-border), :Enter\n    tmux.until do |lines|\n      matches = lines.filter_map { |line| line[/x+XX/] }\n      assert_equal 4, matches.length\n      assert_equal 1, matches.uniq.length\n    end\n  end\n\n  def test_combinations\n    skip unless ENV['LONGTEST']\n\n    base = [\n      '--pointer=@',\n      '--exact',\n      '--query=123',\n      '--header=\"$(seq 101 103)\"',\n      '--header-lines=3',\n      '--footer \"$(seq 201 203)\"',\n      '--preview \"echo foobar\"'\n    ]\n    options = [\n      ['--separator==', '--no-separator'],\n      ['--info=default', '--info=inline', '--info=inline-right'],\n      ['--no-input-border', '--input-border'],\n      ['--no-header-border', '--header-border=none', '--header-border'],\n      ['--no-header-lines-border', '--header-lines-border'],\n      ['--no-footer-border', '--footer-border'],\n      ['--no-list-border', '--list-border'],\n      ['--preview-window=right', '--preview-window=up', '--preview-window=down', '--preview-window=left'],\n      ['--header-first', '--no-header-first'],\n      ['--layout=default', '--layout=reverse', '--layout=reverse-list']\n    ]\n    # Combination of all options\n    combinations = options[0].product(*options.drop(1))\n    combinations.each_with_index do |combination, index|\n      opts = base + combination\n      command = %(seq 1001 2000 | #{FZF} #{opts.join(' ')})\n      puts \"# #{index + 1}/#{combinations.length}\\n#{command}\"\n      tmux.send_keys command, :Enter\n      tmux.until do |lines|\n        layout = combination.find { it.start_with?('--layout=') }.split('=').last\n        header_first = combination.include?('--header-first')\n\n        # Input\n        input = lines.index { it.include?('> 123') }\n        assert(input)\n\n        # Info\n        info = lines.index { it.include?('11/997') }\n        assert(info)\n\n        assert(layout == 'reverse' ? input <= info : input >= info)\n\n        # List\n        item1 = lines.index { it.include?('1230') }\n        item2 = lines.index { it.include?('1231') }\n        assert_equal(item1, layout == 'default' ? item2 + 1 : item2 - 1)\n\n        # Preview\n        assert(lines.any? { it.include?('foobar') })\n\n        # Header\n        header1 = lines.index { it.include?('101') }\n        header2 = lines.index { it.include?('102') }\n        assert_equal(header2, header1 + 1)\n        assert((layout == 'reverse') == header_first ? input > header1 : input < header1)\n\n        # Footer\n        footer1 = lines.index { it.include?('201') }\n        footer2 = lines.index { it.include?('202') }\n        assert_equal(footer2, footer1 + 1)\n        assert(layout == 'reverse' ? footer1 > item2 : footer1 < item2)\n\n        # Header lines\n        hline1 = lines.index { it.include?('1001') }\n        hline2 = lines.index { it.include?('1002') }\n        assert_equal(hline1, layout == 'default' ? hline2 + 1 : hline2 - 1)\n        assert(layout == 'reverse' ? hline1 > header1 : hline1 < header1)\n      end\n      tmux.send_keys :Enter\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_preview.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Test cases for preview\nclass TestPreview < TestInteractive\n  def test_preview\n    tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter\n    tmux.until { |lines| assert_includes lines[1], ' {1-1} ' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[1], ' {-} ' }\n    tmux.send_keys '555'\n    tmux.until { |lines| assert_includes lines[1], ' {555-555} ' }\n    tmux.send_keys '?'\n    tmux.until { |lines| refute_includes lines[1], ' {555-555} ' }\n    tmux.send_keys '?'\n    tmux.until { |lines| assert_includes lines[1], ' {555-555} ' }\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert lines[-2]&.start_with?('  28/1000 ') }\n    tmux.send_keys 'foobar'\n    tmux.until { |lines| refute_includes lines[1], ' {55-55} ' }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' {1-1} ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' {-1} ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' {3-1 } ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' {4-1  3} ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' {5-1  3 4} ' }\n  end\n\n  def test_toggle_preview_without_default_preview_command\n    tmux.send_keys %(seq 100 | #{FZF} --bind 'space:preview(echo [{}]),enter:toggle-preview' --preview-window up,border-double), :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      refute_includes lines[1], '║ [1]'\n    end\n\n    # toggle-preview should do nothing\n    tmux.send_keys :Enter\n    tmux.until { |lines| refute_includes lines[1], '║ [1]' }\n    tmux.send_keys :Up\n    tmux.until do |lines|\n      refute_includes lines[1], '║ [1]'\n      refute_includes lines[1], '║ [2]'\n    end\n\n    tmux.send_keys :Up\n    tmux.until do |lines|\n      assert_includes lines, '> 3'\n      refute_includes lines[1], '║ [3]'\n    end\n\n    # One-off preview action\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines[1], '║ [3]' }\n\n    # toggle-preview to hide it\n    tmux.send_keys :Enter\n    tmux.until { |lines| refute_includes lines[1], '║ [3]' }\n\n    # toggle-preview again does nothing\n    tmux.send_keys :Enter, :Up\n    tmux.until do |lines|\n      assert_includes lines, '> 4'\n      refute_includes lines[1], '║ [4]'\n    end\n  end\n\n  def test_show_and_hide_preview\n    tmux.send_keys %(seq 100 | #{FZF} --preview-window hidden,border-bold --preview 'echo [{}]' --bind 'a:show-preview,b:hide-preview'), :Enter\n\n    # Hidden by default\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      refute_includes lines[1], '┃ [1]'\n    end\n\n    # Show\n    tmux.send_keys :a\n    tmux.until { |lines| assert_includes lines[1], '┃ [1]' }\n\n    # Already shown\n    tmux.send_keys :a\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[1], '┃ [2]' }\n\n    # Hide\n    tmux.send_keys :b\n    tmux.send_keys :Up\n    tmux.until do |lines|\n      assert_includes lines, '> 3'\n      refute_includes lines[1], '┃ [3]'\n    end\n\n    # Already hidden\n    tmux.send_keys :b\n    tmux.send_keys :Up\n    tmux.until do |lines|\n      assert_includes lines, '> 4'\n      refute_includes lines[1], '┃ [4]'\n    end\n\n    # Show it again\n    tmux.send_keys :a\n    tmux.until { |lines| assert_includes lines[1], '┃ [4]' }\n  end\n\n  def test_preview_hidden\n    tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys '?'\n    tmux.until { |lines| assert_match(/ {1-1-1-[0-9]+}/, lines[-2]) }\n    tmux.send_keys '555'\n    tmux.until { |lines| assert_match(/ {555-555-1-[0-9]+}/, lines[-2]) }\n    tmux.send_keys '?'\n    tmux.until { |lines| assert_equal '> 555', lines[-1] }\n  end\n\n  def test_preview_size_0\n    tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter\n    tmux.until do |lines|\n      assert_equal 100, lines.match_count\n      assert_equal '  100/100', lines[1]\n      assert_equal '> 1', lines[2]\n    end\n    wait do\n      assert_path_exists tempname\n      assert_equal %w[1], File.readlines(tempname, chomp: true)\n    end\n    tmux.send_keys :Space, :Down, :Down\n    tmux.until { |lines| assert_equal '> 3', lines[4] }\n    wait do\n      assert_path_exists tempname\n      assert_equal %w[1], File.readlines(tempname, chomp: true)\n    end\n    tmux.send_keys :Space, :Down\n    tmux.until { |lines| assert_equal '> 4', lines[5] }\n    wait do\n      assert_path_exists tempname\n      assert_equal %w[1 3 4], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_preview_size_0_hidden\n    tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0,hidden --bind space:toggle-preview), :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys :Down, :Down\n    tmux.until { |lines| assert_includes lines, '> 3' }\n    wait { refute_path_exists tempname }\n    tmux.send_keys :Space\n    wait do\n      assert_path_exists tempname\n      assert_equal %w[3], File.readlines(tempname, chomp: true)\n    end\n    tmux.send_keys :Down\n    wait do\n      assert_equal %w[3 4], File.readlines(tempname, chomp: true)\n    end\n    tmux.send_keys :Space, :Down\n    tmux.until { |lines| assert_includes lines, '> 5' }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert_includes lines, '> 6' }\n    tmux.send_keys :Space\n    wait do\n      assert_equal %w[3 4 6], File.readlines(tempname, chomp: true)\n    end\n  end\n\n  def test_preview_flags\n    tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/  /' |\n        #{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter\n    tmux.until { |lines| assert_includes lines[1], ' {1/1  /1/1  //0/0} ' }\n    tmux.send_keys '123'\n    tmux.until { |lines| assert_includes lines[1], ' {////123//} ' }\n    tmux.send_keys 'C-u', '1'\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' {1/1  /1/1  /1/0/0} ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' {10/10  /1/1  /1/9/0} ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' {10/10  /1 10/1   10  /1/9/0 9} ' }\n    tmux.send_keys '2'\n    tmux.until { |lines| assert_includes lines[1], ' {//1 10/1   10  /12//0 9} ' }\n    tmux.send_keys '3'\n    tmux.until { |lines| assert_includes lines[1], ' {//1 10/1   10  /123//0 9} ' }\n  end\n\n  def test_preview_asterisk\n    tmux.send_keys %(seq 5 | #{FZF} --multi --preview 'echo [{}/{+}/{*}/{*n}]' --preview-window '+{1}'), :Enter\n    tmux.until { |lines| assert_equal 5, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' [1/1/1 2 3 4 5/0 1 2 3 4] ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' [2/1/1 2 3 4 5/0 1 2 3 4] ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' [3/1 2/1 2 3 4 5/0 1 2 3 4] ' }\n    tmux.send_keys '5'\n    tmux.until { |lines| assert_includes lines[1], ' [5/1 2/5/4] ' }\n    tmux.send_keys '5'\n    tmux.until { |lines| assert_includes lines[1], ' [/1 2//] ' }\n  end\n\n  def test_preview_file\n    tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter\n    tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }\n    tmux.send_keys :BTab\n    tmux.until { |lines| assert_includes lines[1], ' foo barbar foobarfoo0101 ' }\n  end\n\n  def test_preview_q_no_match\n    tmux.send_keys %(: | #{FZF} --preview 'echo foo {q} foo'), :Enter\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], ' foo  foo' }\n    tmux.send_keys 'bar'\n    tmux.until { |lines| assert_includes lines[1], ' foo bar foo' }\n    tmux.send_keys 'C-u'\n    tmux.until { |lines| assert_includes lines[1], ' foo  foo' }\n  end\n\n  def test_preview_q_no_match_with_initial_query\n    tmux.send_keys %(: | #{FZF} --preview 'echo 1. /{q}/{q:1}/; echo 2. /{q:..}/{q:2}/{q:-1}/; echo 3. /{q:s-2}/{q:-2}/{q:x}/' --query 'foo bar'), :Enter\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], '1. /foo bar/foo/' }\n    tmux.until { |lines| assert_includes lines[2], '2. /foo bar/bar/bar/' }\n    tmux.until { |lines| assert_includes lines[3], '3. /foo /foo/{q:x}/' }\n  end\n\n  def test_preview_update_on_select\n    tmux.send_keys %(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),\n                   :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert(lines.any? { |line| line.include?(' 1 2 3 4 5 ') }) }\n    tmux.send_keys 'a'\n    tmux.until { |lines| lines.each { |line| refute_includes line, ' 1 2 3 4 5 ' } }\n  end\n\n  def test_preview_correct_tab_width_after_ansi_reset_code\n    writelines([\"\\x1b[31m+\\x1b[m\\t\\x1b[32mgreen\"])\n    tmux.send_keys \"#{FZF} --preview 'cat #{tempname}'\", :Enter\n    tmux.until { |lines| assert_includes lines[1], ' +       green ' }\n  end\n\n  def test_preview_bindings_with_default_preview\n    tmux.send_keys \"seq 10 | #{FZF} --preview 'echo [{}]' --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'\", :Enter\n    tmux.until { |lines| lines.match_count == 10 }\n    tmux.until { |lines| assert_includes lines[1], '[1]' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines[1], '[11]' }\n    tmux.send_keys 'c'\n    tmux.until { |lines| assert_includes lines[1], '[1]' }\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_includes lines[1], '[111]' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[1], '[2]' }\n  end\n\n  def test_preview_bindings_without_default_preview\n    tmux.send_keys \"seq 10 | #{FZF} --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'\", :Enter\n    tmux.until { |lines| lines.match_count == 10 }\n    tmux.until { |lines| refute_includes lines[1], '1' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines[1], '[11]' }\n    tmux.send_keys 'c' # does nothing\n    tmux.until { |lines| assert_includes lines[1], '[11]' }\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_includes lines[1], '[111]' }\n    tmux.send_keys 9\n    tmux.until { |lines| lines.match_count == 1 }\n    tmux.until { |lines| refute_includes lines[1], '2' }\n    tmux.until { |lines| assert_includes lines[1], '[111]' }\n  end\n\n  def test_preview_scroll_begin_constant\n    tmux.send_keys \"echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+123\", :Enter\n    tmux.until { |lines| assert_match %r{1/1}, lines[-2] }\n    tmux.until { |lines| assert_match %r{123.*123/1000}, lines[1] }\n  end\n\n  def test_preview_scroll_begin_expr\n    tmux.send_keys \"echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{3}\", :Enter\n    tmux.until { |lines| assert_match %r{1/1}, lines[-2] }\n    tmux.until { |lines| assert_match %r{321.*321/1000}, lines[1] }\n  end\n\n  def test_preview_scroll_begin_and_offset\n    ['echo foo 123 321', 'echo foo :123: 321'].each do |input|\n      tmux.send_keys \"#{input} | #{FZF} --preview 'seq 1000' --preview-window left:+{2}-2\", :Enter\n      tmux.until { |lines| assert_match %r{1/1}, lines[-2] }\n      tmux.until { |lines| assert_match %r{121.*121/1000}, lines[1] }\n      tmux.send_keys 'C-c'\n    end\n  end\n\n  def test_preview_clear_screen\n    tmux.send_keys %{seq 100 | #{FZF} --preview 'for i in $(seq 300); do (( i % 200 == 0 )) && printf \"\\\\033[2J\"; echo \"[$i]\"; sleep 0.001; done'}, :Enter\n    tmux.until { |lines| lines.match_count == 100 }\n    tmux.until { |lines| lines[1]&.include?('[200]') }\n  end\n\n  def test_preview_window_follow\n    file = Tempfile.new('fzf-follow')\n    file.sync = true\n\n    tmux.send_keys %(seq 100 | #{FZF} --preview 'echo start; tail -f \"#{file.path}\"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~4'), :Enter\n    tmux.until { |lines| lines.match_count == 100 }\n\n    # Write to the temporary file, and check if the preview window is showing\n    # the last line of the file\n    tmux.until { |lines| assert_includes lines[1], 'start' }\n    3.times { file.puts _1 } # header lines\n    1000.times { file.puts _1 }\n    tmux.until { |lines| assert_includes lines[1], '/1004' }\n    tmux.until { |lines| assert_includes lines[-2], '999' }\n\n    # Scroll the preview window and fzf should stop following the file content\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[-2], '998' }\n    file.puts 'foo', 'bar'\n    tmux.until do |lines|\n      assert_includes lines[1], '/1006'\n      assert_includes lines[-2], '998'\n    end\n\n    # Scroll back to the bottom and fzf should start following the file again\n    %w[999 foo bar].each do |item|\n      wait do\n        tmux.send_keys :Down\n        tmux.until { |lines| assert_includes lines[-2], item }\n      end\n    end\n    file.puts 'baz'\n    tmux.until do |lines|\n      assert_includes lines[1], '/1007'\n      assert_includes lines[-2], 'baz'\n    end\n\n    # Scroll upwards to stop following\n    tmux.send_keys :Up\n    wait { assert_includes lines[-2], 'bar' }\n    file.puts 'aaa'\n    tmux.until do |lines|\n      assert_includes lines[1], '/1008'\n      assert_includes lines[-2], 'bar'\n    end\n\n    # Manually enable following\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines[-2], 'aaa' }\n    file.puts 'bbb'\n    tmux.until do |lines|\n      assert_includes lines[1], '/1009'\n      assert_includes lines[-2], 'bbb'\n    end\n\n    # Disable following\n    tmux.send_keys :Space\n    file.puts 'ccc', 'ddd'\n    tmux.until do |lines|\n      assert_includes lines[1], '/1011'\n      assert_includes lines[-2], 'bbb'\n    end\n  rescue StandardError\n    file.close\n    file.unlink\n  end\n\n  def test_toggle_preview_wrap\n    tmux.send_keys \"#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo wrapped; echo 2nd line' --bind ctrl-w:toggle-preview-wrap\", :Enter\n    2.times do\n      tmux.until { |lines| assert_includes lines[2], '2nd line' }\n      tmux.send_keys 'C-w'\n      tmux.until do |lines|\n        assert_includes lines[2], 'wrapped'\n        assert_includes lines[3], '2nd line'\n      end\n      tmux.send_keys 'C-w'\n    end\n  end\n\n  def test_preview_follow_wrap\n    tmux.send_keys \"seq 1 | #{FZF} --preview 'seq 1000' --preview-window right,2,follow,wrap\", :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.until do |lines|\n      idx = lines.rindex { it.include?('│ 10 │') }\n      assert_includes lines[idx + 1], '│ ↳  │'\n      assert_includes lines[idx + 2], '│ ↳  │'\n    end\n  end\n\n  def test_preview_follow_wrap_long_line\n    tmux.send_keys %(seq 1 | #{FZF} --preview \"seq 2; yes yes | head -10000 | tr '\\n' ' '\" --preview-window follow,wrap --bind up:preview-up,down:preview-down), :Enter\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert lines.any_include?('3/3 │')\n    end\n    tmux.send_keys :Up\n    tmux.until { |lines| assert lines.any_include?('2/3 │') }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert lines.any_include?('1/3 │') }\n    tmux.send_keys :Down\n    tmux.until { |lines| assert lines.any_include?('2/3 │') }\n  end\n\n  def test_close\n    tmux.send_keys \"seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.until { |lines| assert_includes lines[1], 'foo' }\n    tmux.send_keys 'C-c'\n    tmux.until { |lines| refute_includes lines[1], 'foo' }\n    tmux.send_keys '10'\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n    tmux.send_keys 'C-c'\n    tmux.send_keys 'C-l', 'closed'\n    tmux.until { |lines| assert_includes lines[0], 'closed' }\n  end\n\n  def test_preview_header\n    tmux.send_keys \"seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }\n    tmux.until do |lines|\n      assert_includes lines[1], '4/1000'\n      assert_equal(%w[1 2 3 4 5], top5[lines])\n    end\n    tmux.send_keys '55'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_equal(%w[1 2 3 55 56], top5[lines])\n    end\n    tmux.send_keys 'C-J'\n    tmux.until do |lines|\n      assert_equal(%w[1 2 3 58 59], top5[lines])\n    end\n    tmux.send_keys :BSpace\n    tmux.until do |lines|\n      assert_equal 19, lines.match_count\n      assert_equal(%w[1 2 3 5 6], top5[lines])\n    end\n    tmux.send_keys 'C-K'\n    tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }\n  end\n\n  def test_change_preview_window\n    tmux.send_keys \"seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --bind '\" \\\n                   'a:change-preview(echo __{}__),' \\\n                   'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \\\n                   'c:change-preview(),d:change-preview-window(hidden),' \\\n                   \"e:preview(printf ::%${FZF_PREVIEW_COLUMNS}s{})+change-preview-window(up),f:change-preview-window(up,wrap)'\", :Enter\n    tmux.until { |lines| assert_equal 1000, lines.match_count }\n    tmux.until { |lines| assert_includes lines[0], '[[1]]' }\n\n    # change-preview action permanently changes the preview command set by --preview\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines[0], '__1__' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines[0], '__2__' }\n\n    # When multiple change-preview-window actions are bound to a single key,\n    # the last one wins and the updated options are immediately applied to the new preview\n    tmux.send_keys 'b'\n    tmux.until { |lines| assert_equal '==2==', lines[0] }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_equal '==3==', lines[0] }\n\n    # change-preview with an empty preview command closes the preview window\n    tmux.send_keys 'c'\n    tmux.until { |lines| refute_includes lines[0], '==' }\n\n    # change-preview again to re-open the preview window\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_equal '__3__', lines[0] }\n\n    # Hide the preview window with hidden flag\n    tmux.send_keys 'd'\n    tmux.until { |lines| refute_includes lines[0], '__3__' }\n\n    # One-off preview\n    tmux.send_keys 'e'\n    tmux.until do |lines|\n      assert_equal '::', lines[0]\n      refute_includes lines[1], '3'\n    end\n\n    # Wrapped\n    tmux.send_keys 'f'\n    tmux.until do |lines|\n      assert_equal '::', lines[0]\n      assert_equal '↳   3', lines[1]\n    end\n  end\n\n  def test_change_preview_window_should_not_reset_change_preview\n    tmux.send_keys \"#{FZF} --preview-window up,border-none --bind 'start:change-preview(echo hello)' --bind 'enter:change-preview-window(border-left)'\", :Enter\n    tmux.until { |lines| assert_includes lines, 'hello' }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_includes lines, '│ hello' }\n  end\n\n  def test_change_preview_window_rotate\n    tmux.send_keys \"seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '\" \\\n                   \"a:change-preview-window(right|down|up|hidden|)'\", :Enter\n    tmux.until { |lines| assert(lines.any? { _1.include?('100/100') }) }\n    3.times do\n      tmux.until { |lines| lines[0].start_with?('hello') }\n      tmux.send_keys 'a'\n      tmux.until { |lines| lines[0].end_with?('hello') }\n      tmux.send_keys 'a'\n      tmux.until { |lines| lines[-1].start_with?('hello') }\n      tmux.send_keys 'a'\n      tmux.until { |lines| assert_equal 'hello', lines[0] }\n      tmux.send_keys 'a'\n      tmux.until { |lines| refute_includes lines[0], 'hello' }\n      tmux.send_keys 'a'\n    end\n  end\n\n  def test_change_preview_window_rotate_hidden\n    tmux.send_keys \"seq 100 | #{FZF} --preview-window hidden --preview 'echo =={}==' --bind '\" \\\n                   \"a:change-preview-window(nohidden||down,1|)'\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.until { |lines| refute_includes lines[1], '==1==' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines[1], '==1==' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| refute_includes lines[1], '==1==' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines[-2], '==1==' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| refute_includes lines[-2], '==1==' }\n    tmux.send_keys 'a'\n    tmux.until { |lines| assert_includes lines[1], '==1==' }\n  end\n\n  def test_change_preview_window_rotate_hidden_down\n    tmux.send_keys \"seq 100 | #{FZF} --bind '?:change-preview-window:up||down|' --preview 'echo =={}==' --preview-window hidden,down,1\", :Enter\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.until { |lines| refute_includes lines[1], '==1==' }\n    tmux.send_keys '?'\n    tmux.until { |lines| assert_includes lines[1], '==1==' }\n    tmux.send_keys '?'\n    tmux.until { |lines| refute_includes lines[1], '==1==' }\n    tmux.send_keys '?'\n    tmux.until { |lines| assert_includes lines[-2], '==1==' }\n    tmux.send_keys '?'\n    tmux.until { |lines| refute_includes lines[-2], '==1==' }\n    tmux.send_keys '?'\n    tmux.until { |lines| assert_includes lines[1], '==1==' }\n  end\n\n  def test_toggle_alternative_preview_window\n    tmux.send_keys \"seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'\", :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.until { |lines| refute_includes lines, '/1/1/' }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert_includes lines, '/1/1/' }\n  end\n\n  def test_alternative_preview_window_opts\n    tmux.send_keys \"seq 10 | #{FZF} --preview-border rounded --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'\", :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    tmux.until do |lines|\n      assert_equal ['╭────╮', '│ 10 │', '│ ↳  │', '│ 10 │', '│ ↳  │'], lines.take(5).map(&:strip)\n    end\n  end\n\n  def test_preview_window_width_exception\n    tmux.send_keys \"seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'\", :Enter\n    tmux.until do |lines|\n      assert lines[1]&.end_with?(' 1/1000││')\n    end\n  end\n\n  def test_preview_window_hidden_on_focus\n    tmux.send_keys \"seq 3 | #{FZF} --preview 'echo {}' --bind focus:hide-preview\", :Enter\n    tmux.until { |lines| assert_includes lines, '> 1' }\n    tmux.send_keys :Up\n    tmux.until { |lines| assert_includes lines, '> 2' }\n  end\n\n  def test_preview_query_should_not_be_affected_by_search\n    tmux.send_keys \"seq 1 | #{FZF} --bind 'change:transform-search(echo {q:1})' --preview 'echo [{q}/{}]'\", :Enter\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys '1'\n    tmux.until { |lines| assert lines.any_include?('[1/1]') }\n    tmux.send_keys :Space\n    tmux.until { |lines| assert lines.any_include?('[1 /1]') }\n    tmux.send_keys '2'\n    tmux.until do |lines|\n      assert lines.any_include?('[1 2/1]')\n      assert_equal 1, lines.match_count\n    end\n  end\n\n  def test_preview_wrap_sign_between_ansi_fragments\n    tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e \"\\\\x1b[33m1234567890 \\\\x1b[mhello\"; echo -e \"\\\\x1b[33m1234567890 \\\\x1b[mhello\"' --preview-window 10,wrap-word), :Enter\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_equal(2, lines.count { |line| line.include?('│ 1234567890 │') })\n      assert_equal(2, lines.count { |line| line.include?('│ ↳ hello    │') })\n    end\n  end\n\n  def test_preview_wrap_sign_between_ansi_fragments_overflow\n    tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e \"\\\\x1b[33m123 \\\\x1b[mhi\"; echo -e \"\\\\x1b[33m123 \\\\x1b[mhi\"' --preview-window 2,wrap-word,noinfo), :Enter\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_equal(2, lines.count { |line| line.include?('│ 12 │') })\n      assert_equal(0, lines.count { |line| line.include?('│ h') })\n    end\n  end\n\n  def test_preview_wrap_sign_between_ansi_fragments_overflow2\n    tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e \"\\\\x1b[33m123 \\\\x1b[mhi\"; echo -e \"\\\\x1b[33m123 \\\\x1b[mhi\"' --preview-window 1,wrap-word,noinfo), :Enter\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_equal(2, lines.count { |line| line.include?('│ 1 │') })\n      assert_equal(0, lines.count { |line| line.include?('│ h') })\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_raw.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Testing raw mode\nclass TestRaw < TestInteractive\n  def test_raw_mode\n    tmux.send_keys %(seq 1000 | #{FZF} --raw --bind ctrl-x:toggle-raw,a:enable-raw,b:disable-raw --gutter '▌' --multi --bind 'space:transform-prompt:echo \"[[$FZF_RAW]] \"'), :Enter\n    tmux.until { assert_equal 1000, it.match_count }\n\n    tmux.send_keys 1\n    tmux.until { assert_equal 272, it.match_count }\n\n    tmux.send_keys :Up\n    tmux.until { assert_includes it, '> 2' }\n\n    tmux.send_keys 'C-p'\n    tmux.until do\n      assert_includes it, '> 10'\n      assert_includes it, '▖ 9'\n    end\n\n    tmux.send_keys 'C-x'\n    tmux.until do\n      assert_includes it, '> 10'\n      assert_includes it, '▌ 1'\n    end\n\n    tmux.send_keys :Up, 'C-x'\n    tmux.until do\n      assert_includes it, '> 11'\n      assert_includes it, '▖ 10'\n    end\n\n    tmux.send_keys 1\n    tmux.until { assert_equal 28, it.match_count }\n\n    tmux.send_keys 'C-p'\n    tmux.until do\n      assert_includes it, '> 101'\n      assert_includes it, '▖ 100'\n    end\n\n    tmux.send_keys 'C-n'\n    tmux.until do\n      assert_includes it, '> 11'\n      assert_includes it, '▖ 10'\n    end\n\n    tmux.send_keys :Tab, :Tab, :Tab\n    tmux.until { assert_equal 3, it.select_count }\n\n    tmux.send_keys 'C-x'\n    tmux.until do\n      assert_equal 1, it.select_count\n      assert_includes it, '▌ 110'\n      assert_includes it, '>>11'\n    end\n\n    tmux.send_keys 'a'\n    tmux.until do\n      assert_equal 1, it.select_count\n      assert_includes it, '>>11'\n      assert_includes it, '▖ 10'\n    end\n\n    tmux.send_keys :Down, :Space\n    tmux.until { assert_includes it, '[[0]] 11' }\n\n    tmux.send_keys :Up, :Space\n    tmux.until { assert_includes it, '[[1]] 11' }\n\n    tmux.send_keys 'b'\n    tmux.until do\n      assert_equal 1, it.select_count\n      assert_includes it, '▌ 110'\n      assert_includes it, '>>11'\n    end\n\n    tmux.send_keys :Space\n    tmux.until { assert_includes it, '[[]] 11' }\n\n    tmux.send_keys 'C-u', '5'\n    tmux.until { assert_includes it, '> 5' }\n\n    tmux.send_keys 'C-x', 'C-p', 'C-p'\n    tmux.until do\n      assert_includes it, '> 25'\n      assert_includes it, '▖ 24'\n    end\n\n    tmux.send_keys 'C-x'\n    tmux.until do\n      assert_includes it, '> 25'\n      assert_includes it, '▌ 15'\n    end\n\n    # 35 is the closest match in raw mode\n    tmux.send_keys 'C-x', :Up, :Up, :Up, :Up, :Up, :Up, 'C-x'\n    tmux.until do\n      assert_includes it, '> 35'\n      assert_includes it, '▌ 25'\n    end\n  end\n\n  def test_raw_best\n    tmux.send_keys %(seq 1000 | #{FZF} --raw --bind space:best), :Enter\n    tmux.send_keys 999\n    tmux.until { assert_includes it, '> 1' }\n    tmux.send_keys :Space\n    tmux.until { assert_includes it, '> 999' }\n  end\nend\n"
  },
  {
    "path": "test/test_server.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Test cases for API server\nclass TestServer < TestInteractive\n  def test_listen\n    { '--listen 6266' => -> { URI('http://localhost:6266') },\n      \"--listen --sync --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'\" =>\n        -> { URI(\"http://localhost:#{File.read('/tmp/fzf-port').chomp}\") } }.each do |opts, fn|\n      tmux.send_keys \"seq 10 | fzf #{opts}\", :Enter\n      tmux.until { |lines| assert_equal 10, lines.match_count }\n      state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)\n      assert_equal 10, state[:totalCount]\n      assert_equal 10, state[:matchCount]\n      assert_empty state[:query]\n      assert_equal({ index: 0, text: '1' }, state[:current])\n\n      Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')\n      tmux.until { |lines| assert_equal 100, lines.item_count }\n      tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }\n      state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)\n      assert_equal 100, state[:totalCount]\n      assert_equal 0, state[:matchCount]\n      assert_equal 'yo', state[:query]\n      assert_nil state[:current]\n\n      teardown\n      setup\n    end\n  end\n\n  def test_listen_with_api_key\n    uri = URI('http://localhost:6266')\n    tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter\n    tmux.until { |lines| assert_equal 10, lines.match_count }\n    # Incorrect API Key\n    [nil, { 'x-api-key' => '' }, { 'x-api-key' => '124abc' }].each do |headers|\n      res = Net::HTTP.post(uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)\n      assert_equal '401', res.code\n      assert_equal 'Unauthorized', res.message\n      assert_equal \"invalid api key\\n\", res.body\n\n      res = Net::HTTP.get_response(uri, headers)\n      assert_equal '401', res.code\n      assert_equal 'Unauthorized', res.message\n      assert_equal \"invalid api key\\n\", res.body\n    end\n\n    # Valid API Key\n    [{ 'x-api-key' => '123abc' }, { 'X-API-Key' => '123abc' }].each do |headers|\n      res = Net::HTTP.post(uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)\n      assert_equal '200', res.code\n      tmux.until { |lines| assert_equal 100, lines.item_count }\n      tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }\n\n      res = Net::HTTP.get_response(uri, headers)\n      assert_equal '200', res.code\n      assert_equal 'yo', JSON.parse(res.body, symbolize_names: true)[:query]\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_shell_integration.rb",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/common'\n\n# Testing shell integration\nmodule TestShell\n  attr_reader :tmux\n\n  def setup\n    @tmux = Tmux.new(shell)\n    tmux.prepare\n  end\n\n  def teardown\n    @tmux.kill\n  end\n\n  def set_var(name, val)\n    tmux.prepare\n    tmux.send_keys \"export #{name}='#{val}'\", :Enter\n    tmux.prepare\n  end\n\n  def unset_var(name)\n    tmux.prepare\n    tmux.send_keys \"unset #{name}\", :Enter\n    tmux.prepare\n  end\n\n  def trigger\n    '**'\n  end\n\n  def test_ctrl_t\n    set_var('FZF_CTRL_T_COMMAND', 'seq 100')\n\n    tmux.prepare\n    tmux.send_keys 'C-t'\n    tmux.until { |lines| assert_equal 100, lines.match_count }\n    tmux.send_keys :Tab, :Tab, :Tab\n    tmux.until { |lines| assert lines.any_include?(' (3)') }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert lines.any_include?('1 2 3') }\n    tmux.send_keys 'C-c'\n  end\n\n  def test_ctrl_t_unicode\n    writelines(['fzf-unicode 테스트1', 'fzf-unicode 테스트2'])\n    set_var('FZF_CTRL_T_COMMAND', \"cat #{tempname}\")\n\n    tmux.prepare\n    tmux.send_keys 'echo ', 'C-t'\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n    tmux.send_keys 'fzf-unicode'\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n\n    tmux.send_keys '1'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n\n    tmux.send_keys '2'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 2, lines.select_count }\n\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_match(/echo .*fzf-unicode.*1.* .*fzf-unicode.*2/, lines.join) }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal 'fzf-unicode 테스트1 fzf-unicode 테스트2', lines[-1] }\n  end\n\n  def test_alt_c\n    tmux.prepare\n    tmux.send_keys :Escape, :c\n    lines = tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    expected = lines.reverse.find { |l| l.start_with?('> ') }[2..].chomp('/')\n    tmux.send_keys :Enter\n    tmux.prepare\n    tmux.send_keys :pwd, :Enter\n    tmux.until { |lines| assert lines[-1]&.end_with?(expected) }\n  end\n\n  def test_alt_c_command\n    set_var('FZF_ALT_C_COMMAND', 'echo /tmp')\n\n    tmux.prepare\n    tmux.send_keys 'cd /', :Enter\n\n    tmux.prepare\n    tmux.send_keys :Escape, :c\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n\n    tmux.prepare\n    tmux.send_keys :pwd, :Enter\n    tmux.until { |lines| assert_equal '/tmp', lines[-1] }\n  end\n\n  def test_ctrl_r\n    tmux.prepare\n    tmux.send_keys 'echo 1st', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo 2nd', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo 3d', :Enter\n    tmux.prepare\n    3.times do\n      tmux.send_keys 'echo 3rd', :Enter\n      tmux.prepare\n    end\n    tmux.send_keys 'echo 4th', :Enter\n    tmux.prepare\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys 'e3d'\n    # Duplicates removed: 3d (1) + 3rd (1) => 2 matches\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n    tmux.until { |lines| assert lines[-3]&.end_with?(' echo 3d') }\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert lines[-3]&.end_with?(' echo 3rd') }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal 'echo 3rd', lines[-1] }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal '3rd', lines[-1] }\n  end\n\n  def test_ctrl_r_multiline\n    # NOTE: Current bash implementation shows an extra new line if there's\n    # only entry in the history\n    tmux.send_keys ':', :Enter\n    tmux.send_keys 'echo \"foo', :Enter, 'bar\"', :Enter\n    tmux.until { |lines| assert_equal %w[foo bar], lines[-2..] }\n    tmux.prepare\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_equal '>', lines[-1] }\n    tmux.send_keys 'foo bar'\n    tmux.until { |lines| assert_includes lines[-4], '\"foo' } if shell == :bash\n    tmux.until { |lines| assert lines[-3]&.match?(/bar\"␊?/) }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert lines[-1]&.match?(/bar\"␊?/) }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal %w[foo bar], lines[-2..] }\n  end\n\n  def test_ctrl_r_abort\n    skip(\"doesn't restore the original line when search is aborted pre Bash 4\") if shell == :bash && `#{Shell.bash} --version`[/(?<= version )\\d+/].to_i < 4\n    %w[foo ' \"].each do |query|\n      tmux.prepare\n      tmux.send_keys :Enter, query\n      tmux.until { |lines| assert lines[-1]&.start_with?(query) }\n      tmux.send_keys 'C-r'\n      tmux.until { |lines| assert_equal \"> #{query}\", lines[-1] }\n      tmux.send_keys 'C-g'\n      tmux.until { |lines| assert lines[-1]&.start_with?(query) }\n    end\n  end\nend\n\nmodule CompletionTest\n  def test_file_completion\n    FileUtils.mkdir_p('/tmp/fzf-test')\n    FileUtils.mkdir_p('/tmp/fzf test')\n    (1..100).each { |i| FileUtils.touch(\"/tmp/fzf-test/#{i}\") }\n    ['no~such~user', '/tmp/fzf test/foobar'].each do |f|\n      FileUtils.touch(File.expand_path(f))\n    end\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'cat /tmp/fzf-test/10', 'C-t'\n    else\n      tmux.send_keys \"cat /tmp/fzf-test/10#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys ' !d'\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n    tmux.send_keys :Tab, :Tab\n    tmux.until { |lines| assert_equal 2, lines.select_count }\n    tmux.send_keys :Enter\n    tmux.until(true) do |lines|\n      assert_equal 'cat /tmp/fzf-test/10 /tmp/fzf-test/100', lines[-1]\n    end\n\n    # ~USERNAME**<TAB>\n    user = `whoami`.chomp\n    tmux.send_keys 'C-u'\n    if shell == :fish\n      tmux.send_keys \"cat ~#{user}\", 'C-t'\n    else\n      tmux.send_keys \"cat ~#{user}#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys \"/#{user}\"\n    tmux.until { |lines| assert(lines.any? { |l| l.end_with?(\"/#{user}\") }) }\n    tmux.send_keys :Enter\n    tmux.until(true) do |lines|\n      assert_match %r{cat .*/#{user}}, lines[-1]\n    end\n\n    # ~INVALID_USERNAME**<TAB>\n    tmux.send_keys 'C-u'\n    if shell == :fish\n      tmux.send_keys 'cat ~such', 'C-t'\n    else\n      tmux.send_keys \"cat ~such#{trigger}\", :Tab\n    end\n    tmux.until(true) { |lines| assert lines.any_include?('no~such~user') }\n    tmux.send_keys :Enter\n    tmux.until(true) do |lines|\n      if shell == :fish\n        # Fish's string escape quotes filenames with ~ to prevent tilde expansion\n        assert_equal 'cat no\\\\~such\\\\~user', lines[-1]\n      else\n        assert_equal 'cat no~such~user', lines[-1]\n      end\n    end\n\n    # /tmp/fzf\\ test**<TAB>\n    tmux.send_keys 'C-u'\n    if shell == :fish\n      tmux.send_keys 'cat /tmp/fzf\\\\ test/', 'C-t'\n    else\n      tmux.send_keys \"cat /tmp/fzf\\\\ test/#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys 'foobar$'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert lines.any_include?('> /tmp/fzf test/foobar')\n    end\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\\ test/foobar', lines[-1] }\n\n    # Should include hidden files\n    (1..100).each { |i| FileUtils.touch(\"/tmp/fzf-test/.hidden-#{i}\") }\n    tmux.send_keys 'C-u'\n    if shell == :fish\n      tmux.send_keys 'cat /tmp/fzf-test/hidden', 'C-t'\n    else\n      tmux.send_keys \"cat /tmp/fzf-test/hidden#{trigger}\", :Tab\n    end\n    tmux.until(true) do |lines|\n      assert_equal 100, lines.match_count\n      assert lines.any_include?('/tmp/fzf-test/.hidden-')\n    end\n    tmux.send_keys :Enter\n  ensure\n    ['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f|\n      FileUtils.rm_rf(File.expand_path(f))\n    end\n  end\n\n  def test_file_completion_root\n    if shell == :fish\n      tmux.send_keys 'ls /', 'C-t'\n    else\n      tmux.send_keys \"ls /#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys :Enter\n  end\n\n  def test_dir_completion\n    FileUtils.mkdir_p('/tmp/fzf-test-dir')\n    (1..100).each do |idx|\n      FileUtils.mkdir_p(\"/tmp/fzf-test-dir/d#{idx}\")\n    end\n    FileUtils.touch('/tmp/fzf-test-dir/d55/xxx')\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'cd /tmp/fzf-test-dir/', 'C-t'\n    else\n      tmux.send_keys \"cd /tmp/fzf-test-dir/#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    # Tab selects items in C-t's --multi mode, so skip for fish\n    tmux.send_keys :Tab, :Tab unless shell == :fish # Tab does not work here\n    tmux.send_keys '55/$'\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> 55/$'\n      assert_includes lines, '> /tmp/fzf-test-dir/d55/'\n    end\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test-dir/d55/', lines[-1] }\n    # C-t appends a trailing space after the result\n    tmux.send_keys :BSpace if shell == :fish\n    tmux.send_keys :xx\n    tmux.until { |lines| assert_equal 'cd /tmp/fzf-test-dir/d55/xx', lines[-1] }\n\n    # Should not match regular files (bash-only)\n    if instance_of?(TestBash)\n      tmux.send_keys :Tab\n      tmux.until { |lines| assert_equal 'cd /tmp/fzf-test-dir/d55/xx', lines[-1] }\n    end\n\n    # Fail back to plusdirs\n    tmux.send_keys :BSpace, :BSpace, :BSpace\n    tmux.until { |lines| assert_equal 'cd /tmp/fzf-test-dir/d55', lines[-1] }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 'cd /tmp/fzf-test-dir/d55/', lines[-1] }\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-dir')\n  end\n\n  def test_process_completion\n    skip('fish background job format differs') if shell == :fish\n\n    begin\n      tmux.send_keys 'sleep 12345 &', :Enter\n      lines = tmux.until { |lines| assert lines[-1]&.start_with?('[1] ') }\n      pid = lines[-1]&.split&.last\n      tmux.prepare\n      tmux.send_keys 'C-L'\n      tmux.send_keys \"kill #{trigger}\", :Tab\n      tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n      tmux.send_keys 'sleep12345'\n      tmux.until { |lines| assert lines.any_include?('sleep 12345') }\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal \"kill #{pid}\", lines[-1] }\n    ensure\n      if pid\n        begin\n          Process.kill('KILL', pid.to_i)\n        rescue StandardError\n          nil\n        end\n      end\n    end\n  end\n\n  def test_custom_completion\n    skip('fish does not use _fzf_compgen_path; path completion is via ctrl-t') if shell == :fish\n    tmux.send_keys '_fzf_compgen_path() { echo \"$1\"; seq 10; }', :Enter\n    tmux.prepare\n    tmux.send_keys \"ls /tmp/#{trigger}\", :Tab\n    tmux.until { |lines| assert_equal 11, lines.match_count }\n    tmux.send_keys :Tab, :Tab, :Tab\n    tmux.until { |lines| assert_equal 3, lines.select_count }\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'ls /tmp 1 2', lines[-1] }\n  end\n\n  def test_unset_completion\n    skip('fish has native completion for set and unset variables') if shell == :fish\n    tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter\n    tmux.prepare\n\n    # Using tmux\n    tmux.send_keys \"unset FZFFOOBR#{trigger}\", :Tab\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }\n    tmux.send_keys 'C-c'\n\n    # FZF_TMUX=1\n    new_shell\n    tmux.focus\n    tmux.send_keys \"unset FZFFOOBR#{trigger}\", :Tab\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }\n  end\n\n  def test_completion_in_command_sequence\n    if shell == :fish\n      FileUtils.mkdir_p('/tmp/fzf-test-seq')\n      FileUtils.touch('/tmp/fzf-test-seq/fzffoobar')\n      tmux.prepare\n      # Fish uses Shift-Tab for fzf completion (no trigger system)\n      command = 'echo foo; QUX=THUD ls /tmp/fzf-test-seq/fzffoobr'\n      expected = 'echo foo; QUX=THUD ls /tmp/fzf-test-seq/fzffoobar'\n      tmux.send_keys command, :BTab\n      tmux.until { |lines| assert_equal 1, lines.match_count }\n      tmux.send_keys :Enter\n      tmux.until { |lines| assert_equal expected, lines[-1] }\n    else\n      tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter\n      tmux.prepare\n\n      triggers = ['**', '~~', '++', 'ff', '/']\n      triggers.push('&', '[', ';', '`') if instance_of?(TestZsh)\n\n      triggers.each do |trigger|\n        set_var('FZF_COMPLETION_TRIGGER', trigger)\n        command = \"echo foo; QUX=THUD unset FZFFOOBR#{trigger}\"\n        expected = 'echo foo; QUX=THUD unset FZFFOOBAR'\n        tmux.send_keys command.sub(/(;|`)$/, '\\\\\\\\\\1'), :Tab\n        tmux.until { |lines| assert_equal 1, lines.match_count }\n        tmux.send_keys :Enter\n        tmux.until { |lines| assert_equal expected, lines[-1] }\n      end\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-seq') if shell == :fish\n  end\n\n  def test_file_completion_unicode\n    FileUtils.mkdir_p('/tmp/fzf-test')\n    # Shell-agnostic file creation\n    File.write('/tmp/fzf-test/fzf-unicode 테스트1', \"test3\\n\")\n    File.write('/tmp/fzf-test/fzf-unicode 테스트2', \"test4\\n\")\n    tmux.send_keys 'cd /tmp/fzf-test', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'cat fzf-unicode', 'C-t'\n    else\n      tmux.send_keys \"cat fzf-unicode#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n\n    tmux.send_keys '1'\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n\n    tmux.send_keys :BSpace\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n\n    tmux.send_keys '2'\n    tmux.until { |lines| assert_equal 1, lines.select_count }\n    tmux.send_keys :Tab\n    tmux.until { |lines| assert_equal 2, lines.select_count }\n\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_match(/cat .*fzf-unicode.*1.* .*fzf-unicode.*2/, lines[-1]) }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal %w[test3 test4], lines[-2..] }\n  end\n\n  def test_custom_completion_api\n    skip('bash-specific _comprun/declare syntax') if shell == :fish\n\n    begin\n      tmux.send_keys 'eval \"_fzf$(declare -f _comprun)\"', :Enter\n      %w[f g].each do |command|\n        tmux.prepare\n        tmux.send_keys \"#{command} b#{trigger}\", :Tab\n        tmux.until do |lines|\n          assert_equal 2, lines.item_count\n          assert_equal 1, lines.match_count\n          assert lines.any_include?(\"prompt-#{command}\")\n          assert lines.any_include?(\"preview-#{command}-bar\")\n        end\n        tmux.send_keys :Enter\n        tmux.until { |lines| assert_equal \"#{command} #{command}barbar\", lines[-1] }\n        tmux.send_keys 'C-u'\n      end\n    ensure\n      tmux.prepare\n      tmux.send_keys 'unset -f _fzf_comprun', :Enter\n    end\n  end\n\n  def test_ssh_completion\n    skip('fish uses native ssh completion') if shell == :fish\n    (1..5).each { |i| FileUtils.touch(\"/tmp/fzf-test-ssh-#{i}\") }\n\n    tmux.send_keys \"ssh jg@localhost#{trigger}\", :Tab\n    tmux.until do |lines|\n      assert_operator lines.match_count, :>=, 1\n    end\n\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert lines.any_include?('ssh jg@localhost') }\n    tmux.send_keys \" -i /tmp/fzf-test-ssh#{trigger}\", :Tab\n    tmux.until do |lines|\n      assert_operator lines.match_count, :>=, 5\n      assert_equal 0, lines.select_count\n    end\n    tmux.send_keys :Tab, :Tab, :Tab\n    tmux.until do |lines|\n      assert_equal 3, lines.select_count\n    end\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert lines.any_include?('ssh jg@localhost  -i /tmp/fzf-test-ssh-') }\n\n    tmux.send_keys \"localhost#{trigger}\", :Tab\n    tmux.until do |lines|\n      assert_operator lines.match_count, :>=, 1\n    end\n  end\n\n  def test_option_equals_long_option\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-eq-long')\n    FileUtils.touch('/tmp/fzf-test-opt-eq-long/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-eq-long', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command --opt=SECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command --opt=SECURI#{trigger}\", :Tab\n    end\n\n    case shell\n    when :bash\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert_includes lines, '> SECURI'\n      end\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal 'some-command --opt=SECURITY.md', lines[-1] }\n    when :fish\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert lines.any_include?('SECURITY.md')\n      end\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal 'some-command --opt=SECURITY.md', lines[-1] }\n    when :zsh\n      tmux.until do |lines|\n        assert_equal 0, lines.match_count\n        assert_includes lines, '> --opt=SECURI'\n      end\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-eq-long')\n  end\n\n  def test_option_equals_long_option_after_double_dash\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-eq-long-ddash')\n    FileUtils.touch('/tmp/fzf-test-opt-eq-long-ddash/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-eq-long-ddash', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command -- --opt=SECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command -- --opt=SECURI#{trigger}\", :Tab\n    end\n\n    case shell\n    when :bash\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert_includes lines, '> SECURI'\n      end\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal 'some-command -- --opt=SECURITY.md', lines[-1] }\n    when :fish, :zsh\n      tmux.until do |lines|\n        assert_equal 0, lines.match_count\n        assert_includes lines, '> --opt=SECURI'\n      end\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-eq-long-ddash')\n  end\n\n  def test_option_equals_short_option\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-eq-short')\n    FileUtils.touch('/tmp/fzf-test-opt-eq-short/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-eq-short', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command -o=SECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command -o=SECURI#{trigger}\", :Tab\n    end\n\n    case shell\n    when :bash, :fish\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert lines.any_include?('> SECURITY.md')\n      end\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal 'some-command -o=SECURITY.md', lines[-1] }\n    when :zsh\n      tmux.until do |lines|\n        assert_equal 0, lines.match_count\n        assert_includes lines, '> -o=SECURI'\n      end\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-eq-short')\n  end\n\n  def test_option_equals_short_option_after_double_dash\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-eq-short-ddash')\n    FileUtils.touch('/tmp/fzf-test-opt-eq-short-ddash/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-eq-short-ddash', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command -- -o=SECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command -- -o=SECURI#{trigger}\", :Tab\n    end\n\n    case shell\n    when :bash\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert_includes lines, '> SECURITY.md'\n      end\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal 'some-command -- -o=SECURITY.md', lines[-1] }\n    when :fish, :zsh\n      tmux.until do |lines|\n        assert_equal 0, lines.match_count\n        assert_includes lines, '> -o=SECURI'\n      end\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-eq-short-ddash')\n  end\n\n  def test_option_no_equals_long_option\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-no-eq-long')\n    FileUtils.touch('/tmp/fzf-test-opt-no-eq-long/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-no-eq-long', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command --optSECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command --optSECURI#{trigger}\", :Tab\n    end\n\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n      assert_includes lines, '> --optSECURI'\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-no-eq-long')\n  end\n\n  def test_option_no_equals_long_option_after_double_dash\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-no-eq-long-ddash')\n    FileUtils.touch('/tmp/fzf-test-opt-no-eq-long-ddash/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-no-eq-long-ddash', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command -- --optSECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command -- --optSECURI#{trigger}\", :Tab\n    end\n\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n      assert_includes lines, '> --optSECURI'\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-no-eq-long-ddash')\n  end\n\n  def test_option_no_equals_short_option\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-no-eq-short')\n    FileUtils.touch('/tmp/fzf-test-opt-no-eq-short/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-no-eq-short', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command -oSECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command -oSECURI#{trigger}\", :Tab\n    end\n\n    case shell\n    when :bash, :zsh\n      tmux.until do |lines|\n        assert_equal 0, lines.match_count\n        assert_includes lines, '> -oSECURI'\n      end\n    when :fish\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert lines.any_include?('> SECURITY.md')\n      end\n      tmux.send_keys :Enter\n      tmux.until(true) { |lines| assert_equal 'some-command -oSECURITY.md', lines[-1] }\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-no-eq-short')\n  end\n\n  def test_option_no_equals_short_option_after_double_dash\n    FileUtils.mkdir_p('/tmp/fzf-test-opt-no-eq-short-ddash')\n    FileUtils.touch('/tmp/fzf-test-opt-no-eq-short-ddash/SECURITY.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-opt-no-eq-short-ddash', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'some-command -- -oSECURI', 'C-t'\n    else\n      tmux.send_keys \"some-command -- -oSECURI#{trigger}\", :Tab\n    end\n\n    tmux.until do |lines|\n      assert_equal 0, lines.match_count\n      assert_includes lines, '> -oSECURI'\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-opt-no-eq-short-ddash')\n  end\n\n  def test_filename_with_newline\n    FileUtils.mkdir_p('/tmp/fzf-test-newline')\n    FileUtils.touch(\"/tmp/fzf-test-newline/xyz\\nwith\\nnewlines\")\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-newline', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'cat xyz', 'C-t'\n    else\n      tmux.send_keys \"cat xyz#{trigger}\", :Tab\n    end\n\n    case shell\n    when :fish\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert_includes lines, '> xyz'\n      end\n      tmux.send_keys :Enter\n      # fish escapes newlines in filenames\n      tmux.until(true) { |lines| assert_equal 'cat xyz\\\\nwith\\\\nnewlines', lines[-1] }\n    when :bash, :zsh\n      tmux.until do |lines|\n        assert_equal 1, lines.match_count\n        assert_includes lines, '> xyz'\n      end\n      tmux.send_keys :Enter\n      # bash and zsh replace newlines with spaces in filenames\n      tmux.until(true) { |lines| assert_equal 'cat xyz with newlines', lines[-1] }\n    end\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-newline')\n  end\n\n  def test_path_with_special_chars\n    FileUtils.mkdir_p('/tmp/fzf-test-[special]')\n    FileUtils.touch('/tmp/fzf-test-[special]/xyz123')\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'ls /tmp/fzf-test-\\[special\\]/xyz', 'C-t'\n    else\n      tmux.send_keys \"ls /tmp/fzf-test-\\\\[special\\\\]/xyz#{trigger}\", :Tab\n    end\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'ls /tmp/fzf-test-\\\\[special\\\\]/xyz123', lines[-1] }\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-[special]')\n  end\n\n  def test_query_with_dollar_anchor\n    FileUtils.mkdir_p('/tmp/fzf-test-dollar-anchor')\n    FileUtils.touch('/tmp/fzf-test-dollar-anchor/file.txt')\n    FileUtils.touch('/tmp/fzf-test-dollar-anchor/filetxt.md')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-dollar-anchor', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'ls txt$', 'C-t'\n    else\n      tmux.send_keys \"ls txt$#{trigger}\", :Tab\n    end\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> txt$'\n    end\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'ls file.txt', lines[-1] }\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-dollar-anchor')\n  end\n\n  def test_single_flag_completion\n    FileUtils.mkdir_p('/tmp/fzf-test-single-flag')\n    FileUtils.touch('/tmp/fzf-test-single-flag/-testfile.txt')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-single-flag', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'ls -', 'C-t'\n    else\n      tmux.send_keys \"ls -#{trigger}\", :Tab\n    end\n\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> -'\n    end\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'ls -testfile.txt', lines[-1] }\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-single-flag')\n  end\n\n  def test_double_flag_completion\n    FileUtils.mkdir_p('/tmp/fzf-test-double-flag')\n    FileUtils.touch('/tmp/fzf-test-double-flag/--testfile.txt')\n    tmux.prepare\n    tmux.send_keys 'cd /tmp/fzf-test-double-flag', :Enter\n    tmux.prepare\n    if shell == :fish\n      tmux.send_keys 'ls --', 'C-t'\n    else\n      tmux.send_keys \"ls --#{trigger}\", :Tab\n    end\n\n    tmux.until do |lines|\n      assert_equal 1, lines.match_count\n      assert_includes lines, '> --'\n    end\n    tmux.send_keys :Enter\n    tmux.until(true) { |lines| assert_equal 'ls --testfile.txt', lines[-1] }\n  ensure\n    FileUtils.rm_rf('/tmp/fzf-test-double-flag')\n  end\nend\n\nclass TestBash < TestBase\n  include TestShell\n  include CompletionTest\n\n  def shell\n    :bash\n  end\n\n  def new_shell\n    tmux.prepare\n    tmux.send_keys \"FZF_TMUX=1 #{Shell.bash}\", :Enter\n    tmux.prepare\n  end\n\n  def test_dynamic_completion_loader\n    tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'\n    tmux.paste '_completion_loader() { complete -o default fake; }'\n    tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'\n    tmux.send_keys \"fake /tmp/foo#{trigger}\", :Tab\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys 'C-c'\n\n    tmux.prepare\n    tmux.send_keys 'fake /tmp/foo'\n    tmux.send_keys :Tab, 'C-u'\n\n    tmux.prepare\n    tmux.send_keys \"fake /tmp/foo#{trigger}\", :Tab\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n  end\nend\n\nclass TestZsh < TestBase\n  include TestShell\n  include CompletionTest\n\n  def shell\n    :zsh\n  end\n\n  def new_shell\n    tmux.send_keys \"FZF_TMUX=1 #{Shell.zsh}\", :Enter\n    tmux.prepare\n  end\n\n  def test_complete_quoted_command\n    tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter\n    ['unset', '\\unset', \"'unset'\"].each do |command|\n      tmux.prepare\n      tmux.send_keys \"#{command} FZFFOOBR#{trigger}\", :Tab\n      tmux.until { |lines| assert_equal 1, lines.match_count }\n      tmux.send_keys :Enter\n      tmux.until { |lines| assert_equal \"#{command} FZFFOOBAR\", lines[-1] }\n      tmux.send_keys 'C-c'\n    end\n  end\n\n  # Helper function to run test with Perl and again with Awk\n  def self.test_perl_and_awk(name, &block)\n    define_method(:\"test_#{name}\") do\n      instance_eval(&block)\n    end\n\n    define_method(:\"test_#{name}_awk\") do\n      tmux.send_keys \"unset 'commands[perl]'\", :Enter\n      tmux.prepare\n      # Verify perl is actually unset (0 = not found)\n      tmux.send_keys 'echo ${+commands[perl]}', :Enter\n      tmux.until { |lines| assert_equal '0', lines[-1] }\n      tmux.prepare\n      instance_eval(&block)\n    end\n  end\n\n  def prepare_ctrl_r_test\n    tmux.send_keys ':', :Enter\n    tmux.send_keys 'echo match-collision', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo \"line 1', :Enter, '2 line 2\"', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo \"foo', :Enter, 'bar\"', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo \"bar', :Enter, 'foo\"', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo \"trailing_space \"', :Enter\n    tmux.prepare\n    tmux.send_keys 'cat <<EOF | wc -c', :Enter, 'qux thud', :Enter, 'EOF', :Enter\n    tmux.prepare\n    tmux.send_keys 'C-l', 'C-r'\n  end\n\n  test_perl_and_awk 'ctrl_r_accept_or_print_query' do\n    set_var('FZF_CTRL_R_OPTS', '--bind enter:accept-or-print-query')\n    prepare_ctrl_r_test\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys '1 foobar'\n    tmux.until { |lines| assert_equal 0, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until { |lines| assert_equal '1 foobar', lines[-1] }\n  end\n\n  test_perl_and_awk 'ctrl_r_multiline_index_collision' do\n    tmux.send_keys 'setopt sh_glob', :Enter\n    # Leading number in multi-line history content is not confused with index\n    prepare_ctrl_r_test\n    tmux.send_keys \"'line 1\"\n    tmux.until { |lines| assert_equal 1, lines.match_count }\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      assert_equal ['echo \"line 1', '2 line 2\"'], lines[-2..]\n    end\n  end\n\n  test_perl_and_awk 'ctrl_r_multi_selection' do\n    prepare_ctrl_r_test\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| assert_includes lines[-2], '(3)' }\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF', 'echo \"trailing_space \"', 'echo \"bar', 'foo\"'], lines[-6..]\n    end\n  end\n\n  test_perl_and_awk 'ctrl_r_no_multi_selection' do\n    set_var('FZF_CTRL_R_OPTS', '--no-multi')\n    prepare_ctrl_r_test\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys :BTab, :BTab, :BTab\n    tmux.until { |lines| refute_includes lines[-2], '(3)' }\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF'], lines[-3..]\n    end\n  end\n\n  # NOTE: 'Perl/$history' won't see foreign cmds immediately, unlike 'awk/fc'.\n  # Perl passes only because another cmd runs between mocking and triggering C-r\n  # https://github.com/junegunn/fzf/issues/4061\n  # https://zsh.org/mla/users/2024/msg00692.html\n  test_perl_and_awk 'ctrl_r_foreign_commands' do\n    histfile = \"#{tempname}-foreign-hist\"\n    tmux.send_keys \"HISTFILE=#{histfile}\", :Enter\n    tmux.prepare\n    # SHARE_HISTORY picks up foreign commands; marked with * in fc\n    tmux.send_keys 'setopt SHARE_HISTORY', :Enter\n    tmux.prepare\n    tmux.send_keys 'fzf_cmd_local', :Enter\n    tmux.prepare\n    # Mock foreign command (for testing only; don't edit your HISTFILE this way)\n    tmux.send_keys \"echo ': 0:0;fzf_cmd_foreign' >> $HISTFILE\", :Enter\n    tmux.prepare\n    # Verify fc shows foreign command with asterisk\n    tmux.send_keys 'fc -rl -1', :Enter\n    tmux.until { |lines| assert(lines.any? { |l| l.match?(/^\\s*\\d+\\* fzf_cmd_foreign/) }) }\n    tmux.prepare\n    # Test ctrl-r correctly extracts the foreign command\n    tmux.send_keys 'C-r'\n    tmux.until { |lines| assert_operator lines.match_count, :>, 0 }\n    tmux.send_keys '^fzf_cmd_'\n    tmux.until { |lines| assert_equal 2, lines.match_count }\n    tmux.send_keys :BTab, :BTab\n    tmux.until { |lines| assert_includes lines[-2], '(2)' }\n    tmux.send_keys :Enter\n    tmux.until do |lines|\n      assert_equal %w[fzf_cmd_foreign fzf_cmd_local], lines[-2..]\n    end\n  ensure\n    FileUtils.rm_f(histfile)\n  end\nend\n\nclass TestFish < TestBase\n  include TestShell\n  include CompletionTest\n\n  def shell\n    :fish\n  end\n\n  def trigger\n    '++'\n  end\n\n  def new_shell\n    tmux.send_keys 'env FZF_TMUX=1 XDG_CONFIG_HOME=/tmp/fzf-fish fish', :Enter\n    tmux.send_keys 'function fish_prompt; end; clear', :Enter\n    tmux.until { |lines| assert_empty lines }\n  end\n\n  def set_var(name, val)\n    tmux.prepare\n    tmux.send_keys \"set -g #{name} '#{val}'\", :Enter\n    tmux.prepare\n  end\n\n  def test_ctrl_r_multi\n    tmux.send_keys ':', :Enter\n    tmux.send_keys 'echo \"foo', :Enter, 'bar\"', :Enter\n    tmux.prepare\n    tmux.send_keys 'echo \"bar', :Enter, 'foo\"', :Enter\n    tmux.prepare\n    tmux.send_keys 'C-l', 'C-r'\n    offset = -6\n    block = <<~BLOCK\n      echo \"foo\n      bar\"\n      echo \"bar\n      foo\"\n    BLOCK\n    if shell == :fish\n      offset = -4\n      block = <<~FISH\n        echo \"foo␊bar\"\n        echo \"bar␊foo\"\n      FISH\n    end\n    tmux.until do |lines|\n      block.lines.each_with_index do |line, idx|\n        assert_includes lines[idx + offset], line.chomp\n      end\n    end\n    tmux.send_keys :BTab, :BTab\n    tmux.until { |lines| assert_includes lines[-2], '(2)' }\n    tmux.send_keys :Enter\n    block = <<~BLOCK\n      echo \"bar\n      foo\"\n      echo \"foo\n      bar\"\n    BLOCK\n    tmux.until do |lines|\n      assert_equal block.lines.map(&:chomp), lines\n    end\n  end\nend\n"
  },
  {
    "path": "test/vim/fzf.vader",
    "content": "Execute (Setup):\n  let g:dir = fnamemodify(g:vader_file, ':p:h')\n  unlet! g:fzf_layout g:fzf_action g:fzf_history_dir\n  Log 'Test directory: ' . g:dir\n  Save &acd\n\nExecute (fzf#run with dir option):\n  let cwd = getcwd()\n  let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })\n  AssertEqual ['fzf.vader'], result\n  AssertEqual 0, haslocaldir()\n  AssertEqual getcwd(), cwd\n\n  execute 'lcd' fnameescape(cwd)\n  let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))\n  AssertEqual ['fzf.vader'], result\n  AssertEqual 1, haslocaldir()\n  AssertEqual getcwd(), cwd\n\nExecute (fzf#run with Funcref command):\n  let g:ret = []\n  function! g:FzfTest(e)\n    call add(g:ret, a:e)\n  endfunction\n  let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))\n  AssertEqual ['fzf.vader'], result\n  AssertEqual ['fzf.vader'], sort(g:ret)\n\nExecute (fzf#run with string source):\n  let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))\n  AssertEqual ['hi'], result\n\nExecute (fzf#run with list source):\n  let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f e' }))\n  AssertEqual ['hello'], result\n  let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f o' }))\n  AssertEqual ['hello', 'world'], result\n\nExecute (fzf#run with string source):\n  let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))\n  AssertEqual ['hi'], result\n\nExecute (fzf#run with dir option and noautochdir):\n  set noacd\n  let cwd = getcwd()\n  call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})\n  \" No change in working directory\n  AssertEqual cwd, getcwd()\n\n  call fzf#run({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})\n  AssertEqual cwd, getcwd()\n  tabclose\n  AssertEqual cwd, getcwd()\n\nExecute (Incomplete fzf#run with dir option and autochdir):\n  set acd\n  let cwd = getcwd()\n  call fzf#run({'source': [], 'sink': 'e', 'dir': '/tmp', 'options': '-0'})\n  \" No change in working directory even if &acd is set\n  AssertEqual cwd, getcwd()\n\nExecute (FIXME: fzf#run with dir option and autochdir):\n  set acd\n  call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})\n  \" Working directory changed due to &acd\n  AssertEqual '/foobar', expand('%')\n  AssertEqual '/', getcwd()\n\nExecute (fzf#run with dir option and autochdir when final cwd is same as dir):\n  set acd\n  cd /tmp\n  call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/', 'options': '-1'})\n  \" Working directory changed due to &acd\n  AssertEqual '/', getcwd()\n\nExecute (fzf#wrap):\n  AssertThrows fzf#wrap({'foo': 'bar'})\n\n  let opts = fzf#wrap('foobar')\n  Log opts\n  AssertEqual 0.9, opts.window.width\n  Assert opts.options =~ '--expect='\n  Assert !has_key(opts, 'sink')\n  Assert has_key(opts, 'sink*')\n\n  let opts = fzf#wrap('foobar', {}, 0)\n  Log opts\n  AssertEqual 0.9, opts.window.width\n\n  let opts = fzf#wrap('foobar', {}, 1)\n  Log opts\n  Assert !has_key(opts, 'window')\n\n  let opts = fzf#wrap('foobar', {'down': '50%'})\n  Log opts\n  AssertEqual '50%', opts.down\n\n  let opts = fzf#wrap('foobar', {'down': '50%'}, 1)\n  Log opts\n  Assert !has_key(opts, 'down')\n\n  let opts = fzf#wrap('foobar', {'sink': 'e'})\n  Log opts\n  AssertEqual 'e', opts.sink\n  Assert !has_key(opts, 'sink*')\n\n  let opts = fzf#wrap('foobar', {'options': '--reverse'})\n  Log opts\n  Assert opts.options =~ '--expect='\n  Assert opts.options =~ '--reverse'\n\n  let g:fzf_layout = {'window': 'enew'}\n  let opts = fzf#wrap('foobar')\n  Log opts\n  AssertEqual 'enew', opts.window\n\n  let opts = fzf#wrap('foobar', {}, 1)\n  Log opts\n  Assert !has_key(opts, 'window')\n\n  let opts = fzf#wrap('foobar', {'right': '50%'})\n  Log opts\n  Assert !has_key(opts, 'window')\n  AssertEqual '50%', opts.right\n\n  let opts = fzf#wrap('foobar', {'right': '50%'}, 1)\n  Log opts\n  Assert !has_key(opts, 'window')\n  Assert !has_key(opts, 'right')\n\n  let g:fzf_action = {'a': 'tabe'}\n  let opts = fzf#wrap('foobar')\n  Log opts\n  Assert opts.options =~ '--expect=a'\n  Assert !has_key(opts, 'sink')\n  Assert has_key(opts, 'sink*')\n\n  let opts = fzf#wrap('foobar', {'sink': 'e'})\n  Log opts\n  AssertEqual 'e', opts.sink\n  Assert !has_key(opts, 'sink*')\n\n  let g:fzf_history_dir = '/tmp'\n  let opts = fzf#wrap('foobar', {'options': '--color light'})\n  Log opts\n  Assert opts.options =~ \"--history '/tmp/foobar'\"\n  Assert opts.options =~ '--color light'\n\n  let g:fzf_colors = { 'fg': ['fg', 'Error'] }\n  let opts = fzf#wrap({})\n  Assert opts.options =~ '--color=fg:'\n\nExecute (fzf#shellescape with sh):\n  AssertEqual '''''', fzf#shellescape('', 'sh')\n  AssertEqual '''\\''', fzf#shellescape('\\', 'sh')\n  AssertEqual '''\"\"''', fzf#shellescape('\"\"', 'sh')\n  AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')\n  AssertEqual '''\\\\\\\"\\\\\\''', fzf#shellescape('\\\\\\\"\\\\\\', 'sh')\n  AssertEqual '''echo ''\\''''a''\\'''' && echo ''\\''''b''\\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')\n\nExecute (fzf#shellescape with cmd.exe):\n  AssertEqual '^\"^\"', fzf#shellescape('', 'cmd.exe')\n  AssertEqual '^\"\\\\^\"', fzf#shellescape('\\', 'cmd.exe')\n  AssertEqual '^\"\\^\"\\^\"^\"', fzf#shellescape('\"\"', 'cmd.exe')\n  AssertEqual '^\"foobar^>^\"', fzf#shellescape('foobar>', 'cmd.exe')\n  AssertEqual '^\"\\\\\\\\\\\\\\^\"\\\\\\\\\\\\^\"', fzf#shellescape('\\\\\\\"\\\\\\', 'cmd.exe')\n  AssertEqual '^\"echo ''a'' ^&^& echo ''b''^\"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')\n\n  AssertEqual '^\"C:\\Program Files ^(x86^)\\\\^\"', fzf#shellescape('C:\\Program Files (x86)\\', 'cmd.exe')\n  AssertEqual '^\"C:/Program Files ^(x86^)/^\"', fzf#shellescape('C:/Program Files (x86)/', 'cmd.exe')\n  AssertEqual '^\"%%USERPROFILE%%^\"', fzf#shellescape('%USERPROFILE%', 'cmd.exe')\n\nExecute (Cleanup):\n  unlet g:dir\n  Restore\n"
  },
  {
    "path": "typos.toml",
    "content": "# See https://github.com/crate-ci/typos/blob/master/docs/reference.md to configure typos\n[default.extend-words]\nba = \"ba\"\nfo = \"fo\"\nenew = \"enew\"\ntabe = \"tabe\"\nIterm = \"Iterm\"\nser = \"ser\"\n\n[files]\nextend-exclude = [\"README.md\", \"*.s\"]\n"
  },
  {
    "path": "uninstall",
    "content": "#!/usr/bin/env bash\n\nxdg=0\nprefix='~/.fzf'\nprefix_expand=~/.fzf\nfish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish\n\nhelp() {\n  cat << EOF\nusage: $0 [OPTIONS]\n\n    --help               Show this message\n    --xdg                Remove files generated under \\$XDG_CONFIG_HOME/fzf\nEOF\n}\n\nfor opt in \"$@\"; do\n  case $opt in\n    --help)\n      help\n      exit 0\n      ;;\n    --xdg)\n      xdg=1\n      prefix='\"${XDG_CONFIG_HOME:-$HOME/.config}\"/fzf/fzf'\n      prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf\n      ;;\n    *)\n      echo \"unknown option: $opt\"\n      help\n      exit 1\n      ;;\n  esac\ndone\n\ncd \"$(dirname \"${BASH_SOURCE[0]}\")\"\nfzf_base=$(pwd)\n\nask() {\n  while true; do\n    read -p \"$1 ([y]/n) \" -r\n    REPLY=${REPLY:-\"y\"}\n    if [[ $REPLY =~ ^[Yy]$ ]]; then\n      return 0\n    elif [[ $REPLY =~ ^[Nn]$ ]]; then\n      return 1\n    fi\n  done\n}\n\nremove() {\n  echo \"Remove $1\"\n  rm -f \"$1\"\n}\n\nremove_line() {\n  src=$1\n  echo \"Remove from $1:\"\n\n  shift\n  line_no=1\n  match=0\n  while [ -n \"$1\" ]; do\n    line=$(sed -n \"$line_no,\\$p\" \"$src\" | \\grep -m1 -nF \"$1\")\n    if [ $? -ne 0 ]; then\n      shift\n      line_no=1\n      continue\n    fi\n    line_no=$(($(sed 's/:.*//' <<< \"$line\") + line_no - 1))\n    content=$(sed 's/^[0-9]*://' <<< \"$line\")\n    match=1\n    echo \"  - Line #$line_no: $content\"\n    [ \"$content\" = \"$1\" ] || ask \"    - Remove?\"\n    if [ $? -eq 0 ]; then\n      temp=$(mktemp)\n      awk -v n=$line_no 'NR == n {next} {print}' \"$src\" > \"$temp\" &&\n        cat \"$temp\" > \"$src\" && rm -f \"$temp\" || break\n      echo \"      - Removed\"\n    else\n      echo \"      - Skipped\"\n      line_no=$((line_no + 1))\n    fi\n  done\n  [ $match -eq 0 ] && echo \"  - Nothing found\"\n  echo\n}\n\nfor shell in bash zsh; do\n  shell_config=${prefix_expand}.${shell}\n  remove \"${shell_config}\"\n  remove_line ~/.${shell}rc \\\n    \"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}\" \\\n    \"source ${prefix}.${shell}\"\ndone\n\nbind_file=\"${fish_dir}/functions/fish_user_key_bindings.fish\"\nif [ -f \"$bind_file\" ]; then\n  remove_line \"$bind_file\" \"fzf_key_bindings\"\n  remove_line \"$bind_file\" \"fzf_completion_setup\"\n  remove_line \"$bind_file\" \"fzf --fish | source\"\nfi\n\nif [ -d \"${fish_dir}/functions\" ]; then\n  remove \"${fish_dir}/functions/fzf.fish\"\n  remove \"${fish_dir}/functions/fzf_key_bindings.fish\"\n  remove_line \"$bind_file\" \"source \\\"${fzf_base}/shell/completion.fish\\\"\"\n  remove_line \"$bind_file\" \"source \\\"${fzf_base}/shell/key-bindings.fish\\\"\"\n\n  if [ -z \"$(ls -A \"${fish_dir}/functions\")\" ]; then\n    rmdir \"${fish_dir}/functions\"\n  else\n    echo \"Can't delete non-empty directory: \\\"${fish_dir}/functions\\\"\"\n  fi\nfi\n\nconfig_dir=$(dirname \"$prefix_expand\")\nif [[ $xdg == 1 ]] && [[ $config_dir == */fzf ]] && [[ -d $config_dir ]]; then\n  rmdir \"$config_dir\"\nfi\n"
  }
]