Full Code of antonmedv/fx for AI

master 56d0b9be19ee cached
103 files
518.4 KB
168.0k tokens
840 symbols
1 requests
Download .txt
Showing preview only (547K chars total). Download the full file or copy to clipboard to get everything.
Repository: antonmedv/fx
Branch: master
Commit: 56d0b9be19ee
Files: 103
Total size: 518.4 KB

Directory structure:
gitextract_4yli86d0/

├── .gitattributes
├── .github/
│   ├── images/
│   │   ├── autocomplete.tape
│   │   ├── preview-mode.tape
│   │   └── preview.tape
│   ├── stream.mjs
│   └── workflows/
│       ├── brew.yml
│       ├── docker.yml
│       ├── snap.yml
│       └── test.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── RELEASE.md
├── go.mod
├── go.sum
├── help.go
├── internal/
│   ├── complete/
│   │   ├── complete.bash
│   │   ├── complete.fish
│   │   ├── complete.go
│   │   ├── complete.zsh
│   │   ├── complete_test.go
│   │   ├── prelude.js
│   │   └── utils.go
│   ├── engine/
│   │   ├── engine.go
│   │   ├── engine_test.go
│   │   ├── format_err.go
│   │   ├── fxrc.go
│   │   ├── quote.go
│   │   ├── quote_test.go
│   │   ├── slurp.go
│   │   ├── stdlib.js
│   │   ├── stdlib_test.go
│   │   ├── stringify.go
│   │   ├── transpile.go
│   │   ├── transpile_test.go
│   │   ├── utils.go
│   │   └── vm.go
│   ├── fuzzy/
│   │   ├── algo.go
│   │   ├── chars.go
│   │   ├── chars_test.go
│   │   ├── find.go
│   │   ├── fuzzy_test.go
│   │   ├── normalize.go
│   │   └── utils.go
│   ├── ident/
│   │   └── ident.go
│   ├── jsonpath/
│   │   ├── path.go
│   │   ├── path_test.go
│   │   ├── ref.go
│   │   └── ref_test.go
│   ├── jsonx/
│   │   ├── delete.go
│   │   ├── delete_test.go
│   │   ├── format_err.go
│   │   ├── json.go
│   │   ├── jsonx_test.go
│   │   ├── line.go
│   │   ├── node.go
│   │   ├── node_test.go
│   │   ├── string.go
│   │   ├── to_value.go
│   │   └── wrap.go
│   ├── pretty/
│   │   ├── inlineable.go
│   │   ├── inlineable_test.go
│   │   ├── pretty_print.go
│   │   └── pretty_print_test.go
│   ├── shlex/
│   │   ├── shlex.go
│   │   └── shlex_test.go
│   ├── theme/
│   │   └── theme.go
│   ├── toml/
│   │   ├── toml.go
│   │   └── toml_test.go
│   └── utils/
│       ├── image.go
│       ├── life.go
│       ├── utils.go
│       └── utils_test.go
├── keymap.go
├── main.go
├── main_test.go
├── npm/
│   ├── README.md
│   ├── index.js
│   ├── package.json
│   └── test.js
├── preview.go
├── scripts/
│   └── build.mjs
├── search.go
├── search_test.go
├── snap/
│   └── snapcraft.yaml
├── testdata/
│   ├── TestCollapseRecursive.golden
│   ├── TestCollapseRecursiveWithSizes.golden
│   ├── TestGotoLine.golden
│   ├── TestGotoLineCollapsed.golden
│   ├── TestGotoLineInputGreaterThanTotalLines.golden
│   ├── TestGotoLineInputInvalid.golden
│   ├── TestGotoLineInputLessThanOne.golden
│   ├── TestGotoLineKeepsHistory.golden
│   ├── TestNavigation.golden
│   ├── TestOutput.golden
│   ├── blog.json
│   └── example.json
├── utils.go
├── version.go
├── view.go
├── vim.go
└── vim_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
*.golden -text


================================================
FILE: .github/images/autocomplete.tape
================================================
Output autocomplete.gif
Output autocomplete.mp4

Set Shell zsh
Set FontSize 32
Set Width 1800
Set Height 400
Set TypingSpeed 200ms

Hide
Type ". ~/.zshrc && clear"
Enter
Show

Type "fx example.json "
Sleep 1s

Type "."
Tab@500ms 2
Sleep 1s
Tab
Sleep 500ms
Tab
Sleep 500ms
Tab
Sleep 500ms
Tab
Sleep 500ms
Tab
Sleep 500ms
Tab
Sleep 1s

Type "."
Tab@500ms 2
Sleep 500ms
Tab
Sleep 2s

Enter
Sleep 2s



================================================
FILE: .github/images/preview-mode.tape
================================================
Output preview-mode.gif
Output preview-mode.mp4

Set FontSize 32
Set Width 1800
Set Height 1200
Set TypingSpeed 200ms

Hide
Type "fx testdata/blog.json"
Enter
Show

Sleep 1s
Down
Sleep 1s
Down
Sleep 1s

Type "p"
Sleep 1s
Down
Sleep 300ms
Down
Sleep 300ms
Down
Sleep 300ms
Down
Sleep 300ms
Down
Sleep 300ms
Down
Sleep 300ms
Down
Sleep 4s
Type "p"

Sleep 1s
Up
Sleep 1s
Up


================================================
FILE: .github/images/preview.tape
================================================
Output preview.gif
Output preview.mp4

Set FontSize 32
Set Width 1800
Set Height 1200
Set TypingSpeed 200ms

Hide
Type "fx testdata/example.json"
Enter
Show

Sleep 1s
Down
Sleep 1s
Down
Sleep 1s
Down 6
Sleep 1s
Left
Sleep 1s
Down
Sleep 1s
Down
Sleep 1s
Down
Sleep 1s
Left
Sleep 1s
Up 12
Sleep 1s


================================================
FILE: .github/stream.mjs
================================================
#!/usr/bin/env zx

process.on('SIGPIPE', () => process.exit(0))
process.on('SIGINT', () => process.exit(0))
process.on('SIGTERM', () => process.exit(0))
process.stdout.on('error', (err) => {
  if (err.code === 'EPIPE') process.exit(0)
  throw err
})

const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Henry']
const cities = ['New York', 'London', 'Tokyo', 'Paris', 'Berlin', 'Sydney', 'Toronto', 'Mumbai']
const colors = ['red', 'green', 'blue', 'yellow', 'purple', 'orange', 'pink', 'cyan']
const departments = ['Engineering', 'Sales', 'Marketing', 'HR', 'Finance', 'Operations']
const skills = ['JavaScript', 'Python', 'Go', 'Rust', 'SQL', 'Docker', 'Kubernetes', 'AWS']

function randomItem(arr) {
  return arr[Math.floor(Math.random() * arr.length)]
}

function randomItems(arr, min = 1, max = 3) {
  const count = min + Math.floor(Math.random() * (max - min + 1))
  const shuffled = [...arr].sort(() => Math.random() - 0.5)
  return shuffled.slice(0, count)
}

function randomObject() {
  return {
    id: Math.floor(Math.random() * 1000000),
    name: randomItem(names),
    active: Math.random() > 0.5,
    timestamp: new Date().toISOString(),
    profile: {
      age: 20 + Math.floor(Math.random() * 40),
      city: randomItem(cities),
      preferences: {
        color: randomItem(colors),
        notifications: Math.random() > 0.5,
        theme: Math.random() > 0.5 ? 'dark' : 'light',
      },
    },
    work: {
      department: randomItem(departments),
      salary: Math.floor(50000 + Math.random() * 100000),
      skills: randomItems(skills, 2, 5),
    },
    scores: Array.from({length: 3}, () => Math.round(Math.random() * 100)),
    tags: randomItems(colors, 1, 3),
  }
}

const randomTexts = [
  'Processing records...',
  'Loading next batch',
  '--- checkpoint ---',
  'Fetching data from server',
  'INFO: Connection stable',
  'DEBUG: Buffer flushed',
  'Waiting for response...',
  '>> sync complete',
]

const count = parseInt(argv._[0]) || Infinity
const delay = parseInt(argv.delay) || 100
const withText = argv['with-text'] || argv.withText

for (let i = 0; i < count; i++) {
  if (withText && Math.random() < 0.15) {
    console.log(randomItem(randomTexts))
    if (delay > 0) await sleep(delay)
  }
  console.log(JSON.stringify(randomObject()))
  if (delay > 0 && i < count - 1) {
    await sleep(delay)
  }
}


================================================
FILE: .github/workflows/brew.yml
================================================
name: brew

on: [workflow_dispatch]

jobs:
  brew:
    runs-on: macos-latest
    steps:
      - name: Set up Homebrew
        id: set-up-homebrew
        uses: Homebrew/actions/setup-homebrew@master
        with:
          test-bot: false

      - name: Install Homebrew Bundler RubyGems
        run: brew install-bundler-gems

      - name: Configure Git user
        uses: Homebrew/actions/git-user-config@master

      - name: Update brew
        run: brew update

      - name: Bump formulae
        uses: Homebrew/actions/bump-packages@master
        with:
          token: ${{ secrets.MY_HOMEBREW_RELEASE_GITHUB_TOKEN }}
          formulae: |
            fx


================================================
FILE: .github/workflows/docker.yml
================================================
name: docker

on: [workflow_dispatch]

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: antonmedv
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: antonmedv/fx:latest


================================================
FILE: .github/workflows/snap.yml
================================================
name: snap

on: [workflow_dispatch]

jobs:
  snap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: master

      - name: Set up Go
        uses: actions/setup-go@v3
        with:
          go-version: 1.21

      - uses: snapcore/action-build@v1
        id: build

      - uses: snapcore/action-publish@v1
        env:
          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.MY_SNAPCRAFT_CREDENTIALS }}
        with:
          snap: ${{ steps.build.outputs.snap }}
          release: stable


================================================
FILE: .github/workflows/test.yml
================================================
name: test

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  go:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v3
        with:
          go-version: 1.21
      - name: Test
        run: go test ./...

  go-arm:
    runs-on: ubuntu-24.04-arm
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v3
        with:
          go-version: 1.21
      - name: Test on ARM
        run: go test ./...

  node:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Test NPM version
        run: cd npm && node test.js


================================================
FILE: .gitignore
================================================
*.prof
fx
fx.exe


================================================
FILE: Dockerfile
================================================
FROM golang:latest as builder

WORKDIR /go

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 go build -o fx .

FROM alpine

COPY --from=builder /go/fx /bin/fx

WORKDIR /data

ENV COLORTERM=truecolor

ENTRYPOINT ["/bin/fx"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Anton Medvedev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# f(x)

<p align="center"><a href="https://fx.wtf"><img src=".github/images/preview.gif" width="500" alt="fx preview"></a></p>

## Documentation

See full documentation at [fx.wtf](https://fx.wtf).

## Related

- [walk](https://github.com/antonmedv/walk) – terminal file manager
- [howto](https://github.com/antonmedv/howto) – terminal command LLM helper
- [countdown](https://github.com/antonmedv/countdown) – terminal countdown timer

## License

[MIT](LICENSE)

<p align="center">
  <a href="https://crow.watch/join/fx">
    <img src="https://github.com/user-attachments/assets/37c84073-6533-4746-951d-d879f90a7fd2" alt="Join Crow Watch" width="900" hight="600">  
  </a>
</p>


================================================
FILE: RELEASE.md
================================================
# Release

1. Bump version in [version.go](version.go).
2. Bump version in [snapcraft.yaml](snap/snapcraft.yaml).
3. Bump version in [package.json](npm/package.json).
4. Commit changes.
5. Publish npm package.
6. Trigger [GitHub Actions](https://github.com/antonmedv/fx/actions) (brew, snap, docker).
7. Create a new release on [GitHub](https://github.com/antonmedv/fx/releases/new).
8. Run [build.mjs](scripts/build.mjs) to upload binaries to the release.
   ```sh
   npx zx scripts/build.mjs
   ```
9. Bump version in [install.sh](https://github.com/antonmedv/fx.wtf/blob/master/public/install.sh) and upload it
   to [fx.wtf](https://fx.wtf).


================================================
FILE: go.mod
================================================
module github.com/antonmedv/fx

go 1.23.0

toolchain go1.23.6

require (
	github.com/antonmedv/clipboard v1.0.1
	github.com/charmbracelet/bubbles v0.21.0
	github.com/charmbracelet/bubbletea v1.3.6
	github.com/charmbracelet/lipgloss v1.1.0
	github.com/charmbracelet/x/exp/teatest v0.0.0-20231025135604-4a717d4fb812
	github.com/charmbracelet/x/term v0.2.1
	github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994
	github.com/goccy/go-yaml v1.18.0
	github.com/mattn/go-isatty v0.0.20
	github.com/mattn/go-runewidth v0.0.16
	github.com/muesli/termenv v0.16.0
	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
	github.com/pelletier/go-toml/v2 v2.2.3
	github.com/rivo/uniseg v0.4.7
	github.com/stretchr/testify v1.9.0
)

require (
	github.com/atotto/clipboard v0.1.4 // indirect
	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
	github.com/aymanbagabas/go-udiff v0.2.0 // indirect
	github.com/charmbracelet/colorprofile v0.3.1 // indirect
	github.com/charmbracelet/x/ansi v0.10.1 // indirect
	github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dlclark/regexp2 v1.11.5 // indirect
	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
	github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
	github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
	github.com/mattn/go-localereader v0.0.1 // indirect
	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
	github.com/muesli/cancelreader v0.2.2 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
	golang.org/x/sync v0.16.0 // indirect
	golang.org/x/sys v0.35.0 // indirect
	golang.org/x/text v0.27.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/antonmedv/clipboard v1.0.1 h1:z9rRBhSKt4lDb6uNcMykUmNbspk/6v07JeiTaOfYYOY=
github.com/antonmedv/clipboard v1.0.1/go.mod h1:3jcOUCdraVHehZaOsMaJZoE92MxURt5fovC1gDAiZ2s=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/teatest v0.0.0-20231025135604-4a717d4fb812 h1:W/hU7Z+y+QsZo2qg0hwjv56qSMP12Z72DJR8k+ULbA4=
github.com/charmbracelet/x/exp/teatest v0.0.0-20231025135604-4a717d4fb812/go.mod h1:TckAxPtan3aJ5wbTgBkySpc50SZhXJRZ8PtYICnZJEw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994 h1:aQYWswi+hRL2zJqGacdCZx32XjKYV8ApXFGntw79XAM=
github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: help.go
================================================
package main

import (
	"fmt"
	"os"
	"reflect"
	"strings"
	"time"

	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/lipgloss"
)

func usage() string {
	title := lipgloss.NewStyle().Bold(true)
	return fmt.Sprintf(`
  %v
    Terminal JSON viewer

  %v
    fx data.json
    fx data.json .field
    curl ... | fx

  %v
    -h, --help            print help
    -v, --version         print version
    --themes              print themes
    --comp <shell>        print completion script
    -r, --raw             treat input as a raw string
    -s, --slurp           read all inputs into an array
    --yaml                parse input as YAML
    --toml                parse input as TOML
    --strict              strict mode
    --no-inline           disable inlining in output
    --game-of-life        play the game of life

  %v
    https://fx.wtf

  %v
    Anton Medvedev <anton@medv.io>
`,
		title.Render("fx "+version),
		title.Render("Usage"),
		title.Render("Flags"),
		title.Render("More info"),
		title.Render("Author"),
	)
}

var categoryOrder = []string{
	"Navigation",
	"Expand / Collapse",
	"Search",
	"Actions",
	"View",
	"Other",
}

func help(keyMap KeyMap) string {
	titleStyle := lipgloss.NewStyle().
		Bold(true)

	categoryStyle := lipgloss.NewStyle().
		Foreground(lipgloss.Color("8")).
		MarginTop(1)

	keyStyle := lipgloss.NewStyle()

	descStyle := lipgloss.NewStyle().
		Foreground(lipgloss.Color("8"))

	dimStyle := lipgloss.NewStyle().
		Foreground(lipgloss.Color("8"))

	// Group bindings by category using struct tags
	v := reflect.ValueOf(keyMap)
	t := v.Type()
	categories := make(map[string][]key.Binding)

	for _, field := range reflect.VisibleFields(t) {
		category := field.Tag.Get("category")
		if category == "" {
			continue
		}
		binding := v.FieldByName(field.Name).Interface().(key.Binding)
		categories[category] = append(categories[category], binding)
	}

	var sb strings.Builder

	// Header
	sb.WriteString("\n")
	sb.WriteString(titleStyle.Render("  Key Bindings"))
	sb.WriteString("\n")
	sb.WriteString(dimStyle.Render("  ─────────────────────────────────────────"))
	sb.WriteString("\n")

	for _, cat := range categoryOrder {
		bindings, ok := categories[cat]
		if !ok || len(bindings) == 0 {
			continue
		}

		sb.WriteString(categoryStyle.Render("  " + cat))
		sb.WriteString("\n")

		for _, binding := range bindings {
			keyStr := binding.Help().Key
			if len(keyStr) == 0 {
				keys := binding.Keys()
				if len(keys) > 5 {
					keyStr = fmt.Sprintf("%v-%v", keys[0], keys[len(keys)-1])
				} else {
					keyStr = strings.Join(keys, ", ")
				}
			}

			desc := binding.Help().Desc
			keyFormatted := keyStyle.Render(fmt.Sprintf("%-20s", keyStr))
			descFormatted := descStyle.Render(desc)

			sb.WriteString(fmt.Sprintf("    %s  %s\n", keyFormatted, descFormatted))
		}
	}

	sb.WriteString("\n")
	sb.WriteString(dimStyle.Render("  ─────────────────────────────────────────"))
	sb.WriteString("\n")
	sb.WriteString(dimStyle.Render("  Press q or ? to close"))
	sb.WriteString("\n")

	return sb.String()
}

func exit() {
	if showLetter(time.Now()) {
		style := lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).Padding(1, 2)
		_, _ = fmt.Fprintln(os.Stderr, style.Render(`Hello, kind human. :)

This is fx speaking. I know you’re busy, and I won’t take much
of your time.

Every day, I quietly sit in your terminal, helping you explore
and shape your data. No popups, no ads, quiet, helpful work.

But today is different.

Today, I’m asking for something small in return. Just for today.

If fx has saved you time, solved a problem, or simply made your 
life in the terminal a little easier, please consider supporting 
the developer who made me:

    https://github.com/sponsors/antonmedv

He built fx as a passion project, shared it freely with the world, 
and has kept improving it—all without asking much.

Your support helps keep fx alive, maintained, and improving.
Even a small donation means a lot. It shows that you care, that 
this kind of work matters.

This message only appears once, on the first Tuesday of December.
Tomorrow I’ll be silent again.

Thank you for reading. And thank you for using fx.`))
	}
}

func showLetter(t time.Time) bool {
	if t.Month() != time.December {
		return false
	}
	firstOfDecember := time.Date(t.Year(), time.December, 1, 0, 0, 0, 0, t.Location())
	offset := (int(time.Tuesday) - int(firstOfDecember.Weekday()) + 7) % 7
	firstTuesday := firstOfDecember.AddDate(0, 0, offset)
	return t.Year() == firstTuesday.Year() &&
		t.Month() == firstTuesday.Month() &&
		t.Day() == firstTuesday.Day()
}


================================================
FILE: internal/complete/complete.bash
================================================
complete -o filenames -C fx fx


================================================
FILE: internal/complete/complete.fish
================================================
complete --command fx --arguments '(COMP_FISH=(commandline -cp) fx)'


================================================
FILE: internal/complete/complete.go
================================================
package complete

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/dop251/goja"
	"github.com/goccy/go-yaml"
	"github.com/pelletier/go-toml/v2"

	"github.com/antonmedv/fx/internal/engine"
	"github.com/antonmedv/fx/internal/jsonx"
	"github.com/antonmedv/fx/internal/shlex"
)

type Reply struct {
	Display string
	Value   string
	Type    string // "file" for files, others optional
}

var Flags []Reply

//go:embed complete.bash
var Bash string

//go:embed complete.zsh
var Zsh string

//go:embed complete.fish
var Fish string

//go:embed prelude.js
var prelude string

func Complete() bool {
	compLine, ok := os.LookupEnv("COMP_LINE")
	if ok && len(os.Args) >= 3 {
		doComplete(compLine, os.Args[2], false)
		return true
	}

	compZsh, ok := os.LookupEnv("COMP_ZSH")
	if ok {
		doComplete(compZsh, lastWord(compZsh), true)
		return true
	}

	compFish, ok := os.LookupEnv("COMP_FISH")
	if ok {
		doComplete(compFish, lastWord(compFish), false)
		return true
	}

	return false
}

func doComplete(compLine string, compWord string, withDisplay bool) {
	if strings.HasPrefix(compWord, "-") {
		compReply(filterReply(Flags, compWord), withDisplay)
		return
	}

	args, err := shlex.Split(compLine)
	if err != nil {
		return
	}

	compWord = shlex.Parse(compWord)

	var flagYaml bool
	var flagToml bool
	for _, arg := range args {
		if arg == "--yaml" {
			flagYaml = true
		}
		if arg == "--toml" {
			flagToml = true
		}
	}

	// Remove Flags from args.
	args = filterArgs(args)

	isSecondArgIsFile := false
	if len(args) == 0 {
		return
	} else if len(args) == 1 {
		reply := fileComplete(compWord)
		compReply(reply, withDisplay)
		return
	} else if len(args) == 2 {
		isSecondArgIsFile = isFile(args[1])
		if !isSecondArgIsFile {
			reply := fileComplete(compWord)
			compReply(reply, withDisplay)
			return
		}
	} else {
		isSecondArgIsFile = isFile(args[1])
	}

	var reply []Reply

	if isSecondArgIsFile {
		file := args[1]

		hasYamlExt, _ := regexp.MatchString(`(?i)\.ya?ml$`, file)
		hasTomlExt, _ := regexp.MatchString(`(?i)\.toml$`, file)
		if !flagYaml && hasYamlExt {
			flagYaml = true
		}
		if !flagToml && hasTomlExt {
			flagToml = true
		}

		if strings.HasPrefix(file, "~") {
			home, err := os.UserHomeDir()
			if err == nil {
				file = filepath.Join(home, file[1:])
			}
		}

		resultCh := make(chan []Reply, 1)

		go func() {
			input, err := os.ReadFile(file)
			if err != nil {
				resultCh <- []Reply{}
				return
			}

			if flagYaml {
				input, err = yaml.YAMLToJSON(input)
				if err != nil {
					resultCh <- []Reply{}
					return
				}
			} else if flagToml {
				var v any
				if err := toml.Unmarshal(input, &v); err != nil {
					resultCh <- []Reply{}
					return
				}
				b, err := json.Marshal(v)
				if err != nil {
					resultCh <- []Reply{}
					return
				}
				input = b
			}

			node, err := jsonx.Parse(input)
			if err != nil {
				resultCh <- []Reply{}
				return
			}

			resultCh <- KeysComplete(node, args, compWord)
		}()

		select {
		case result := <-resultCh:
			reply = append(reply, result...)
		case <-time.After(3 * time.Second):
			return
		}
	}

	reply = filterReply(reply, compWord)
	if len(reply) > 0 {
		compReply(reply, withDisplay)
		return
	}

	if len(compWord) > 0 {
		// Only show globals if compWord is not empty,
		// as we do not want to be very verbose and show all globals.
		compReply(filterReply(globalsComplete(), compWord), withDisplay)
	}
}

func globalsComplete() []Reply {
	var code strings.Builder
	code.WriteString(prelude)
	code.WriteString(engine.Stdlib)
	code.WriteString("\n__autocomplete()\n")

	vm := goja.New()
	value, err := vm.RunString(code.String())
	if err != nil {
		return nil
	}

	if array, ok := value.Export().([]any); ok {
		var reply []Reply
		for _, key := range array {
			reply = append(reply, Reply{
				Display: key.(string),
				Value:   key.(string),
				Type:    "global",
			})
		}
		return reply
	}
	return nil
}

func KeysComplete(input *jsonx.Node, args []string, compWord string) []Reply {
	args = args[2:] // Drop binary & file from the args.

	if compWord == "" {
		args = append(args, ".__keys()")
	} else {
		if len(args) > 0 {
			last := args[len(args)-1]
			last = dropTail(args[len(args)-1])
			last = last + ".__keys()"
			last = balanceBrackets(last)
			args[len(args)-1] = last
		}
	}

	var code strings.Builder
	code.WriteString(prelude)
	code.WriteString(engine.Stdlib)
	code.WriteString(engine.JS(args))
	code.WriteString("\n__main__(json)\n__keys\n")

	vm := goja.New()
	if err := vm.Set("json", input.ToValue(vm)); err != nil {
		return nil
	}
	value, err := vm.RunString(code.String())
	if err != nil {
		return nil
	}

	if array, ok := value.Export().([]interface{}); ok {
		prefix := dropTail(compWord)
		var reply []Reply
		for _, key := range array {
			k := key.(string)
			reply = append(reply, Reply{
				Display: join("", k),
				Value:   join(prefix, k),
				Type:    "key",
			})
		}
		return reply
	}
	return nil
}

var alphaRe = regexp.MustCompile(`^[A-Za-z_$][A-Za-z0-9_$]*$`)

func join(prefix, key string) string {
	if alphaRe.MatchString(key) {
		return prefix + "." + key
	} else {
		if prefix == "" {
			return fmt.Sprintf(".[%q]", key)
		}
		return fmt.Sprintf("%s[%q]", prefix, key)
	}
}

func filterArgs(args []string) []string {
	filtered := make([]string, 0, len(args))
	for _, arg := range args {
		found := false
		for _, flag := range Flags {
			if arg == flag.Value {
				found = true
				break
			}
		}
		if !found {
			filtered = append(filtered, arg)
		}
	}
	return filtered
}

func fileComplete(compWord string) []Reply {
	original := compWord

	// Step 1: Expand ~ to home directory
	if strings.HasPrefix(compWord, "~") {
		if compWord == "~" || strings.HasPrefix(compWord, "~/") {
			home, err := os.UserHomeDir()
			if err == nil {
				compWord = filepath.Join(home, compWord[1:])
			}
		} else {
			// We don't support ~username completion
			return nil
		}
	}

	// Step 2: If compWord ends in "/", treat it as a directory and add a "*" pattern
	info, err := os.Stat(compWord)
	if err == nil && info.IsDir() && !strings.HasSuffix(compWord, "*") {
		compWord = filepath.Join(compWord, "*")
	} else if !strings.HasSuffix(compWord, "*") {
		// Add wildcard if not already present
		compWord = compWord + "*"
	}

	// Step 3: Perform globbing
	files, err := filepath.Glob(compWord)
	if err != nil {
		return nil
	}

	// Step 4: Format matches
	var matches []Reply
	for _, match := range files {
		if match == "." || match == ".." {
			continue
		}

		var suggestion string
		if strings.HasPrefix(original, "~") {
			home, _ := os.UserHomeDir()
			if strings.HasPrefix(match, home) {
				suggestion = "~" + strings.TrimPrefix(match, home)
			} else {
				suggestion = match
			}
		} else if filepath.IsAbs(original) {
			suggestion = match
		} else {
			rel, err := filepath.Rel(".", match)
			if err != nil {
				continue
			}
			suggestion = rel
		}

		dirSuffix := ""
		info, err := os.Stat(match)
		if err == nil {
			if info.IsDir() {
				dirSuffix = "/"
			}
		}

		matches = append(matches, Reply{
			Display: filepath.Base(suggestion) + dirSuffix,
			Value:   suggestion,
			Type:    "file",
		})
	}

	return matches
}


================================================
FILE: internal/complete/complete.zsh
================================================
#compdef fx

_fx() {
    local -a reply
    reply=("${(@f)$(COMP_ZSH="${LBUFFER}" fx)}")
    if (( ${#reply} )); then
        local -a insert_files display_files insert_other display_other
        local line display rest value typ
        
        for line in "${reply[@]}"; do
            display="${line%%$'\t'*}"
            rest="${line#*$'\t'}"
            value="${rest%%$'\t'*}"
            typ="${rest#*$'\t'}"

            if [[ "$typ" == "file" ]]; then
                display_files+=("$display")
                insert_files+=("$value")
            else
                display_other+=("$display")
                insert_other+=("$value")
            fi
        done

        if (( ${#insert_files} )); then
            compadd -f -d display_files -a insert_files
        fi
        if (( ${#insert_other} )); then
            compadd -S '' -d display_other -a insert_other
        fi
    fi
}

if [ "$funcstack[1]" = "_fx" ]; then
    _fx "$@"
else
    compdef _fx fx
fi


================================================
FILE: internal/complete/complete_test.go
================================================
package complete

import (
	"testing"

	"github.com/antonmedv/fx/internal/jsonx"
)

func TestKeysComplete(t *testing.T) {
	tests := []struct {
		name     string
		json     string
		args     []string
		compWord string
		want     []string
	}{
		{
			name:     "simple object keys with empty compWord",
			json:     `{"foo": 1, "bar": 2, "baz": 3}`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     []string{".foo", ".bar", ".baz"},
		},
		{
			name:     "nested object keys with trailing dot",
			json:     `{"outer": {"inner1": 1, "inner2": 2}}`,
			args:     []string{"fx", "file.json", ".outer."},
			compWord: ".outer.",
			want:     []string{".outer.inner1", ".outer.inner2"},
		},
		{
			name:     "nested object without trailing dot returns root keys",
			json:     `{"outer": {"inner1": 1, "inner2": 2}}`,
			args:     []string{"fx", "file.json", ".outer"},
			compWord: ".outer",
			want:     []string{".outer"},
		},
		{
			name:     "nested object with partial compWord",
			json:     `{"data": {"name": "test", "value": 42}}`,
			args:     []string{"fx", "file.json", ".data."},
			compWord: ".data.",
			want:     []string{".data.name", ".data.value"},
		},
		{
			name:     "empty object",
			json:     `{}`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     nil,
		},
		{
			name:     "key with special characters",
			json:     `{"normal": 1, "with-dash": 2, "with space": 3}`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     []string{".normal", ".[\"with-dash\"]", ".[\"with space\"]"},
		},
		{
			name:     "deeply nested with trailing dot",
			json:     `{"a": {"b": {"c": {"d": 1}}}}`,
			args:     []string{"fx", "file.json", ".a.b.c."},
			compWord: ".a.b.c.",
			want:     []string{".a.b.c.d"},
		},
		{
			name:     "deeply nested without trailing dot returns parent keys",
			json:     `{"a": {"b": {"c": {"d": 1}}}}`,
			args:     []string{"fx", "file.json", ".a.b.c"},
			compWord: ".a.b.c",
			want:     []string{".a.b.c"},
		},
		{
			name:     "array returns no keys",
			json:     `[1, 2, 3]`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     nil,
		},
		{
			name:     "primitive value returns no keys",
			json:     `"hello"`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     nil,
		},
		{
			name:     "numeric keys use bracket notation",
			json:     `{"123": "numeric", "abc": "alpha"}`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     []string{".[\"123\"]", ".abc"},
		},
		{
			name:     "key starting with underscore",
			json:     `{"_private": 1, "public": 2}`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     []string{"._private", ".public"},
		},
		{
			name:     "key starting with dollar",
			json:     `{"$ref": "#/defs", "name": "test"}`,
			args:     []string{"fx", "file.json"},
			compWord: "",
			want:     []string{".$ref", ".name"},
		},
		{
			name:     "object inside array via bracket access with trailing dot",
			json:     `{"items": [{"x": 1, "y": 2}]}`,
			args:     []string{"fx", "file.json", ".items[0]."},
			compWord: ".items[0].",
			want:     []string{".items[0].x", ".items[0].y"},
		},
		{
			name:     "multiple args combines path",
			json:     `{"a": {"b": {"c": 1}}}`,
			args:     []string{"fx", "file.json", ".a", ".b."},
			compWord: ".b.",
			want:     []string{".b.c"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			node, err := jsonx.Parse([]byte(tt.json))
			if err != nil {
				t.Fatalf("failed to parse JSON: %v", err)
			}

			got := KeysComplete(node, tt.args, tt.compWord)

			if len(got) != len(tt.want) {
				t.Errorf("KeysComplete() returned %d replies, want %d", len(got), len(tt.want))
				t.Errorf("got: %v", replyValues(got))
				t.Errorf("want: %v", tt.want)
				return
			}

			gotValues := replyValues(got)
			for i, want := range tt.want {
				if gotValues[i] != want {
					t.Errorf("KeysComplete()[%d].Value = %q, want %q", i, gotValues[i], want)
				}
			}
		})
	}
}

func replyValues(replies []Reply) []string {
	values := make([]string, len(replies))
	for i, r := range replies {
		values[i] = r.Value
	}
	return values
}

func TestKeysComplete_Display(t *testing.T) {
	json := `{"foo": 1, "bar": 2}`
	node, err := jsonx.Parse([]byte(json))
	if err != nil {
		t.Fatalf("failed to parse JSON: %v", err)
	}

	got := KeysComplete(node, []string{"fx", "file.json"}, "")

	for _, r := range got {
		if r.Type != "key" {
			t.Errorf("expected Type to be 'key', got %q", r.Type)
		}
		if r.Display == "" {
			t.Error("expected Display to be non-empty")
		}
	}
}

func TestKeysComplete_DisplayVsValue(t *testing.T) {
	tests := []struct {
		name        string
		json        string
		args        []string
		compWord    string
		wantDisplay string
		wantValue   string
	}{
		{
			name:        "display shows key with dot prefix, value shows full path",
			json:        `{"outer": {"inner": 1}}`,
			args:        []string{"fx", "file.json", ".outer."},
			compWord:    ".outer.",
			wantDisplay: ".inner",
			wantValue:   ".outer.inner",
		},
		{
			name:        "special key display and value both use bracket notation",
			json:        `{"key-dash": 1}`,
			args:        []string{"fx", "file.json"},
			compWord:    "",
			wantDisplay: ".[\"key-dash\"]",
			wantValue:   ".[\"key-dash\"]",
		},
		{
			name:        "regular key at root",
			json:        `{"foo": 1}`,
			args:        []string{"fx", "file.json"},
			compWord:    "",
			wantDisplay: ".foo",
			wantValue:   ".foo",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			node, err := jsonx.Parse([]byte(tt.json))
			if err != nil {
				t.Fatalf("failed to parse JSON: %v", err)
			}

			got := KeysComplete(node, tt.args, tt.compWord)
			if len(got) == 0 {
				t.Fatal("expected at least one reply")
			}

			if got[0].Display != tt.wantDisplay {
				t.Errorf("Display = %q, want %q", got[0].Display, tt.wantDisplay)
			}
			if got[0].Value != tt.wantValue {
				t.Errorf("Value = %q, want %q", got[0].Value, tt.wantValue)
			}
		})
	}
}


================================================
FILE: internal/complete/prelude.js
================================================
const __keys = new Set()

Object.prototype.__keys = function () {
  if (Array.isArray(this)) return
  if (typeof this === 'string') return
  if (this instanceof String) return
  if (this === globalThis) return
  if (typeof this === 'object' && this !== null)
    Object.keys(this).forEach(x => __keys.add(x))
}

function __autocomplete() {
  const keys = []
  for (const key of Object.keys(globalThis)) {
    if (key.startsWith('__')) continue
    keys.push(key)
  }
  keys.push(
    'JSON.stringify',
    'JSON.parse',
    'YAML.stringify',
    'YAML.parse',
    'Object.keys',
    'Object.values',
    'Object.entries',
    'Object.fromEntries',
    'Array.isArray',
    'Array.from',
    'console.log',
  )
  return keys
}


================================================
FILE: internal/complete/utils.go
================================================
package complete

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func compReply(reply []Reply, withDisplay bool) {
	var lines []string
	for _, line := range reply {
		if withDisplay {
			lines = append(lines, fmt.Sprintf("%s\t%s\t%s", line.Display, line.Value, line.Type))
		} else {
			lines = append(lines, line.Value)
		}
	}
	fmt.Print(strings.Join(lines, "\n"))
}

func filterReply(reply []Reply, compWord string) []Reply {
	var filtered []Reply
	for _, word := range reply {
		if strings.HasPrefix(word.Value, compWord) {
			filtered = append(filtered, word)
		}
	}
	return filtered
}

func isFile(path string) bool {
	if strings.HasPrefix(path, "~") {
		home, err := os.UserHomeDir()
		if err == nil {
			path = filepath.Join(home, path[1:])
		}
	}
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return !info.IsDir()
}

func dropTail(s string) string {
	parts := strings.Split(s, ".")
	if len(parts) == 1 {
		return s
	}
	return strings.Join(parts[:len(parts)-1], ".")
}

func balanceBrackets(code string) string {
	var stack []rune
	brackets := map[rune]rune{')': '(', '}': '{', ']': '['}
	reverseBrackets := map[rune]rune{'(': ')', '{': '}', '[': ']'}

	for _, char := range code {
		switch char {
		case '(', '{', '[':
			stack = append(stack, char)
		case ')', '}', ']':
			if len(stack) > 0 && brackets[char] == stack[len(stack)-1] {
				stack = stack[:len(stack)-1] // Pop
			}
		}
	}

	for i := len(stack) - 1; i >= 0; i-- {
		code += string(reverseBrackets[stack[i]])
	}

	return code
}

func lastWord(line string) string {
	words := strings.Split(line, " ")
	var s string
	if len(words) > 0 {
		s = words[len(words)-1]
	}
	return s
}

func writeLog(args ...interface{}) {
	file, err := os.OpenFile("complete.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return
	}
	_, _ = fmt.Fprintln(file, args...)
	_ = file.Close()
}


================================================
FILE: internal/engine/engine.go
================================================
package engine

import (
	_ "embed"
	"io"
	"reflect"
	"strconv"
	"strings"

	"github.com/dop251/goja"

	"github.com/antonmedv/fx/internal/jsonx"
	"github.com/antonmedv/fx/internal/pretty"
)

//go:embed stdlib.js
var Stdlib string

func init() {
	fxrc, err := readFxrc()
	if err != nil {
		panic(err)
	}
	Stdlib += fxrc
}

type Parser interface {
	Parse() (*jsonx.Node, error)
	Recover() *jsonx.Node
}

type Options struct {
	Slurp      bool
	WithInline bool
	WriteOut   func(string)
	WriteErr   func(string)
}

func Start(parser Parser, args []string, opts Options) int {
	if opts.Slurp {
		var ok bool
		parser, ok = Slurp(parser, opts.WriteErr)
		if !ok {
			return 1
		}
	}

	isPrettyPrintArg := len(args) == 1 && (args[0] == "." || args[0] == "this" || args[0] == "x")

	// Fast path.
	if isPrettyPrintArg {
		for {
			node, err := parser.Parse()

			if err != nil {
				if err == io.EOF {
					break
				}
				opts.WriteErr(err.Error())
				return 1
			}

			if node.Kind == jsonx.String {
				unquoted, err := strconv.Unquote(node.Value)
				if err != nil {
					panic(err)
				}
				opts.WriteOut(unquoted)
			} else {
				opts.WriteOut(pretty.Print(node, opts.WithInline))
			}
		}

		return 0
	}

	for i := range args {
		if err := validateSyntax(args, i); err != nil {
			jsCode := transpile(args[i])
			snippet := formatErr(args, i, jsCode)
			message := errorToString(err)
			opts.WriteErr(snippet + message)
			return 1
		}
	}

	var code strings.Builder
	code.WriteString(Stdlib)
	code.WriteString(JS(args))

	vm := NewVM(opts.WriteOut)
	if _, err := vm.RunString(code.String()); err != nil {
		opts.WriteErr(errorToString(err))
		return 1
	}

	skip := vm.Get("skip")
	undefined := vm.Get("undefined")
	main, _ := goja.AssertFunction(vm.Get("__main__"))

	echo := func(output goja.Value) {
		rtype := output.ExportType()
		if output.StrictEquals(undefined) {
			opts.WriteErr("undefined")
		} else if rtype != nil && rtype.Kind() == reflect.String {
			opts.WriteOut(output.String())
		} else {
			jsonOut := Stringify(output, vm, 0)
			nodeOut, err := jsonx.Parse([]byte(jsonOut))
			if err != nil {
				panic(err)
			}
			opts.WriteOut(pretty.Print(nodeOut, opts.WithInline))
		}
	}

	for {
		node, err := parser.Parse()
		if err != nil {
			if err == io.EOF {
				break
			}
			opts.WriteErr(err.Error())
			return 1
		}

		input := node.ToValue(vm)
		output, exitCode, err := callMain(main, input)
		if exitCode >= 0 {
			return exitCode
		}
		if err != nil {
			opts.WriteErr(errorToString(err))
			return 1
		}

		if output.StrictEquals(skip) {
			continue
		}
		echo(output)
	}

	return 0
}

func callMain(main goja.Callable, input goja.Value) (output goja.Value, exitCode int, err error) {
	exitCode = -1
	defer func() {
		if r := recover(); r != nil {
			if e, ok := r.(ExitError); ok {
				exitCode = e.Code
			} else {
				panic(r)
			}
		}
	}()
	output, err = main(goja.Undefined(), input)
	return
}

func validateSyntax(args []string, i int) error {
	var code strings.Builder
	code.WriteString("\nfunction __main__(json) {\n")
	code.WriteString(Body(args, i))
	code.WriteString("  return json\n}\n")

	vm := goja.New()
	_, err := vm.RunString(code.String())
	return err
}


================================================
FILE: internal/engine/engine_test.go
================================================
package engine_test

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/antonmedv/fx/internal/engine"
	"github.com/antonmedv/fx/internal/jsonx"
)

func TestEngine(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		args     []string
		expects  []string
		errCount int
	}{
		{
			name:     "fast path: string as raw",
			input:    `"Hello, world!"`,
			args:     []string{"."},
			expects:  []string{"Hello, world!"},
			errCount: 0,
		},
		{
			name:     "string as raw",
			input:    `"Hello, world!"`,
			args:     []string{"x => this"},
			expects:  []string{"Hello, world!"},
			errCount: 0,
		},
		{
			name:     "skip works",
			input:    "1 2 3 4",
			args:     []string{"x % 2 != 0 ? skip : x"},
			expects:  []string{"2", "4"},
			errCount: 0,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			parser := jsonx.NewJsonParser(strings.NewReader(tc.input), false)

			var outs, errs []string
			writeOut := func(s string) { outs = append(outs, s) }
			writeErr := func(s string) { errs = append(errs, s) }

			opts := engine.Options{
				Slurp:      false,
				WithInline: false,
				WriteOut:   writeOut,
				WriteErr:   writeErr,
			}
			exitCode := engine.Start(parser, tc.args, opts)

			assert.Equal(t, 0, exitCode)
			assert.Len(t, errs, tc.errCount, "%s: unexpected error count", tc.name)
			assert.Equal(t, tc.expects, outs, "%s: outputs mismatch", tc.name)
		})
	}
}

func TestStart_InvalidJSON(t *testing.T) {
	input := `{"unclosed": 1`
	parser := jsonx.NewJsonParser(strings.NewReader(input), false)

	var outs, errs []string
	writeOut := func(s string) { outs = append(outs, s) }
	writeErr := func(s string) { errs = append(errs, s) }

	opts := engine.Options{
		Slurp:      false,
		WithInline: false,
		WriteOut:   writeOut,
		WriteErr:   writeErr,
	}
	exitCode := engine.Start(parser, []string{".unclosed + '!'"}, opts)

	assert.Equal(t, 1, exitCode)
	assert.Len(t, errs, 1, "Expected one error message")
}

func TestStart_FastPath_InvalidJSON(t *testing.T) {
	input := `{"unclosed": 1`
	parser := jsonx.NewJsonParser(strings.NewReader(input), false)

	var outs, errs []string
	writeOut := func(s string) { outs = append(outs, s) }
	writeErr := func(s string) { errs = append(errs, s) }

	opts := engine.Options{
		Slurp:      false,
		WithInline: false,
		WriteOut:   writeOut,
		WriteErr:   writeErr,
	}
	exitCode := engine.Start(parser, []string{"."}, opts)

	assert.Equal(t, 1, exitCode)
	assert.Len(t, errs, 1, "Expected one error message")
}

func TestStart_EscapeSequences(t *testing.T) {
	input := `{"emoji": "\ud83d\ude80"}`
	parser := jsonx.NewJsonParser(strings.NewReader(input), false)

	var outs, errs []string
	writeOut := func(s string) { outs = append(outs, s) }
	writeErr := func(s string) { errs = append(errs, s) }

	opts := engine.Options{
		Slurp:      false,
		WithInline: false,
		WriteOut:   writeOut,
		WriteErr:   writeErr,
	}
	exitCode := engine.Start(parser, []string{".emoji"}, opts)

	assert.Equal(t, 0, exitCode)
	assert.Len(t, errs, 0, "Expected no error messages")
	assert.Equal(t, "🚀", outs[0])
}

func TestStart_EscapeSequences_in_key(t *testing.T) {
	input := `{"\ud83d\ude80": "\ud83d\ude80"}`
	parser := jsonx.NewJsonParser(strings.NewReader(input), false)

	var outs, errs []string
	writeOut := func(s string) { outs = append(outs, s) }
	writeErr := func(s string) { errs = append(errs, s) }

	opts := engine.Options{
		Slurp:      false,
		WithInline: false,
		WriteOut:   writeOut,
		WriteErr:   writeErr,
	}
	exitCode := engine.Start(parser, []string{"x => x"}, opts)

	assert.Equal(t, 0, exitCode)
	assert.Len(t, errs, 0, "Expected no error messages")
}


================================================
FILE: internal/engine/format_err.go
================================================
package engine

import (
	"fmt"
	"os"
	"strings"

	"github.com/charmbracelet/x/term"
	"github.com/mattn/go-runewidth"
)

func formatErr(args []string, i int, jsCode string) string {
	width, _, err := term.GetSize(os.Stdout.Fd())
	if err != nil || width <= 0 {
		width = 80
	}

	if i < 0 || i >= len(args) {
		return fmt.Sprintf("Invalid argument index: %d", i)
	}
	code := args[i]

	const indentCols = 2 // we print "  " before everything
	const sepCols = 1    // the single space between pre and code
	reserve := indentCols + sepCols + runewidth.StringWidth(code) + sepCols

	available := width - reserve
	if available < 0 {
		available = 0
	}
	maxCtx := available / 2

	pre := strings.Join(args[:i], " ")
	post := strings.Join(args[i+1:], " ")

	pre = trimLeft(pre, maxCtx)
	post = trimRight(post, maxCtx)

	leftSep := 0
	if pre != "" {
		leftSep = sepCols
	}
	spacerCols := indentCols + runewidth.StringWidth(pre) + leftSep
	spacer := strings.Repeat(" ", spacerCols)

	var sb strings.Builder
	sb.WriteString("\n")
	sb.WriteString(strings.Repeat(" ", indentCols))
	if pre != "" {
		sb.WriteString(pre)
		sb.WriteByte(' ')
	}
	sb.WriteString(code)
	if post != "" {
		sb.WriteByte(' ')
		sb.WriteString(post)
	}
	sb.WriteByte('\n')

	sb.WriteString(spacer)
	sb.WriteString(strings.Repeat("^", runewidth.StringWidth(code)))
	sb.WriteByte('\n')

	if jsCode != "" && jsCode != code {
		snippet := jsCode
		if runewidth.StringWidth(snippet) > width {
			snippet = trimRight(snippet, width)
		}
		sb.WriteByte('\n')
		sb.WriteString(snippet)
		sb.WriteByte('\n')
	}

	sb.WriteString("\n")

	return sb.String()
}

func trimLeft(s string, ctx int) string {
	if runewidth.StringWidth(s) <= ctx {
		return s
	}
	rs := []rune(s)
	widthAccum := 0
	var out []rune
	for i := len(rs) - 1; i >= 0; i-- {
		w := runewidth.RuneWidth(rs[i])
		if widthAccum+w > ctx-1 {
			break
		}
		widthAccum += w
		out = append([]rune{rs[i]}, out...)
	}
	return "…" + string(out)
}

func trimRight(s string, ctx int) string {
	if runewidth.StringWidth(s) <= ctx {
		return s
	}
	rs := []rune(s)
	widthAccum := 0
	var out []rune
	for _, r := range rs {
		w := runewidth.RuneWidth(r)
		if widthAccum+w > ctx-1 {
			break
		}
		out = append(out, r)
		widthAccum += w
	}
	return string(out) + "…"
}


================================================
FILE: internal/engine/fxrc.go
================================================
package engine

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func readFxrc() (string, error) {
	var builder strings.Builder

	// Determine search paths
	cwd, err := os.Getwd()
	if err != nil {
		return "", fmt.Errorf("get cwd: %w", err)
	}
	paths := []string{filepath.Join(cwd, ".fxrc.js")}

	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("get home: %w", err)
	}
	paths = append(paths, filepath.Join(home, ".fxrc.js"))

	xdgHome := os.Getenv("XDG_CONFIG_HOME")
	if xdgHome == "" {
		xdgHome = filepath.Join(home, ".config")
	}
	paths = append(paths, filepath.Join(xdgHome, "fx", ".fxrc.js"))

	xdgDirs := os.Getenv("XDG_CONFIG_DIRS")
	if xdgDirs == "" {
		xdgDirs = "/etc/xdg"
	}
	for _, dir := range strings.Split(xdgDirs, ":") {
		paths = append(paths, filepath.Join(dir, "fx", ".fxrc.js"))
	}

	// Read and combine
	for _, path := range uniq(paths) {
		info, err := os.Stat(path)
		if err != nil || info.IsDir() {
			continue // skip missing or directories
		}
		data, err := os.ReadFile(path)
		if err != nil {
			return "", fmt.Errorf("read %s: %w", path, err)
		}
		builder.Write(data)
		builder.WriteString("\n")
	}

	return builder.String(), nil
}

func uniq(paths []string) []string {
	seen := make(map[string]bool)
	result := []string{}
	for _, path := range paths {
		if !seen[path] {
			seen[path] = true
			result = append(result, path)
		}
	}
	return result
}


================================================
FILE: internal/engine/quote.go
================================================
package engine

import (
	"fmt"
	"strings"
	"unicode/utf8"
)

func Quote(s string) string {
	var err error
	var b strings.Builder
	b.WriteByte('"')

	for i := 0; i < len(s); {
		r, width := utf8.DecodeRuneInString(s[i:])

		switch r {
		case '"':
			b.WriteString(`\"`)
		case '\\':
			b.WriteString(`\\`)
		case '\b':
			b.WriteString(`\b`)
		case '\f':
			b.WriteString(`\f`)
		case '\n':
			b.WriteString(`\n`)
		case '\r':
			b.WriteString(`\r`)
		case '\t':
			b.WriteString(`\t`)
		default:
			if r < 0x20 || r == 0x7F {
				// Control characters must be escaped as \uXXXX
				_, err = fmt.Fprintf(&b, `\u%04x`, r)
				if err != nil {
					panic(err)
				}
			} else if r == utf8.RuneError && width == 1 {
				// Invalid UTF-8 sequence - escape the byte
				_, err = fmt.Fprintf(&b, `\u%04x`, s[i])
				if err != nil {
					panic(err)
				}
			} else {
				// Regular character - write as-is
				b.WriteRune(r)
			}
		}

		i += width
	}

	b.WriteByte('"')
	return b.String()
}


================================================
FILE: internal/engine/quote_test.go
================================================
package engine_test

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/antonmedv/fx/internal/engine"
)

func TestQuote_BasicASCII(t *testing.T) {
	assert.Equal(t, "\"hello\"", engine.Quote("hello"))
	assert.Equal(t, "\"\"", engine.Quote(""))
	assert.Equal(t, "\"Hello, world!\"", engine.Quote("Hello, world!"))
}

func TestQuote_EscapesSpecialCharacters(t *testing.T) {
	assert.Equal(t, `"\""`, engine.Quote("\""))
	assert.Equal(t, `"\\"`, engine.Quote("\\"))
	assert.Equal(t, `"\b"`, engine.Quote("\b"))
	assert.Equal(t, `"\f"`, engine.Quote("\f"))
	assert.Equal(t, `"\n"`, engine.Quote("\n"))
	assert.Equal(t, `"\r"`, engine.Quote("\r"))
	assert.Equal(t, `"\t"`, engine.Quote("\t"))
}

func TestQuote_ControlCharactersAndDEL(t *testing.T) {
	hex4Lower := func(n int) string {
		const hexdigits = "0123456789abcdef"
		b0 := hexdigits[(n>>12)&0xF]
		b1 := hexdigits[(n>>8)&0xF]
		b2 := hexdigits[(n>>4)&0xF]
		b3 := hexdigits[n&0xF]
		return string([]byte{b0, b1, b2, b3})
	}

	// 0x00 .. 0x1F should be \uXXXX
	for b := 0; b < 0x20; b++ {
		s := string([]byte{byte(b)})
		q := engine.Quote(s)
		expected := "\"\\u" + hex4Lower(b) + "\""
		// For those with dedicated escapes, engine.Quote uses short escapes; both are valid.
		// We'll accept either short escape or \uXXXX for those particular bytes.
		switch b {
		case '\b':
			assert.Equal(t, `"\b"`, q)
		case '\f':
			assert.Equal(t, `"\f"`, q)
		case '\n':
			assert.Equal(t, `"\n"`, q)
		case '\r':
			assert.Equal(t, `"\r"`, q)
		case '\t':
			assert.Equal(t, `"\t"`, q)
		default:
			assert.Equal(t, expected, q, "byte %d", b)
		}
	}
	// 0x7F DEL
	assert.Equal(t, `"\u007f"`, engine.Quote(string([]byte{0x7F})))
}

func TestQuote_BMP_Characters_AsIs(t *testing.T) {
	// Latin-1 supplement, Cyrillic, CJK BMP characters should appear as-is
	assert.Equal(t, "\"café\"", engine.Quote("café"))
	assert.Equal(t, "\"Привет\"", engine.Quote("Привет"))
	assert.Equal(t, "\"漢字\"", engine.Quote("漢字"))
}

func TestQuote_SurrogatePairs_AsIs(t *testing.T) {
	assert.Equal(t, `"🚀"`, engine.Quote("🚀"))
	assert.Equal(t, `"👍🏻"`, engine.Quote("👍🏻"))
	assert.Equal(t, `"𝄞"`, engine.Quote("𝄞"))
}

func TestQuote_InvalidUTF8BytesAreEscaped(t *testing.T) {
	// Construct a string with invalid UTF-8 byte 0xFF and 0xC0 (overlong lead)
	s := string([]byte{'A', 0xFF, 'B', 0xC0, 'C'})
	got := engine.Quote(s)
	// Expect bytes to be escaped as \u00xx in lowercase hex
	want := `"A\u00ffB\u00c0C"`
	assert.Equal(t, want, got)
}

func TestQuote_JSONRoundTrip_ValidUTF8(t *testing.T) {
	tests := []struct{ input string }{
		{""},
		{"simple"},
		{"line\nfeed"},
		{"tab\tchar"},
		{"quote \" here"},
		{"backslash \\"},
		{"café"},
		{"Привет"},
		{"漢字"},
		{"emoji 🚀"},
		{"mix: \b\f\n\r\t and \u007F:" + string([]byte{0x7F})},
		{"Line1\n\t\"Quote\" and backslash \\ and DEL:" + string([]byte{0x7F}) + " and emoji 🚀"},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			q := engine.Quote(tt.input)
			var v string
			err := json.Unmarshal([]byte(q), &v)
			assert.NoError(t, err, "failed to unmarshal: %q", q)
			assert.Equal(t, tt.input, v)
		})
	}
}


================================================
FILE: internal/engine/slurp.go
================================================
package engine

import (
	"io"

	"github.com/antonmedv/fx/internal/jsonx"
)

func Slurp(parser Parser, writeErr func(string)) (Parser, bool) {
	arr := &jsonx.Node{
		Kind:       jsonx.Array,
		Value:      "[",
		LineNumber: 1,
	}

	end := arr
	for {
		node, err := parser.Parse()
		if err != nil {
			if err == io.EOF {
				break
			}
			writeErr(err.Error())
			return nil, false
		}

		node.Parent = arr

		it := node
		for it != nil {
			it.Depth++
			it.LineNumber++
			it = it.Next
		}

		end.Next = node
		end = node.Bottom()
		end.Comma = true
	}

	end.Comma = false
	end.Next = &jsonx.Node{
		Kind:       jsonx.Array,
		LineNumber: end.LineNumber + 1,
		Value:      "]",
	}
	arr.End = end.Next

	return &slurpParser{node: arr}, true
}

type slurpParser struct {
	node *jsonx.Node
}

func (p *slurpParser) Parse() (*jsonx.Node, error) {
	if p.node == nil {
		return nil, io.EOF
	}
	node := p.node
	p.node = nil
	return node, nil
}

func (p *slurpParser) Recover() *jsonx.Node {
	return nil
}


================================================
FILE: internal/engine/stdlib.js
================================================
'use strict'

const console = {
  log: function (...args) {
    const parts = []
    for (const arg of args) {
      if (typeof arg === 'undefined') {
        parts.push('undefined')
      } else if (typeof arg === 'string') {
        parts.push(arg)
      } else {
        parts.push(JSON.stringify(arg, null, 2))
      }
    }
    println(parts.join(' '))
  },
}

const skip = Symbol('skip')

function apply(fn, ...args) {
  if (typeof fn === 'function') return fn(...args)
  return fn
}

function len(x) {
  if (Array.isArray(x)) return x.length
  if (typeof x === 'string') return x.length
  if (typeof x === 'object' && x !== null) return Object.keys(x).length
  throw new Error(`Cannot get length of ${typeof x}`)
}

function uniq(x) {
  if (Array.isArray(x)) return [...new Set(x)]
  throw new Error(`Cannot get unique values of ${typeof x}`)
}

function sort(x) {
  if (Array.isArray(x)) return x.sort()
  throw new Error(`Cannot sort ${typeof x}`)
}

function isFalsely(x) {
  return x === false || x === null || x === undefined
}

function filter(fn) {
  return function (x) {
    if (Array.isArray(x)) {
      return x.filter((v, i) => !isFalsely(fn(v, i)))
    }
    return isFalsely(fn(x)) ? skip : x
  }
}

function map(fn) {
  return function (x) {
    if (Array.isArray(x)) {
      return x.map((v, i) => fn(v, i))
    }
    return fn(x)
  }
}

function walk(fn) {
  return function recurse(value, key = null) {
    if (Array.isArray(value)) {
      const mapped = value.map((v, i) => recurse(v, i))
      return fn(mapped, key)
    } else if (value !== null && typeof value === 'object') {
      const result = {}
      for (const [k, v] of Object.entries(value)) {
        result[k] = recurse(v, k)
      }
      return fn(result, key)
    } else {
      return fn(value, key)
    }
  }
}

function sortBy(fn) {
  return function (x) {
    if (Array.isArray(x)) return x.sort((a, b) => {
      const fa = fn(a)
      const fb = fn(b)
      return fa < fb ? -1 : fa > fb ? 1 : 0
    })
    throw new Error(`Cannot sort ${typeof x}`)
  }
}

function sortKeys(x) {
  if (Array.isArray(x)) {
    return x.map(sortKeys)
  }
  if (typeof x === 'object' && x !== null) {
    const sorted = {}
    for (const key of Object.keys(x).sort()) {
      sorted[key] = sortKeys(x[key])
    }
    return sorted
  }
  return x
}

function groupBy(keyFn) {
  return function (x) {
    const grouped = {}
    for (const item of x) {
      const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn]
      if (!Object.prototype.hasOwnProperty.call(grouped, key)) grouped[key] = []
      grouped[key].push(item)
    }
    return grouped
  }
}

function chunk(size) {
  return function (x) {
    const res = []
    let i = 0
    while (i < x.length) {
      res.push(x.slice(i, i += size))
    }
    return res
  }
}

function zip(...x) {
  const length = Math.min(...x.map(a => a.length))
  const res = []
  for (let i = 0; i < length; i++) {
    res.push(x.map(a => a[i]))
  }
  return res
}

function flatten(x) {
  if (Array.isArray(x)) return x.flat()
  throw new Error(`Cannot flatten ${typeof x}`)
}

function reverse(x) {
  if (Array.isArray(x)) return x.reverse()
  throw new Error(`Cannot reverse ${typeof x}`)
}

function keys(x) {
  if (typeof x === 'object' && x !== null) return Object.keys(x)
  throw new Error(`Cannot get keys of ${typeof x}`)
}

function values(x) {
  if (typeof x === 'object' && x !== null) return Object.values(x)
  throw new Error(`Cannot get values of ${typeof x}`)
}

function list(x) {
  if (Array.isArray(x)) {
    for (const y of x) console.log(y)
    return skip
  }
  throw new Error(`Cannot list ${typeof x}`)
}

function del(key) {
  return function (x) {
    if (Array.isArray(x)) {
      const copy = [...x]
      copy.splice(key, 1)
      return copy
    }
    if (typeof x === 'object' && x !== null) {
      const copy = {...x}
      delete copy[key]
      return copy
    }
    throw new Error(`Cannot delete key from ${typeof x}`)
  }
}

function exit(code) {
  __exit__(code)
}

function save(x) {
  if (typeof x === 'undefined') throw new Error('Cannot save undefined')
  __save__(__stringify__(x, null, 2))
  return x
}

function toBase64(x) {
  return __toBase64__(x)
}

function fromBase64(x) {
  return __fromBase64__(x)
}

const YAML = {
  stringify: x => __yaml_stringify__(x),
  parse: x => JSON.parse(__yaml_parse__(x)),
}


================================================
FILE: internal/engine/stdlib_test.go
================================================
package engine_test

import (
	"strings"
	"testing"

	"github.com/dop251/goja"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/antonmedv/fx/internal/engine"
)

// setupVM creates a new goja runtime with stdlib loaded.
func setupVM(t *testing.T) *goja.Runtime {
	var output []string
	vm := engine.NewVM(func(s string) {
		output = append(output, s)
	})
	_, err := vm.RunString(engine.Stdlib)
	require.NoError(t, err, "Failed to load stdlib")
	return vm
}

// setupVMWithOutput creates a new goja runtime with stdlib loaded and returns output slice.
func setupVMWithOutput(t *testing.T) (*goja.Runtime, *[]string) {
	output := &[]string{}
	vm := engine.NewVM(func(s string) {
		*output = append(*output, s)
	})
	_, err := vm.RunString(engine.Stdlib)
	require.NoError(t, err, "Failed to load stdlib")
	return vm, output
}

// TestApply tests the apply function.
func TestApply(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"apply function", "apply(x => x * 2, 5)", int64(10)},
		{"apply non-function", "apply(42)", int64(42)},
		{"apply with multiple args", "apply((a, b) => a + b, 2, 3)", int64(5)},
		{"apply string", "apply('hello')", "hello"},
		{"apply null", "apply(null)", nil},
		{"apply undefined", "apply(undefined)", goja.Undefined()},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			if tc.expected == goja.Undefined() {
				assert.True(t, goja.IsUndefined(result))
			} else {
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestLen tests the len function.
func TestLen(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"array length", "len([1, 2, 3])", int64(3), false},
		{"empty array", "len([])", int64(0), false},
		{"string length", "len('hello')", int64(5), false},
		{"empty string", "len('')", int64(0), false},
		{"object keys count", "len({a: 1, b: 2})", int64(2), false},
		{"empty object", "len({})", int64(0), false},
		{"number error", "len(42)", nil, true},
		{"null error", "len(null)", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestUniq tests the uniq function.
func TestUniq(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"unique numbers", "uniq([1, 2, 2, 3, 3, 3])", []interface{}{int64(1), int64(2), int64(3)}, false},
		{"unique strings", "uniq(['a', 'b', 'a', 'c'])", []interface{}{"a", "b", "c"}, false},
		{"already unique", "uniq([1, 2, 3])", []interface{}{int64(1), int64(2), int64(3)}, false},
		{"empty array", "uniq([])", []interface{}{}, false},
		{"non-array error", "uniq('hello')", nil, true},
		{"number error", "uniq(42)", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestSort tests the sort function.
func TestSort(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"sort numbers", "sort([3, 1, 2])", []interface{}{int64(1), int64(2), int64(3)}, false},
		{"sort strings", "sort(['c', 'a', 'b'])", []interface{}{"a", "b", "c"}, false},
		{"empty array", "sort([])", []interface{}{}, false},
		{"already sorted", "sort([1, 2, 3])", []interface{}{int64(1), int64(2), int64(3)}, false},
		{"non-array error", "sort('hello')", nil, true},
		{"number error", "sort(42)", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestFilter tests the filter function.
func TestFilter(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"filter even", "filter(x => x % 2 === 0)([1, 2, 3, 4])", []interface{}{int64(2), int64(4)}},
		{"filter with index", "filter((x, i) => i > 0)(['a', 'b', 'c'])", []interface{}{"b", "c"}},
		{"filter all true", "filter(x => true)([1, 2, 3])", []interface{}{int64(1), int64(2), int64(3)}},
		{"filter all false", "filter(x => false)([1, 2, 3])", []interface{}{}},
		{"filter empty", "filter(x => true)([])", []interface{}{}},
		{"filter non-array truthy", "filter(x => x > 0)(5)", int64(5)},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}

	// Test skip symbol for non-array falsy
	t.Run("filter non-array falsy returns skip", func(t *testing.T) {
		result, err := vm.RunString("filter(x => x > 10)(5) === skip")
		require.NoError(t, err)
		assert.Equal(t, true, result.Export())
	})

	// Test null/undefined/false filtering
	t.Run("filter removes null", func(t *testing.T) {
		result, err := vm.RunString("filter(x => null)([1, 2, 3])")
		require.NoError(t, err)
		assert.Equal(t, []interface{}{}, result.Export())
	})

	t.Run("filter removes undefined", func(t *testing.T) {
		result, err := vm.RunString("filter(x => undefined)([1, 2, 3])")
		require.NoError(t, err)
		assert.Equal(t, []interface{}{}, result.Export())
	})
}

// TestMap tests the map function.
func TestMap(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"map double", "map(x => x * 2)([1, 2, 3])", []interface{}{int64(2), int64(4), int64(6)}},
		{"map with index", "map((x, i) => i)(['a', 'b', 'c'])", []interface{}{int64(0), int64(1), int64(2)}},
		{"map empty", "map(x => x * 2)([])", []interface{}{}},
		{"map non-array", "map(x => x * 2)(5)", int64(10)},
		{"map to string", "map(x => String(x))([1, 2, 3])", []interface{}{"1", "2", "3"}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestWalk tests the walk function.
func TestWalk(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"walk multiply primitives", "walk(x => typeof x === 'number' ? x * 2 : x)({a: 1, b: 2})",
			map[string]interface{}{"a": int64(2), "b": int64(4)}},
		{"walk nested array", "walk(x => typeof x === 'number' ? x + 1 : x)([[1, 2], [3, 4]])",
			[]interface{}{[]interface{}{int64(2), int64(3)}, []interface{}{int64(4), int64(5)}}},
		{"walk primitive", "walk(x => x * 2)(5)", int64(10)},
		{"walk with key", "walk((v, k) => k === 'double' ? v * 2 : v)({double: 5, keep: 10})",
			map[string]interface{}{"double": int64(10), "keep": int64(10)}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestSortBy tests the sortBy function.
func TestSortBy(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"sortBy property", "sortBy(x => x.age)([{name: 'b', age: 30}, {name: 'a', age: 20}])",
			[]interface{}{
				map[string]interface{}{"name": "a", "age": int64(20)},
				map[string]interface{}{"name": "b", "age": int64(30)},
			}, false},
		{"sortBy computed", "sortBy(x => -x)([1, 3, 2])",
			[]interface{}{int64(3), int64(2), int64(1)}, false},
		{"sortBy string length", "sortBy(x => x.length)(['aaa', 'a', 'aa'])",
			[]interface{}{"a", "aa", "aaa"}, false},
		{"sortBy non-array error", "sortBy(x => x)('hello')", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestSortKeys tests the sortKeys function.
func TestSortKeys(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name  string
		code  string
		check func(t *testing.T, result goja.Value)
	}{
		{
			name: "sort object keys",
			code: "JSON.stringify(sortKeys({c: 1, a: 2, b: 3}))",
			check: func(t *testing.T, result goja.Value) {
				assert.Equal(t, `{"a":2,"b":3,"c":1}`, result.Export())
			},
		},
		{
			name: "sort nested object keys",
			code: "JSON.stringify(sortKeys({z: {c: 1, a: 2}, y: 3}))",
			check: func(t *testing.T, result goja.Value) {
				assert.Equal(t, `{"y":3,"z":{"a":2,"c":1}}`, result.Export())
			},
		},
		{
			name: "sort array of objects",
			code: "JSON.stringify(sortKeys([{b: 1, a: 2}, {d: 3, c: 4}]))",
			check: func(t *testing.T, result goja.Value) {
				assert.Equal(t, `[{"a":2,"b":1},{"c":4,"d":3}]`, result.Export())
			},
		},
		{
			name: "primitives unchanged",
			code: "sortKeys(42)",
			check: func(t *testing.T, result goja.Value) {
				assert.Equal(t, int64(42), result.Export())
			},
		},
		{
			name: "null unchanged",
			code: "sortKeys(null)",
			check: func(t *testing.T, result goja.Value) {
				assert.Nil(t, result.Export())
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			tc.check(t, result)
		})
	}
}

// TestGroupBy tests the groupBy function.
func TestGroupBy(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"groupBy function", "groupBy(x => x % 2 === 0 ? 'even' : 'odd')([1, 2, 3, 4])",
			map[string]interface{}{
				"odd":  []interface{}{int64(1), int64(3)},
				"even": []interface{}{int64(2), int64(4)},
			}},
		{"groupBy property name", "groupBy('type')([{type: 'a', v: 1}, {type: 'b', v: 2}, {type: 'a', v: 3}])",
			map[string]interface{}{
				"a": []interface{}{
					map[string]interface{}{"type": "a", "v": int64(1)},
					map[string]interface{}{"type": "a", "v": int64(3)},
				},
				"b": []interface{}{
					map[string]interface{}{"type": "b", "v": int64(2)},
				},
			}},
		{"groupBy empty", "groupBy(x => x)([])",
			map[string]interface{}{}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestChunk tests the chunk function.
func TestChunk(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"chunk by 2", "chunk(2)([1, 2, 3, 4, 5])",
			[]interface{}{
				[]interface{}{int64(1), int64(2)},
				[]interface{}{int64(3), int64(4)},
				[]interface{}{int64(5)},
			}},
		{"chunk by 3", "chunk(3)([1, 2, 3, 4, 5, 6])",
			[]interface{}{
				[]interface{}{int64(1), int64(2), int64(3)},
				[]interface{}{int64(4), int64(5), int64(6)},
			}},
		{"chunk empty", "chunk(2)([])",
			[]interface{}{}},
		{"chunk by 1", "chunk(1)([1, 2, 3])",
			[]interface{}{
				[]interface{}{int64(1)},
				[]interface{}{int64(2)},
				[]interface{}{int64(3)},
			}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestZip tests the zip function.
func TestZip(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"zip two arrays", "zip([1, 2, 3], ['a', 'b', 'c'])",
			[]interface{}{
				[]interface{}{int64(1), "a"},
				[]interface{}{int64(2), "b"},
				[]interface{}{int64(3), "c"},
			}},
		{"zip three arrays", "zip([1, 2], ['a', 'b'], [true, false])",
			[]interface{}{
				[]interface{}{int64(1), "a", true},
				[]interface{}{int64(2), "b", false},
			}},
		{"zip different lengths", "zip([1, 2, 3], ['a', 'b'])",
			[]interface{}{
				[]interface{}{int64(1), "a"},
				[]interface{}{int64(2), "b"},
			}},
		{"zip empty", "zip([], [])",
			[]interface{}{}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestFlatten tests the flatten function.
func TestFlatten(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"flatten nested", "flatten([[1, 2], [3, 4]])",
			[]interface{}{int64(1), int64(2), int64(3), int64(4)}, false},
		{"flatten mixed", "flatten([[1], 2, [3, 4]])",
			[]interface{}{int64(1), int64(2), int64(3), int64(4)}, false},
		{"flatten one level", "flatten([[[1, 2]], [[3, 4]]])",
			[]interface{}{
				[]interface{}{int64(1), int64(2)},
				[]interface{}{int64(3), int64(4)},
			}, false},
		{"flatten empty", "flatten([])", []interface{}{}, false},
		{"flatten non-array error", "flatten('hello')", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestReverse tests the reverse function.
func TestReverse(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"reverse numbers", "reverse([1, 2, 3])",
			[]interface{}{int64(3), int64(2), int64(1)}, false},
		{"reverse strings", "reverse(['a', 'b', 'c'])",
			[]interface{}{"c", "b", "a"}, false},
		{"reverse empty", "reverse([])", []interface{}{}, false},
		{"reverse single", "reverse([1])", []interface{}{int64(1)}, false},
		{"reverse non-array error", "reverse('hello')", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestKeys tests the keys function.
func TestKeys(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		hasError bool
	}{
		{"object keys", "keys({a: 1, b: 2}).sort()", false},
		{"empty object", "keys({})", false},
		{"array keys", "keys([10, 20, 30])", false},
		{"null error", "keys(null)", true},
		{"number error", "keys(42)", true},
		{"string error", "keys('hello')", true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.NotNil(t, result.Export())
			}
		})
	}

	// Specific value checks
	t.Run("object keys values", func(t *testing.T) {
		result, err := vm.RunString("keys({a: 1, b: 2}).sort()")
		require.NoError(t, err)
		assert.Equal(t, []interface{}{"a", "b"}, result.Export())
	})

	t.Run("array keys values", func(t *testing.T) {
		result, err := vm.RunString("keys([10, 20])")
		require.NoError(t, err)
		assert.Equal(t, []interface{}{"0", "1"}, result.Export())
	})
}

// TestValues tests the values function.
func TestValues(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		hasError bool
	}{
		{"object values", "values({a: 1, b: 2}).sort()", false},
		{"empty object", "values({})", false},
		{"array values", "values([10, 20, 30])", false},
		{"null error", "values(null)", true},
		{"number error", "values(42)", true},
		{"string error", "values('hello')", true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.NotNil(t, result.Export())
			}
		})
	}

	// Specific value checks
	t.Run("object values sorted", func(t *testing.T) {
		result, err := vm.RunString("values({a: 1, b: 2}).sort()")
		require.NoError(t, err)
		assert.Equal(t, []interface{}{int64(1), int64(2)}, result.Export())
	})

	t.Run("array values", func(t *testing.T) {
		result, err := vm.RunString("values([10, 20, 30])")
		require.NoError(t, err)
		assert.Equal(t, []interface{}{int64(10), int64(20), int64(30)}, result.Export())
	})
}

// TestList tests the list function.
func TestList(t *testing.T) {
	vm, output := setupVMWithOutput(t)

	t.Run("list prints each item", func(t *testing.T) {
		*output = []string{} // Reset output
		result, err := vm.RunString("list([1, 2, 3]) === skip")
		require.NoError(t, err)
		assert.True(t, result.Export().(bool))
		assert.Equal(t, []string{"1", "2", "3"}, *output)
	})

	t.Run("list with objects", func(t *testing.T) {
		*output = []string{}
		result, err := vm.RunString(`list([{a: 1}]) === skip`)
		require.NoError(t, err)
		assert.True(t, result.Export().(bool))
		assert.Len(t, *output, 1)
	})

	t.Run("list non-array error", func(t *testing.T) {
		_, err := vm.RunString("list('hello')")
		assert.Error(t, err)
	})
}

// TestDel tests the del function.
func TestDel(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
		hasError bool
	}{
		{"del from object", "del('a')({a: 1, b: 2})",
			map[string]interface{}{"b": int64(2)}, false},
		{"del non-existent key", "del('c')({a: 1, b: 2})",
			map[string]interface{}{"a": int64(1), "b": int64(2)}, false},
		{"del from array", "del(1)([1, 2, 3])",
			[]interface{}{int64(1), int64(3)}, false},
		{"del first from array", "del(0)([1, 2, 3])",
			[]interface{}{int64(2), int64(3)}, false},
		{"del last from array", "del(2)([1, 2, 3])",
			[]interface{}{int64(1), int64(2)}, false},
		{"del from empty array", "del(0)([])", []interface{}{}, false},
		{"del from primitive error", "del('a')(42)", nil, true},
		{"del from null error", "del('a')(null)", nil, true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}
}

// TestSkipSymbol tests the skip symbol.
func TestSkipSymbol(t *testing.T) {
	vm := setupVM(t)

	t.Run("skip is a symbol", func(t *testing.T) {
		result, err := vm.RunString("typeof skip")
		require.NoError(t, err)
		assert.Equal(t, "symbol", result.Export())
	})

	t.Run("skip is unique", func(t *testing.T) {
		result, err := vm.RunString("skip === skip")
		require.NoError(t, err)
		assert.True(t, result.Export().(bool))
	})
}

// TestConsoleLog tests console.log.
func TestConsoleLog(t *testing.T) {
	vm, output := setupVMWithOutput(t)

	tests := []struct {
		name     string
		code     string
		expected []string
	}{
		{"log string", "console.log('hello')", []string{"hello"}},
		{"log number", "console.log(42)", []string{"42"}},
		{"log object", "console.log({a: 1})", []string{"{\n  \"a\": 1\n}"}},
		{"log undefined", "console.log(undefined)", []string{"undefined"}},
		{"log multiple args", "console.log('a', 'b')", []string{"a b"}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			*output = []string{}
			_, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, *output)
		})
	}
}

// TestToBase64 tests the toBase64 function.
func TestToBase64(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"encode hello", "toBase64('hello')", "aGVsbG8="},
		{"encode empty", "toBase64('')", ""},
		{"encode unicode", "toBase64('こんにちは')", "44GT44KT44Gr44Gh44Gv"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.input)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestFromBase64 tests the fromBase64 function.
func TestFromBase64(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		input    string
		expected string
		hasError bool
	}{
		{"decode hello", "fromBase64('aGVsbG8=')", "hello", false},
		{"decode empty", "fromBase64('')", "", false},
		{"decode unicode", "fromBase64('44GT44KT44Gr44Gh44Gv')", "こんにちは", false},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.input)
			if tc.hasError {
				assert.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tc.expected, result.Export())
			}
		})
	}

	t.Run("invalid base64 error", func(t *testing.T) {
		_, err := vm.RunString("fromBase64('not-valid-base64!!!')")
		assert.Error(t, err)
	})
}

// TestYAML tests YAML.parse and YAML.stringify.
func TestYAML(t *testing.T) {
	vm := setupVM(t)

	t.Run("YAML.parse simple", func(t *testing.T) {
		result, err := vm.RunString(`YAML.parse('name: John\nage: 30')`)
		require.NoError(t, err)
		expected := map[string]interface{}{"name": "John", "age": int64(30)}
		assert.Equal(t, expected, result.Export())
	})

	t.Run("YAML.parse array", func(t *testing.T) {
		result, err := vm.RunString(`YAML.parse('- 1\n- 2\n- 3')`)
		require.NoError(t, err)
		expected := []interface{}{int64(1), int64(2), int64(3)}
		assert.Equal(t, expected, result.Export())
	})

	t.Run("YAML.stringify simple", func(t *testing.T) {
		result, err := vm.RunString(`YAML.stringify({name: 'John', age: 30})`)
		require.NoError(t, err)
		yamlStr := result.Export().(string)
		assert.Contains(t, yamlStr, "name: John")
		assert.Contains(t, yamlStr, "age: 30")
	})

	t.Run("YAML.stringify array", func(t *testing.T) {
		result, err := vm.RunString(`YAML.stringify([1, 2, 3])`)
		require.NoError(t, err)
		yamlStr := result.Export().(string)
		assert.Contains(t, yamlStr, "- 1")
		assert.Contains(t, yamlStr, "- 2")
		assert.Contains(t, yamlStr, "- 3")
	})

	t.Run("YAML.parse invalid", func(t *testing.T) {
		_, err := vm.RunString(`YAML.parse('invalid: [unclosed')`)
		assert.Error(t, err)
	})
}

// TestIsFalsely tests the internal isFalsely function behavior.
func TestIsFalsely(t *testing.T) {
	vm := setupVM(t)

	// isFalsely is used internally by filter
	// Testing through filter behavior

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{"false is falsely", "filter(x => false)([1])", []interface{}{}},
		{"null is falsely", "filter(x => null)([1])", []interface{}{}},
		{"undefined is falsely", "filter(x => undefined)([1])", []interface{}{}},
		{"0 is not falsely", "filter(x => 0)([1])", []interface{}{int64(1)}},
		{"empty string is not falsely", "filter(x => '')([1])", []interface{}{int64(1)}},
		{"true is not falsely", "filter(x => true)([1])", []interface{}{int64(1)}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestChainedOperations tests chaining multiple stdlib functions.
func TestChainedOperations(t *testing.T) {
	vm := setupVM(t)

	tests := []struct {
		name     string
		code     string
		expected interface{}
	}{
		{
			"filter then map",
			"map(x => x * 2)(filter(x => x > 1)([1, 2, 3]))",
			[]interface{}{int64(4), int64(6)},
		},
		{
			"map then filter",
			"filter(x => x > 3)(map(x => x * 2)([1, 2, 3]))",
			[]interface{}{int64(4), int64(6)},
		},
		{
			"sort then reverse",
			"reverse(sort([3, 1, 2]))",
			[]interface{}{int64(3), int64(2), int64(1)},
		},
		{
			"flatten then uniq",
			"uniq(flatten([[1, 2], [2, 3]]))",
			[]interface{}{int64(1), int64(2), int64(3)},
		},
		{
			"groupBy then keys",
			"keys(groupBy(x => x % 2)([1, 2, 3, 4])).sort()",
			[]interface{}{"0", "1"},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := vm.RunString(tc.code)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, result.Export())
		})
	}
}

// TestEdgeCases tests various edge cases.
func TestEdgeCases(t *testing.T) {
	vm := setupVM(t)

	t.Run("deeply nested walk", func(t *testing.T) {
		result, err := vm.RunString(`
			walk(x => typeof x === 'number' ? x * 2 : x)({
				a: {
					b: {
						c: 1
					}
				}
			})
		`)
		require.NoError(t, err)
		expected := map[string]interface{}{
			"a": map[string]interface{}{
				"b": map[string]interface{}{
					"c": int64(2),
				},
			},
		}
		assert.Equal(t, expected, result.Export())
	})

	t.Run("empty input handling", func(t *testing.T) {
		tests := []string{
			"len([])",
			"len({})",
			"len('')",
			"uniq([])",
			"sort([])",
			"flatten([])",
			"reverse([])",
			"filter(x => true)([])",
			"map(x => x)([])",
			"chunk(2)([])",
			"zip([], [])",
			"keys({})",
			"values({})",
		}
		for _, code := range tests {
			_, err := vm.RunString(code)
			assert.NoError(t, err, "Failed for: %s", code)
		}
	})

	t.Run("large array handling", func(t *testing.T) {
		result, err := vm.RunString(`
			const arr = [];
			for (let i = 0; i < 1000; i++) arr.push(i);
			len(arr)
		`)
		require.NoError(t, err)
		assert.Equal(t, int64(1000), result.Export())
	})

	t.Run("unicode strings", func(t *testing.T) {
		result, err := vm.RunString(`len('你好世界')`)
		require.NoError(t, err)
		assert.Equal(t, int64(4), result.Export())
	})
}

// TestBase64RoundTrip tests that base64 encoding/decoding is reversible.
func TestBase64RoundTrip(t *testing.T) {
	vm := setupVM(t)

	tests := []string{
		"hello",
		"",
		"Hello, 世界!",
		"Special chars: !@#$%^&*()",
		strings.Repeat("a", 1000),
	}

	for _, input := range tests {
		t.Run(input[:min(len(input), 20)], func(t *testing.T) {
			code := "fromBase64(toBase64('" + escapeJS(input) + "'))"
			result, err := vm.RunString(code)
			require.NoError(t, err)
			assert.Equal(t, input, result.Export())
		})
	}
}

func escapeJS(s string) string {
	s = strings.ReplaceAll(s, "\\", "\\\\")
	s = strings.ReplaceAll(s, "'", "\\'")
	s = strings.ReplaceAll(s, "\n", "\\n")
	s = strings.ReplaceAll(s, "\r", "\\r")
	return s
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// TestExit tests the exit function.
func TestExit(t *testing.T) {
	vm := setupVM(t)

	t.Run("exit panics with ExitError", func(t *testing.T) {
		defer func() {
			r := recover()
			require.NotNil(t, r, "Expected panic from exit()")
			exitErr, ok := r.(engine.ExitError)
			require.True(t, ok, "Expected ExitError, got %T", r)
			assert.Equal(t, 42, exitErr.Code)
		}()
		_, _ = vm.RunString("exit(42)")
	})

	t.Run("exit with 0", func(t *testing.T) {
		defer func() {
			r := recover()
			require.NotNil(t, r)
			exitErr, ok := r.(engine.ExitError)
			require.True(t, ok)
			assert.Equal(t, 0, exitErr.Code)
		}()
		_, _ = vm.RunString("exit(0)")
	})
}

// TestSave tests the save function.
func TestSave(t *testing.T) {
	vm := setupVM(t)

	t.Run("save undefined throws error", func(t *testing.T) {
		_, err := vm.RunString("save(undefined)")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "Cannot save undefined")
	})

	t.Run("save without file path throws error", func(t *testing.T) {
		// FilePath is empty by default in tests
		_, err := vm.RunString("save({a: 1})")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "specify a file")
	})
}

// TestSortMutation tests that sort mutates the original array (JavaScript behavior).
func TestSortMutation(t *testing.T) {
	vm := setupVM(t)

	t.Run("sort mutates original array", func(t *testing.T) {
		result, err := vm.RunString(`
			const arr = [3, 1, 2];
			sort(arr);
			arr[0]
		`)
		require.NoError(t, err)
		// JavaScript sort mutates the array
		assert.Equal(t, int64(1), result.Export())
	})
}

// TestReverseMutation tests that reverse mutates the original array (JavaScript behavior).
func TestReverseMutation(t *testing.T) {
	vm := setupVM(t)

	t.Run("reverse mutates original array", func(t *testing.T) {
		result, err := vm.RunString(`
			const arr = [1, 2, 3];
			reverse(arr);
			arr[0]
		`)
		require.NoError(t, err)
		// JavaScript reverse mutates the array
		assert.Equal(t, int64(3), result.Export())
	})
}

// TestDelImmutability tests that del does not mutate the original.
func TestDelImmutability(t *testing.T) {
	vm := setupVM(t)

	t.Run("del does not mutate original object", func(t *testing.T) {
		result, err := vm.RunString(`
			const obj = {a: 1, b: 2};
			del('a')(obj);
			obj.a
		`)
		require.NoError(t, err)
		assert.Equal(t, int64(1), result.Export())
	})

	t.Run("del does not mutate original array", func(t *testing.T) {
		result, err := vm.RunString(`
			const arr = [1, 2, 3];
			del(0)(arr);
			arr[0]
		`)
		require.NoError(t, err)
		assert.Equal(t, int64(1), result.Export())
	})
}

// TestWalkWithNull tests walk behavior with null values.
func TestWalkWithNull(t *testing.T) {
	vm := setupVM(t)

	t.Run("walk handles null values", func(t *testing.T) {
		result, err := vm.RunString(`
			walk(x => x === null ? 'was null' : x)({a: null, b: 1})
		`)
		require.NoError(t, err)
		expected := map[string]interface{}{"a": "was null", "b": int64(1)}
		assert.Equal(t, expected, result.Export())
	})
}

// TestGroupByWithPrototypeKeys tests groupBy doesn't have prototype pollution issues.
func TestGroupByWithPrototypeKeys(t *testing.T) {
	vm := setupVM(t)

	t.Run("groupBy with hasOwnProperty key", func(t *testing.T) {
		result, err := vm.RunString(`
			groupBy(x => x)(['hasOwnProperty', 'toString', 'normal'])
		`)
		require.NoError(t, err)
		exported := result.Export().(map[string]interface{})
		assert.Len(t, exported["hasOwnProperty"], 1)
		assert.Len(t, exported["toString"], 1)
		assert.Len(t, exported["normal"], 1)
	})
}

// TestChunkEdgeCases tests chunk with edge case sizes.
func TestChunkEdgeCases(t *testing.T) {
	vm := setupVM(t)

	t.Run("chunk size larger than array", func(t *testing.T) {
		result, err := vm.RunString("chunk(10)([1, 2, 3])")
		require.NoError(t, err)
		expected := []interface{}{[]interface{}{int64(1), int64(2), int64(3)}}
		assert.Equal(t, expected, result.Export())
	})

	t.Run("chunk size equals array length", func(t *testing.T) {
		result, err := vm.RunString("chunk(3)([1, 2, 3])")
		require.NoError(t, err)
		expected := []interface{}{[]interface{}{int64(1), int64(2), int64(3)}}
		assert.Equal(t, expected, result.Export())
	})
}

// TestZipEdgeCases tests zip with edge cases.
func TestZipEdgeCases(t *testing.T) {
	vm := setupVM(t)

	t.Run("zip single array", func(t *testing.T) {
		result, err := vm.RunString("zip([1, 2, 3])")
		require.NoError(t, err)
		expected := []interface{}{
			[]interface{}{int64(1)},
			[]interface{}{int64(2)},
			[]interface{}{int64(3)},
		}
		assert.Equal(t, expected, result.Export())
	})

	t.Run("zip with one empty array", func(t *testing.T) {
		result, err := vm.RunString("zip([1, 2, 3], [])")
		require.NoError(t, err)
		expected := []interface{}{}
		assert.Equal(t, expected, result.Export())
	})
}

// TestFilterWithObjects tests filter with object predicates.
func TestFilterWithObjects(t *testing.T) {
	vm := setupVM(t)

	t.Run("filter objects by property", func(t *testing.T) {
		result, err := vm.RunString(`
			filter(x => x.active)([
				{name: 'a', active: true},
				{name: 'b', active: false},
				{name: 'c', active: true}
			])
		`)
		require.NoError(t, err)
		exported := result.Export().([]interface{})
		assert.Len(t, exported, 2)
	})
}

// TestMapWithObjects tests map transforming objects.
func TestMapWithObjects(t *testing.T) {
	vm := setupVM(t)

	t.Run("map extract property", func(t *testing.T) {
		result, err := vm.RunString(`
			map(x => x.name)([
				{name: 'a', value: 1},
				{name: 'b', value: 2}
			])
		`)
		require.NoError(t, err)
		expected := []interface{}{"a", "b"}
		assert.Equal(t, expected, result.Export())
	})

	t.Run("map transform object", func(t *testing.T) {
		result, err := vm.RunString(`
			map(x => ({...x, doubled: x.value * 2}))([
				{value: 1},
				{value: 2}
			])
		`)
		require.NoError(t, err)
		exported := result.Export().([]interface{})
		assert.Equal(t, int64(2), exported[0].(map[string]interface{})["doubled"])
		assert.Equal(t, int64(4), exported[1].(map[string]interface{})["doubled"])
	})
}

// TestSortByStability tests sortBy with equal keys.
func TestSortByStability(t *testing.T) {
	vm := setupVM(t)

	t.Run("sortBy preserves order for equal keys", func(t *testing.T) {
		// Note: JavaScript sort is not guaranteed to be stable, but this tests the behavior
		result, err := vm.RunString(`
			sortBy(x => x.group)([
				{name: 'a', group: 1},
				{name: 'b', group: 2},
				{name: 'c', group: 1}
			]).map(x => x.name)
		`)
		require.NoError(t, err)
		exported := result.Export().([]interface{})
		// Group 1 items should come before group 2
		assert.Equal(t, "b", exported[2])
	})
}

// TestNestedOperations tests deeply nested function compositions.
func TestNestedOperations(t *testing.T) {
	vm := setupVM(t)

	t.Run("complex nested operations", func(t *testing.T) {
		result, err := vm.RunString(`
			map(x => x * 2)(
				filter(x => x > 0)(
					flatten([
						[-1, 0, 1],
						[2, 3]
					])
				)
			)
		`)
		require.NoError(t, err)
		expected := []interface{}{int64(2), int64(4), int64(6)}
		assert.Equal(t, expected, result.Export())
	})
}


================================================
FILE: internal/engine/stringify.go
================================================
package engine

import (
	"fmt"
	"math"
	"math/big"
	"reflect"
	"strings"
	"time"

	"github.com/dop251/goja"
)

func Stringify(value goja.Value, vm *goja.Runtime, depth int) string {
	rtype := value.ExportType()
	if rtype == nil {
		// Convert both null and undefined to null (save as JSON.stringify)
		return "null"
	}

	switch rtype {
	case bigIntType:
		bi := value.Export().(*big.Int)
		return bi.String()
	case timeTimeType:
		t := value.Export().(time.Time)
		quoted := Quote(t.String())
		return quoted
	}

	switch rtype.Kind() {
	case reflect.Bool:
		if value.ToBoolean() {
			return "true"
		} else {
			return "false"
		}

	case reflect.Int64:
		return value.String()

	case reflect.Float64:
		f := value.ToFloat()
		if math.IsInf(f, 0) {
			return value.String()
		} else if math.IsNaN(f) {
			return value.String()
		}
		return value.String()

	case reflect.String:
		return Quote(value.String())

	case reflect.Map:
		obj := value.ToObject(vm)
		keys := obj.Keys()

		if len(keys) == 0 {
			return "{}"
		}

		var out strings.Builder
		out.WriteString("{")
		out.WriteString("\n")

		ident := strings.Repeat("  ", depth)
		identKey := strings.Repeat("  ", depth+1)

		for i, key := range keys {
			out.WriteString(identKey)
			out.WriteString(Quote(key))
			out.WriteString(":")
			out.WriteString(" ")
			out.WriteString(Stringify(obj.Get(key), vm, depth+1))
			if i < len(keys)-1 {
				out.WriteString(",")
			}
			out.WriteString("\n")

		}

		out.WriteString(ident)
		out.WriteString("}")
		return out.String()

	case reflect.Slice:
		arr := value.ToObject(vm)
		keys := arr.Keys()

		if len(keys) == 0 {
			return "[]"
		}

		var out strings.Builder
		out.WriteString("[")
		out.WriteString("\n")

		for i, key := range keys {
			item := arr.Get(key)
			out.WriteString(strings.Repeat("  ", depth+1))
			out.WriteString(Stringify(item, vm, depth+1))
			if i < len(keys)-1 {
				out.WriteString(",")
			}
			out.WriteString("\n")
		}

		out.WriteString(strings.Repeat("  ", depth))
		out.WriteString("]")
		return out.String()
	}
	panic(fmt.Sprintf("Unsupported value type: %v", rtype.Kind()))
}


================================================
FILE: internal/engine/transpile.go
================================================
package engine

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

func JS(args []string) string {
	var code strings.Builder
	code.WriteString("\nfunction __main__(json) {\n")
	for i := range args {
		if args[i] == "" {
			// In autocomplete: after dropTail, we can have empty strings.
			continue
		}
		code.WriteString(Body(args, i))
	}
	code.WriteString("\n  return json\n}\n")
	return code.String()
}

func Body(args []string, i int) string {
	jsCode := transpile(args[i])
	snippet := formatErr(args, i, jsCode)
	return fmt.Sprintf(`
  try {
    json = apply((function () {
      const x = this
      return %s
    }).call(json), json)
  } catch (e) {
    throw %s
  }

  if (json === skip) return skip
`, jsCode, strconv.Quote(snippet)+" + e.toString()")
}

var (
	reBracket      = regexp.MustCompile(`^(\.\w*)+\[]`)
	reBracketStart = regexp.MustCompile(`^\.\[`)
	reDotStart     = regexp.MustCompile(`^\.`)
	reAt           = regexp.MustCompile(`^@`)
	reFilter       = regexp.MustCompile(`^\?`)
)

func transpile(code string) string {
	if code == "." {
		return "x"
	}

	if reBracket.MatchString(code) {
		return fmt.Sprintf("(%s)(x)", fold(strings.Split(code, "[]")))
	}

	if reBracketStart.MatchString(code) {
		return "x" + code[1:]
	}

	if reDotStart.MatchString(code) {
		return "x" + code
	}

	if reAt.MatchString(code) {
		jsCode := transpile(code[1:])
		return fmt.Sprintf(`map((x, i) => apply(%s, x, i))`, jsCode)
	}

	if reFilter.MatchString(code) {
		jsCode := transpile(code[1:])
		return fmt.Sprintf(`filter((x, i) => apply(%s, x, i))`, jsCode)
	}

	return code
}

func fold(s []string) string {
	if len(s) == 1 {
		return "x => x" + s[0]
	}
	obj := s[0]
	s = s[1:]
	if obj == "." {
		obj = "x"
	} else {
		obj = "x" + obj
	}
	return fmt.Sprintf(`x => %s.flatMap(%s)`, obj, fold(s))
}


================================================
FILE: internal/engine/transpile_test.go
================================================
package engine

import (
	"testing"
)

func TestTranspile(t *testing.T) {
	tests := []struct {
		code string
		want string
	}{
		{".", "x"},
		{".foo", "x.foo"},
		{".[0]", "x[0]"},
		{"foo", "foo"},
		{"@.baz", "map((x, i) => apply(x.baz, x, i))"},
		{"?.foo > 42", "filter((x, i) => apply(x.foo > 42, x, i))"},
		{".foo[].bar[]", "(x => x.foo.flatMap(x => x.bar.flatMap(x => x)))(x)"},
	}
	for _, tt := range tests {
		t.Run(tt.code, func(t *testing.T) {
			got := transpile(tt.code)
			if got != tt.want {
				t.Errorf("transpile(%q) = %q; want %q", tt.code, got, tt.want)
			}
		})
	}
}

func TestFoldSimple(t *testing.T) {
	tests := []struct {
		parts []string
		want  string
	}{
		{[]string{".foo"}, "x => x.foo"},
		{[]string{".foo", ".bar"}, "x => x.foo.flatMap(x => x.bar)"},
	}
	for _, tt := range tests {
		got := fold(tt.parts)
		if got != tt.want {
			t.Errorf("fold(%v) = %q; want %q", tt.parts, got, tt.want)
		}
	}
}


================================================
FILE: internal/engine/utils.go
================================================
package engine

import (
	"math/big"
	"reflect"
	"regexp"
	"time"

	"github.com/dop251/goja"
)

var (
	bigIntType   = reflect.TypeOf((*big.Int)(nil))
	timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
)

var (
	syntaxErrorRe = regexp.MustCompile(`^SyntaxError: SyntaxError: \(anonymous\): Line \d+:\d+\s+`)
	andMoreErrors = regexp.MustCompile(`\(and \d+ more errors\)$`)
)

func extractErrorMessage(s string) string {
	s = syntaxErrorRe.ReplaceAllString(s, "")
	s = andMoreErrors.ReplaceAllString(s, "")
	return s
}

func errorToString(err error) string {
	if exception, ok := err.(*goja.Exception); ok {
		message := exception.Value().String()
		message = extractErrorMessage(message)
		return message
	}
	return err.Error()
}


================================================
FILE: internal/engine/vm.go
================================================
package engine

import (
	"encoding/base64"
	"fmt"
	"os"

	"github.com/dop251/goja"
	"github.com/goccy/go-yaml"
)

// FilePath is the file being processed, empty if stdin.
var FilePath string

// ExitError is used by exit() to signal a specific exit code.
type ExitError struct {
	Code int
}

func NewVM(writeOut func(string)) *goja.Runtime {
	vm := goja.New()

	if err := vm.Set("println", func(s string) any {
		writeOut(s)
		return nil
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__save__", func(json string) error {
		if FilePath == "" {
			return fmt.Errorf("specify a file as the first argument to be able to save: fx file.json ")
		}
		if info, err := os.Lstat(FilePath); err == nil && info.Mode()&os.ModeSymlink != 0 {
			return fmt.Errorf("cannot save to a symbolic link: %s", FilePath)
		}
		if err := os.WriteFile(FilePath, []byte(json), 0644); err != nil {
			return err
		}
		return nil
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__stringify__", func(x goja.Value) string {
		return Stringify(x, vm, 0) + "\n"
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__toBase64__", func(x string) string {
		return base64.StdEncoding.EncodeToString([]byte(x))
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__fromBase64__", func(x string) (string, error) {
		decoded, err := base64.StdEncoding.DecodeString(x)
		if err != nil {
			return "", err
		}
		return string(decoded), err
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__yaml_parse__", func(x string) (string, error) {
		b, err := yaml.YAMLToJSON([]byte(x))
		if err != nil {
			return "", err
		}
		return string(b), err
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__yaml_stringify__", func(x goja.Value) string {
		b, err := yaml.JSONToYAML([]byte(Stringify(x, vm, 0)))
		if err != nil {
			return ""
		}
		return string(b)
	}); err != nil {
		panic(err)
	}

	if err := vm.Set("__exit__", func(code int) {
		panic(ExitError{Code: code})
	}); err != nil {
		panic(err)
	}

	return vm
}


================================================
FILE: internal/fuzzy/algo.go
================================================
package fuzzy

import (
	"bytes"
	"strings"
	"unicode"
	"unicode/utf8"
)

var delimiterChars = "."

const whiteChars = " \t\n\v\f\r\x85\xA0"

type Result struct {
	Start int
	End   int
	Score int
}

const (
	scoreMatch        = 16
	scoreGapStart     = -3
	scoreGapExtension = -1

	// We prefer matches at the beginning of a word, but the bonus should not be
	// too great to prevent the longer acronym matches from always winning over
	// shorter fuzzy matches. The bonus point here was specifically chosen that
	// the bonus is cancelled when the gap between the acronyms grows over
	// 8 characters, which is approximately the average length of the words found
	// in web2 dictionary and my file system.
	bonusBoundary = scoreMatch / 2

	// Although bonus point for non-word characters is non-contextual, we need it
	// for computing bonus points for consecutive chunks starting with a non-word
	// character.
	bonusNonWord = scoreMatch / 2

	// Edge-triggered bonus for matches in camelCase words.
	// Compared to word-boundary case, they don't accompany single-character gaps
	// (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.
	bonusCamel123 = bonusBoundary + scoreGapExtension

	// Minimum bonus point given to characters in consecutive chunks.
	// Note that bonus points for consecutive matches shouldn't have needed if we
	// used fixed match score as in the original algorithm.
	bonusConsecutive = -(scoreGapStart + scoreGapExtension)

	// The first character in the typed pattern usually has more significance
	// than the rest so it's important that it appears at special positions where
	// bonus points are given, e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
	// The amount of the extra bonus should be limited so that the gap penalty is
	// still respected.
	bonusFirstCharMultiplier = 2
)

var (
	// Extra bonus for word boundary after whitespace character or beginning of the string
	bonusBoundaryWhite int16 = bonusBoundary

	// Extra bonus for word boundary after slash, colon, semi-colon, and comma
	bonusBoundaryDelimiter int16 = bonusBoundary + 1

	initialCharClass = charDelimiter

	// A minor optimization that can give 15%+ performance boost
	asciiCharClasses [unicode.MaxASCII + 1]charClass

	// A minor optimization that can give yet another 5% performance boost
	bonusMatrix [charNumber + 1][charNumber + 1]int16
)

type charClass int

const (
	charWhite charClass = iota
	charNonWord
	charDelimiter
	charLower
	charUpper
	charLetter
	charNumber
)

func init() {
	for i := 0; i <= unicode.MaxASCII; i++ {
		char := rune(i)
		c := charNonWord
		if char >= 'a' && char <= 'z' {
			c = charLower
		} else if char >= 'A' && char <= 'Z' {
			c = charUpper
		} else if char >= '0' && char <= '9' {
			c = charNumber
		} else if strings.ContainsRune(whiteChars, char) {
			c = charWhite
		} else if strings.ContainsRune(delimiterChars, char) {
			c = charDelimiter
		}
		asciiCharClasses[i] = c
	}
	for i := 0; i <= int(charNumber); i++ {
		for j := 0; j <= int(charNumber); j++ {
			bonusMatrix[i][j] = bonusFor(charClass(i), charClass(j))
		}
	}
}

func posArray(withPos bool, len int) *[]int {
	if withPos {
		pos := make([]int, 0, len)
		return &pos
	}
	return nil
}

func alloc16(offset int, slab *Slab, size int) (int, []int16) {
	if slab != nil && cap(slab.I16) > offset+size {
		slice := slab.I16[offset : offset+size]
		return offset + size, slice
	}
	return offset, make([]int16, size)
}

func alloc32(offset int, slab *Slab, size int) (int, []int32) {
	if slab != nil && cap(slab.I32) > offset+size {
		slice := slab.I32[offset : offset+size]
		return offset + size, slice
	}
	return offset, make([]int32, size)
}

func charClassOfNonAscii(char rune) charClass {
	if unicode.IsLower(char) {
		return charLower
	} else if unicode.IsUpper(char) {
		return charUpper
	} else if unicode.IsNumber(char) {
		return charNumber
	} else if unicode.IsLetter(char) {
		return charLetter
	} else if unicode.IsSpace(char) {
		return charWhite
	} else if strings.ContainsRune(delimiterChars, char) {
		return charDelimiter
	}
	return charNonWord
}

func charClassOf(char rune) charClass {
	if char <= unicode.MaxASCII {
		return asciiCharClasses[char]
	}
	return charClassOfNonAscii(char)
}

func bonusFor(prevClass charClass, class charClass) int16 {
	if class > charNonWord {
		switch prevClass {
		case charWhite:
			// Word boundary after whitespace
			return bonusBoundaryWhite
		case charDelimiter:
			// Word boundary after a delimiter character
			return bonusBoundaryDelimiter
		case charNonWord:
			// Word boundary
			return bonusBoundary
		}
	}

	if prevClass == charLower && class == charUpper ||
		prevClass != charNumber && class == charNumber {
		// camelCase letter123
		return bonusCamel123
	}

	switch class {
	case charNonWord, charDelimiter:
		return bonusNonWord
	case charWhite:
		return bonusBoundaryWhite
	}
	return 0
}

func normalizeRune(r rune) rune {
	if r < 0x00C0 || r > 0x2184 {
		return r
	}

	n := normalized[r]
	if n > 0 {
		return n
	}
	return r
}

func trySkip(input *Chars, caseSensitive bool, b byte, from int) int {
	byteArray := input.Bytes()[from:]
	idx := bytes.IndexByte(byteArray, b)
	if idx == 0 {
		// Can't skip any further
		return from
	}
	// We may need to search for the uppercase letter again. We don't have to
	// consider normalization as we can be sure that this is an ASCII string.
	if !caseSensitive && b >= 'a' && b <= 'z' {
		if idx > 0 {
			byteArray = byteArray[:idx]
		}
		uidx := bytes.IndexByte(byteArray, b-32)
		if uidx >= 0 {
			idx = uidx
		}
	}
	if idx < 0 {
		return -1
	}
	return from + idx
}

func isAscii(runes []rune) bool {
	for _, r := range runes {
		if r >= utf8.RuneSelf {
			return false
		}
	}
	return true
}

func asciiFuzzyIndex(input *Chars, pattern []rune, caseSensitive bool) (int, int) {
	// Can't determine
	if !input.IsBytes() {
		return 0, input.Length()
	}

	// Not possible
	if !isAscii(pattern) {
		return -1, -1
	}

	firstIdx, idx, lastIdx := 0, 0, 0
	var b byte
	for pidx := 0; pidx < len(pattern); pidx++ {
		b = byte(pattern[pidx])
		idx = trySkip(input, caseSensitive, b, idx)
		if idx < 0 {
			return -1, -1
		}
		if pidx == 0 && idx > 0 {
			// Step back to find the right bonus point
			firstIdx = idx - 1
		}
		lastIdx = idx
		idx++
	}

	// Find the last appearance of the last character of the pattern to limit the search scope
	bu := b
	if !caseSensitive && b >= 'a' && b <= 'z' {
		bu = b - 32
	}
	scope := input.Bytes()[lastIdx:]
	for offset := len(scope) - 1; offset > 0; offset-- {
		if scope[offset] == b || scope[offset] == bu {
			return firstIdx, lastIdx + offset + 1
		}
	}
	return firstIdx, lastIdx + 1
}

type Slab struct {
	I16 []int16
	I32 []int32
}

const (
	caseSensitive = false
	normalize     = true
	forward       = true
)

func fuzzyMatch(input *Chars, pattern []rune) (Result, *[]int) {
	var slab *Slab
	// Assume that pattern is given in lowercase if case-insensitive.
	// First check if there's a match and calculate bonus for each position.
	// If the input string is too long, consider finding the matching chars in
	// this phase as well (non-optimal alignment).
	M := len(pattern)
	if M == 0 {
		return Result{0, 0, 0}, posArray(true, M)
	}
	N := input.Length()
	if M > N {
		return Result{-1, -1, 0}, nil
	}

	// Phase 1. Optimized search for ASCII string
	minIdx, maxIdx := asciiFuzzyIndex(input, pattern, caseSensitive)
	if minIdx < 0 {
		return Result{-1, -1, 0}, nil
	}
	// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())
	N = maxIdx - minIdx

	// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
	offset16 := 0
	offset32 := 0
	offset16, H0 := alloc16(offset16, slab, N)
	offset16, C0 := alloc16(offset16, slab, N)
	// Bonus point for each position
	offset16, B := alloc16(offset16, slab, N)
	// The first occurrence of each character in the pattern
	offset32, F := alloc32(offset32, slab, M)
	// Rune array
	_, T := alloc32(offset32, slab, N)
	input.CopyRunes(T, minIdx)

	// Phase 2. Calculate bonus for each point
	maxScore, maxScorePos := int16(0), 0
	pidx, lastIdx := 0, 0
	pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
	for off, char := range T {
		var class charClass
		if char <= unicode.MaxASCII {
			class = asciiCharClasses[char]
			if !caseSensitive && class == charUpper {
				char += 32
				T[off] = char
			}
		} else {
			class = charClassOfNonAscii(char)
			if !caseSensitive && class == charUpper {
				char = unicode.To(unicode.LowerCase, char)
			}
			if normalize {
				char = normalizeRune(char)
			}
			T[off] = char
		}

		bonus := bonusMatrix[prevClass][class]
		B[off] = bonus
		prevClass = class

		if char == pchar {
			if pidx < M {
				F[pidx] = int32(off)
				pidx++
				pchar = pattern[min(pidx, M-1)]
			}
			lastIdx = off
		}

		if char == pchar0 {
			score := scoreMatch + bonus*bonusFirstCharMultiplier
			H0[off] = score
			C0[off] = 1
			if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
				maxScore, maxScorePos = score, off
				if forward && bonus >= bonusBoundary {
					break
				}
			}
			inGap = false
		} else {
			if inGap {
				H0[off] = max(prevH0+scoreGapExtension, 0)
			} else {
				H0[off] = max(prevH0+scoreGapStart, 0)
			}
			C0[off] = 0
			inGap = true
		}
		prevH0 = H0[off]
	}
	if pidx != M {
		return Result{-1, -1, 0}, nil
	}
	if M == 1 {
		result := Result{minIdx + maxScorePos, minIdx + maxScorePos + 1, int(maxScore)}
		pos := []int{minIdx + maxScorePos}
		return result, &pos
	}

	// Phase 3. Fill in score matrix (H)
	// Unlike the original algorithm, we do not allow omission.
	f0 := int(F[0])
	width := lastIdx - f0 + 1
	offset16, H := alloc16(offset16, slab, width*M)
	copy(H, H0[f0:lastIdx+1])

	// Possible length of consecutive chunk at each position.
	_, C := alloc16(offset16, slab, width*M)
	copy(C, C0[f0:lastIdx+1])

	Fsub := F[1:]
	Psub := pattern[1:][:len(Fsub)]
	for off, f := range Fsub {
		f := int(f)
		pchar := Psub[off]
		pidx := off + 1
		row := pidx * width
		inGap := false
		Tsub := T[f : lastIdx+1]
		Bsub := B[f:][:len(Tsub)]
		Csub := C[row+f-f0:][:len(Tsub)]
		Cdiag := C[row+f-f0-1-width:][:len(Tsub)]
		Hsub := H[row+f-f0:][:len(Tsub)]
		Hdiag := H[row+f-f0-1-width:][:len(Tsub)]
		Hleft := H[row+f-f0-1:][:len(Tsub)]
		Hleft[0] = 0
		for off, char := range Tsub {
			col := off + f
			var s1, s2, consecutive int16

			if inGap {
				s2 = Hleft[off] + scoreGapExtension
			} else {
				s2 = Hleft[off] + scoreGapStart
			}

			if pchar == char {
				s1 = Hdiag[off] + scoreMatch
				b := Bsub[off]
				consecutive = Cdiag[off] + 1
				if consecutive > 1 {
					fb := B[col-int(consecutive)+1]
					// Break consecutive chunk
					if b >= bonusBoundary && b > fb {
						consecutive = 1
					} else {
						b = max(b, max(bonusConsecutive, fb))
					}
				}
				if s1+b < s2 {
					s1 += Bsub[off]
					consecutive = 0
				} else {
					s1 += b
				}
			}
			Csub[off] = consecutive

			inGap = s1 < s2
			score := max(max(s1, s2), 0)
			if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
				maxScore, maxScorePos = score, col
			}
			Hsub[off] = score
		}
	}

	// Phase 4. (Optional) Backtrace to find character positions
	pos := posArray(true, M)
	j := f0
	i := M - 1
	j = maxScorePos
	preferMatch := true
	for {
		I := i * width
		j0 := j - f0
		s := H[I+j0]

		var s1, s2 int16
		if i > 0 && j >= int(F[i]) {
			s1 = H[I-width+j0-1]
		}
		if j > int(F[i]) {
			s2 = H[I+j0-1]
		}

		if s > s1 && (s > s2 || s == s2 && preferMatch) {
			*pos = append(*pos, j+minIdx)
			if i == 0 {
				break
			}
			i--
		}
		preferMatch = C[I+j0] > 1 || I+width+j0+1 < len(C) && C[I+width+j0+1] > 0
		j--
	}
	// Start offset we return here is only relevant when begin tiebreak is used.
	// However finding the accurate offset requires backtracking, and we don't
	// want to pay extra cost for the option that has lost its importance.
	return Result{minIdx + j, minIdx + maxScorePos + 1, int(maxScore)}, pos
}


================================================
FILE: internal/fuzzy/chars.go
================================================
package fuzzy

import (
	"bytes"
	"unicode"
	"unicode/utf8"
	"unsafe"
)

const (
	overflow64 uint64 = 0x8080808080808080
	overflow32 uint32 = 0x80808080
)

type Chars struct {
	slice           []byte // or []rune
	inBytes         bool
	trimLengthKnown bool
	trimLength      uint16

	// XXX Piggybacking item index here is a horrible idea. But I'm trying to
	// minimize the memory footprint by not wasting padded spaces.
	Index int32
}

func checkAscii(bytes []byte) (bool, int) {
	i := 0
	for ; i <= len(bytes)-8; i += 8 {
		if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
			return false, i
		}
	}
	for ; i <= len(bytes)-4; i += 4 {
		if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
			return false, i
		}
	}
	for ; i < len(bytes); i++ {
		if bytes[i] >= utf8.RuneSelf {
			return false, i
		}
	}
	return true, 0
}

// ToChars converts byte array into rune array
func ToChars(bytes []byte) Chars {
	inBytes, bytesUntil := checkAscii(bytes)
	if inBytes {
		return Chars{slice: bytes, inBytes: inBytes}
	}

	runes := make([]rune, bytesUntil, len(bytes))
	for i := 0; i < bytesUntil; i++ {
		runes[i] = rune(bytes[i])
	}
	for i := bytesUntil; i < len(bytes); {
		r, sz := utf8.DecodeRune(bytes[i:])
		i += sz
		runes = append(runes, r)
	}
	return RunesToChars(runes)
}

func RunesToChars(runes []rune) Chars {
	return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
}

func (chars *Chars) IsBytes() bool {
	return chars.inBytes
}

func (chars *Chars) Bytes() []byte {
	return chars.slice
}

func (chars *Chars) NumLines(atMost int) (int, bool) {
	lines := 1
	if runes := chars.optionalRunes(); runes != nil {
		for _, r := range runes {
			if r == '\n' {
				lines++
			}
			if lines > atMost {
				return atMost, true
			}
		}
		return lines, false
	}

	for idx := 0; idx < len(chars.slice); idx++ {
		found := bytes.IndexByte(chars.slice[idx:], '\n')
		if found < 0 {
			break
		}

		idx += found
		lines++
		if lines > atMost {
			return atMost, true
		}
	}
	return lines, false
}

func (chars *Chars) optionalRunes() []rune {
	if chars.inBytes {
		return nil
	}
	return *(*[]rune)(unsafe.Pointer(&chars.slice))
}

func (chars *Chars) Get(i int) rune {
	if runes := chars.optionalRunes(); runes != nil {
		return runes[i]
	}
	return rune(chars.slice[i])
}

func (chars *Chars) Length() int {
	if runes := chars.optionalRunes(); runes != nil {
		return len(runes)
	}
	return len(chars.slice)
}

// TrimLength returns the length after trimming leading and trailing whitespaces
func (chars *Chars) TrimLength() uint16 {
	if chars.trimLengthKnown {
		return chars.trimLength
	}
	chars.trimLengthKnown = true
	var i int
	len := chars.Length()
	for i = len - 1; i >= 0; i-- {
		char := chars.Get(i)
		if !unicode.IsSpace(char) {
			break
		}
	}
	// Completely empty
	if i < 0 {
		return 0
	}

	var j int
	for j = 0; j < len; j++ {
		char := chars.Get(j)
		if !unicode.IsSpace(char) {
			break
		}
	}
	chars.trimLength = AsUint16(i - j + 1)
	return chars.trimLength
}

func (chars *Chars) LeadingWhitespaces() int {
	whitespaces := 0
	for i := 0; i < chars.Length(); i++ {
		char := chars.Get(i)
		if !unicode.IsSpace(char) {
			break
		}
		whitespaces++
	}
	return whitespaces
}

func (chars *Chars) TrailingWhitespaces() int {
	whitespaces := 0
	for i := chars.Length() - 1; i >= 0; i-- {
		char := chars.Get(i)
		if !unicode.IsSpace(char) {
			break
		}
		whitespaces++
	}
	return whitespaces
}

func (chars *Chars) TrimTrailingWhitespaces() {
	whitespaces := chars.TrailingWhitespaces()
	chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
}

func (chars *Chars) TrimSuffix(runes []rune) {
	lastIdx := len(chars.slice)
	firstIdx := lastIdx - len(runes)
	if firstIdx < 0 {
		return
	}

	for i := firstIdx; i < lastIdx; i++ {
		char := chars.Get(i)
		if char != runes[i-firstIdx] {
			return
		}
	}

	chars.slice = chars.slice[0:firstIdx]
}

func (chars *Chars) SliceRight(last int) {
	chars.slice = chars.slice[:last]
}

func (chars *Chars) ToString() string {
	if runes := chars.optionalRunes(); runes != nil {
		return string(runes)
	}
	return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
}

func (chars *Chars) ToRunes() []rune {
	if runes := chars.optionalRunes(); runes != nil {
		return runes
	}
	bytes := chars.slice
	runes := make([]rune, len(bytes))
	for idx, b := range bytes {
		runes[idx] = rune(b)
	}
	return runes
}

func (chars *Chars) CopyRunes(dest []rune, from int) {
	if runes := chars.optionalRunes(); runes != nil {
		copy(dest, runes[from:])
		return
	}
	for idx, b := range chars.slice[from:][:len(dest)] {
		dest[idx] = rune(b)
	}
}

func (chars *Chars) Prepend(prefix string) {
	if runes := chars.optionalRunes(); runes != nil {
		runes = append([]rune(prefix), runes...)
		chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
	} else {
		chars.slice = append([]byte(prefix), chars.slice...)
	}
}

func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int) ([][]rune, bool) {
	text := make([]rune, chars.Length())
	copy(text, chars.ToRunes())

	lines := [][]rune{}
	overflow := false
	if !multiLine {
		lines = append(lines, text)
	} else {
		from := 0
		for off := 0; off < len(text); off++ {
			if text[off] == '\n' {
				lines = append(lines, text[from:off+1]) // Include '\n'
				from = off + 1
				if len(lines) >= maxLines {
					break
				}
			}
		}

		var lastLine []rune
		if from < len(text) {
			lastLine = text[from:]
		}

		overflow = false
		if len(lines) >= maxLines {
			overflow = true
		} else {
			lines = append(lines, lastLine)
		}
	}

	// If wrapping is disabled, we're done
	if wrapCols == 0 {
		return lines, overflow
	}

	wrapped := [][]rune{}
	for _, line := range lines {
		// Remove trailing '\n' and remember if it was there
		newline := len(line) > 0 && line[len(line)-1] == '\n'
		if newline {
			line = line[:len(line)-1]
		}

		hasWrapSign := false
		for {
			cols := wrapCols
			if hasWrapSign {
				cols -= wrapSignWidth
			}
			_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
			if overflowIdx >= 0 {
				// Might be a wide character
				if overflowIdx == 0 {
					overflowIdx = 1
				}
				if len(wrapped) >= maxLines {
					return wrapped, true
				}
				wrapped = append(wrapped, line[:overflowIdx])
				hasWrapSign = true
				line = line[overflowIdx:]
				continue
			}
			hasWrapSign = false

			// Restore trailing '\n'
			if newline {
				line = append(line, '\n')
			}

			if len(wrapped) >= maxLines {
				return wrapped, true
			}

			wrapped = append(wrapped, line)
			break
		}
	}

	return wrapped, overflow
}


================================================
FILE: internal/fuzzy/chars_test.go
================================================
package fuzzy

import (
	"fmt"
	"testing"
)

func TestToCharsAscii(t *testing.T) {
	chars := ToChars([]byte("foobar"))
	if !chars.inBytes || chars.ToString() != "foobar" || !chars.inBytes {
		t.Error()
	}
}

func TestCharsLength(t *testing.T) {
	chars := ToChars([]byte("\tabc한글  "))
	if chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {
		t.Error()
	}
}

func TestCharsToString(t *testing.T) {
	text := "\tabc한글  "
	chars := ToChars([]byte(text))
	if chars.ToString() != text {
		t.Error()
	}
}

func TestTrimLength(t *testing.T) {
	check := func(str string, exp uint16) {
		chars := ToChars([]byte(str))
		trimmed := chars.TrimLength()
		if trimmed != exp {
			t.Errorf("Invalid TrimLength result for '%s': %d (expected %d)",
				str, trimmed, exp)
		}
	}
	check("hello", 5)
	check("hello ", 5)
	check("hello  ", 5)
	check(" hello", 5)
	check("  hello", 5)
	check(" hello ", 5)
	check("  hello  ", 5)
	check("h   o", 5)
	check("  h   o  ", 5)
	check("         ", 0)
}

func TestCharsLines(t *testing.T) {
	chars := ToChars([]byte("abcdef\n가나다\n\tdef"))
	check := func(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, expectedNumLines int, expectedOverflow bool) {
		lines, overflow := chars.Lines(multiLine, maxLines, wrapCols, wrapSignWidth, tabstop)
		fmt.Println(lines, overflow)
		if len(lines) != expectedNumLines || overflow != expectedOverflow {
			t.Errorf("Invalid result: %d %v (expected %d %v)", len(lines), overflow, expectedNumLines, expectedOverflow)
		}
	}

	// No wrap
	check(true, 1, 0, 0, 8, 1, true)
	check(true, 2, 0, 0, 8, 2, true)
	check(true, 3, 0, 0, 8, 3, false)

	// Wrap (2)
	check(true, 4, 2, 0, 8, 4, true)
	check(true, 5, 2, 0, 8, 5, true)
	check(true, 6, 2, 0, 8, 6, true)
	check(true, 7, 2, 0, 8, 7, true)
	check(true, 8, 2, 0, 8, 8, true)
	check(true, 9, 2, 0, 8, 9, false)
	check(true, 9, 2, 0, 1, 8, false) // Smaller tab size

	// With wrap sign (3 + 1)
	check(true, 100, 3, 1, 1, 8, false)

	// With wrap sign (3 + 2)
	check(true, 100, 3, 2, 1, 10, false)

	// With wrap sign (3 + 2) and no multi-line
	check(false, 100, 3, 2, 1, 13, false)
}


================================================
FILE: internal/fuzzy/find.go
================================================
package fuzzy

type Match struct {
	Index int
	Str   string
	Score int
	Pos   []int
}

func Find(pattern []rune, array []string) *Match {
	var result Result
	var pos *[]int
	foundIndex := -1
	for i := range array {
		input := ToChars([]byte(array[i]))
		r, p := fuzzyMatch(&input, pattern)
		if r.Score > result.Score {
			result = r
			pos = p
			foundIndex = i
		}
	}
	if foundIndex >= 0 && pos != nil {
		return &Match{
			Index: foundIndex,
			Str:   array[foundIndex],
			Score: result.Score,
			Pos:   *pos,
		}
	}
	return nil
}


================================================
FILE: internal/fuzzy/fuzzy_test.go
================================================
package fuzzy

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestFind(t *testing.T) {
	tests := []struct {
		name      string
		pattern   string
		array     []string
		expectNil bool
		expectIdx int
		expectStr string
	}{
		{
			name:      "exact match",
			pattern:   "hello",
			array:     []string{"world", "hello", "foo"},
			expectNil: false,
			expectIdx: 1,
			expectStr: "hello",
		},
		{
			name:      "fuzzy match",
			pattern:   "hlo",
			array:     []string{"world", "hello", "foo"},
			expectNil: false,
			expectIdx: 1,
			expectStr: "hello",
		},
		{
			name:      "no match",
			pattern:   "xyz",
			array:     []string{"hello", "world", "foo"},
			expectNil: true,
		},
		{
			name:      "empty array",
			pattern:   "hello",
			array:     []string{},
			expectNil: true,
		},
		{
			name:      "empty pattern",
			pattern:   "",
			array:     []string{"hello", "world"},
			expectNil: true, // Empty pattern returns nil
		},
		{
			name:      "single char pattern",
			pattern:   "w",
			array:     []string{"hello", "world"},
			expectNil: false,
			expectIdx: 1,
			expectStr: "world",
		},
		{
			name:      "best match selected",
			pattern:   "foo",
			array:     []string{"foobar", "foo", "barfoo"},
			expectNil: false,
			expectIdx: 0, // Algorithm scores "foobar" highest due to consecutive bonus
			expectStr: "foobar",
		},
		{
			name:      "case insensitive",
			pattern:   "hello",
			array:     []string{"HELLO", "world"},
			expectNil: false,
			expectIdx: 0,
			expectStr: "HELLO",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := Find([]rune(tc.pattern), tc.array)
			if tc.expectNil {
				assert.Nil(t, result)
			} else {
				require.NotNil(t, result)
				assert.Equal(t, tc.expectIdx, result.Index)
				assert.Equal(t, tc.expectStr, result.Str)
				assert.GreaterOrEqual(t, result.Score, 0)
			}
		})
	}
}

func TestFuzzyMatch(t *testing.T) {
	tests := []struct {
		name        string
		input       string
		pattern     string
		expectMatch bool
	}{
		{"exact match", "hello", "hello", true},
		{"prefix match", "hello", "hel", true},
		{"suffix match", "hello", "llo", true},
		{"scattered match", "hello", "hlo", true},
		{"no match", "hello", "xyz", false},
		{"empty pattern", "hello", "", true},
		{"pattern longer than input", "hi", "hello", false},
		{"case insensitive", "HELLO", "hello", true},
		{"unicode input", "你好世界", "好界", true},
		{"unicode no match", "你好世界", "abc", false},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			input := ToChars([]byte(tc.input))
			result, pos := fuzzyMatch(&input, []rune(tc.pattern))
			if tc.expectMatch {
				assert.GreaterOrEqual(t, result.Start, 0, "Expected match but got Start=-1")
				if tc.pattern != "" {
					assert.NotNil(t, pos)
				}
			} else {
				assert.Equal(t, -1, result.Start, "Expected no match but got Start>=0")
			}
		})
	}
}

func TestFuzzyMatchScoring(t *testing.T) {
	input1 := ToChars([]byte("foobar"))
	input2 := ToChars([]byte("foo_bar"))

	// Exact prefix should score higher
	result1, _ := fuzzyMatch(&input1, []rune("foo"))
	result2, _ := fuzzyMatch(&input2, []rune("foo"))

	assert.Greater(t, result1.Score, 0)
	assert.Greater(t, result2.Score, 0)
}

func TestNormalizeRune(t *testing.T) {
	tests := []struct {
		name     string
		input    rune
		expected rune
	}{
		{"ascii a", 'a', 'a'},
		{"ascii A", 'A', 'A'},
		{"ascii 0", '0', '0'},
		{"a with acute", 'á', 'a'},
		{"a with grave", 'à', 'a'},
		{"a with circumflex", 'â', 'a'},
		{"e with acute", 'é', 'e'},
		{"o with umlaut", 'ö', 'o'},
		{"n with tilde", 'ñ', 'n'},
		{"c with cedilla", 'ç', 'c'},
		// Capital letters
		{"A with acute", 'Á', 'A'},
		{"O with umlaut", 'Ö', 'O'},
		// Characters outside normalization range
		{"chinese", '中', '中'},
		{"emoji", '😀', '😀'},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := normalizeRune(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestNormalizeRunes(t *testing.T) {
	tests := []struct {
		name     string
		input    []rune
		expected []rune
	}{
		{"ascii only", []rune("hello"), []rune("hello")},
		{"with accents", []rune("héllo"), []rune("hello")},
		{"all accents", []rune("àéîõü"), []rune("aeiou")},
		{"mixed", []rune("café"), []rune("cafe")},
		{"empty", []rune(""), []rune("")},
		{"no change needed", []rune("test123"), []rune("test123")},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := NormalizeRunes(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestCharClass(t *testing.T) {
	tests := []struct {
		name     string
		char     rune
		expected charClass
	}{
		{"lowercase a", 'a', charLower},
		{"lowercase z", 'z', charLower},
		{"uppercase A", 'A', charUpper},
		{"uppercase Z", 'Z', charUpper},
		{"digit 0", '0', charNumber},
		{"digit 9", '9', charNumber},
		{"space", ' ', charWhite},
		{"tab", '\t', charWhite},
		{"newline", '\n', charWhite},
		{"dot delimiter", '.', charDelimiter},
		{"special char", '@', charNonWord},
		{"underscore", '_', charNonWord},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := charClassOf(tc.char)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestCharClassOfNonAscii(t *testing.T) {
	tests := []struct {
		name     string
		char     rune
		expected charClass
	}{
		{"unicode lower", 'ä', charLower},
		{"unicode upper", 'Ä', charUpper},
		{"unicode number", '①', charNumber},
		{"unicode letter", '中', charLetter},
		{"unicode space", '\u00A0', charWhite}, // non-breaking space
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := charClassOfNonAscii(tc.char)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestBonusFor(t *testing.T) {
	// Test word boundary bonuses
	assert.Equal(t, bonusBoundaryWhite, bonusFor(charWhite, charLower))
	assert.Equal(t, bonusBoundaryDelimiter, bonusFor(charDelimiter, charLower))
	assert.Equal(t, int16(bonusBoundary), bonusFor(charNonWord, charLower))

	// Test camelCase bonus
	assert.Equal(t, int16(bonusCamel123), bonusFor(charLower, charUpper))

	// Test number after non-number
	assert.Equal(t, int16(bonusCamel123), bonusFor(charLower, charNumber))
	assert.Equal(t, int16(0), bonusFor(charNumber, charNumber))
}

func TestAsUint16(t *testing.T) {
	tests := []struct {
		name     string
		input    int
		expected uint16
	}{
		{"zero", 0, 0},
		{"positive", 100, 100},
		{"max uint16", 65535, 65535},
		{"above max", 70000, 65535},
		{"negative", -1, 0},
		{"large negative", -1000, 0},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := AsUint16(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestStringWidth(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected int
	}{
		{"ascii", "hello", 5},
		{"empty", "", 0},
		{"with newline", "a\nb", 3}, // 2 chars + 1 for newline
		{"with cr", "a\rb", 3},
		{"wide chars", "你好", 4}, // each Chinese char is 2 columns
		{"mixed", "ab你好cd", 8},  // 4 ascii + 4 wide
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := StringWidth(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestRunesWidth(t *testing.T) {
	tests := []struct {
		name          string
		runes         []rune
		prefixWidth   int
		tabstop       int
		limit         int
		expectWidth   int
		expectOverIdx int
	}{
		{
			name:          "ascii within limit",
			runes:         []rune("hello"),
			prefixWidth:   0,
			tabstop:       8,
			limit:         10,
			expectWidth:   5,
			expectOverIdx: -1,
		},
		{
			name:          "ascii exceeds limit",
			runes:         []rune("hello"),
			prefixWidth:   0,
			tabstop:       8,
			limit:         3,
			expectWidth:   4,
			expectOverIdx: 3,
		},
		{
			name:          "tab expansion",
			runes:         []rune("\t"),
			prefixWidth:   0,
			tabstop:       8,
			limit:         10,
			expectWidth:   8,
			expectOverIdx: -1,
		},
		{
			name:          "tab with prefix",
			runes:         []rune("\t"),
			prefixWidth:   3,
			tabstop:       8,
			limit:         10,
			expectWidth:   5, // 8 - 3 = 5
			expectOverIdx: -1,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			width, overIdx := RunesWidth(tc.runes, tc.prefixWidth, tc.tabstop, tc.limit)
			assert.Equal(t, tc.expectWidth, width)
			assert.Equal(t, tc.expectOverIdx, overIdx)
		})
	}
}

func TestTrySkip(t *testing.T) {
	tests := []struct {
		name          string
		input         string
		caseSensitive bool
		b             byte
		from          int
		expected      int
	}{
		{"find at start", "hello", false, 'h', 0, 0},
		{"find in middle", "hello", false, 'l', 0, 2},
		{"find from offset", "hello", false, 'l', 3, 3},
		{"not found", "hello", false, 'x', 0, -1},
		{"case insensitive upper", "HELLO", false, 'h', 0, 0},
		{"case sensitive no match", "HELLO", true, 'h', 0, -1},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			input := ToChars([]byte(tc.input))
			result := trySkip(&input, tc.caseSensitive, tc.b, tc.from)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestIsAscii(t *testing.T) {
	tests := []struct {
		name     string
		runes    []rune
		expected bool
	}{
		{"ascii only", []rune("hello"), true},
		{"empty", []rune(""), true},
		{"with unicode", []rune("hello世界"), false},
		{"unicode only", []rune("世界"), false},
		{"edge of ascii", []rune{127}, true},
		{"beyond ascii", []rune{128}, false},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := isAscii(tc.runes)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestAsciiFuzzyIndex(t *testing.T) {
	tests := []struct {
		name          string
		input         string
		pattern       string
		caseSensitive bool
		expectMin     int
		expectMax     int
	}{
		{"exact match", "hello", "hello", false, 0, 5},
		{"partial match", "hello world", "wor", false, 5, 9},
		{"no match", "hello", "xyz", false, -1, -1},
		{"case insensitive", "HELLO", "hel", false, 0, 3},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			input := ToChars([]byte(tc.input))
			minIdx, maxIdx := asciiFuzzyIndex(&input, []rune(tc.pattern), tc.caseSensitive)
			assert.Equal(t, tc.expectMin, minIdx)
			if tc.expectMin >= 0 {
				assert.GreaterOrEqual(t, maxIdx, tc.expectMax)
			}
		})
	}
}


================================================
FILE: internal/fuzzy/normalize.go
================================================
// Normalization of latin script letters
// Reference: http://www.unicode.org/Public/UCD/latest/ucd/Index.txt

package fuzzy

var normalized = map[rune]rune{
	0x00E1: 'a', //  WITH ACUTE, LATIN SMALL LETTER
	0x0103: 'a', //  WITH BREVE, LATIN SMALL LETTER
	0x01CE: 'a', //  WITH CARON, LATIN SMALL LETTER
	0x00E2: 'a', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x00E4: 'a', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x0227: 'a', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1EA1: 'a', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0201: 'a', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER
	0x00E0: 'a', //  WITH GRAVE, LATIN SMALL LETTER
	0x1EA3: 'a', //  WITH HOOK ABOVE, LATIN SMALL LETTER
	0x0203: 'a', //  WITH INVERTED BREVE, LATIN SMALL LETTER
	0x0101: 'a', //  WITH MACRON, LATIN SMALL LETTER
	0x0105: 'a', //  WITH OGONEK, LATIN SMALL LETTER
	0x1E9A: 'a', //  WITH RIGHT HALF RING, LATIN SMALL LETTER
	0x00E5: 'a', //  WITH RING ABOVE, LATIN SMALL LETTER
	0x1E01: 'a', //  WITH RING BELOW, LATIN SMALL LETTER
	0x00E3: 'a', //  WITH TILDE, LATIN SMALL LETTER
	0x0363: 'a', // , COMBINING LATIN SMALL LETTER
	0x0250: 'a', // , LATIN SMALL LETTER TURNED
	0x1E03: 'b', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E05: 'b', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0253: 'b', //  WITH HOOK, LATIN SMALL LETTER
	0x1E07: 'b', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x0180: 'b', //  WITH STROKE, LATIN SMALL LETTER
	0x0183: 'b', //  WITH TOPBAR, LATIN SMALL LETTER
	0x0107: 'c', //  WITH ACUTE, LATIN SMALL LETTER
	0x010D: 'c', //  WITH CARON, LATIN SMALL LETTER
	0x00E7: 'c', //  WITH CEDILLA, LATIN SMALL LETTER
	0x0109: 'c', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x0255: 'c', //  WITH CURL, LATIN SMALL LETTER
	0x010B: 'c', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x0188: 'c', //  WITH HOOK, LATIN SMALL LETTER
	0x023C: 'c', //  WITH STROKE, LATIN SMALL LETTER
	0x0368: 'c', // , COMBINING LATIN SMALL LETTER
	0x0297: 'c', // , LATIN LETTER STRETCHED
	0x2184: 'c', // , LATIN SMALL LETTER REVERSED
	0x010F: 'd', //  WITH CARON, LATIN SMALL LETTER
	0x1E11: 'd', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E13: 'd', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
	0x0221: 'd', //  WITH CURL, LATIN SMALL LETTER
	0x1E0B: 'd', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E0D: 'd', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0257: 'd', //  WITH HOOK, LATIN SMALL LETTER
	0x1E0F: 'd', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x0111: 'd', //  WITH STROKE, LATIN SMALL LETTER
	0x0256: 'd', //  WITH TAIL, LATIN SMALL LETTER
	0x018C: 'd', //  WITH TOPBAR, LATIN SMALL LETTER
	0x0369: 'd', // , COMBINING LATIN SMALL LETTER
	0x00E9: 'e', //  WITH ACUTE, LATIN SMALL LETTER
	0x0115: 'e', //  WITH BREVE, LATIN SMALL LETTER
	0x011B: 'e', //  WITH CARON, LATIN SMALL LETTER
	0x0229: 'e', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E19: 'e', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
	0x00EA: 'e', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x00EB: 'e', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x0117: 'e', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1EB9: 'e', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0205: 'e', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER
	0x00E8: 'e', //  WITH GRAVE, LATIN SMALL LETTER
	0x1EBB: 'e', //  WITH HOOK ABOVE, LATIN SMALL LETTER
	0x025D: 'e', //  WITH HOOK, LATIN SMALL LETTER REVERSED OPEN
	0x0207: 'e', //  WITH INVERTED BREVE, LATIN SMALL LETTER
	0x0113: 'e', //  WITH MACRON, LATIN SMALL LETTER
	0x0119: 'e', //  WITH OGONEK, LATIN SMALL LETTER
	0x0247: 'e', //  WITH STROKE, LATIN SMALL LETTER
	0x1E1B: 'e', //  WITH TILDE BELOW, LATIN SMALL LETTER
	0x1EBD: 'e', //  WITH TILDE, LATIN SMALL LETTER
	0x0364: 'e', // , COMBINING LATIN SMALL LETTER
	0x029A: 'e', // , LATIN SMALL LETTER CLOSED OPEN
	0x025E: 'e', // , LATIN SMALL LETTER CLOSED REVERSED OPEN
	0x025B: 'e', // , LATIN SMALL LETTER OPEN
	0x0258: 'e', // , LATIN SMALL LETTER REVERSED
	0x025C: 'e', // , LATIN SMALL LETTER REVERSED OPEN
	0x01DD: 'e', // , LATIN SMALL LETTER TURNED
	0x1D08: 'e', // , LATIN SMALL LETTER TURNED OPEN
	0x1E1F: 'f', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x0192: 'f', //  WITH HOOK, LATIN SMALL LETTER
	0x01F5: 'g', //  WITH ACUTE, LATIN SMALL LETTER
	0x011F: 'g', //  WITH BREVE, LATIN SMALL LETTER
	0x01E7: 'g', //  WITH CARON, LATIN SMALL LETTER
	0x0123: 'g', //  WITH CEDILLA, LATIN SMALL LETTER
	0x011D: 'g', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x0121: 'g', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x0260: 'g', //  WITH HOOK, LATIN SMALL LETTER
	0x1E21: 'g', //  WITH MACRON, LATIN SMALL LETTER
	0x01E5: 'g', //  WITH STROKE, LATIN SMALL LETTER
	0x0261: 'g', // , LATIN SMALL LETTER SCRIPT
	0x1E2B: 'h', //  WITH BREVE BELOW, LATIN SMALL LETTER
	0x021F: 'h', //  WITH CARON, LATIN SMALL LETTER
	0x1E29: 'h', //  WITH CEDILLA, LATIN SMALL LETTER
	0x0125: 'h', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x1E27: 'h', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1E23: 'h', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E25: 'h', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x02AE: 'h', //  WITH FISHHOOK, LATIN SMALL LETTER TURNED
	0x0266: 'h', //  WITH HOOK, LATIN SMALL LETTER
	0x1E96: 'h', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x0127: 'h', //  WITH STROKE, LATIN SMALL LETTER
	0x036A: 'h', // , COMBINING LATIN SMALL LETTER
	0x0265: 'h', // , LATIN SMALL LETTER TURNED
	0x2095: 'h', // , LATIN SUBSCRIPT SMALL LETTER
	0x00ED: 'i', //  WITH ACUTE, LATIN SMALL LETTER
	0x012D: 'i', //  WITH BREVE, LATIN SMALL LETTER
	0x01D0: 'i', //  WITH CARON, LATIN SMALL LETTER
	0x00EE: 'i', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x00EF: 'i', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1ECB: 'i', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0209: 'i', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER
	0x00EC: 'i', //  WITH GRAVE, LATIN SMALL LETTER
	0x1EC9: 'i', //  WITH HOOK ABOVE, LATIN SMALL LETTER
	0x020B: 'i', //  WITH INVERTED BREVE, LATIN SMALL LETTER
	0x012B: 'i', //  WITH MACRON, LATIN SMALL LETTER
	0x012F: 'i', //  WITH OGONEK, LATIN SMALL LETTER
	0x0268: 'i', //  WITH STROKE, LATIN SMALL LETTER
	0x1E2D: 'i', //  WITH TILDE BELOW, LATIN SMALL LETTER
	0x0129: 'i', //  WITH TILDE, LATIN SMALL LETTER
	0x0365: 'i', // , COMBINING LATIN SMALL LETTER
	0x0131: 'i', // , LATIN SMALL LETTER DOTLESS
	0x1D09: 'i', // , LATIN SMALL LETTER TURNED
	0x1D62: 'i', // , LATIN SUBSCRIPT SMALL LETTER
	0x2071: 'i', // , SUPERSCRIPT LATIN SMALL LETTER
	0x01F0: 'j', //  WITH CARON, LATIN SMALL LETTER
	0x0135: 'j', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x029D: 'j', //  WITH CROSSED-TAIL, LATIN SMALL LETTER
	0x0249: 'j', //  WITH STROKE, LATIN SMALL LETTER
	0x025F: 'j', //  WITH STROKE, LATIN SMALL LETTER DOTLESS
	0x0237: 'j', // , LATIN SMALL LETTER DOTLESS
	0x1E31: 'k', //  WITH ACUTE, LATIN SMALL LETTER
	0x01E9: 'k', //  WITH CARON, LATIN SMALL LETTER
	0x0137: 'k', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E33: 'k', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0199: 'k', //  WITH HOOK, LATIN SMALL LETTER
	0x1E35: 'k', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x029E: 'k', // , LATIN SMALL LETTER TURNED
	0x2096: 'k', // , LATIN SUBSCRIPT SMALL LETTER
	0x013A: 'l', //  WITH ACUTE, LATIN SMALL LETTER
	0x019A: 'l', //  WITH BAR, LATIN SMALL LETTER
	0x026C: 'l', //  WITH BELT, LATIN SMALL LETTER
	0x013E: 'l', //  WITH CARON, LATIN SMALL LETTER
	0x013C: 'l', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E3D: 'l', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
	0x0234: 'l', //  WITH CURL, LATIN SMALL LETTER
	0x1E37: 'l', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x1E3B: 'l', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x0140: 'l', //  WITH MIDDLE DOT, LATIN SMALL LETTER
	0x026B: 'l', //  WITH MIDDLE TILDE, LATIN SMALL LETTER
	0x026D: 'l', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER
	0x0142: 'l', //  WITH STROKE, LATIN SMALL LETTER
	0x2097: 'l', // , LATIN SUBSCRIPT SMALL LETTER
	0x1E3F: 'm', //  WITH ACUTE, LATIN SMALL LETTER
	0x1E41: 'm', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E43: 'm', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0271: 'm', //  WITH HOOK, LATIN SMALL LETTER
	0x0270: 'm', //  WITH LONG LEG, LATIN SMALL LETTER TURNED
	0x036B: 'm', // , COMBINING LATIN SMALL LETTER
	0x1D1F: 'm', // , LATIN SMALL LETTER SIDEWAYS TURNED
	0x026F: 'm', // , LATIN SMALL LETTER TURNED
	0x2098: 'm', // , LATIN SUBSCRIPT SMALL LETTER
	0x0144: 'n', //  WITH ACUTE, LATIN SMALL LETTER
	0x0148: 'n', //  WITH CARON, LATIN SMALL LETTER
	0x0146: 'n', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E4B: 'n', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
	0x0235: 'n', //  WITH CURL, LATIN SMALL LETTER
	0x1E45: 'n', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E47: 'n', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x01F9: 'n', //  WITH GRAVE, LATIN SMALL LETTER
	0x0272: 'n', //  WITH LEFT HOOK, LATIN SMALL LETTER
	0x1E49: 'n', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x019E: 'n', //  WITH LONG RIGHT LEG, LATIN SMALL LETTER
	0x0273: 'n', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER
	0x00F1: 'n', //  WITH TILDE, LATIN SMALL LETTER
	0x2099: 'n', // , LATIN SUBSCRIPT SMALL LETTER
	0x00F3: 'o', //  WITH ACUTE, LATIN SMALL LETTER
	0x014F: 'o', //  WITH BREVE, LATIN SMALL LETTER
	0x01D2: 'o', //  WITH CARON, LATIN SMALL LETTER
	0x00F4: 'o', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x00F6: 'o', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x022F: 'o', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1ECD: 'o', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0151: 'o', //  WITH DOUBLE ACUTE, LATIN SMALL LETTER
	0x020D: 'o', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER
	0x00F2: 'o', //  WITH GRAVE, LATIN SMALL LETTER
	0x1ECF: 'o', //  WITH HOOK ABOVE, LATIN SMALL LETTER
	0x01A1: 'o', //  WITH HORN, LATIN SMALL LETTER
	0x020F: 'o', //  WITH INVERTED BREVE, LATIN SMALL LETTER
	0x014D: 'o', //  WITH MACRON, LATIN SMALL LETTER
	0x01EB: 'o', //  WITH OGONEK, LATIN SMALL LETTER
	0x00F8: 'o', //  WITH STROKE, LATIN SMALL LETTER
	0x1D13: 'o', //  WITH STROKE, LATIN SMALL LETTER SIDEWAYS
	0x00F5: 'o', //  WITH TILDE, LATIN SMALL LETTER
	0x0366: 'o', // , COMBINING LATIN SMALL LETTER
	0x0275: 'o', // , LATIN SMALL LETTER BARRED
	0x1D17: 'o', // , LATIN SMALL LETTER BOTTOM HALF
	0x0254: 'o', // , LATIN SMALL LETTER OPEN
	0x1D11: 'o', // , LATIN SMALL LETTER SIDEWAYS
	0x1D12: 'o', // , LATIN SMALL LETTER SIDEWAYS OPEN
	0x1D16: 'o', // , LATIN SMALL LETTER TOP HALF
	0x1E55: 'p', //  WITH ACUTE, LATIN SMALL LETTER
	0x1E57: 'p', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x01A5: 'p', //  WITH HOOK, LATIN SMALL LETTER
	0x209A: 'p', // , LATIN SUBSCRIPT SMALL LETTER
	0x024B: 'q', //  WITH HOOK TAIL, LATIN SMALL LETTER
	0x02A0: 'q', //  WITH HOOK, LATIN SMALL LETTER
	0x0155: 'r', //  WITH ACUTE, LATIN SMALL LETTER
	0x0159: 'r', //  WITH CARON, LATIN SMALL LETTER
	0x0157: 'r', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E59: 'r', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E5B: 'r', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0211: 'r', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER
	0x027E: 'r', //  WITH FISHHOOK, LATIN SMALL LETTER
	0x027F: 'r', //  WITH FISHHOOK, LATIN SMALL LETTER REVERSED
	0x027B: 'r', //  WITH HOOK, LATIN SMALL LETTER TURNED
	0x0213: 'r', //  WITH INVERTED BREVE, LATIN SMALL LETTER
	0x1E5F: 'r', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x027C: 'r', //  WITH LONG LEG, LATIN SMALL LETTER
	0x027A: 'r', //  WITH LONG LEG, LATIN SMALL LETTER TURNED
	0x024D: 'r', //  WITH STROKE, LATIN SMALL LETTER
	0x027D: 'r', //  WITH TAIL, LATIN SMALL LETTER
	0x036C: 'r', // , COMBINING LATIN SMALL LETTER
	0x0279: 'r', // , LATIN SMALL LETTER TURNED
	0x1D63: 'r', // , LATIN SUBSCRIPT SMALL LETTER
	0x015B: 's', //  WITH ACUTE, LATIN SMALL LETTER
	0x0161: 's', //  WITH CARON, LATIN SMALL LETTER
	0x015F: 's', //  WITH CEDILLA, LATIN SMALL LETTER
	0x015D: 's', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x0219: 's', //  WITH COMMA BELOW, LATIN SMALL LETTER
	0x1E61: 's', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E9B: 's', //  WITH DOT ABOVE, LATIN SMALL LETTER LONG
	0x1E63: 's', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0282: 's', //  WITH HOOK, LATIN SMALL LETTER
	0x023F: 's', //  WITH SWASH TAIL, LATIN SMALL LETTER
	0x017F: 's', // , LATIN SMALL LETTER LONG
	0x00DF: 's', // , LATIN SMALL LETTER SHARP
	0x209B: 's', // , LATIN SUBSCRIPT SMALL LETTER
	0x0165: 't', //  WITH CARON, LATIN SMALL LETTER
	0x0163: 't', //  WITH CEDILLA, LATIN SMALL LETTER
	0x1E71: 't', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
	0x021B: 't', //  WITH COMMA BELOW, LATIN SMALL LETTER
	0x0236: 't', //  WITH CURL, LATIN SMALL LETTER
	0x1E97: 't', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1E6B: 't', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E6D: 't', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x01AD: 't', //  WITH HOOK, LATIN SMALL LETTER
	0x1E6F: 't', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x01AB: 't', //  WITH PALATAL HOOK, LATIN SMALL LETTER
	0x0288: 't', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER
	0x0167: 't', //  WITH STROKE, LATIN SMALL LETTER
	0x036D: 't', // , COMBINING LATIN SMALL LETTER
	0x0287: 't', // , LATIN SMALL LETTER TURNED
	0x209C: 't', // , LATIN SUBSCRIPT SMALL LETTER
	0x0289: 'u', //  BAR, LATIN SMALL LETTER
	0x00FA: 'u', //  WITH ACUTE, LATIN SMALL LETTER
	0x016D: 'u', //  WITH BREVE, LATIN SMALL LETTER
	0x01D4: 'u', //  WITH CARON, LATIN SMALL LETTER
	0x1E77: 'u', //  WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
	0x00FB: 'u', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x1E73: 'u', //  WITH DIAERESIS BELOW, LATIN SMALL LETTER
	0x00FC: 'u', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1EE5: 'u', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0171: 'u', //  WITH DOUBLE ACUTE, LATIN SMALL LETTER
	0x0215: 'u', //  WITH DOUBLE GRAVE, LATIN SMALL LETTER
	0x00F9: 'u', //  WITH GRAVE, LATIN SMALL LETTER
	0x1EE7: 'u', //  WITH HOOK ABOVE, LATIN SMALL LETTER
	0x01B0: 'u', //  WITH HORN, LATIN SMALL LETTER
	0x0217: 'u', //  WITH INVERTED BREVE, LATIN SMALL LETTER
	0x016B: 'u', //  WITH MACRON, LATIN SMALL LETTER
	0x0173: 'u', //  WITH OGONEK, LATIN SMALL LETTER
	0x016F: 'u', //  WITH RING ABOVE, LATIN SMALL LETTER
	0x1E75: 'u', //  WITH TILDE BELOW, LATIN SMALL LETTER
	0x0169: 'u', //  WITH TILDE, LATIN SMALL LETTER
	0x0367: 'u', // , COMBINING LATIN SMALL LETTER
	0x1D1D: 'u', // , LATIN SMALL LETTER SIDEWAYS
	0x1D1E: 'u', // , LATIN SMALL LETTER SIDEWAYS DIAERESIZED
	0x1D64: 'u', // , LATIN SUBSCRIPT SMALL LETTER
	0x1E7F: 'v', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x028B: 'v', //  WITH HOOK, LATIN SMALL LETTER
	0x1E7D: 'v', //  WITH TILDE, LATIN SMALL LETTER
	0x036E: 'v', // , COMBINING LATIN SMALL LETTER
	0x028C: 'v', // , LATIN SMALL LETTER TURNED
	0x1D65: 'v', // , LATIN SUBSCRIPT SMALL LETTER
	0x1E83: 'w', //  WITH ACUTE, LATIN SMALL LETTER
	0x0175: 'w', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x1E85: 'w', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1E87: 'w', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E89: 'w', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x1E81: 'w', //  WITH GRAVE, LATIN SMALL LETTER
	0x1E98: 'w', //  WITH RING ABOVE, LATIN SMALL LETTER
	0x028D: 'w', // , LATIN SMALL LETTER TURNED
	0x1E8D: 'x', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1E8B: 'x', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x036F: 'x', // , COMBINING LATIN SMALL LETTER
	0x00FD: 'y', //  WITH ACUTE, LATIN SMALL LETTER
	0x0177: 'y', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x00FF: 'y', //  WITH DIAERESIS, LATIN SMALL LETTER
	0x1E8F: 'y', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1EF5: 'y', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x1EF3: 'y', //  WITH GRAVE, LATIN SMALL LETTER
	0x1EF7: 'y', //  WITH HOOK ABOVE, LATIN SMALL LETTER
	0x01B4: 'y', //  WITH HOOK, LATIN SMALL LETTER
	0x0233: 'y', //  WITH MACRON, LATIN SMALL LETTER
	0x1E99: 'y', //  WITH RING ABOVE, LATIN SMALL LETTER
	0x024F: 'y', //  WITH STROKE, LATIN SMALL LETTER
	0x1EF9: 'y', //  WITH TILDE, LATIN SMALL LETTER
	0x028E: 'y', // , LATIN SMALL LETTER TURNED
	0x017A: 'z', //  WITH ACUTE, LATIN SMALL LETTER
	0x017E: 'z', //  WITH CARON, LATIN SMALL LETTER
	0x1E91: 'z', //  WITH CIRCUMFLEX, LATIN SMALL LETTER
	0x0291: 'z', //  WITH CURL, LATIN SMALL LETTER
	0x017C: 'z', //  WITH DOT ABOVE, LATIN SMALL LETTER
	0x1E93: 'z', //  WITH DOT BELOW, LATIN SMALL LETTER
	0x0225: 'z', //  WITH HOOK, LATIN SMALL LETTER
	0x1E95: 'z', //  WITH LINE BELOW, LATIN SMALL LETTER
	0x0290: 'z', //  WITH RETROFLEX HOOK, LATIN SMALL LETTER
	0x01B6: 'z', //  WITH STROKE, LATIN SMALL LETTER
	0x0240: 'z', //  WITH SWASH TAIL, LATIN SMALL LETTER
	0x0251: 'a', // , latin small letter script
	0x00C1: 'A', //  WITH ACUTE, LATIN CAPITAL LETTER
	0x00C2: 'A', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER
	0x00C4: 'A', //  WITH DIAERESIS, LATIN CAPITAL LETTER
	0x00C0: 'A', //  WITH GRAVE, LATIN CAPITAL LETTER
	0x00C5: 'A', //  WITH RING ABOVE, LATIN CAPITAL LETTER
	0x023A: 'A', //  WITH STROKE, LATIN CAPITAL LETTER
	0x00C3: 'A', //  WITH TILDE, LATIN CAPITAL LETTER
	0x1D00: 'A', // , LATIN LETTER SMALL CAPITAL
	0x0181: 'B', //  WITH HOOK, LATIN CAPITAL LETTER
	0x0243: 'B', //  WITH STROKE, LATIN CAPITAL LETTER
	0x0299: 'B', // , LATIN LETTER SMALL CAPITAL
	0x1D03: 'B', // , LATIN LETTER SMALL CAPITAL BARRED
	0x00C7: 'C', //  WITH CEDILLA, LATIN CAPITAL LETTER
	0x023B: 'C', //  WITH STROKE, LATIN CAPITAL LETTER
	0x1D04: 'C', // , LATIN LETTER SMALL CAPITAL
	0x018A: 'D', //  WITH HOOK, LATIN CAPITAL LETTER
	0x0189: 'D', // , LATIN CAPITAL LETTER AFRICAN
	0x1D05: 'D', // , LATIN LETTER SMALL CAPITAL
	0x00C9: 'E', //  WITH ACUTE, LATIN CAPITAL LETTER
	0x00CA: 'E', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER
	0x00CB: 'E', //  WITH DIAERESIS, LATIN CAPITAL LETTER
	0x00C8: 'E', //  WITH GRAVE, LATIN CAPITAL LETTER
	0x0246: 'E', //  WITH STROKE, LATIN CAPITAL LETTER
	0x0190: 'E', // , LATIN CAPITAL LETTER OPEN
	0x018E: 'E', // , LATIN CAPITAL LETTER REVERSED
	0x1D07: 'E', // , LATIN LETTER SMALL CAPITAL
	0x0193: 'G', //  WITH HOOK, LATIN CAPITAL LETTER
	0x029B: 'G', //  WITH HOOK, LATIN LETTER SMALL CAPITAL
	0x0262: 'G', // , LATIN LETTER SMALL CAPITAL
	0x029C: 'H', // , LATIN LETTER SMALL CAPITAL
	0x00CD: 'I', //  WITH ACUTE, LATIN CAPITAL LETTER
	0x00CE: 'I', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER
	0x00CF: 'I', //  WITH DIAERESIS, LATIN CAPITAL LETTER
	0x0130: 'I', //  WITH DOT ABOVE, LATIN CAPITAL LETTER
	0x00CC: 'I', //  WITH GRAVE, LATIN CAPITAL LETTER
	0x0197: 'I', //  WITH STROKE, LATIN CAPITAL LETTER
	0x026A: 'I', // , LATIN LETTER SMALL CAPITAL
	0x0248: 'J', //  WITH STROKE, LATIN CAPITAL LETTER
	0x1D0A: 'J', // , LATIN LETTER SMALL CAPITAL
	0x1D0B: 'K', // , LATIN LETTER SMALL CAPITAL
	0x023D: 'L', //  WITH BAR, LATIN CAPITAL LETTER
	0x1D0C: 'L', //  WITH STROKE, LATIN LETTER SMALL CAPITAL
	0x029F: 'L', // , LATIN LETTER SMALL CAPITAL
	0x019C: 'M', // , LATIN CAPITAL LETTER TURNED
	0x1D0D: 'M', // , LATIN LETTER SMALL CAPITAL
	0x019D: 'N', //  WITH LEFT HOOK, LATIN CAPITAL LETTER
	0x0220: 'N', //  WITH LONG RIGHT LEG, LATIN CAPITAL LETTER
	0x00D1: 'N', //  WITH TILDE, LATIN CAPITAL LETTER
	0x0274: 'N', // , LATIN LETTER SMALL CAPITAL
	0x1D0E: 'N', // , LATIN LETTER SMALL CAPITAL REVERSED
	0x00D3: 'O', //  WITH ACUTE, LATIN CAPITAL LETTER
	0x00D4: 'O', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER
	0x00D6: 'O', //  WITH DIAERESIS, LATIN CAPITAL LETTER
	0x00D2: 'O', //  WITH GRAVE, LATIN CAPITAL LETTER
	0x019F: 'O', //  WITH MIDDLE TILDE, LATIN CAPITAL LETTER
	0x00D8: 'O', //  WITH STROKE, LATIN CAPITAL LETTER
	0x00D5: 'O', //  WITH TILDE, LATIN CAPITAL LETTER
	0x0186: 'O', // , LATIN CAPITAL LETTER OPEN
	0x1D0F: 'O', // , LATIN LETTER SMALL CAPITAL
	0x1D10: 'O', // , LATIN LETTER SMALL CAPITAL OPEN
	0x1D18: 'P', // , LATIN LETTER SMALL CAPITAL
	0x024A: 'Q', //  WITH HOOK TAIL, LATIN CAPITAL LETTER SMALL
	0x024C: 'R', //  WITH STROKE, LATIN CAPITAL LETTER
	0x0280: 'R', // , LATIN LETTER SMALL CAPITAL
	0x0281: 'R', // , LATIN LETTER SMALL CAPITAL INVERTED
	0x1D19: 'R', // , LATIN LETTER SMALL CAPITAL REVERSED
	0x1D1A: 'R', // , LATIN LETTER SMALL CAPITAL TURNED
	0x023E: 'T', //  WITH DIAGONAL STROKE, LATIN CAPITAL LETTER
	0x01AE: 'T', //  WITH RETROFLEX HOOK, LATIN CAPITAL LETTER
	0x1D1B: 'T', // , LATIN LETTER SMALL CAPITAL
	0x0244: 'U', //  BAR, LATIN CAPITAL LETTER
	0x00DA: 'U', //  WITH ACUTE, LATIN CAPITAL LETTER
	0x00DB: 'U', //  WITH CIRCUMFLEX, LATIN CAPITAL LETTER
	0x00DC: 'U', //  WITH DIAERESIS, LATIN CAPITAL LETTER
	0x00D9: 'U', //  WITH GRAVE, LATIN CAPITAL LETTER
	0x1D1C: 'U', // , LATIN LETTER SMALL CAPITAL
	0x01B2: 'V', //  WITH HOOK, LATIN CAPITAL LETTER
	0x0245: 'V', // , LATIN CAPITAL LETTER TURNED
	0x1D20: 'V', // , LATIN LETTER SMALL CAPITAL
	0x1D21: 'W', // , LATIN LETTER SMALL CAPITAL
	0x00DD: 'Y', //  WITH ACUTE, LATIN CAPITAL LETTER
	0x0178: 'Y', //  WITH DIAERESIS, LATIN CAPITAL LETTER
	0x024E: 'Y', //  WITH STROKE, LATIN CAPITAL LETTER
	0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL
	0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL

	'Ắ': 'A',
	'Ấ': 'A',
	'Ằ': 'A',
	'Ầ': 'A',
	'Ẳ': 'A',
	'Ẩ': 'A',
	'Ẵ': 'A',
	'Ẫ': 'A',
	'Ặ': 'A',
	'Ậ': 'A',

	'ắ': 'a',
	'ấ': 'a',
	'ằ': 'a',
	'ầ': 'a',
	'ẳ': 'a',
	'ẩ': 'a',
	'ẵ': 'a',
	'ẫ': 'a',
	'ặ': 'a',
	'ậ': 'a',

	'Ế': 'E',
	'Ề': 'E',
	'Ể': 'E',
	'Ễ': 'E',
	'Ệ': 'E',

	'ế': 'e',
	'ề': 'e',
	'ể': 'e',
	'ễ': 'e',
	'ệ': 'e',

	'Ố': 'O',
	'Ớ': 'O',
	'Ồ': 'O',
	'Ờ': 'O',
	'Ổ': 'O',
	'Ở': 'O',
	'Ỗ': 'O',
	'Ỡ': 'O',
	'Ộ': 'O',
	'Ợ': 'O',

	'ố': 'o',
	'ớ': 'o',
	'ồ': 'o',
	'ờ': 'o',
	'ổ': 'o',
	'ở': 'o',
	'ỗ': 'o',
	'ỡ': 'o',
	'ộ': 'o',
	'ợ': 'o',

	'Ứ': 'U',
	'Ừ': 'U',
	'Ử': 'U',
	'Ữ': 'U',
	'Ự': 'U',

	'ứ': 'u',
	'ừ': 'u',
	'ử': 'u',
	'ữ': 'u',
	'ự': 'u',
}

// NormalizeRunes normalizes latin script letters
func NormalizeRunes(runes []rune) []rune {
	ret := make([]rune, len(runes))
	copy(ret, runes)
	for idx, r := range runes {
		if r < 0x00C0 || r > 0x2184 {
			continue
		}
		n := normalized[r]
		if n > 0 {
			ret[idx] = normalized[r]
		}
	}
	return ret
}


================================================
FILE: internal/fuzzy/utils.go
================================================
package fuzzy

import (
	"math"
	"strings"

	"github.com/rivo/uniseg"
)

func AsUint16(val int) uint16 {
	if val > math.MaxUint16 {
		return math.MaxUint16
	} else if val < 0 {
		return 0
	}
	return uint16(val)
}

// StringWidth returns string width where each CR/LF character takes 1 column
func StringWidth(s string) int {
	return uniseg.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
}

// RunesWidth returns runes width
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
	width := 0
	gr := uniseg.NewGraphemes(string(runes))
	idx := 0
	for gr.Next() {
		rs := gr.Runes()
		var w int
		if len(rs) == 1 && rs[0] == '\t' {
			w = tabstop - (prefixWidth+width)%tabstop
		} else {
			w = StringWidth(string(rs))
		}
		width += w
		if width > limit {
			return width, idx
		}
		idx += len(rs)
	}
	return width, -1
}


================================================
FILE: internal/ident/ident.go
================================================
package ident

import (
	"os"
	"strconv"
	"strings"
)

var Ident = "  "
var IdentBytes []byte
var IdentWidth int

func init() {
	identValue, ok := os.LookupEnv("FX_INDENT")
	if ok {
		identInt, err := strconv.Atoi(identValue)
		if err == nil {
			Ident = strings.Repeat(" ", identInt)
		} else {
			Ident = identValue
		}
	}
	for _, r := range Ident {
		if r == '\n' {
			continue
		}
		if r == '\t' {
			IdentBytes = append(IdentBytes, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ')
			IdentWidth += 8
			continue
		}
		IdentBytes = append(IdentBytes, byte(r))
		IdentWidth++
	}
}


================================================
FILE: internal/jsonpath/path.go
================================================
package jsonpath

import (
	"regexp"
	"strconv"
	"unicode"
)

type state int

const (
	start state = iota
	unknown
	propOrIndex
	prop
	index
	indexEnd
	number
	doubleQuote
	doubleQuoteEscape
	singleQuote
	singleQuoteEscape
)

func Split(p string) ([]any, bool) {
	path := make([]any, 0)
	s := ""
	state := start
	for _, ch := range p {
		switch state {

		case start:
			switch {
			case ch == 'x':
				state = unknown
			case ch == '.':
				state = propOrIndex
			case ch == '[':
				state = index
			default:
				return path, false
			}

		case unknown:
			switch {
			case ch == '.':
				state = prop
				s = ""
			case ch == '[':
				state = index
				s = ""
			default:
				return path, false
			}

		case propOrIndex:
			switch {
			case isProp(ch):
				state = prop
				s = string(ch)
			case ch == '[':
				state = index
			default:
				return path, false
			}

		case prop:
			switch {
			case isProp(ch):
				s += string(ch)
			case ch == '.':
				state = prop
				path = append(path, s)
				s = ""
			case ch == '[':
				state = index
				path = append(path, s)
				s = ""
			default:
				return path, false
			}

		case index:
			switch {
			case unicode.IsDigit(ch):
				state = number
				s = string(ch)
			case ch == '"':
				state = doubleQuote
				s = ""
			case ch == '\'':
				state = singleQuote
				s = ""
			default:
				return path, false
			}

		case indexEnd:
			switch {
			case ch == ']':
				state = unknown
			default:
				return path, false
			}

		case number:
			switch {
			case unicode.IsDigit(ch):
				s += string(ch)
			case ch == ']':
				state = unknown
				n, err := strconv.Atoi(s)
				if err != nil {
					return path, false
				}
				path = append(path, n)
				s = ""
			default:
				return path, false
			}

		case doubleQuote:
			switch ch {
			case '"':
				state = indexEnd
				path = append(path, s)
				s = ""
			case '\\':
				state = doubleQuoteEscape
			default:
				s += string(ch)
			}

		case doubleQuoteEscape:
			switch ch {
			case '"':
				state = doubleQuote
				s += string(ch)
			default:
				return path, false
			}

		case singleQuote:
			switch ch {
			case '\'':
				state = indexEnd
				path = append(path, s)
				s = ""
			case '\\':
				state = singleQuoteEscape
				s += string(ch)
			default:
				s += string(ch)
			}

		case singleQuoteEscape:
			switch ch {
			case '\'':
				state = singleQuote
				s += string(ch)
			default:
				return path, false
			}
		}
	}
	if len(s) > 0 {
		if state == prop {
			path = append(path, s)
		} else {
			return path, false
		}

	}
	return path, true
}

func isProp(ch rune) bool {
	return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '$'
}

var Identifier = regexp.MustCompile(`^[$a-zA-Z_][$a-zA-Z0-9_]*$`)

func Join(path []any) string {
	s := ""
	for _, v := range path {
		switch v := v.(type) {
		case string:
			if Identifier.MatchString(v) {
				s += "." + v
			} else {
				s += "[" + strconv.Quote(v) + "]"
			}
		case int:
			s += "[" + strconv.Itoa(v) + "]"
		}
	}
	return s
}


================================================
FILE: internal/jsonpath/path_test.go
================================================
package jsonpath_test

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/antonmedv/fx/internal/jsonpath"
)

func Test_SplitPath(t *testing.T) {
	tests := []struct {
		input string
		want  []any
	}{
		{
			input: "",
			want:  []any{},
		},
		{
			input: ".",
			want:  []any{},
		},
		{
			input: "x",
			want:  []any{},
		},
		{
			input: ".foo",
			want:  []any{"foo"},
		},
		{
			input: "x.foo",
			want:  []any{"foo"},
		},
		{
			input: "x[42]",
			want:  []any{42},
		},
		{
			input: ".[42]",
			want:  []any{42},
		},
		{
			input: ".42",
			want:  []any{"42"},
		},
		{
			input: ".физ",
			want:  []any{"физ"},
		},
		{
			input: ".foo.bar",
			want:  []any{"foo", "bar"},
		},
		{
			input: ".foo[42]",
			want:  []any{"foo", 42},
		},
		{
			input: ".foo[42].bar",
			want:  []any{"foo", 42, "bar"},
		},
		{
			input: ".foo[1][2]",
			want:  []any{"foo", 1, 2},
		},
		{
			input: ".foo[\"bar\"]",
			want:  []any{"foo", "bar"},
		},
		{
			input: ".foo[\"bar\\\"\"]",
			want:  []any{"foo", "bar\""},
		},
		{
			input: ".foo['bar']['baz\\'']",
			want:  []any{"foo", "bar", "baz\\'"},
		},
		{
			input: "[42]",
			want:  []any{42},
		},
		{
			input: "[42].foo",
			want:  []any{42, "foo"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			p, ok := jsonpath.Split(tt.input)
			require.Equal(t, tt.want, p)
			require.True(t, ok)
		})
	}
}

func Test_SplitPath_negative(t *testing.T) {
	tests := []struct {
		input string
	}{
		{
			input: "./",
		},
		{
			input: "x/",
		},
		{
			input: "1+1",
		},
		{
			input: "x[42",
		},
		{
			input: ".i % 2",
		},
		{
			input: "x[for x]",
		},
		{
			input: "x['y'.",
		},
		{
			input: "x[0?",
		},
		{
			input: "x[\"\\u",
		},
		{
			input: "x['\\n",
		},
		{
			input: "x[9999999999999999999999999999999999999]",
		},
		{
			input: "x[]",
		},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			p, ok := jsonpath.Split(tt.input)
			require.False(t, ok, p)
		})
	}
}

func TestJoin(t *testing.T) {
	tests := []struct {
		input []any
		want  string
	}{
		{
			input: []any{},
			want:  "",
		},
		{
			input: []any{"foo"},
			want:  ".foo",
		},
		{
			input: []any{"foo", "bar"},
			want:  ".foo.bar",
		},
		{
			input: []any{"foo", 42},
			want:  ".foo[42]",
		},
		{
			input: []any{"foo", "bar", 42},
			want:  ".foo.bar[42]",
		},
		{
			input: []any{"foo", "bar", 42, "baz"},
			want:  ".foo.bar[42].baz",
		},
		{
			input: []any{"foo", "bar", 42, "baz", 1},
			want:  ".foo.bar[42].baz[1]",
		},
		{
			input: []any{"foo", "bar", 42, "baz", 1, "qux"},
			want:  ".foo.bar[42].baz[1].qux",
		},
		{
			input: []any{"foo bar"},
			want:  "[\"foo bar\"]",
		},
	}
	for _, tt := range tests {
		t.Run(tt.want, func(t *testing.T) {
			require.Equal(t, tt.want, jsonpath.Join(tt.input))
		})
	}
}


================================================
FILE: internal/jsonpath/ref.go
================================================
package jsonpath

import (
	"net/url"
	"strings"
)

func ParseSchemaRef(ref string) ([]any, bool) {
	// Must start with '#'
	if len(ref) == 0 || ref[0] != '#' {
		return nil, false
	}

	// An empty fragment refers to the whole document
	if ref == "#" {
		return []any{}, true
	}

	// Must be a pointer ("#/...")
	if !strings.HasPrefix(ref, "#/") {
		return nil, false
	}

	// Split the pointer without the leading '#/'
	parts := strings.Split(ref[2:], "/")
	out := make([]any, len(parts))
	for i, part := range parts {
		// JSON Pointer unescaping
		s := strings.ReplaceAll(strings.ReplaceAll(part, "~1", "/"), "~0", "~")
		// Percent-unescape
		unescaped, err := url.PathUnescape(s)
		if err != nil {
			return nil, false
		}
		out[i] = unescaped
	}
	return out, true
}


================================================
FILE: internal/jsonpath/ref_test.go
================================================
package jsonpath_test

import (
	"reflect"
	"testing"

	"github.com/antonmedv/fx/internal/jsonpath"
)

func TestParseSchemaRef(t *testing.T) {
	tests := []struct {
		name   string
		input  string
		want   []any
		wantOk bool
	}{
		{
			name:   "empty fragment",
			input:  "#",
			want:   []any{},
			wantOk: true,
		},
		{
			name:   "simple defs",
			input:  "#/$defs/OrganizationConfig/Options",
			want:   []any{"$defs", "OrganizationConfig", "Options"},
			wantOk: true,
		},
		{
			name:   "with slash escape",
			input:  "#/path/with~1slash",
			want:   []any{"path", "with/slash"},
			wantOk: true,
		},
		{
			name:   "with tilde escape",
			input:  "#/path/with~0tilde",
			want:   []any{"path", "with~tilde"},
			wantOk: true,
		},
		{
			name:   "with percent",
			input:  "#/a%20b/c%2Fd",
			want:   []any{"a b", "c/d"},
			wantOk: true,
		},
		{
			name:   "invalid no prefix",
			input:  "foo/bar",
			want:   nil,
			wantOk: false,
		},
		{
			name:   "invalid bad escape",
			input:  "#/bad/%GG",
			want:   nil,
			wantOk: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, ok := jsonpath.ParseSchemaRef(tt.input)
			if ok != tt.wantOk {
				t.Errorf("ParseSchemaRef(%q) ok = %v, want %v", tt.input, ok, tt.wantOk)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ParseSchemaRef(%q) = %v, want %v", tt.input, got, tt.want)
			}
		})
	}
}


================================================
FILE: internal/jsonx/delete.go
================================================
package jsonx

// DeleteNode removes the node at from the linked structure and returns a node to select next.
// It returns (nextToSelect, true) if deletion happened, or (nil, false) if nothing was deleted.
// Rules:
// - Do nothing if at is nil, points to root (no parent), or is a bracket/closing node (Index == -1).
// - If at is a wrap placeholder (Chunk set, Value empty), operate on its parent value.
// - Maintain Prev/Next links skipping the deleted range [at..endOf].
// - Clear trailing comma on the previous sibling when deleting the last child before parent's End.
// - Decrement parent.Size and reindex subsequent array siblings.
// - Choose selection: prefer next; if next is nil or parent.End, prefer prev; else parent.
func DeleteNode(at *Node) (*Node, bool) {
	if at == nil {
		return nil, false
	}
	// Avoid closing bracket nodes (Index == -1 used for brackets)
	if at.Index == -1 {
		return nil, false
	}
	parent := at.Parent
	if parent == nil { // avoid deleting root
		return nil, false
	}
	// If current points to a wrap placeholder, move to its parent value
	if at.Chunk != "" && at.Value == "" && at.Parent != nil {
		at = at.Parent
		parent = at.Parent
		if parent == nil {
			return nil, false
		}
	}

	// Determine the last node of this item (to skip its subtree or chunks)
	endOf := at
	if at.End != nil {
		endOf = at.End
	} else if at.ChunkEnd != nil {
		endOf = at.ChunkEnd
	}
	prev := at.Prev
	next := endOf.Next

	// If deleting the last child before parent's closing bracket, clear trailing comma on previous sibling
	isLast := next == parent.End
	if isLast && prev != nil && prev != parent {
		prev.Comma = false
	}

	// Relink to remove [at..endOf] from the chain
	if prev != nil {
		prev.Next = next
	}
	if next != nil {
		next.Prev = prev
	}

	// Update parent size and array indices if needed
	if parent.Size > 0 {
		parent.Size--
	}
	if parent.Kind == Array {
		for it := next; it != nil && it != parent.End; {
			if it.Parent == parent && it.Index >= 0 {
				it.Index = it.Index - 1
			}
			if it.HasChildren() {
				it = it.End.Next
			} else {
				it = it.Next
			}
		}
	}

	// Select a sensible node after deletion
	selectTo := next
	if selectTo == nil || selectTo == parent.End {
		if prev != nil && prev != parent {
			selectTo = prev
		} else {
			selectTo = parent
		}
	}
	return selectTo, true
}


================================================
FILE: internal/jsonx/delete_test.go
================================================
package jsonx_test

import (
	"testing"

	"github.com/stretchr/testify/require"

	. "github.com/antonmedv/fx/internal/jsonx"
)

func TestDeleteNode_ObjectScenarios(t *testing.T) {
	root, err := Parse([]byte(`{"a":1,"b":2,"c":3}`))
	require.NoError(t, err)
	obj := root
	require.Equal(t, Object, obj.Kind)

	// delete middle key b
	b := obj.FindByPath([]any{"b"})
	require.NotNil(t, b)
	next, ok := DeleteNode(b)
	require.True(t, ok)
	require.NotNil(t, next)
	// after deleting b, next should be c (or its start)
	c := obj.FindByPath([]any{"c"})
	require.NotNil(t, c)

	// ensure size updated and comma on previous cleared
	require.Equal(t, 2, obj.Size)

	// delete last key c -> previous comma should be cleared and selection fallback
	next2, ok := DeleteNode(c)
	require.True(t, ok)
	require.NotNil(t, next2)
	// now only {"a":1}
	require.Equal(t, 1, obj.Size)

	// delete first/only key a -> object empty
	a := obj.FindByPath([]any{"a"})
	require.NotNil(t, a)
	_, ok = DeleteNode(a)
	require.True(t, ok)
	require.Equal(t, 0, obj.Size)
}

func TestDeleteNode_ArrayScenarios(t *testing.T) {
	root, err := Parse([]byte(`[10,20,30,40]`))
	require.NoError(t, err)
	arr := root
	require.Equal(t, Array, arr.Kind)

	// delete middle index 1 (20)
	idx1 := arr.FindByPath([]any{1})
	require.NotNil(t, idx1)
	_, ok := DeleteNode(idx1)
	require.True(t, ok)
	// remaining should be [10,30,40], indices 0..2
	zero := arr.FindByPath([]any{0})
	require.NotNil(t, zero)
	require.Equal(t, "10", zero.Value)
	one := arr.FindByPath([]any{1})
	require.NotNil(t, one)
	require.Equal(t, "30", one.Value)
	two := arr.FindByPath([]any{2})
	require.NotNil(t, two)
	require.Equal(t, "40", two.Value)
	require.Equal(t, 3, arr.Size)

	// delete last element (now index 2 -> 40)
	last := arr.FindByPath([]any{2})
	require.NotNil(t, last)
	_, ok = DeleteNode(last)
	require.True(t, ok)
	require.Equal(t, 2, arr.Size)

	// delete first element (10)
	first := arr.FindByPath([]any{0})
	require.NotNil(t, first)
	_, ok = DeleteNode(first)
	require.True(t, ok)
	require.Equal(t, 1, arr.Size)
	only := arr.FindByPath([]any{0})
	require.NotNil(t, only)
	require.Equal(t, "30", only.Value)

	// delete the only element
	_, ok = DeleteNode(only)
	require.True(t, ok)
	require.Equal(t, 0, arr.Size)
}

func TestDeleteNode_EdgeCases(t *testing.T) {
	root, err := Parse([]byte(`{"k": {"x":1}, "arr":[{"y":2},3]}`))
	require.NoError(t, err)

	// try delete root -> ignored
	next, ok := DeleteNode(root)
	require.False(t, ok)
	require.Nil(t, next)

	// delete nested object {"y":2} inside arr[0]
	nested := root.FindByPath([]any{"arr", 0})
	require.NotNil(t, nested)
	_, ok = DeleteNode(nested)
	require.True(t, ok)
	// arr should become [3]
	arr := root.FindByPath([]any{"arr"})
	require.NotNil(t, arr)
	require.Equal(t, 1, arr.Size)
	el := root.FindByPath([]any{"arr", 0})
	require.NotNil(t, el)
	require.Equal(t, Number, el.Kind)
	require.Equal(t, "3", el.Value)
}


================================================
FILE: internal/jsonx/format_err.go
================================================
package jsonx

import (
	"fmt"
	"os"
	"strings"
	"unicode/utf8"

	"github.com/charmbracelet/x/term"
	"github.com/mattn/go-runewidth"
)

func (p *JsonParser) errorSnippet(message string) error {
	termWidth, _, err := term.GetSize(os.Stdout.Fd())
	if err != nil {
		termWidth = 80
	}
	maxWidth := min(termWidth, 60)
	maxWidth -= 2
	maxWidth = max(maxWidth, 10)

	// As we already moved end pointer in next(), we need to move it back.
	p.end -= 1

	before, width := p.contextBefore(maxWidth / 2)
	after, _ := p.contextAfter(maxWidth - width)
	snippet := "  " + before + after
	snippet += "\n  " + strings.Repeat(".", max(0, width-1)) + "^"

	return fmt.Errorf(
		"%s on line %d.\n\n%s\n",
		message,
		p.realLineNumber,
		snippet,
	)
}

func (p *JsonParser) contextBefore(maxWidth int) (s string, width int) {
	pos := p.end + 1
	if pos > len(p.data) {
		pos = len(p.data)
	}
	data := p.data[:pos]
	for len(data) > 0 {
		r, size := utf8.DecodeLastRune(data)
		if r == '\n' {
			break
		}
		runeWidth := runewidth.RuneWidth(r)
		if width+runeWidth > maxWidth {
			break
		}
		width += runeWidth
		pos -= size
		data = data[:pos]
	}
	s = string(p.data[pos:min(p.end+1, len(p.data))])
	return
}

func (p *JsonParser) contextAfter(maxWidth int) (s string, width int) {
	pos := p.end + 1
	if pos >= len(p.data) {
		return
	}
	data := p.data[pos:]
	for len(data) > 0 {
		r, size := utf8.DecodeRune(data)
		if r == '\n' {
			break
		}
		runeWidth := runewidth.RuneWidth(r)
		if width+runeWidth > maxWidth {
			break
		}
		width += runeWidth
		pos += size
		data = data[size:]
	}
	s = string(p.data[p.end+1 : pos]) // +1 to exclude the current character.
	return
}


================================================
FILE: internal/jsonx/json.go
================================================
package jsonx

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
	"unicode"

	"github.com/antonmedv/fx/internal/utils"
)

type JsonParser struct {
	strict         bool
	rd             io.Reader
	buf            []byte
	data           []byte
	end            int
	eof            bool
	char           byte
	lineNumber     int
	realLineNumber int
	depth          uint8
	count          int
}

func Parse(b []byte) (*Node, error) {
	p := NewJsonParser(bytes.NewReader(b), false)
	node, err := p.Parse()
	if err == io.EOF {
		err = nil
	}
	return node, err
}

func NewJsonParser(rd io.Reader, strict bool) *JsonParser {
	p := &JsonParser{
		strict:         strict,
		rd:             rd,
		buf:            make([]byte, 4096),
		lineNumber:     1,
		realLineNumber: 1,
	}
	p.next() // Should be called here, to support streaming.
	return p
}

func (p *JsonParser) Parse() (node *Node, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = p.errorSnippet(fmt.Sprintf("%v", r))
		}
	}()
	if p.count > 0 {
		p.skipWhitespace()
	}
	if p.eof {
		return nil, io.EOF
	}
	node = p.parseValue(true)
	p.count++
	return
}

func (p *JsonParser) Recover() *Node {
	p.eof = false
	p.depth = 0

	start := p.end - 1
	for {
		p.next()
		if p.eof {
			break
		}
		if p.char == '{' || p.char == '[' {
			break
		}
	}

	end := p.end - 1
	if p.data[end-1] == '\n' {
		end-- // Trim trailing newline.
	}

	start = max(0, min(start, end))
	text := string(p.data[start:end])
	text = strings.ReplaceAll(text, "\t", "    ")
	text = strings.ReplaceAll(text, "\r", "")
	lines := strings.Split(text, "\n")

	textNode := &Node{
		Kind:       Err,
		Value:      lines[0],
		Index:      -1,
		LineNumber: p.lineNumberPlusPlus(),
	}
	for i := 1; i < len(lines); i++ {
		textNode.Append(&Node{
			Kind:       Err,
			Value:      lines[i],
			Index:      -1,
			Parent:     textNode,
			LineNumber: p.lineNumberPlusPlus(),
		})
	}
	return textNode
}

func (p *JsonParser) refill() {
	n, err := p.rd.Read(p.buf)
	if err != nil {
		if err == io.EOF {
			p.eof = true
			return
		} else {
			panic(err)
		}
	}
	p.data = append(p.data, p.buf[:n]...)
}

func (p *JsonParser) next() {
	if p.end >= len(p.data) {
		p.refill()
	}
	if p.eof {
		p.char = 0
		p.end = len(p.data) + 1
		return
	}
	p.char = p.data[p.end]
	if p.char == '\n' {
		p.realLineNumber++
	}
	p.end++
}

func (p *JsonParser) back() {
	p.end--
	p.char = p.data[p.end]
}

func (p *JsonParser) set(pos int) {
	p.end = pos
	p.char = p.data[p.end]
}

func (p *JsonParser) lineNumberPlusPlus() int {
	n := p.lineNumber
	p.lineNumber++
	return n
}

func (p *JsonParser) parseValue(root bool) *Node {
	p.skipWhitespace()

	var l *Node
	switch p.char {
	case '"':
		l = p.parseString()
	case '-':
		l = p.parseMinus()
	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		l = p.parseNumber(p.end - 1)
	case '{':
		l = p.parseObject()
	case '[':
		l = p.parseArray()
	case 't':
		l = p.parseKeyword("true", Bool)
	case 'f':
		l = p.parseKeyword("false", Bool)
	case 'n':
		l = p.parseNullOrNan()
	case 'N':
		l = p.parseNan(p.end - 1)
	case 'i', 'I':
		l = p.parseInfinity(p.end - 1)
	case 'u':
		if p.strict {
			panic(fmt.Sprintf("Unexpected character %q", p.char))
		}
		l = p.parseKeyword("undefined", Undefined)
	default:
		panic(fmt.Sprintf("Unexpected character %q", p.char))
	}

	// Skip whitespace will block parseValue (with io.Read in refill func),
	// as soon as we parsed the root value, return and ignore remining whitespaces.
	if !root {
		p.skipWhitespace()
	}

	return l
}

func (p *JsonParser) parseString() *Node {
	return &Node{
		Kind:       String,
		Depth:      p.depth,
		Value:      p.scanString(),
		LineNumber: p.lineNumberPlusPlus(),
	}
}

func (p *JsonParser) scanString() string {
	start := p.end - 1
	p.next()
	escaped := false
	for {
		if escaped {
			escaped = false
			if p.strict {
				switch p.char {
				case 'u':
					var s string
					for i := 0; i < 4; i++ {
						p.next()
						if !utils.IsHexDigit(p.char) {
							panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s%c'", s, p.char))
						}
						s += string(p.char)
					}
					_, err := strconv.ParseInt(s, 16, 32)
					if err != nil {
						panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s'", s))
					}
				case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
				default:
					panic(fmt.Sprintf("Invalid escape sequence '\\%c'", p.char))
				}
			}
		} else if p.char == '\\' {
			escaped = true
		} else if p.char == '"' {
			break
		} else if p.char == 0 {
			panic("Unexpected end of input in string")
		} else if rune(p.char) > unicode.MaxRune {
			panic(fmt.Sprintf("Invalid character code point %d in string", p.char))
		}
		p.next()
	}

	str := string(p.data[start:p.end])
	p.next()

	return str
}

func (p *JsonParser) parseMinus() *Node {
	start := p.end - 1
	p.next()
	switch p.char {
	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		return p.parseNumber(start)
	}
	if !p.strict {
		switch p.char {
		case 'n', 'N':
			return p.parseNan(start)
		case 'i', 'I':
			return p.parseInfinity(start)
		}
	}
	panic(fmt.Sprintf("Invalid character %q in number", p.char))
}

func (p *JsonParser) parseNumber(start int) *Node {
	num := &Node{
		Kind:       Number,
		Depth:      p.depth,
		LineNumber: p.lineNumberPlusPlus(),
	}

	// Leading zero
	if p.char == '0' {
		p.next()
	} else {
		for utils.IsDigit(p.char) {
			p.next()
		}
	}

	// Decimal portion
	if p.char == '.' {
		p.next()
		if !utils.IsDigit(p.char) {
			panic(fmt.Sprintf("Invalid character %q in number", p.char))
		}
		for utils.IsDigit(p.char) {
			p.next()
		}
	}

	// Exponent
	if p.char == 'e' || p.char == 'E' {
		p.next()
		if p.char == '+' || p.char == '-' {
			p.next()
		}
		if !utils.IsDigit(p.char) {
			panic(fmt.Sprintf("Invalid character %q in number", p.char))
		}
		for utils.IsDigit(p.char) {
			p.next()
		}
	}

	num.Value = string(p.data[start : p.end-1])
	return num
}

func (p *JsonParser) parseObject() *Node {
	object := &Node{
		Kind:       Object,
		Depth:      p.depth,
		LineNumber: p.lineNumberPlusPlus(),
	}
	object.Value = curlyBracketOpen

	p.next()
	p.skipWhitespace()

	// Empty object
	if p.char == '}' {
		object.Value = curlyBracketPair
		p.next()
		return object
	}

	for {
		// Expecting a key which should be a string
		if p.char != '"' {
			panic(fmt.Sprintf("Expected object key to be a string, got %q", p.char))
		}

		keyBytes := p.scanString()

		p.skipWhitespace()

		// Expecting colon after key
		if p.char != ':' {
			panic(fmt.Sprintf("Expected colon after object key, got %q", p.char))
		}

		p.next()

		p.depth++
		value := p.parseValue(false)
		value.Key = keyBytes
		value.Parent = object
		p.depth--

		object.Append(value)
		object.Size += 1

		p.skipWhitespace()

		commaPos := p.end
		if p.char == ',' {
			object.End.Comma = true
			p.next()
			p.skipWhitespace()
			if p.char == '}' {
				if p.strict {
					p.set(commaPos)
					panic("Trailing comma is not allowed in strict mode")
				}
				object.End.Comma = false
			} else {
				continue
			}
		}

		if p.char == '}' {
			closeBracket := &Node{
				Kind:       Object,
				Depth:      p.depth,
				LineNumber: p.lineNumberPlusPlus(),
			}
			closeBracket.Value = curlyBracketClose
			closeBracket.Parent = object
			closeBracket.Index = -1
			object.Append(closeBracket)
			p.next()
			return object
		}

		panic(fmt.Sprintf("Unexpected character %q in object", p.char))
	}
}

func (p *JsonParser) parseArray() *Node {
	arr := &Node{
		Kind:       Array,
		Depth:      p.depth,
		LineNumber: p.lineNumberPlusPlus(),
	}
	arr.Value = squareBracketOpen

	p.next()
	p.skipWhitespace()

	if p.char == ']' {
		arr.Value = squareBracketPair
		p.next()
		return arr
	}

	for i := 0; ; i++ {
		p.depth++
		value := p.parseValue(false)
		value.Parent = arr
		arr.Size += 1
		value.Index = i
		p.depth--

		arr.Append(value)
		p.skipWhitespace()

		commaPos := p.end
		if p.char == ',' {
			arr.End.Comma = true
			p.next()
			p.skipWhitespace()
			if p.char == ']' {
				if p.strict {
					p.set(commaPos)
					panic("Trailing comma is not allowed in strict mode")
				}
				arr.End.Comma = false
			} else {
				continue
			}
		}

		if p.char == ']' {
			closeBracket := &Node{
				Kind:       Array,
				Depth:      p.depth,
				LineNumber: p.lineNumberPlusPlus(),
			}
			closeBracket.Value = squareBracketClose
			closeBracket.Parent = arr
			closeBracket.Index = -1
			arr.Append(closeBracket)
			p.next()
			return arr
		}

		panic(fmt.Sprintf("Invalid character %q in array", p.char))
	}
}

func (p *JsonParser) parseKeyword(name string, kind Kind) *Node {
	start := p.end - 1
	for i := 1; i < len(name); i++ {
		p.next()
		if p.char != name[i] {
			panic(fmt.Sprintf("Unexpected character %q in keyword", p.char))
		}
	}
	p.next()
	if isEndOfValue(p.char) {
		keyword := &Node{
			Kind:       kind,
			Depth:      p.depth,
			Value:      string(p.data[start : p.end-1]),
			LineNumber: p.lineNumberPlusPlus(),
		}
		return keyword
	}

	panic(fmt.Sprintf("Unexpected character %q in keyword", p.char))
}

func (p *JsonParser) parseNullOrNan() *Node {
	p.next()
	if p.char == 'u' {
		p.next()
		if p.char == 'l' {
			p.next()
			if p.char == 'l' {
				p.next()
				if isEndOfValue(p.char) {
					return &Node{
						Kind:       Null,
						Depth:      p.depth,
						Value:      "null",
						LineNumber: p.lineNumberPlusPlus(),
					}
				}
			}
		}
	} else if p.char == 'a' {
		p.back() // Put back the 'a'.
		return p.parseNan(p.end - 1)
	}
	panic(fmt.Sprintf("Unexpected character %q", p.char))
}

func (p *JsonParser) parseNan(start int) *Node {
	if p.strict {
		panic(fmt.Sprintf("Unexpected character %q", p.char))
	}
	p.next()
	if p.char == 'a' || p.char == 'A' {
		p.next()
		if p.char == 'n' || p.char == 'N' {
			p.next()
			if isEndOfValue(p.char) {
				return &Node{
					Kind:       NaN,
					Depth:      p.depth,
					Value:      string(p.data[start : p.end-1]),
					LineNumber: p.lineNumberPlusPlus(),
				}
			}
		}
	}

	panic(fmt.Sprintf("Unexpected character %q", p.char))
}

func (p *JsonParser) parseInfinity(start int) *Node {
	if p.strict {
		panic(fmt.Sprintf("Unexpected character %q", p.char))
	}
	p.next()
	if p.char == 'n' || p.char == 'N' {
		p.next()
		if p.char == 'f' || p.char == 'F' {
			p.next()
			if isEndOfValue(p.char) {
				return &Node{
					Kind:       Infinity,
					Depth:      p.depth,
					Value:      string(p.data[start : p.end-1]),
					LineNumber: p.lineNumberPlusPlus(),
				}
			}
			if p.char == 'i' {
				p.next()
				if p.char == 'n' {
					p.next()
					if p.char == 'i' {
						p.next()
						if p.char == 't' {
							p.next()
							if p.char == 'y' {
								p.next()
								if isEndOfValue(p.char) {
									return &Node{
										Kind:       Infinity,
										Depth:      p.depth,
										Value:      string(p.data[start : p.end-1]),
										LineNumber: p.lineNumberPlusPlus(),
									}
								}
							}
						}
					}
				}
			}
		}
	}
	panic(fmt.Sprintf("Unexpected character %q", p.char))
}

func isEndOfValue(ch byte) bool {
	return isWhitespace(ch) || ch == ',' || ch == '}' || ch == ']' || ch == 0 // 0 is EOF
}

func isWhitespace(ch byte) bool {
	return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}

func (p *JsonParser) skipWhitespace() {
	for {
		switch p.char {
		case ' ', '\t', '\n', '\r':
			p.next()
		case '/':
			if p.strict {
				panic("Comments are not allowed in strict mode")
			}
			p.skipComment()
		default:
			return
		}
	}
}

func (p *JsonParser) skipComment() {
	p.next()
	switch p.char {
	case '/':
		for p.char != '\n' && p.char != 0 {
			p.next()
		}
	case '*':
		for {
			p.next()
			if p.char == '*' {
				p.next()
				if p.char == '/' {
					p.next()
					return
				}
			}
			if p.char == 0 {
				panic("Unexpected end of input in comment")
			}
		}
	default:
		panic(fmt.Sprintf("Invalid comment: '/%c'", p.char))
	}
}


================================================
FILE: internal/jsonx/jsonx_test.go
================================================
package jsonx_test

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/antonmedv/fx/internal/jsonx"
)

func TestJsonParser_Parse(t *testing.T) {
	tests := []struct {
		input    string
		wantKind jsonx.Kind
	}{
		{`"hello"`, jsonx.String},
		{`42`, jsonx.Number},
		{`-123.45`, jsonx.Number},
		{`true`, jsonx.Bool},
		{`false`, jsonx.Bool},
		{`null`, jsonx.Null},
		{`{}`, jsonx.Object},
		{`[]`, jsonx.Array},
		{`{"key":"value"}`, jsonx.Object},
		{`[1, 2, 3]`, jsonx.Array},
		{`   "test"   `, jsonx.String},
		{`// comment
		"test"`, jsonx.String},
		{`/* comment */"test"`, jsonx.String},
		{`{"a":1,}`, jsonx.Object},
		{`[1,2,]`, jsonx.Array},
		{`NaN`, jsonx.NaN},
		{`-NaN`, jsonx.NaN},
		{`nan`, jsonx.NaN},
		{`Infinity`, jsonx.Infinity},
		{`-Infinity`, jsonx.Infinity},
		{`infinity`, jsonx.Infinity},
		{`inf`, jsonx.Infinity},
		{`INF`, jsonx.Infinity},
		{`undefined`, jsonx.Undefined},
		{`"\g"`, jsonx.String},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			node, err := jsonx.Parse([]byte(tt.input))
			assert.NoError(t, err, "unexpected error for input: %s", tt.input)
			assert.Equal(t, tt.wantKind, node.Kind, "unexpected kind for input: %s", tt.input)
		})
	}
}

func TestJsonParser_Parse_error(t *testing.T) {
	tests := []struct {
		input string
	}{
		{`"abc`},
		{`truth`},
		{`1e`},
		{`[1, 2`},
		{`/* test`},
		{`[,]`},
		{`{,}`},
		{`[1,,]`},
		{`{"a":1,,}`},
		{`-null`},
		{`Null`},
		{`-Null`},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			_, err := jsonx.Parse([]byte(tt.input))
			assert.Error(t, err, "expected error for input: %s", tt.input)
		})
	}
}

func TestJsonParser_Parse_strict(t *testing.T) {
	tests := []struct {
		input string
	}{
		{`{"a":1,}`},
		{`[1,2,]`},
		{`NaN`},
		{`-NaN`},
		{`nan`},
		{`Infinity`},
		{`-Infinity`},
		{`infinity`},
		{`inf`},
		{`INF`},
		{`-null`},
		{`Null`},
		{`-Null`},
		{`/*comment*/ 42`},
		{`undefined`},
		{`"\g"`},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			parser := jsonx.NewJsonParser(strings.NewReader(tt.input), true)
			_, err := parser.Parse()
			assert.Error(t, err, "expected error for input: %s", tt.input)
		})
	}
}

func TestJsonParser_Recovery(t *testing.T) {
	brokenJSON := `{ "a": 1 }here goes the text`
	t.Run("Recover", func(t *testing.T) {
		p := jsonx.NewJsonParser(strings.NewReader(brokenJSON), false)
		_, _ = p.Parse() // trigger error
		node := p.Recover()

		assert.Equal(t, jsonx.Err, node.Kind, "expected recovery node to be of Kind Err")
		assert.NotEmpty(t, node.Value, "expected recovery node to contain error snippet")
		assert.Equal(t, string(node.Value), "here goes the text", "expected recovery node to contain error snippet")
	})
}

func TestJsonParser_NestedStructureVerification(t *testing.T) {
	input := `{
		"user": {
			"name": "John",
			"age": 30,
			"active": true,
			"contacts": {
				"email": "john@example.com",
				"phone": "123456789"
			},
			"roles": ["admin", "editor"]
		}
	}`

	node, err := jsonx.Parse([]byte(input))
	assert.NoError(t, err)
	assert.Equal(t, jsonx.Object, node.Kind)

	// user object
	user := node.FindByPath([]any{"user"})
	assert.NotNil(t, user)
	assert.Equal(t, jsonx.Object, user.Kind)

	// user.name
	name := node.FindByPath([]any{"user", "name"})
	assert.NotNil(t, name)
	assert.Equal(t, jsonx.String, name.Kind)
	assert.Equal(t, `"John"`, string(name.Value))

	// user.age
	age := node.FindByPath([]any{"user", "age"})
	assert.NotNil(t, age)
	assert.Equal(t, jsonx.Number, age.Kind)
	assert.Equal(t, "30", string(age.Value))

	// user.active
	active := node.FindByPath([]any{"user", "active"})
	assert.NotNil(t, active)
	assert.Equal(t, jsonx.Bool, active.Kind)
	assert.Equal(t, "true", string(active.Value))

	// user.contacts.email
	email := node.FindByPath([]any{"user", "contacts", "email"})
	assert.NotNil(t, email)
	assert.Equal(t, jsonx.String, email.Kind)
	assert.Equal(t, `"john@example.com"`, string(email.Value))

	// user.roles[1]
	role := node.FindByPath([]any{"user", "roles", 1})
	assert.NotNil(t, role)
	assert.Equal(t, jsonx.String, role.Kind)
	assert.Equal(t, `"editor"`, string(role.Value))
}

func TestJsonParser_FindByPathWithCollapsedNodes(t *testing.T) {
	t.Run("collapsed array access", func(t *testing.T) {
		node, err := jsonx.Parse([]byte(`{
			"items": [1, 2, 3, 4, 5]
		}`))
		require.NoError(t, err)

		node.CollapseRecursively()

		items := node.FindByPath([]any{"items"})
		require.NotNil(t, items)

		element := node.FindByPath([]any{"items", 2})
		require.NotNil(t, element)
		require.Equal(t, jsonx.Number, element.Kind)
		require.Equal(t, "3", element.Value)
	})

	t.Run("collapsed object access", func(t *testing.T) {
		node, err := jsonx.Parse([]byte(`{
			"user": {
				"settings": {
					"theme": "dark",
					"notifications": true
				}
			}
		}`))
		require.NoError(t, err)

		node.CollapseRecursively()

		settings := node.FindByPath([]any{"user", "settings"})
		require.NotNil(t, settings)

		theme := node.FindByPath([]any{"user", "settings", "theme"})
		require.NotNil(t, theme)
		require.Equal(t, jsonx.String, theme.Kind)
		require.Equal(t, `"dark"`, string(theme.Value))
	})

	t.Run("nested collapsed structures", func(t *testing.T) {
		node, err := jsonx.Parse([]byte(`{
			"data": {
				"users": [
					{"id": 1, "name": "John"},
					{"id": 2, "name": "Jane"}
				]
			}
		}`))
		require.NoError(t, err)

		node.CollapseRecursively()

		users := node.FindByPath([]any{"data", "users"})
		require.NotNil(t, users)

		userName := node.FindByPath([]any{"data", "users", 1, "name"})
		require.NotNil(t, userName)
		require.Equal(t, jsonx.String, userName.Kind)
		require.Equal(t, `"Jane"`, string(userName.Value))
	})

	t.Run("nested collapsed structures with arrays", func(t *testing.T) {
		node, err := jsonx.Parse([]byte(`{
		  "data": [
			{
			  "first": [
				"tmp",
				{
				  "foo": [
					1,
					2,
					true
				  ]
				}
			  ]
			},
			{
			  "second": []
			}
		  ]
		}`))
		require.NoError(t, err)

		node.CollapseRecursively()

		value := node.FindByPath([]any{"data", 0, "first", 1, "foo", 2})
		require.NotNil(t, value)
		require.Equal(t, jsonx.Bool, value.Kind)
	})
}


================================================
FILE: internal/jsonx/line.go
================================================
package jsonx

import (
	"bufio"
	"io"
	"strconv"
	"strings"
)

type LineParser struct {
	buf        *bufio.Reader
	eof        error
	lineNumber int
}

func NewLineParser(in io.Reader) *LineParser {
	p := &LineParser{
		buf:        bufio.NewReader(in),
		lineNumber: 1,
	}
	return p
}

func (p *LineParser) Parse() (*Node, error) {
	if p.eof != nil {
		return nil, p.eof
	}
	b, err := p.buf.ReadBytes('\n')
	if err != nil {
		if err == io.EOF {
			p.eof = err
		} else {
			return nil, err
		}
	}
	if len(b) == 0 {
		return nil, err
	}
	s := strings.TrimRight(string(b), "\r\n")
	quoted := strconv.Quote(s)
	node := &Node{
		Kind:       String,
		Value:      quoted,
		LineNumber: p.lineNumber,
		Depth:      0,
	}
	p.lineNumber++
	return node, nil
}

func (p *LineParser) Recover() *Node {
	return nil
}


================================================
FILE: internal/jsonx/node.go
================================================
package jsonx

import (
	"strconv"

	"github.com/antonmedv/fx/internal/jsonpath"
)

type Kind byte

const (
	Err Kind = iota
	Null
	Bool
	Number
	String
	Object
	Array
	NaN
	Infinity
	Undefined
)

type Node struct {
	Prev, Next, End *Node
	Parent          *Node
	Collapsed       *Node
	Depth           uint8
	Kind            Kind
	Key             string
	Value           string
	Size            int
	Chunk           string
	ChunkEnd        *Node
	Comma           bool
	Index           int
	LineNumber      int
}

// Append ands a node as a child to the current node (body of {...} or [...]).
func (n *Node) Append(child *Node) {
	if n.End == nil {
		n.End = n
	}
	n.End.Next = child
	child.Prev = n.End
	if child.End == nil {
		n.End = child
	} else {
		n.End = child.End
	}
}

// Adjacent adds a node as a sibling to the current node ({}{}{} or [][][]).
func (n *Node) Adjacent(child *Node) {
	end := n.End
	if end == nil {
		end = n
	}
	end.Next = child
	child.Prev = end
	if n.IsCollapsed() {
		// Also attach to collapsed node.
		n.Next = child
		child.Prev = n
	}
}

func (n *Node) insertChunk(chunk *Node) {
	if n.ChunkEnd == nil {
		n.insertAfter(chunk)
	} else {
		n.ChunkEnd.insertAfter(chunk)
	}
	n.ChunkEnd = chunk
}

func (n *Node) insertAfter(child *Node) {
	if n.Next == nil {
		n.Next = child
		child.Prev = n
	} else {
		old := n.Next
		n.Next = child
		child.Prev = n
		child.Next = old
		old.Prev = child
	}
}

func (n *Node) dropChunks() {
	if n.ChunkEnd == nil {
		return
	}

	n.Chunk = ""

	n.Next = n.ChunkEnd.Next
	if n.Next != nil {
		n.Next.Prev = n
	}

	n.ChunkEnd = nil
}

func (n *Node) HasChildren() bool {
	return n.End != nil
}

func (n *Node) Root() *Node {
	parent := n.Parent
	for parent != nil {
		n = parent
		parent = n.Parent
	}
	return n
}

func (n *Node) IsWrap() bool {
	return n.Value == "" && n.Chunk != ""
}

func (n *Node) IsCollapsed() bool {
	return n.Collapsed != nil
}

func (n *Node) Collapse() *Node {
	if n.End != nil && !n.IsCollapsed() {
		n.Collapsed = n.Next
		n.Next = n.End.Next
		if n.Next != nil {
			n.Next.Prev = n
		}
	}
	return n
}

func (n *Node) CollapseRecursively() {
	var at *Node
	if n.IsCollapsed() {
		at = n.Collapsed
	} else {
		at = n.Next
	}
	for at != nil && at != n.End {
		if at.HasChildren() {
			at.CollapseRecursively()
			at.Collapse()
		}
		at = at.Next
	}
}

func (n *Node) Expand() {
	if n.IsCollapsed() {
		if n.Next != nil {
			n.Next.Prev = n.End
		}
		n.Next = n.Collapsed
		n.Collapsed = nil
	}
}

func (n *Node) ExpandRecursively(level, maxLevel int) {
	if level >= maxLevel {
		return
	}
	if n.IsCollapsed() {
		n.Expand()
	}
	it := n.Next
	for it != nil && it != n.End {
		if it.HasChildren() {
			it.ExpandRecursively(level+1, maxLevel)
			it = it.End.Next
		} else {
Download .txt
gitextract_4yli86d0/

├── .gitattributes
├── .github/
│   ├── images/
│   │   ├── autocomplete.tape
│   │   ├── preview-mode.tape
│   │   └── preview.tape
│   ├── stream.mjs
│   └── workflows/
│       ├── brew.yml
│       ├── docker.yml
│       ├── snap.yml
│       └── test.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── RELEASE.md
├── go.mod
├── go.sum
├── help.go
├── internal/
│   ├── complete/
│   │   ├── complete.bash
│   │   ├── complete.fish
│   │   ├── complete.go
│   │   ├── complete.zsh
│   │   ├── complete_test.go
│   │   ├── prelude.js
│   │   └── utils.go
│   ├── engine/
│   │   ├── engine.go
│   │   ├── engine_test.go
│   │   ├── format_err.go
│   │   ├── fxrc.go
│   │   ├── quote.go
│   │   ├── quote_test.go
│   │   ├── slurp.go
│   │   ├── stdlib.js
│   │   ├── stdlib_test.go
│   │   ├── stringify.go
│   │   ├── transpile.go
│   │   ├── transpile_test.go
│   │   ├── utils.go
│   │   └── vm.go
│   ├── fuzzy/
│   │   ├── algo.go
│   │   ├── chars.go
│   │   ├── chars_test.go
│   │   ├── find.go
│   │   ├── fuzzy_test.go
│   │   ├── normalize.go
│   │   └── utils.go
│   ├── ident/
│   │   └── ident.go
│   ├── jsonpath/
│   │   ├── path.go
│   │   ├── path_test.go
│   │   ├── ref.go
│   │   └── ref_test.go
│   ├── jsonx/
│   │   ├── delete.go
│   │   ├── delete_test.go
│   │   ├── format_err.go
│   │   ├── json.go
│   │   ├── jsonx_test.go
│   │   ├── line.go
│   │   ├── node.go
│   │   ├── node_test.go
│   │   ├── string.go
│   │   ├── to_value.go
│   │   └── wrap.go
│   ├── pretty/
│   │   ├── inlineable.go
│   │   ├── inlineable_test.go
│   │   ├── pretty_print.go
│   │   └── pretty_print_test.go
│   ├── shlex/
│   │   ├── shlex.go
│   │   └── shlex_test.go
│   ├── theme/
│   │   └── theme.go
│   ├── toml/
│   │   ├── toml.go
│   │   └── toml_test.go
│   └── utils/
│       ├── image.go
│       ├── life.go
│       ├── utils.go
│       └── utils_test.go
├── keymap.go
├── main.go
├── main_test.go
├── npm/
│   ├── README.md
│   ├── index.js
│   ├── package.json
│   └── test.js
├── preview.go
├── scripts/
│   └── build.mjs
├── search.go
├── search_test.go
├── snap/
│   └── snapcraft.yaml
├── testdata/
│   ├── TestCollapseRecursive.golden
│   ├── TestCollapseRecursiveWithSizes.golden
│   ├── TestGotoLine.golden
│   ├── TestGotoLineCollapsed.golden
│   ├── TestGotoLineInputGreaterThanTotalLines.golden
│   ├── TestGotoLineInputInvalid.golden
│   ├── TestGotoLineInputLessThanOne.golden
│   ├── TestGotoLineKeepsHistory.golden
│   ├── TestNavigation.golden
│   ├── TestOutput.golden
│   ├── blog.json
│   └── example.json
├── utils.go
├── version.go
├── view.go
├── vim.go
└── vim_test.go
Download .txt
SYMBOL INDEX (840 symbols across 69 files)

FILE: .github/stream.mjs
  function randomItem (line 17) | function randomItem(arr) {
  function randomItems (line 21) | function randomItems(arr, min = 1, max = 3) {
  function randomObject (line 27) | function randomObject() {

FILE: help.go
  function usage (line 14) | func usage() string {
  function help (line 61) | func help(keyMap KeyMap) string {
  function exit (line 137) | func exit() {
  function showLetter (line 172) | func showLetter(t time.Time) bool {

FILE: internal/complete/complete.go
  type Reply (line 22) | type Reply struct
  function Complete (line 42) | func Complete() bool {
  function doComplete (line 64) | func doComplete(compLine string, compWord string, withDisplay bool) {
  function globalsComplete (line 189) | func globalsComplete() []Reply {
  function KeysComplete (line 215) | func KeysComplete(input *jsonx.Node, args []string, compWord string) []R...
  function join (line 263) | func join(prefix, key string) string {
  function filterArgs (line 274) | func filterArgs(args []string) []string {
  function fileComplete (line 291) | func fileComplete(compWord string) []Reply {

FILE: internal/complete/complete_test.go
  function TestKeysComplete (line 9) | func TestKeysComplete(t *testing.T) {
  function replyValues (line 150) | func replyValues(replies []Reply) []string {
  function TestKeysComplete_Display (line 158) | func TestKeysComplete_Display(t *testing.T) {
  function TestKeysComplete_DisplayVsValue (line 177) | func TestKeysComplete_DisplayVsValue(t *testing.T) {

FILE: internal/complete/prelude.js
  function __autocomplete (line 12) | function __autocomplete() {

FILE: internal/complete/utils.go
  function compReply (line 10) | func compReply(reply []Reply, withDisplay bool) {
  function filterReply (line 22) | func filterReply(reply []Reply, compWord string) []Reply {
  function isFile (line 32) | func isFile(path string) bool {
  function dropTail (line 46) | func dropTail(s string) string {
  function balanceBrackets (line 54) | func balanceBrackets(code string) string {
  function lastWord (line 77) | func lastWord(line string) string {
  function writeLog (line 86) | func writeLog(args ...interface{}) {

FILE: internal/engine/engine.go
  function init (line 19) | func init() {
  type Parser (line 27) | type Parser interface
  type Options (line 32) | type Options struct
  function Start (line 39) | func Start(parser Parser, args []string, opts Options) int {
  function callMain (line 146) | func callMain(main goja.Callable, input goja.Value) (output goja.Value, ...
  function validateSyntax (line 161) | func validateSyntax(args []string, i int) error {

FILE: internal/engine/engine_test.go
  function TestEngine (line 13) | func TestEngine(t *testing.T) {
  function TestStart_InvalidJSON (line 67) | func TestStart_InvalidJSON(t *testing.T) {
  function TestStart_FastPath_InvalidJSON (line 87) | func TestStart_FastPath_InvalidJSON(t *testing.T) {
  function TestStart_EscapeSequences (line 107) | func TestStart_EscapeSequences(t *testing.T) {
  function TestStart_EscapeSequences_in_key (line 128) | func TestStart_EscapeSequences_in_key(t *testing.T) {

FILE: internal/engine/format_err.go
  function formatErr (line 12) | func formatErr(args []string, i int, jsCode string) string {
  function trimLeft (line 79) | func trimLeft(s string, ctx int) string {
  function trimRight (line 97) | func trimRight(s string, ctx int) string {

FILE: internal/engine/fxrc.go
  function readFxrc (line 10) | func readFxrc() (string, error) {
  function uniq (line 57) | func uniq(paths []string) []string {

FILE: internal/engine/quote.go
  function Quote (line 9) | func Quote(s string) string {

FILE: internal/engine/quote_test.go
  function TestQuote_BasicASCII (line 12) | func TestQuote_BasicASCII(t *testing.T) {
  function TestQuote_EscapesSpecialCharacters (line 18) | func TestQuote_EscapesSpecialCharacters(t *testing.T) {
  function TestQuote_ControlCharactersAndDEL (line 28) | func TestQuote_ControlCharactersAndDEL(t *testing.T) {
  function TestQuote_BMP_Characters_AsIs (line 64) | func TestQuote_BMP_Characters_AsIs(t *testing.T) {
  function TestQuote_SurrogatePairs_AsIs (line 71) | func TestQuote_SurrogatePairs_AsIs(t *testing.T) {
  function TestQuote_InvalidUTF8BytesAreEscaped (line 77) | func TestQuote_InvalidUTF8BytesAreEscaped(t *testing.T) {
  function TestQuote_JSONRoundTrip_ValidUTF8 (line 86) | func TestQuote_JSONRoundTrip_ValidUTF8(t *testing.T) {

FILE: internal/engine/slurp.go
  function Slurp (line 9) | func Slurp(parser Parser, writeErr func(string)) (Parser, bool) {
  type slurpParser (line 52) | type slurpParser struct
    method Parse (line 56) | func (p *slurpParser) Parse() (*jsonx.Node, error) {
    method Recover (line 65) | func (p *slurpParser) Recover() *jsonx.Node {

FILE: internal/engine/stdlib.js
  function apply (line 21) | function apply(fn, ...args) {
  function len (line 26) | function len(x) {
  function uniq (line 33) | function uniq(x) {
  function sort (line 38) | function sort(x) {
  function isFalsely (line 43) | function isFalsely(x) {
  function filter (line 47) | function filter(fn) {
  function map (line 56) | function map(fn) {
  function walk (line 65) | function walk(fn) {
  function sortBy (line 82) | function sortBy(fn) {
  function sortKeys (line 93) | function sortKeys(x) {
  function groupBy (line 107) | function groupBy(keyFn) {
  function chunk (line 119) | function chunk(size) {
  function zip (line 130) | function zip(...x) {
  function flatten (line 139) | function flatten(x) {
  function reverse (line 144) | function reverse(x) {
  function keys (line 149) | function keys(x) {
  function values (line 154) | function values(x) {
  function list (line 159) | function list(x) {
  function del (line 167) | function del(key) {
  function exit (line 183) | function exit(code) {
  function save (line 187) | function save(x) {
  function toBase64 (line 193) | function toBase64(x) {
  function fromBase64 (line 197) | function fromBase64(x) {
  constant YAML (line 201) | const YAML = {

FILE: internal/engine/stdlib_test.go
  function setupVM (line 15) | func setupVM(t *testing.T) *goja.Runtime {
  function setupVMWithOutput (line 26) | func setupVMWithOutput(t *testing.T) (*goja.Runtime, *[]string) {
  function TestApply (line 37) | func TestApply(t *testing.T) {
  function TestLen (line 67) | func TestLen(t *testing.T) {
  function TestUniq (line 100) | func TestUniq(t *testing.T) {
  function TestSort (line 131) | func TestSort(t *testing.T) {
  function TestFilter (line 162) | func TestFilter(t *testing.T) {
  function TestMap (line 208) | func TestMap(t *testing.T) {
  function TestWalk (line 233) | func TestWalk(t *testing.T) {
  function TestSortBy (line 260) | func TestSortBy(t *testing.T) {
  function TestSortKeys (line 295) | func TestSortKeys(t *testing.T) {
  function TestGroupBy (line 350) | func TestGroupBy(t *testing.T) {
  function TestChunk (line 387) | func TestChunk(t *testing.T) {
  function TestZip (line 426) | func TestZip(t *testing.T) {
  function TestFlatten (line 464) | func TestFlatten(t *testing.T) {
  function TestReverse (line 500) | func TestReverse(t *testing.T) {
  function TestKeys (line 532) | func TestKeys(t *testing.T) {
  function TestValues (line 575) | func TestValues(t *testing.T) {
  function TestList (line 618) | func TestList(t *testing.T) {
  function TestDel (line 644) | func TestDel(t *testing.T) {
  function TestSkipSymbol (line 682) | func TestSkipSymbol(t *testing.T) {
  function TestConsoleLog (line 699) | func TestConsoleLog(t *testing.T) {
  function TestToBase64 (line 725) | func TestToBase64(t *testing.T) {
  function TestFromBase64 (line 748) | func TestFromBase64(t *testing.T) {
  function TestYAML (line 781) | func TestYAML(t *testing.T) {
  function TestIsFalsely (line 822) | func TestIsFalsely(t *testing.T) {
  function TestChainedOperations (line 851) | func TestChainedOperations(t *testing.T) {
  function TestEdgeCases (line 896) | func TestEdgeCases(t *testing.T) {
  function TestBase64RoundTrip (line 960) | func TestBase64RoundTrip(t *testing.T) {
  function escapeJS (line 981) | func escapeJS(s string) string {
  function min (line 989) | func min(a, b int) int {
  function TestExit (line 997) | func TestExit(t *testing.T) {
  function TestSave (line 1024) | func TestSave(t *testing.T) {
  function TestSortMutation (line 1042) | func TestSortMutation(t *testing.T) {
  function TestReverseMutation (line 1058) | func TestReverseMutation(t *testing.T) {
  function TestDelImmutability (line 1074) | func TestDelImmutability(t *testing.T) {
  function TestWalkWithNull (line 1099) | func TestWalkWithNull(t *testing.T) {
  function TestGroupByWithPrototypeKeys (line 1113) | func TestGroupByWithPrototypeKeys(t *testing.T) {
  function TestChunkEdgeCases (line 1129) | func TestChunkEdgeCases(t *testing.T) {
  function TestZipEdgeCases (line 1148) | func TestZipEdgeCases(t *testing.T) {
  function TestFilterWithObjects (line 1171) | func TestFilterWithObjects(t *testing.T) {
  function TestMapWithObjects (line 1189) | func TestMapWithObjects(t *testing.T) {
  function TestSortByStability (line 1219) | func TestSortByStability(t *testing.T) {
  function TestNestedOperations (line 1239) | func TestNestedOperations(t *testing.T) {

FILE: internal/engine/stringify.go
  function Stringify (line 14) | func Stringify(value goja.Value, vm *goja.Runtime, depth int) string {

FILE: internal/engine/transpile.go
  function JS (line 10) | func JS(args []string) string {
  function Body (line 24) | func Body(args []string, i int) string {
  function transpile (line 49) | func transpile(code string) string {
  function fold (line 79) | func fold(s []string) string {

FILE: internal/engine/transpile_test.go
  function TestTranspile (line 7) | func TestTranspile(t *testing.T) {
  function TestFoldSimple (line 30) | func TestFoldSimple(t *testing.T) {

FILE: internal/engine/utils.go
  function extractErrorMessage (line 22) | func extractErrorMessage(s string) string {
  function errorToString (line 28) | func errorToString(err error) string {

FILE: internal/engine/vm.go
  type ExitError (line 16) | type ExitError struct
  function NewVM (line 20) | func NewVM(writeOut func(string)) *goja.Runtime {

FILE: internal/fuzzy/algo.go
  constant whiteChars (line 12) | whiteChars = " \t\n\v\f\r\x85\xA0"
  type Result (line 14) | type Result struct
  constant scoreMatch (line 21) | scoreMatch        = 16
  constant scoreGapStart (line 22) | scoreGapStart     = -3
  constant scoreGapExtension (line 23) | scoreGapExtension = -1
  constant bonusBoundary (line 31) | bonusBoundary = scoreMatch / 2
  constant bonusNonWord (line 36) | bonusNonWord = scoreMatch / 2
  constant bonusCamel123 (line 41) | bonusCamel123 = bonusBoundary + scoreGapExtension
  constant bonusConsecutive (line 46) | bonusConsecutive = -(scoreGapStart + scoreGapExtension)
  constant bonusFirstCharMultiplier (line 53) | bonusFirstCharMultiplier = 2
  type charClass (line 72) | type charClass
  constant charWhite (line 75) | charWhite charClass = iota
  constant charNonWord (line 76) | charNonWord
  constant charDelimiter (line 77) | charDelimiter
  constant charLower (line 78) | charLower
  constant charUpper (line 79) | charUpper
  constant charLetter (line 80) | charLetter
  constant charNumber (line 81) | charNumber
  function init (line 84) | func init() {
  function posArray (line 108) | func posArray(withPos bool, len int) *[]int {
  function alloc16 (line 116) | func alloc16(offset int, slab *Slab, size int) (int, []int16) {
  function alloc32 (line 124) | func alloc32(offset int, slab *Slab, size int) (int, []int32) {
  function charClassOfNonAscii (line 132) | func charClassOfNonAscii(char rune) charClass {
  function charClassOf (line 149) | func charClassOf(char rune) charClass {
  function bonusFor (line 156) | func bonusFor(prevClass charClass, class charClass) int16 {
  function normalizeRune (line 186) | func normalizeRune(r rune) rune {
  function trySkip (line 198) | func trySkip(input *Chars, caseSensitive bool, b byte, from int) int {
  function isAscii (line 222) | func isAscii(runes []rune) bool {
  function asciiFuzzyIndex (line 231) | func asciiFuzzyIndex(input *Chars, pattern []rune, caseSensitive bool) (...
  type Slab (line 272) | type Slab struct
  constant caseSensitive (line 278) | caseSensitive = false
  constant normalize (line 279) | normalize     = true
  constant forward (line 280) | forward       = true
  function fuzzyMatch (line 283) | func fuzzyMatch(input *Chars, pattern []rune) (Result, *[]int) {

FILE: internal/fuzzy/chars.go
  constant overflow64 (line 11) | overflow64 uint64 = 0x8080808080808080
  constant overflow32 (line 12) | overflow32 uint32 = 0x80808080
  type Chars (line 15) | type Chars struct
    method IsBytes (line 69) | func (chars *Chars) IsBytes() bool {
    method Bytes (line 73) | func (chars *Chars) Bytes() []byte {
    method NumLines (line 77) | func (chars *Chars) NumLines(atMost int) (int, bool) {
    method optionalRunes (line 106) | func (chars *Chars) optionalRunes() []rune {
    method Get (line 113) | func (chars *Chars) Get(i int) rune {
    method Length (line 120) | func (chars *Chars) Length() int {
    method TrimLength (line 128) | func (chars *Chars) TrimLength() uint16 {
    method LeadingWhitespaces (line 157) | func (chars *Chars) LeadingWhitespaces() int {
    method TrailingWhitespaces (line 169) | func (chars *Chars) TrailingWhitespaces() int {
    method TrimTrailingWhitespaces (line 181) | func (chars *Chars) TrimTrailingWhitespaces() {
    method TrimSuffix (line 186) | func (chars *Chars) TrimSuffix(runes []rune) {
    method SliceRight (line 203) | func (chars *Chars) SliceRight(last int) {
    method ToString (line 207) | func (chars *Chars) ToString() string {
    method ToRunes (line 214) | func (chars *Chars) ToRunes() []rune {
    method CopyRunes (line 226) | func (chars *Chars) CopyRunes(dest []rune, from int) {
    method Prepend (line 236) | func (chars *Chars) Prepend(prefix string) {
    method Lines (line 245) | func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, ...
  function checkAscii (line 26) | func checkAscii(bytes []byte) (bool, int) {
  function ToChars (line 47) | func ToChars(bytes []byte) Chars {
  function RunesToChars (line 65) | func RunesToChars(runes []rune) Chars {

FILE: internal/fuzzy/chars_test.go
  function TestToCharsAscii (line 8) | func TestToCharsAscii(t *testing.T) {
  function TestCharsLength (line 15) | func TestCharsLength(t *testing.T) {
  function TestCharsToString (line 22) | func TestCharsToString(t *testing.T) {
  function TestTrimLength (line 30) | func TestTrimLength(t *testing.T) {
  function TestCharsLines (line 51) | func TestCharsLines(t *testing.T) {

FILE: internal/fuzzy/find.go
  type Match (line 3) | type Match struct
  function Find (line 10) | func Find(pattern []rune, array []string) *Match {

FILE: internal/fuzzy/fuzzy_test.go
  function TestFind (line 10) | func TestFind(t *testing.T) {
  function TestFuzzyMatch (line 94) | func TestFuzzyMatch(t *testing.T) {
  function TestFuzzyMatchScoring (line 129) | func TestFuzzyMatchScoring(t *testing.T) {
  function TestNormalizeRune (line 141) | func TestNormalizeRune(t *testing.T) {
  function TestNormalizeRunes (line 173) | func TestNormalizeRunes(t *testing.T) {
  function TestCharClass (line 195) | func TestCharClass(t *testing.T) {
  function TestCharClassOfNonAscii (line 223) | func TestCharClassOfNonAscii(t *testing.T) {
  function TestBonusFor (line 244) | func TestBonusFor(t *testing.T) {
  function TestAsUint16 (line 258) | func TestAsUint16(t *testing.T) {
  function TestStringWidth (line 280) | func TestStringWidth(t *testing.T) {
  function TestRunesWidth (line 302) | func TestRunesWidth(t *testing.T) {
  function TestTrySkip (line 359) | func TestTrySkip(t *testing.T) {
  function TestIsAscii (line 385) | func TestIsAscii(t *testing.T) {
  function TestAsciiFuzzyIndex (line 407) | func TestAsciiFuzzyIndex(t *testing.T) {

FILE: internal/fuzzy/normalize.go
  function NormalizeRunes (line 479) | func NormalizeRunes(runes []rune) []rune {

FILE: internal/fuzzy/utils.go
  function AsUint16 (line 10) | func AsUint16(val int) uint16 {
  function StringWidth (line 20) | func StringWidth(s string) int {
  function RunesWidth (line 25) | func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (...

FILE: internal/ident/ident.go
  function init (line 13) | func init() {

FILE: internal/jsonpath/path.go
  type state (line 9) | type state
  constant start (line 12) | start state = iota
  constant unknown (line 13) | unknown
  constant propOrIndex (line 14) | propOrIndex
  constant prop (line 15) | prop
  constant index (line 16) | index
  constant indexEnd (line 17) | indexEnd
  constant number (line 18) | number
  constant doubleQuote (line 19) | doubleQuote
  constant doubleQuoteEscape (line 20) | doubleQuoteEscape
  constant singleQuote (line 21) | singleQuote
  constant singleQuoteEscape (line 22) | singleQuoteEscape
  function Split (line 25) | func Split(p string) ([]any, bool) {
  function isProp (line 177) | func isProp(ch rune) bool {
  function Join (line 183) | func Join(path []any) string {

FILE: internal/jsonpath/path_test.go
  function Test_SplitPath (line 11) | func Test_SplitPath(t *testing.T) {
  function Test_SplitPath_negative (line 98) | func Test_SplitPath_negative(t *testing.T) {
  function TestJoin (line 147) | func TestJoin(t *testing.T) {

FILE: internal/jsonpath/ref.go
  function ParseSchemaRef (line 8) | func ParseSchemaRef(ref string) ([]any, bool) {

FILE: internal/jsonpath/ref_test.go
  function TestParseSchemaRef (line 10) | func TestParseSchemaRef(t *testing.T) {

FILE: internal/jsonx/delete.go
  function DeleteNode (line 12) | func DeleteNode(at *Node) (*Node, bool) {

FILE: internal/jsonx/delete_test.go
  function TestDeleteNode_ObjectScenarios (line 11) | func TestDeleteNode_ObjectScenarios(t *testing.T) {
  function TestDeleteNode_ArrayScenarios (line 45) | func TestDeleteNode_ArrayScenarios(t *testing.T) {
  function TestDeleteNode_EdgeCases (line 91) | func TestDeleteNode_EdgeCases(t *testing.T) {

FILE: internal/jsonx/format_err.go
  method errorSnippet (line 13) | func (p *JsonParser) errorSnippet(message string) error {
  method contextBefore (line 38) | func (p *JsonParser) contextBefore(maxWidth int) (s string, width int) {
  method contextAfter (line 61) | func (p *JsonParser) contextAfter(maxWidth int) (s string, width int) {

FILE: internal/jsonx/json.go
  type JsonParser (line 14) | type JsonParser struct
    method Parse (line 49) | func (p *JsonParser) Parse() (node *Node, err error) {
    method Recover (line 66) | func (p *JsonParser) Recover() *Node {
    method refill (line 110) | func (p *JsonParser) refill() {
    method next (line 123) | func (p *JsonParser) next() {
    method back (line 139) | func (p *JsonParser) back() {
    method set (line 144) | func (p *JsonParser) set(pos int) {
    method lineNumberPlusPlus (line 149) | func (p *JsonParser) lineNumberPlusPlus() int {
    method parseValue (line 155) | func (p *JsonParser) parseValue(root bool) *Node {
    method parseString (line 198) | func (p *JsonParser) parseString() *Node {
    method scanString (line 207) | func (p *JsonParser) scanString() string {
    method parseMinus (line 252) | func (p *JsonParser) parseMinus() *Node {
    method parseNumber (line 270) | func (p *JsonParser) parseNumber(start int) *Node {
    method parseObject (line 315) | func (p *JsonParser) parseObject() *Node {
    method parseArray (line 395) | func (p *JsonParser) parseArray() *Node {
    method parseKeyword (line 457) | func (p *JsonParser) parseKeyword(name string, kind Kind) *Node {
    method parseNullOrNan (line 479) | func (p *JsonParser) parseNullOrNan() *Node {
    method parseNan (line 504) | func (p *JsonParser) parseNan(start int) *Node {
    method parseInfinity (line 527) | func (p *JsonParser) parseInfinity(start int) *Node {
    method skipWhitespace (line 580) | func (p *JsonParser) skipWhitespace() {
    method skipComment (line 596) | func (p *JsonParser) skipComment() {
  function Parse (line 28) | func Parse(b []byte) (*Node, error) {
  function NewJsonParser (line 37) | func NewJsonParser(rd io.Reader, strict bool) *JsonParser {
  function isEndOfValue (line 572) | func isEndOfValue(ch byte) bool {
  function isWhitespace (line 576) | func isWhitespace(ch byte) bool {

FILE: internal/jsonx/jsonx_test.go
  function TestJsonParser_Parse (line 13) | func TestJsonParser_Parse(t *testing.T) {
  function TestJsonParser_Parse_error (line 55) | func TestJsonParser_Parse_error(t *testing.T) {
  function TestJsonParser_Parse_strict (line 81) | func TestJsonParser_Parse_strict(t *testing.T) {
  function TestJsonParser_Recovery (line 112) | func TestJsonParser_Recovery(t *testing.T) {
  function TestJsonParser_NestedStructureVerification (line 125) | func TestJsonParser_NestedStructureVerification(t *testing.T) {
  function TestJsonParser_FindByPathWithCollapsedNodes (line 179) | func TestJsonParser_FindByPathWithCollapsedNodes(t *testing.T) {

FILE: internal/jsonx/line.go
  type LineParser (line 10) | type LineParser struct
    method Parse (line 24) | func (p *LineParser) Parse() (*Node, error) {
    method Recover (line 51) | func (p *LineParser) Recover() *Node {
  function NewLineParser (line 16) | func NewLineParser(in io.Reader) *LineParser {

FILE: internal/jsonx/node.go
  type Kind (line 9) | type Kind
  constant Err (line 12) | Err Kind = iota
  constant Null (line 13) | Null
  constant Bool (line 14) | Bool
  constant Number (line 15) | Number
  constant String (line 16) | String
  constant Object (line 17) | Object
  constant Array (line 18) | Array
  constant NaN (line 19) | NaN
  constant Infinity (line 20) | Infinity
  constant Undefined (line 21) | Undefined
  type Node (line 24) | type Node struct
    method Append (line 41) | func (n *Node) Append(child *Node) {
    method Adjacent (line 55) | func (n *Node) Adjacent(child *Node) {
    method insertChunk (line 69) | func (n *Node) insertChunk(chunk *Node) {
    method insertAfter (line 78) | func (n *Node) insertAfter(child *Node) {
    method dropChunks (line 91) | func (n *Node) dropChunks() {
    method HasChildren (line 106) | func (n *Node) HasChildren() bool {
    method Root (line 110) | func (n *Node) Root() *Node {
    method IsWrap (line 119) | func (n *Node) IsWrap() bool {
    method IsCollapsed (line 123) | func (n *Node) IsCollapsed() bool {
    method Collapse (line 127) | func (n *Node) Collapse() *Node {
    method CollapseRecursively (line 138) | func (n *Node) CollapseRecursively() {
    method Expand (line 154) | func (n *Node) Expand() {
    method ExpandRecursively (line 164) | func (n *Node) ExpandRecursively(level, maxLevel int) {
    method FindByPath (line 182) | func (n *Node) FindByPath(path []any) *Node {
    method findChildByKey (line 198) | func (n *Node) findChildByKey(key string) *Node {
    method findChildByIndex (line 226) | func (n *Node) findChildByIndex(index int) *Node {
    method FindNextNonErr (line 246) | func (n *Node) FindNextNonErr() *Node {
    method Children (line 254) | func (n *Node) Children() ([]string, []*Node) {
    method Bottom (line 290) | func (n *Node) Bottom() *Node {
    method Paths (line 302) | func (n *Node) Paths(paths *[]string, nodes *[]*Node) {
    method ForEach (line 359) | func (n *Node) ForEach(cb func(*Node)) {

FILE: internal/jsonx/node_test.go
  function TestNode_children (line 11) | func TestNode_children(t *testing.T) {
  function TestNode_expandRecursively (line 19) | func TestNode_expandRecursively(t *testing.T) {
  function TestNode_Paths (line 28) | func TestNode_Paths(t *testing.T) {
  function TestNode_Paths_Collapsed (line 46) | func TestNode_Paths_Collapsed(t *testing.T) {
  function TestNode_ForEach (line 65) | func TestNode_ForEach(t *testing.T) {
  function TestNode_ForEach_Empty (line 78) | func TestNode_ForEach_Empty(t *testing.T) {
  function TestNode_ForEach_SkipsNested (line 89) | func TestNode_ForEach_SkipsNested(t *testing.T) {

FILE: internal/jsonx/string.go
  constant curlyBracketOpen (line 8) | curlyBracketOpen   = "{"
  constant curlyBracketClose (line 9) | curlyBracketClose  = "}"
  constant curlyBracketPair (line 10) | curlyBracketPair   = "{}"
  constant squareBracketOpen (line 11) | squareBracketOpen  = "["
  constant squareBracketClose (line 12) | squareBracketClose = "]"
  constant squareBracketPair (line 13) | squareBracketPair  = "[]"
  method String (line 16) | func (n *Node) String() string {

FILE: internal/jsonx/to_value.go
  method ToValue (line 14) | func (n *Node) ToValue(vm *goja.Runtime) goja.Value {
  constant maxSafeInt (line 117) | maxSafeInt = 1<<53 - 1
  constant minSafeInt (line 120) | minSafeInt = -maxSafeInt
  function ParseNumber (line 123) | func ParseNumber(s string) (interface{}, bool) {

FILE: internal/jsonx/wrap.go
  function DropWrapAll (line 11) | func DropWrapAll(n *Node) {
  function Wrap (line 24) | func Wrap(n *Node, termWidth int) {
  function doWrap (line 56) | func doWrap(n *Node, termWidth int) ([]string, int) {

FILE: internal/pretty/inlineable.go
  function isInlineable (line 7) | func isInlineable(n *jsonx.Node) bool {
  function isSingleElementArray (line 19) | func isSingleElementArray(n *jsonx.Node) bool {
  function isSimpleNumbersArray (line 34) | func isSimpleNumbersArray(n *jsonx.Node) bool {
  function isSimpleObject (line 49) | func isSimpleObject(n *jsonx.Node) bool {
  function isNestedArrays (line 109) | func isNestedArrays(n *jsonx.Node) bool {
  function isArrayOfSimpleObject (line 129) | func isArrayOfSimpleObject(n *jsonx.Node) bool {

FILE: internal/pretty/inlineable_test.go
  function TestIsInlineable (line 14) | func TestIsInlineable(t *testing.T) {
  function TestIsNestedArrays (line 116) | func TestIsNestedArrays(t *testing.T) {
  function TestIsArrayOfSimpleObject (line 263) | func TestIsArrayOfSimpleObject(t *testing.T) {

FILE: internal/pretty/pretty_print.go
  function Print (line 13) | func Print(n *jsonx.Node, withInline bool) string {
  function table (line 44) | func table(out *strings.Builder, n *jsonx.Node) *jsonx.Node {
  function inline (line 66) | func inline(out *strings.Builder, n *jsonx.Node) *jsonx.Node {
  function printIdent (line 86) | func printIdent(out *strings.Builder, n *jsonx.Node) {
  function printKey (line 92) | func printKey(out *strings.Builder, n *jsonx.Node) {
  function printValue (line 99) | func printValue(out *strings.Builder, n *jsonx.Node) {
  function next (line 108) | func next(n *jsonx.Node) *jsonx.Node {
  function afterEnd (line 116) | func afterEnd(n *jsonx.Node) *jsonx.Node {

FILE: internal/pretty/pretty_print_test.go
  function stripEscapeSequences (line 14) | func stripEscapeSequences(s string) string {
  constant yes (line 20) | yes byte = iota
  constant no (line 21) | no
  constant both (line 22) | both
  function TestPrettyPrint (line 25) | func TestPrettyPrint(t *testing.T) {

FILE: internal/shlex/shlex.go
  type TokenType (line 49) | type TokenType
  type runeTokenClass (line 52) | type runeTokenClass
  type lexerState (line 55) | type lexerState
  type Token (line 58) | type Token struct
    method Equal (line 66) | func (a *Token) Equal(b *Token) bool {
  constant spaceRunes (line 78) | spaceRunes            = " \t\r\n"
  constant escapingQuoteRunes (line 79) | escapingQuoteRunes    = `"`
  constant nonEscapingQuoteRunes (line 80) | nonEscapingQuoteRunes = "'"
  constant escapeRunes (line 81) | escapeRunes           = `\`
  constant commentRunes (line 82) | commentRunes          = "#"
  constant unknownRuneClass (line 87) | unknownRuneClass runeTokenClass = iota
  constant spaceRuneClass (line 88) | spaceRuneClass
  constant escapingQuoteRuneClass (line 89) | escapingQuoteRuneClass
  constant nonEscapingQuoteRuneClass (line 90) | nonEscapingQuoteRuneClass
  constant escapeRuneClass (line 91) | escapeRuneClass
  constant commentRuneClass (line 92) | commentRuneClass
  constant eofRuneClass (line 93) | eofRuneClass
  constant UnknownToken (line 98) | UnknownToken TokenType = iota
  constant WordToken (line 99) | WordToken
  constant SpaceToken (line 100) | SpaceToken
  constant CommentToken (line 101) | CommentToken
  constant startState (line 106) | startState           lexerState = iota
  constant inWordState (line 107) | inWordState
  constant escapingState (line 108) | escapingState
  constant escapingQuotedState (line 109) | escapingQuotedState
  constant quotingEscapingState (line 110) | quotingEscapingState
  constant quotingState (line 111) | quotingState
  constant commentState (line 112) | commentState
  type tokenClassifier (line 116) | type tokenClassifier
    method addRuneClass (line 118) | func (typeMap tokenClassifier) addRuneClass(runes string, tokenType ru...
    method ClassifyRune (line 136) | func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass {
  function newDefaultClassifier (line 125) | func newDefaultClassifier() tokenClassifier {
  type Lexer (line 141) | type Lexer
    method Next (line 150) | func (l *Lexer) Next() (string, error) {
  function NewLexer (line 144) | func NewLexer(r io.Reader) *Lexer {
  type Tokenizer (line 168) | type Tokenizer struct
    method scanStream (line 184) | func (t *Tokenizer) scanStream() (*Token, error) {
    method Next (line 392) | func (t *Tokenizer) Next() (*Token, error) {
  function NewTokenizer (line 174) | func NewTokenizer(r io.Reader) *Tokenizer {
  function Split (line 397) | func Split(s string) ([]string, error) {
  function Parse (line 413) | func Parse(s string) string {

FILE: internal/shlex/shlex_test.go
  function TestClassifier (line 32) | func TestClassifier(t *testing.T) {
  function TestTokenizer (line 47) | func TestTokenizer(t *testing.T) {
  function TestLexer (line 73) | func TestLexer(t *testing.T) {
  function TestSplit (line 89) | func TestSplit(t *testing.T) {
  function TestSplit_unfinished (line 105) | func TestSplit_unfinished(t *testing.T) {

FILE: internal/theme/theme.go
  type Theme (line 16) | type Theme struct
  type Color (line 33) | type Color
  function Value (line 35) | func Value(kind jsonx.Kind) Color {
  function init (line 63) | func init() {
  function noColor (line 354) | func noColor(s string) string {
  function toColor (line 358) | func toColor(f func(s ...string) string) Color {
  function fg (line 364) | func fg(color string) Color {
  function underlineFg (line 368) | func underlineFg(color string) Color {
  function boldFg (line 372) | func boldFg(color string) Color {
  function ThemeTester (line 376) | func ThemeTester() {
  function ExportThemes (line 419) | func ExportThemes() {

FILE: internal/toml/toml.go
  type jnode (line 15) | type jnode interface
  type jobject (line 17) | type jobject struct
  type jfield (line 21) | type jfield struct
  type jarray (line 26) | type jarray struct
  function ToJSON (line 30) | func ToJSON(in []byte) ([]byte, error) {
  function keyParts (line 82) | func keyParts(it unstable.Iterator) []string {
  function dot (line 89) | func dot(parts []string) string { return strings.Join(parts, ".") }
  function ensureContainer (line 91) | func ensureContainer(root *jobject, tablePath []string, aotActive map[st...
  function setNested (line 154) | func setNested(obj *jobject, parts []string, val jnode) {
  function getField (line 186) | func getField(obj *jobject, key string) (jfield, bool) {
  function replaceField (line 194) | func replaceField(obj *jobject, key string, val jnode) {
  function toJ (line 207) | func toJ(v any) jnode {
  function lookupTyped (line 227) | func lookupTyped(typed any, tablePath, rel []string, aotActive map[strin...
  function asMap (line 265) | func asMap(x any) (map[string]any, bool) {
  function writeJSON (line 275) | func writeJSON(w io.Writer, n jnode) error {

FILE: internal/toml/toml_test.go
  function assertJSONBytesEqual (line 12) | func assertJSONBytesEqual(t *testing.T, got []byte, want string) {
  function assertJSONStructEqual (line 18) | func assertJSONStructEqual(t *testing.T, got []byte, want string) {
  function TestToJSON_SimpleScalars (line 31) | func TestToJSON_SimpleScalars(t *testing.T) {
  function TestToJSON_NestedTable (line 41) | func TestToJSON_NestedTable(t *testing.T) {
  function TestToJSON_DottedKeys (line 50) | func TestToJSON_DottedKeys(t *testing.T) {
  function TestToJSON_ArraysAndInlineTables (line 59) | func TestToJSON_ArraysAndInlineTables(t *testing.T) {
  function TestToJSON_ArraysOfTables (line 69) | func TestToJSON_ArraysOfTables(t *testing.T) {
  function TestToJSON_NestedAOT (line 88) | func TestToJSON_NestedAOT(t *testing.T) {
  function TestToJSON_MixedTablesAndKeysOrder (line 100) | func TestToJSON_MixedTablesAndKeysOrder(t *testing.T) {
  function TestToJSON_Datetime (line 117) | func TestToJSON_Datetime(t *testing.T) {

FILE: internal/utils/image.go
  function DrawImage (line 16) | func DrawImage(r io.Reader, width, height int) (string, error) {

FILE: internal/utils/life.go
  function GameOfLife (line 14) | func GameOfLife() {

FILE: internal/utils/utils.go
  function IsHexDigit (line 7) | func IsHexDigit(ch byte) bool {
  function IsDigit (line 11) | func IsDigit(ch byte) bool {
  function Contains (line 15) | func Contains(needle int, haystack []int) bool {
  function Unquote (line 24) | func Unquote(s string) (string, error) {

FILE: internal/utils/utils_test.go
  function TestIsHexDigit (line 9) | func TestIsHexDigit(t *testing.T) {
  function TestIsDigit (line 37) | func TestIsDigit(t *testing.T) {
  function TestContains (line 60) | func TestContains(t *testing.T) {
  function TestUnquote (line 86) | func TestUnquote(t *testing.T) {

FILE: keymap.go
  type KeyMap (line 5) | type KeyMap struct
  function init (line 45) | func init() {

FILE: main.go
  function init (line 59) | func init() {
  function main (line 65) | func main() {
  type model (line 329) | type model struct
    method Init (line 395) | func (m *model) Init() tea.Cmd {
    method Update (line 399) | func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    method handleHelpKey (line 553) | func (m *model) handleHelpKey(msg tea.Msg) (tea.Model, tea.Cmd) {
    method handleGotoLineKey (line 565) | func (m *model) handleGotoLineKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
    method handleSearchKey (line 585) | func (m *model) handleSearchKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
    method handleGotoSymbolKey (line 607) | func (m *model) handleGotoSymbolKey(msg tea.KeyMsg) (tea.Model, tea.Cm...
    method handleYankKey (line 636) | func (m *model) handleYankKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
    method handleShowSelectorKey (line 654) | func (m *model) handleShowSelectorKey(msg tea.KeyMsg) (tea.Model, tea....
    method handlePendingDelete (line 666) | func (m *model) handlePendingDelete(msg tea.Msg) {
    method handleKey (line 680) | func (m *model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
    method up (line 983) | func (m *model) up() {
    method down (line 997) | func (m *model) down() {
    method recordHistory (line 1016) | func (m *model) recordHistory() {
    method scrollToBottom (line 1038) | func (m *model) scrollToBottom() {
    method visibleLines (line 1048) | func (m *model) visibleLines() int {
    method scrollIntoView (line 1058) | func (m *model) scrollIntoView() {
    method scrollBackward (line 1073) | func (m *model) scrollBackward(lines int) {
    method scrollForward (line 1084) | func (m *model) scrollForward(lines int) {
    method prettyKey (line 1098) | func (m *model) prettyKey(node *Node, selected bool) []byte {
    method prettyPrint (line 1123) | func (m *model) prettyPrint(node *Node, isSelected, isRef bool) string {
    method viewWidth (line 1168) | func (m *model) viewWidth() int {
    method viewHeight (line 1177) | func (m *model) viewHeight() int {
    method cursorPointsTo (line 1196) | func (m *model) cursorPointsTo() (*Node, bool) {
    method at (line 1201) | func (m *model) at(pos int) *Node {
    method nodeInsideView (line 1212) | func (m *model) nodeInsideView(n *Node) bool {
    method selectNodeInView (line 1229) | func (m *model) selectNodeInView(n *Node) {
    method selectNode (line 1243) | func (m *model) selectNode(n *Node) {
    method cursorPath (line 1266) | func (m *model) cursorPath() string {
    method cursorValue (line 1294) | func (m *model) cursorValue() string {
    method cursorKey (line 1354) | func (m *model) cursorKey() string {
    method findByPath (line 1370) | func (m *model) findByPath(path []any) *Node {
    method currentTopNode (line 1375) | func (m *model) currentTopNode() *Node {
    method createKeysIndex (line 1386) | func (m *model) createKeysIndex() {
    method dig (line 1405) | func (m *model) dig(v string) *Node {
    method print (line 1437) | func (m *model) print() tea.Cmd {
    method open (line 1442) | func (m *model) open() tea.Cmd {
    method deleteAtCursor (line 1465) | func (m *model) deleteAtCursor() {
  type location (line 370) | type location struct
  type nodeMsg (line 375) | type nodeMsg struct
  type errorMsg (line 379) | type errorMsg struct
  type eofMsg (line 383) | type eofMsg struct
  type searchResultMsg (line 385) | type searchResultMsg struct
  type searchCancelledMsg (line 391) | type searchCancelledMsg struct

FILE: main_test.go
  function init (line 20) | func init() {
  type options (line 24) | type options struct
  function prepare (line 29) | func prepare(t *testing.T, opts ...options) *teatest.TestModel {
  function read (line 64) | func read(t *testing.T, tm *teatest.TestModel) []byte {
  function TestOutput (line 78) | func TestOutput(t *testing.T) {
  function TestNavigation (line 87) | func TestNavigation(t *testing.T) {
  function TestCollapseRecursive (line 99) | func TestCollapseRecursive(t *testing.T) {
  function TestCollapseRecursiveWithSizes (line 109) | func TestCollapseRecursiveWithSizes(t *testing.T) {

FILE: npm/index.js
  function transform (line 67) | async function transform(json, args, theme) {
  function transpile (line 106) | function transpile(code) {
  function run (line 140) | async function run(json, code) {
  function read (line 327) | async function read(fd = 0) {
  function isFile (line 355) | function isFile(fs, path) {
  function sleepSync (line 364) | function sleepSync(ms) {
  function next (line 397) | function next() {
  function parseValue (line 411) | function parseValue() {
  function parseString (line 425) | function parseString() {
  function parseNumber (line 475) | function parseNumber() {
  function parseObject (line 523) | function parseObject() {
  function parseArray (line 562) | function parseArray() {
  function parseKeyword (line 592) | function parseKeyword(name, value) {
  function skipWhitespace (line 607) | function skipWhitespace() {
  function skipComment (line 614) | function skipComment() {
  function isWhitespace (line 640) | function isWhitespace(ch) {
  function isHexDigit (line 644) | function isHexDigit(ch) {
  function isDigit (line 648) | function isDigit(ch) {
  function isInteger (line 652) | function isInteger(value) {
  function toSafeNumber (line 656) | function toSafeNumber(str) {
  function expectValue (line 663) | function expectValue(value) {
  function errorSnippet (line 669) | function errorSnippet(message = `Unexpected character '${lastChar}'`) {
  function readEOL (line 682) | function readEOL() {
  function stringify (line 692) | function stringify(value, theme) {
  function themes (line 740) | function themes(id) {
  function importFxrc (line 760) | async function importFxrc(path) {
  function loadFxrc (line 770) | function loadFxrc(os, fs, path, process) {
  function printUsage (line 801) | function printUsage() {
  function isCollection (line 815) | function isCollection(node){if(node&&typeof node==="object")switch(node[...
  function isNode (line 815) | function isNode(node){if(node&&typeof node==="object")switch(node[NODE_T...
  function visit (line 815) | function visit(node,visitor){const visitor_=initVisitor(visitor);if(isDo...
  function visit_ (line 815) | function visit_(key,node,visitor,path){const ctrl=callVisitor(key,node,v...
  function visitAsync (line 815) | async function visitAsync(node,visitor){const visitor_=initVisitor(visit...
  function visitAsync_ (line 815) | async function visitAsync_(key,node,visitor,path){const ctrl=await callV...
  function initVisitor (line 815) | function initVisitor(visitor){if(typeof visitor==="object"&&(visitor.Col...
  function callVisitor (line 815) | function callVisitor(key,node,visitor,path){if(typeof visitor==="functio...
  function replaceNode (line 815) | function replaceNode(key,path,node){const parent=path[path.length-1];if(...
  method constructor (line 815) | constructor(yaml,tags){this.docStart=null;this.docEnd=false;this.yaml=Ob...
  method clone (line 815) | clone(){const copy=new _Directives(this.yaml,this.tags);copy.docStart=th...
  method atDocument (line 815) | atDocument(){const res=new _Directives(this.yaml,this.tags);switch(this....
  method add (line 815) | add(line,onError){if(this.atNextDocument){this.yaml={explicit:_Directive...
  method tagName (line 815) | tagName(source,onError){if(source==="!")return"!";if(source[0]!=="!"){on...
  method tagString (line 815) | tagString(tag){for(const[handle,prefix]of Object.entries(this.tags)){if(...
  method toString (line 815) | toString(doc){const lines=this.yaml.explicit?[`%YAML ${this.yaml.version...
  function anchorIsValid (line 815) | function anchorIsValid(anchor){if(/[\x00-\x19\s,[\]{}]/.test(anchor)){co...
  function anchorNames (line 815) | function anchorNames(root){const anchors=new Set;visit(root,{Value(_key,...
  function findNewAnchor (line 815) | function findNewAnchor(prefix,exclude){for(let i=1;true;++i){const name=...
  function createNodeAnchors (line 815) | function createNodeAnchors(doc,prefix){const aliasObjects=[];const sourc...
  function applyReviver (line 815) | function applyReviver(reviver,obj,key,val){if(val&&typeof val==="object"...
  function toJS (line 815) | function toJS(value,arg,ctx){if(Array.isArray(value))return value.map((v...
  method constructor (line 815) | constructor(type){Object.defineProperty(this,NODE_TYPE,{value:type})}
  method clone (line 815) | clone(){const copy=Object.create(Object.getPrototypeOf(this),Object.getO...
  method toJS (line 815) | toJS(doc,{mapAsMap,maxAliasCount,onAnchor,reviver}={}){if(!isDocument(do...
  method constructor (line 815) | constructor(source){super(ALIAS);this.source=source;Object.definePropert...
  method resolve (line 815) | resolve(doc){let found=void 0;visit(doc,{Node:(_key,node)=>{if(node===th...
  method toJSON (line 815) | toJSON(_arg,ctx){if(!ctx)return{source:this.source};const{anchors,doc,ma...
  method toString (line 815) | toString(ctx,_onComment,_onChompKeep){const src=`*${this.source}`;if(ctx...
  function getAliasCount (line 815) | function getAliasCount(doc,node,anchors){if(isAlias(node)){const source=...
  method constructor (line 815) | constructor(value){super(SCALAR);this.value=value}
  method toJSON (line 815) | toJSON(arg,ctx){return ctx?.keep?this.value:toJS(this.value,arg,ctx)}
  method toString (line 815) | toString(){return String(this.value)}
  function findTagObject (line 815) | function findTagObject(value,tagName,tags){if(tagName){const match=tags....
  function createNode (line 815) | function createNode(value,tagName,ctx){if(isDocument(value))value=value....
  function collectionFromPath (line 815) | function collectionFromPath(schema4,path,value){let v=value;for(let i=pa...
  method constructor (line 815) | constructor(type,schema4){super(type);Object.defineProperty(this,"schema...
  method clone (line 815) | clone(schema4){const copy=Object.create(Object.getPrototypeOf(this),Obje...
  method addIn (line 815) | addIn(path,value){if(isEmptyPath(path))this.add(value);else{const[key,.....
  method deleteIn (line 815) | deleteIn(path){const[key,...rest]=path;if(rest.length===0)return this.de...
  method getIn (line 815) | getIn(path,keepScalar){const[key,...rest]=path;const node=this.get(key,t...
  method hasAllNullValues (line 815) | hasAllNullValues(allowScalar){return this.items.every(node=>{if(!isPair(...
  method hasIn (line 815) | hasIn(path){const[key,...rest]=path;if(rest.length===0)return this.has(k...
  method setIn (line 815) | setIn(path,value){const[key,...rest]=path;if(rest.length===0){this.set(k...
  function indentComment (line 815) | function indentComment(comment,indent){if(/^\n+$/.test(comment))return c...
  function foldFlowLines (line 815) | function foldFlowLines(text,indent,mode="flow",{indentAtStart,lineWidth=...
  function consumeMoreIndentedLines (line 817) | function consumeMoreIndentedLines(text,i){let ch=text[i+1];while(ch===" ...
  function lineLengthOverLimit (line 817) | function lineLengthOverLimit(str,lineWidth,indentLength){if(!lineWidth||...
  function doubleQuotedString (line 817) | function doubleQuotedString(value,ctx){const json=JSON.stringify(value);...
  function singleQuotedString (line 817) | function singleQuotedString(value,ctx){if(ctx.options.singleQuote===fals...
  function quotedString (line 818) | function quotedString(value,ctx){const{singleQuote}=ctx.options;let qs;i...
  function blockString (line 818) | function blockString({comment,type,value},ctx,onComment,onChompKeep){con...
  function plainString (line 820) | function plainString(item,ctx,onComment,onChompKeep){const{type,value}=i...
  function stringifyString (line 821) | function stringifyString(item,ctx,onComment,onChompKeep){const{implicitK...
  function createStringifyContext (line 821) | function createStringifyContext(doc,options){const opt=Object.assign({bl...
  function getTagObject (line 821) | function getTagObject(tags,item){if(item.tag){const match=tags.filter(t=...
  function stringifyProps (line 821) | function stringifyProps(node,tagObj,{anchors,doc}){if(!doc.directives)re...
  function stringify (line 821) | function stringify(item,ctx,onComment,onChompKeep){if(isPair(item))retur...
  function stringifyPair (line 822) | function stringifyPair({key,value},ctx,onComment,onChompKeep){const{allN...
  function warn (line 826) | function warn(logLevel,warning){if(logLevel==="debug"||logLevel==="warn"...
  function addPairToJSMap (line 826) | function addPairToJSMap(ctx,map2,{key,value}){if(ctx?.doc.schema.merge&&...
  function mergeToJSMap (line 826) | function mergeToJSMap(ctx,map2,value){const source=ctx&&isAlias(value)?v...
  function stringifyKey (line 826) | function stringifyKey(key,jsKey,ctx){if(jsKey===null)return"";if(typeof ...
  function createPair (line 826) | function createPair(key,value,ctx){const k=createNode(key,void 0,ctx);co...
  method constructor (line 826) | constructor(key,value=null){Object.defineProperty(this,NODE_TYPE,{value:...
  method clone (line 826) | clone(schema4){let{key,value}=this;if(isNode(key))key=key.clone(schema4)...
  method toJSON (line 826) | toJSON(_,ctx){const pair=ctx?.mapAsMap?new Map:{};return addPairToJSMap(...
  method toString (line 826) | toString(ctx,onComment,onChompKeep){return ctx?.doc?stringifyPair(this,c...
  function stringifyCollection (line 826) | function stringifyCollection(collection,ctx,options){const flow=ctx.inFl...
  function stringifyBlockCollection (line 826) | function stringifyBlockCollection({comment,items},ctx,{blockItemPrefix,f...
  function stringifyFlowCollection (line 827) | function stringifyFlowCollection({comment,items},ctx,{flowChars,itemInde...
  function addCommentBefore (line 829) | function addCommentBefore({indent,options:{commentString}},lines,comment...
  function findPair (line 829) | function findPair(items,key){const k=isScalar(key)?key.value:key;for(con...
  method tagName (line 829) | static get tagName(){return"tag:yaml.org,2002:map"}
  method constructor (line 829) | constructor(schema4){super(MAP,schema4);this.items=[]}
  method from (line 829) | static from(schema4,obj,ctx){const{keepUndefined,replacer}=ctx;const map...
  method add (line 829) | add(pair,overwrite){let _pair;if(isPair(pair))_pair=pair;else if(!pair||...
  method delete (line 829) | delete(key){const it=findPair(this.items,key);if(!it)return false;const ...
  method get (line 829) | get(key,keepScalar){const it=findPair(this.items,key);const node=it?.val...
  method has (line 829) | has(key){return!!findPair(this.items,key)}
  method set (line 829) | set(key,value){this.add(new Pair(key,value),true)}
  method toJSON (line 829) | toJSON(_,ctx,Type){const map2=Type?new Type:ctx?.mapAsMap?new Map:{};if(...
  method toString (line 829) | toString(ctx,onComment,onChompKeep){if(!ctx)return JSON.stringify(this);...
  method resolve (line 829) | resolve(map2,onError){if(!isMap(map2))onError("Expected a mapping for th...
  method tagName (line 829) | static get tagName(){return"tag:yaml.org,2002:seq"}
  method constructor (line 829) | constructor(schema4){super(SEQ,schema4);this.items=[]}
  method add (line 829) | add(value){this.items.push(value)}
  method delete (line 829) | delete(key){const idx=asItemIndex(key);if(typeof idx!=="number")return f...
  method get (line 829) | get(key,keepScalar){const idx=asItemIndex(key);if(typeof idx!=="number")...
  method has (line 829) | has(key){const idx=asItemIndex(key);return typeof idx==="number"&&idx<th...
  method set (line 829) | set(key,value){const idx=asItemIndex(key);if(typeof idx!=="number")throw...
  method toJSON (line 829) | toJSON(_,ctx){const seq2=[];if(ctx?.onCreate)ctx.onCreate(seq2);let i=0;...
  method toString (line 829) | toString(ctx,onComment,onChompKeep){if(!ctx)return JSON.stringify(this);...
  method from (line 829) | static from(schema4,obj,ctx){const{replacer}=ctx;const seq2=new this(sch...
  function asItemIndex (line 829) | function asItemIndex(key){let idx=isScalar(key)?key.value:key;if(idx&&ty...
  method resolve (line 829) | resolve(seq2,onError){if(!isSeq(seq2))onError("Expected a sequence for t...
  method stringify (line 829) | stringify(item,ctx,onComment,onChompKeep){ctx=Object.assign({actualStrin...
  method stringify (line 829) | stringify({source,value},ctx){if(source&&boolTag.test.test(source)){cons...
  function stringifyNumber (line 829) | function stringifyNumber({format,minFractionDigits,tag,value}){if(typeof...
  method stringify (line 829) | stringify(node){const num=Number(node.value);return isFinite(num)?num.to...
  method resolve (line 829) | resolve(str){const node=new Scalar(parseFloat(str));const dot=str.indexO...
  function intStringify (line 829) | function intStringify(node,radix,prefix){const{value}=node;if(intIdentif...
  function intIdentify2 (line 829) | function intIdentify2(value){return typeof value==="bigint"||Number.isIn...
  method resolve (line 829) | resolve(str,onError){onError(`Unresolved plain scalar ${JSON.stringify(s...
  method resolve (line 829) | resolve(src,onError){if(typeof Buffer==="function"){return Buffer.from(s...
  method stringify (line 829) | stringify({comment,type,value},ctx,onComment,onChompKeep){const buf=valu...
  function resolvePairs (line 829) | function resolvePairs(seq2,onError){if(isSeq(seq2)){for(let i=0;i<seq2.i...
  function createPairs (line 831) | function createPairs(schema4,iterable,ctx){const{replacer}=ctx;const pai...
  method constructor (line 831) | constructor(){super();this.add=YAMLMap.prototype.add.bind(this);this.del...
  method toJSON (line 831) | toJSON(_,ctx){if(!ctx)return super.toJSON(_);const map2=new Map;if(ctx?....
  method from (line 831) | static from(schema4,iterable,ctx){const pairs2=createPairs(schema4,itera...
  method resolve (line 831) | resolve(seq2,onError){const pairs2=resolvePairs(seq2,onError);const seen...
  function boolStringify (line 831) | function boolStringify({value,source},ctx){const boolObj=value?trueTag:f...
  method stringify (line 831) | stringify(node){const num=Number(node.value);return isFinite(num)?num.to...
  method resolve (line 831) | resolve(str){const node=new Scalar(parseFloat(str.replace(/_/g,"")));con...
  function intResolve2 (line 831) | function intResolve2(str,offset,radix,{intAsBigInt}){const sign=str[0];i...
  function intStringify2 (line 831) | function intStringify2(node,radix,prefix){const{value}=node;if(intIdenti...
  method constructor (line 831) | constructor(schema4){super(schema4);this.tag=_YAMLSet.tag}
  method add (line 831) | add(key){let pair;if(isPair(key))pair=key;else if(key&&typeof key==="obj...
  method get (line 831) | get(key,keepPair){const pair=findPair(this.items,key);return!keepPair&&i...
  method set (line 831) | set(key,value){if(typeof value!=="boolean")throw new Error(`Expected boo...
  method toJSON (line 831) | toJSON(_,ctx){return super.toJSON(_,ctx,Set)}
  method toString (line 831) | toString(ctx,onComment,onChompKeep){if(!ctx)return JSON.stringify(this);...
  method from (line 831) | static from(schema4,iterable,ctx){const{replacer}=ctx;const set2=new thi...
  method resolve (line 831) | resolve(map2,onError){if(isMap(map2)){if(map2.hasAllNullValues(true))ret...
  function parseSexagesimal (line 831) | function parseSexagesimal(str,asBigInt){const sign=str[0];const parts=si...
  function stringifySexagesimal (line 831) | function stringifySexagesimal(node){let{value}=node;let num=n=>n;if(type...
  method resolve (line 831) | resolve(str){const match=str.match(timestamp.test);if(!match)throw new E...
  function getTags (line 831) | function getTags(customTags,schemaName){let tags=schemas.get(schemaName)...
  method constructor (line 831) | constructor({compat,customTags,merge,resolveKnownTags,schema:schema4,sor...
  method clone (line 831) | clone(){const copy=Object.create(_Schema.prototype,Object.getOwnProperty...
  function stringifyDocument (line 831) | function stringifyDocument(doc,options){const lines=[];let hasDirectives...
  method constructor (line 831) | constructor(value,replacer,options){this.commentBefore=null;this.comment...
  method clone (line 831) | clone(){const copy=Object.create(_Document.prototype,{[NODE_TYPE]:{value...
  method add (line 831) | add(value){if(assertCollection(this.contents))this.contents.add(value)}
  method addIn (line 831) | addIn(path,value){if(assertCollection(this.contents))this.contents.addIn...
  method createAlias (line 831) | createAlias(node,name){if(!node.anchor){const prev=anchorNames(this);nod...
  method createNode (line 831) | createNode(value,replacer,options){let _replacer=void 0;if(typeof replac...
  method createPair (line 831) | createPair(key,value,options={}){const k=this.createNode(key,null,option...
  method delete (line 831) | delete(key){return assertCollection(this.contents)?this.contents.delete(...
  method deleteIn (line 831) | deleteIn(path){if(isEmptyPath(path)){if(this.contents==null)return false...
  method get (line 831) | get(key,keepScalar){return isCollection(this.contents)?this.contents.get...
  method getIn (line 831) | getIn(path,keepScalar){if(isEmptyPath(path))return!keepScalar&&isScalar(...
  method has (line 831) | has(key){return isCollection(this.contents)?this.contents.has(key):false}
  method hasIn (line 831) | hasIn(path){if(isEmptyPath(path))return this.contents!==void 0;return is...
  method set (line 831) | set(key,value){if(this.contents==null){this.contents=collectionFromPath(...
  method setIn (line 831) | setIn(path,value){if(isEmptyPath(path)){this.contents=value}else if(this...
  method setSchema (line 831) | setSchema(version,options={}){if(typeof version==="number")version=Strin...
  method toJS (line 831) | toJS({json,jsonArg,mapAsMap,maxAliasCount,onAnchor,reviver}={}){const ct...
  method toJSON (line 831) | toJSON(jsonArg,onAnchor){return this.toJS({json:true,jsonArg,mapAsMap:fa...
  method toString (line 831) | toString(options={}){if(this.errors.length>0)throw new Error("Document w...
  function assertCollection (line 831) | function assertCollection(contents){if(isCollection(contents))return tru...
  method constructor (line 831) | constructor(name,pos,code,message){super();this.name=name;this.code=code...
  method constructor (line 831) | constructor(pos,code,message){super("YAMLParseError",pos,code,message)}
  method constructor (line 831) | constructor(pos,code,message){super("YAMLWarning",pos,code,message)}
  function resolveProps (line 835) | function resolveProps(tokens,{flow,indicator,next,offset,onError,startOn...
  function containsNewline (line 835) | function containsNewline(key){if(!key)return null;switch(key.type){case"...
  function flowIndentCheck (line 835) | function flowIndentCheck(indent,fc,onError){if(fc?.type==="flow-collecti...
  function mapIncludes (line 835) | function mapIncludes(ctx,items,search){const{uniqueKeys}=ctx.options;if(...
  function resolveBlockMap (line 835) | function resolveBlockMap({composeNode:composeNode2,composeEmptyNode:comp...
  function resolveBlockSeq (line 835) | function resolveBlockSeq({composeNode:composeNode2,composeEmptyNode:comp...
  function resolveEnd (line 835) | function resolveEnd(end,offset,reqSpace,onError){let comment="";if(end){...
  function resolveFlowCollection (line 835) | function resolveFlowCollection({composeNode:composeNode2,composeEmptyNod...
  function resolveCollection (line 835) | function resolveCollection(CN2,ctx,token,onError,tagName,tag){const coll...
  function composeCollection (line 835) | function composeCollection(CN2,ctx,token,tagToken,onError){const tagName...
  function resolveBlockScalar (line 835) | function resolveBlockScalar(scalar,strict,onError){const start=scalar.of...
  function parseBlockScalarHeader (line 835) | function parseBlockScalarHeader({offset,props},strict,onError){if(props[...
  function splitLines (line 835) | function splitLines(source){const split=source.split(/\n( *)/);const fir...
  function resolveFlowScalar (line 835) | function resolveFlowScalar(scalar,strict,onError){const{offset,type,sour...
  function plainValue (line 835) | function plainValue(source,onError){let badChar="";switch(source[0]){cas...
  function singleQuotedValue (line 835) | function singleQuotedValue(source,onError){if(source[source.length-1]!==...
  function foldLines (line 835) | function foldLines(source){let first,line;try{first=new RegExp("(.*?)(?<...
  function doubleQuotedValue (line 835) | function doubleQuotedValue(source,onError){let res="";for(let i=1;i<sour...
  function foldNewline (line 835) | function foldNewline(source,offset){let fold="";let ch=source[offset+1];...
  function parseCharCode (line 835) | function parseCharCode(source,offset,length,onError){const cc=source.sub...
  function composeScalar (line 835) | function composeScalar(ctx,token,tagToken,onError){const{value,type,comm...
  function findScalarTagByName (line 835) | function findScalarTagByName(schema4,value,tagName,tagToken,onError){if(...
  function findScalarTagByTest (line 835) | function findScalarTagByTest({directives,schema:schema4},value,token,onE...
  function emptyScalarPosition (line 835) | function emptyScalarPosition(offset,before,pos){if(before){if(pos===null...
  function composeNode (line 835) | function composeNode(ctx,token,props,onError){const{spaceBefore,comment,...
  function composeEmptyNode (line 835) | function composeEmptyNode(ctx,offset,before,pos,{spaceBefore,comment,anc...
  function composeAlias (line 835) | function composeAlias({options},{offset,source,end},onError){const alias...
  function composeDoc (line 835) | function composeDoc(options,directives,{offset,start,value,end},onError)...
  function getErrorPos (line 835) | function getErrorPos(src){if(typeof src==="number")return[src,src+1];if(...
  function parsePrelude (line 835) | function parsePrelude(prelude){let comment="";let atComment=false;let af...
  method constructor (line 835) | constructor(options={}){this.doc=null;this.atDirectives=false;this.prelu...
  method decorate (line 835) | decorate(doc,afterDoc){const{comment,afterEmptyLine}=parsePrelude(this.p...
  method streamInfo (line 838) | streamInfo(){return{comment:parsePrelude(this.prelude).comment,directive...
  method compose (line 838) | *compose(tokens,forceDoc=false,endOffset=-1){for(const token of tokens)y...
  method next (line 838) | *next(token){switch(token.type){case"directive":this.directives.add(toke...
  method end (line 839) | *end(forceDoc=false,endOffset=-1){if(this.doc){this.decorate(this.doc,tr...
  function visit2 (line 839) | function visit2(cst,visitor){if("type"in cst&&cst.type==="document")cst=...
  function _visit (line 839) | function _visit(path,item,visitor){let ctrl=visitor(item,path);if(typeof...
  function tokenType (line 839) | function tokenType(source){switch(source){case BOM:return"byte-order-mar...
  function isEmpty (line 839) | function isEmpty(ch){switch(ch){case void 0:case" ":case"\n":case"\r":ca...
  method constructor (line 839) | constructor(){this.atEnd=false;this.blockScalarIndent=-1;this.blockScala...
  method lex (line 839) | *lex(source,incomplete=false){if(source){this.buffer=this.buffer?this.bu...
  method atLineEnd (line 839) | atLineEnd(){let i=this.pos;let ch=this.buffer[i];while(ch===" "||ch==="	...
  method charAt (line 839) | charAt(n){return this.buffer[this.pos+n]}
  method continueScalar (line 839) | continueScalar(offset){let ch=this.buffer[offset];if(this.indentNext>0){...
  method getLine (line 839) | getLine(){let end=this.lineEndPos;if(typeof end!=="number"||end!==-1&&en...
  method hasChars (line 839) | hasChars(n){return this.pos+n<=this.buffer.length}
  method setNext (line 839) | setNext(state){this.buffer=this.buffer.substring(this.pos);this.pos=0;th...
  method peek (line 839) | peek(n){return this.buffer.substr(this.pos,n)}
  method parseNext (line 839) | *parseNext(next){switch(next){case"stream":return yield*this.parseStream...
  method parseStream (line 839) | *parseStream(){let line=this.getLine();if(line===null)return this.setNex...
  method parseLineStart (line 839) | *parseLineStart(){const ch=this.charAt(0);if(!ch&&!this.atEnd)return thi...
  method parseBlockStart (line 839) | *parseBlockStart(){const[ch0,ch1]=this.peek(2);if(!ch1&&!this.atEnd)retu...
  method parseDocument (line 839) | *parseDocument(){yield*this.pushSpaces(true);const line=this.getLine();i...
  method parseFlowCollection (line 839) | *parseFlowCollection(){let nl,sp;let indent=-1;do{nl=yield*this.pushNewl...
  method parseQuotedScalar (line 839) | *parseQuotedScalar(){const quote=this.charAt(0);let end=this.buffer.inde...
  method parseBlockScalarHeader (line 839) | *parseBlockScalarHeader(){this.blockScalarIndent=-1;this.blockScalarKeep...
  method parseBlockScalar (line 839) | *parseBlockScalar(){let nl=this.pos-1;let indent=0;let ch;loop:for(let i...
  method parsePlainScalar (line 839) | *parsePlainScalar(){const inFlow=this.flowLevel>0;let end=this.pos-1;let...
  method pushCount (line 839) | *pushCount(n){if(n>0){yield this.buffer.substr(this.pos,n);this.pos+=n;r...
  method pushToIndex (line 839) | *pushToIndex(i,allowEmpty){const s=this.buffer.slice(this.pos,i);if(s){y...
  method pushIndicators (line 839) | *pushIndicators(){switch(this.charAt(0)){case"!":return(yield*this.pushT...
  method pushTag (line 839) | *pushTag(){if(this.charAt(1)==="<"){let i=this.pos+2;let ch=this.buffer[...
  method pushNewline (line 839) | *pushNewline(){const ch=this.buffer[this.pos];if(ch==="\n")return yield*...
  method pushSpaces (line 839) | *pushSpaces(allowTabs){let i=this.pos-1;let ch;do{ch=this.buffer[++i]}wh...
  method pushUntil (line 839) | *pushUntil(test){let i=this.pos;let ch=this.buffer[i];while(!test(ch))ch...
  method constructor (line 839) | constructor(){this.lineStarts=[];this.addNewLine=offset=>this.lineStarts...
  function includesToken (line 839) | function includesToken(list,type){for(let i=0;i<list.length;++i)if(list[...
  function findNonEmptyIndex (line 839) | function findNonEmptyIndex(list){for(let i=0;i<list.length;++i){switch(l...
  function isFlowToken (line 839) | function isFlowToken(token){switch(token?.type){case"alias":case"scalar"...
  function getPrevProps (line 839) | function getPrevProps(parent){switch(parent.type){case"document":return ...
  function getFirstKeyStartProps (line 839) | function getFirstKeyStartProps(prev){if(prev.length===0)return[];let i=p...
  function fixFlowSeqItems (line 839) | function fixFlowSeqItems(fc){if(fc.start.type==="flow-seq-start"){for(co...
  method constructor (line 839) | constructor(onNewLine){this.atNewLine=true;this.atScalar=false;this.inde...
  method parse (line 839) | *parse(source,incomplete=false){if(this.onNewLine&&this.offset===0)this....
  method next (line 839) | *next(source){this.source=source;if(this.atScalar){this.atScalar=false;y...
  method end (line 839) | *end(){while(this.stack.length>0)yield*this.pop()}
  method sourceToken (line 839) | get sourceToken(){const st={type:this.type,offset:this.offset,indent:thi...
  method step (line 839) | *step(){const top=this.peek(1);if(this.type==="doc-end"&&(!top||top.type...
  method peek (line 839) | peek(n){return this.stack[this.stack.length-n]}
  method pop (line 839) | *pop(error){const token=error??this.stack.pop();if(!token){const message...
  method stream (line 839) | *stream(){switch(this.type){case"directive-line":yield{type:"directive",...
  method document (line 839) | *document(doc){if(doc.value)return yield*this.lineEnd(doc);switch(this.t...
  method scalar (line 839) | *scalar(scalar){if(this.type==="map-value-ind"){const prev=getPrevProps(...
  method blockScalar (line 839) | *blockScalar(scalar){switch(this.type){case"space":case"comment":case"ne...
  method blockMap (line 839) | *blockMap(map2){const it=map2.items[map2.items.length-1];switch(this.typ...
  method blockSequence (line 839) | *blockSequence(seq2){const it=seq2.items[seq2.items.length-1];switch(thi...
  method flowCollection (line 839) | *flowCollection(fc){const it=fc.items[fc.items.length-1];if(this.type===...
  method flowScalar (line 839) | flowScalar(type){if(this.onNewLine){let nl=this.source.indexOf("\n")+1;w...
  method startBlockValue (line 839) | startBlockValue(parent){switch(this.type){case"alias":case"scalar":case"...
  method atIndentedComment (line 839) | atIndentedComment(start,indent){if(this.type!=="comment")return false;if...
  method documentEnd (line 839) | *documentEnd(docEnd){if(this.type!=="doc-mode"){if(docEnd.end)docEnd.end...
  method lineEnd (line 839) | *lineEnd(token){switch(this.type){case"comma":case"doc-start":case"doc-e...
  function parseOptions (line 839) | function parseOptions(options){const prettyErrors=options.prettyErrors!=...
  function parseDocument (line 839) | function parseDocument(source,options={}){const{lineCounter,prettyErrors...
  function parse (line 839) | function parse(src,reviver,options){let _reviver=void 0;if(typeof revive...
  function stringify3 (line 839) | function stringify3(value,replacer,options){let _replacer=null;if(typeof...

FILE: npm/test.js
  function test (line 1) | async function test(name, fn) {
  function run (line 11) | async function run(json, code = '') {
  function runNoPipe (line 20) | async function runNoPipe(code = '') {

FILE: preview.go
  method handlePreviewKey (line 15) | func (m *model) handlePreviewKey(msg tea.Msg) (tea.Model, tea.Cmd) {
  method handlePreviewSearchInput (line 73) | func (m *model) handlePreviewSearchInput(msg tea.KeyMsg) (tea.Model, tea...
  method doPreviewSearch (line 100) | func (m *model) doPreviewSearch(pattern string) bool {
  method selectPreviewSearchResult (line 183) | func (m *model) selectPreviewSearchResult(i int) {
  method previewSearchStatusBar (line 199) | func (m *model) previewSearchStatusBar() string {
  method wrapString (line 223) | func (m *model) wrapString(value string) string {

FILE: search.go
  method doSearch (line 11) | func (m *model) doSearch(s string) tea.Cmd {
  method cancelSearch (line 39) | func (m *model) cancelSearch() {
  method selectSearchResult (line 47) | func (m *model) selectSearchResult(i int) {
  method redoSearch (line 63) | func (m *model) redoSearch() {
  type search (line 83) | type search struct
  function newSearch (line 91) | func newSearch() *search {
  type match (line 99) | type match struct
  type piece (line 104) | type piece struct
  function executeSearch (line 111) | func executeSearch(top *Node, s string, cancel <-chan struct{}) (*search...
  function splitByIndexes (line 188) | func splitByIndexes(s string, indexes []match) []piece {
  function splitIndexesToChunks (line 200) | func splitIndexesToChunks(chunks []string, indexes [][]int, searchIndex ...

FILE: search_test.go
  function doSearch (line 13) | func doSearch(m *model, s string) {
  function TestBasicSearch (line 29) | func TestBasicSearch(t *testing.T) {
  function TestRegexSearch (line 75) | func TestRegexSearch(t *testing.T) {
  function TestCaseInsensitiveSearch (line 141) | func TestCaseInsensitiveSearch(t *testing.T) {
  function TestSearchInDifferentNodeTypes (line 184) | func TestSearchInDifferentNodeTypes(t *testing.T) {
  function TestSearchResultDetails (line 231) | func TestSearchResultDetails(t *testing.T) {
  function TestSearchNavigation (line 265) | func TestSearchNavigation(t *testing.T) {
  function TestSpecialCharacterSearch (line 306) | func TestSpecialCharacterSearch(t *testing.T) {
  function TestEmptyAndEdgeCases (line 351) | func TestEmptyAndEdgeCases(t *testing.T) {
  function TestLargeJSONSearch (line 388) | func TestLargeJSONSearch(t *testing.T) {
  function TestSearchInWrappedStrings (line 434) | func TestSearchInWrappedStrings(t *testing.T) {
  function TestSearchChunkBoundaryMatches (line 481) | func TestSearchChunkBoundaryMatches(t *testing.T) {
  function TestSearchMultipleMatchesInChunks (line 530) | func TestSearchMultipleMatchesInChunks(t *testing.T) {
  function TestSearchWrappedVsUnwrapped (line 561) | func TestSearchWrappedVsUnwrapped(t *testing.T) {
  function TestSearchChunkIndexMapping (line 591) | func TestSearchChunkIndexMapping(t *testing.T) {
  function TestSearchEmptyAndShortStringsWithWrap (line 633) | func TestSearchEmptyAndShortStringsWithWrap(t *testing.T) {
  function TestSearchRegexAcrossChunks (line 674) | func TestSearchRegexAcrossChunks(t *testing.T) {

FILE: utils.go
  function lookup (line 20) | func lookup(names []string, defaultEditor string) string {
  function open (line 30) | func open(filePath string, flagYaml, flagToml *bool) *os.File {
  function regexCase (line 53) | func regexCase(code string) (string, bool) {
  function flex (line 63) | func flex(width int, a, b string) string {
  function safeSlice (line 67) | func safeSlice(s string, start, end int) string {
  function parseYAML (line 87) | func parseYAML(b []byte) ([]byte, error) {
  function isRefNode (line 110) | func isRefNode(n *jsonx.Node) (string, bool) {

FILE: version.go
  constant version (line 3) | version = "39.2.0"

FILE: view.go
  method View (line 15) | func (m *model) View() string {
  method centerLine (line 222) | func (m *model) centerLine(n *Node) {

FILE: vim.go
  method runCommand (line 11) | func (m *model) runCommand(s string) (tea.Model, tea.Cmd) {
  function gotoLine (line 22) | func gotoLine(m *model, num int) {
  function findNode (line 29) | func findNode(m *model, line int) *Node {

FILE: vim_test.go
  function init (line 13) | func init() {
  function TestGotoLine (line 17) | func TestGotoLine(t *testing.T) {
  function TestGotoLineCollapsed (line 30) | func TestGotoLineCollapsed(t *testing.T) {
  function TestGotoLineInputInvalid (line 45) | func TestGotoLineInputInvalid(t *testing.T) {
  function TestGotoLineInputGreaterThanTotalLines (line 63) | func TestGotoLineInputGreaterThanTotalLines(t *testing.T) {
  function TestGotoLineInputLessThanOne (line 76) | func TestGotoLineInputLessThanOne(t *testing.T) {
  function TestGotoLineKeepsHistory (line 92) | func TestGotoLineKeepsHistory(t *testing.T) {
Condensed preview — 103 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (589K chars).
[
  {
    "path": ".gitattributes",
    "chars": 15,
    "preview": "*.golden -text\n"
  },
  {
    "path": ".github/images/autocomplete.tape",
    "chars": 397,
    "preview": "Output autocomplete.gif\nOutput autocomplete.mp4\n\nSet Shell zsh\nSet FontSize 32\nSet Width 1800\nSet Height 400\nSet TypingS"
  },
  {
    "path": ".github/images/preview-mode.tape",
    "chars": 371,
    "preview": "Output preview-mode.gif\nOutput preview-mode.mp4\n\nSet FontSize 32\nSet Width 1800\nSet Height 1200\nSet TypingSpeed 200ms\n\nH"
  },
  {
    "path": ".github/images/preview.tape",
    "chars": 296,
    "preview": "Output preview.gif\nOutput preview.mp4\n\nSet FontSize 32\nSet Width 1800\nSet Height 1200\nSet TypingSpeed 200ms\n\nHide\nType \""
  },
  {
    "path": ".github/stream.mjs",
    "chars": 2374,
    "preview": "#!/usr/bin/env zx\n\nprocess.on('SIGPIPE', () => process.exit(0))\nprocess.on('SIGINT', () => process.exit(0))\nprocess.on('"
  },
  {
    "path": ".github/workflows/brew.yml",
    "chars": 664,
    "preview": "name: brew\n\non: [workflow_dispatch]\n\njobs:\n  brew:\n    runs-on: macos-latest\n    steps:\n      - name: Set up Homebrew\n  "
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 635,
    "preview": "name: docker\n\non: [workflow_dispatch]\n\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n    "
  },
  {
    "path": ".github/workflows/snap.yml",
    "chars": 539,
    "preview": "name: snap\n\non: [workflow_dispatch]\n\njobs:\n  snap:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 658,
    "preview": "name: test\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  go:\n    runs-on: ubun"
  },
  {
    "path": ".gitignore",
    "chars": 17,
    "preview": "*.prof\nfx\nfx.exe\n"
  },
  {
    "path": "Dockerfile",
    "chars": 246,
    "preview": "FROM golang:latest as builder\n\nWORKDIR /go\n\nCOPY go.mod go.sum ./\n\nRUN go mod download\n\nCOPY . .\n\nRUN CGO_ENABLED=0 go b"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2018 Anton Medvedev\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 680,
    "preview": "# f(x)\n\n<p align=\"center\"><a href=\"https://fx.wtf\"><img src=\".github/images/preview.gif\" width=\"500\" alt=\"fx preview\"></"
  },
  {
    "path": "RELEASE.md",
    "chars": 646,
    "preview": "# Release\n\n1. Bump version in [version.go](version.go).\n2. Bump version in [snapcraft.yaml](snap/snapcraft.yaml).\n3. Bum"
  },
  {
    "path": "go.mod",
    "chars": 1879,
    "preview": "module github.com/antonmedv/fx\n\ngo 1.23.0\n\ntoolchain go1.23.6\n\nrequire (\n\tgithub.com/antonmedv/clipboard v1.0.1\n\tgithub."
  },
  {
    "path": "go.sum",
    "chars": 7735,
    "preview": "github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=\ngithub.com/Masterminds/semver/v3"
  },
  {
    "path": "help.go",
    "chars": 4601,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/"
  },
  {
    "path": "internal/complete/complete.bash",
    "chars": 31,
    "preview": "complete -o filenames -C fx fx\n"
  },
  {
    "path": "internal/complete/complete.fish",
    "chars": 69,
    "preview": "complete --command fx --arguments '(COMP_FISH=(commandline -cp) fx)'\n"
  },
  {
    "path": "internal/complete/complete.go",
    "chars": 7235,
    "preview": "package complete\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"git"
  },
  {
    "path": "internal/complete/complete.zsh",
    "chars": 984,
    "preview": "#compdef fx\n\n_fx() {\n    local -a reply\n    reply=(\"${(@f)$(COMP_ZSH=\"${LBUFFER}\" fx)}\")\n    if (( ${#reply} )); then\n  "
  },
  {
    "path": "internal/complete/complete_test.go",
    "chars": 6111,
    "preview": "package complete\n\nimport (\n\t\"testing\"\n\n\t\"github.com/antonmedv/fx/internal/jsonx\"\n)\n\nfunc TestKeysComplete(t *testing.T) "
  },
  {
    "path": "internal/complete/prelude.js",
    "chars": 726,
    "preview": "const __keys = new Set()\n\nObject.prototype.__keys = function () {\n  if (Array.isArray(this)) return\n  if (typeof this =="
  },
  {
    "path": "internal/complete/utils.go",
    "chars": 1880,
    "preview": "package complete\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nfunc compReply(reply []Reply, withDisplay bool) {"
  },
  {
    "path": "internal/engine/engine.go",
    "chars": 3198,
    "preview": "package engine\n\nimport (\n\t_ \"embed\"\n\t\"io\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/dop251/goja\"\n\n\t\"github.com/anto"
  },
  {
    "path": "internal/engine/engine_test.go",
    "chars": 3701,
    "preview": "package engine_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/antonmedv/fx/in"
  },
  {
    "path": "internal/engine/format_err.go",
    "chars": 2264,
    "preview": "package engine\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/mattn/go-runewidth\"\n)\n"
  },
  {
    "path": "internal/engine/fxrc.go",
    "chars": 1407,
    "preview": "package engine\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nfunc readFxrc() (string, error) {\n\tvar builder stri"
  },
  {
    "path": "internal/engine/quote.go",
    "chars": 983,
    "preview": "package engine\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\nfunc Quote(s string) string {\n\tvar err error\n\tvar b string"
  },
  {
    "path": "internal/engine/quote_test.go",
    "chars": 3150,
    "preview": "package engine_test\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/antonmedv"
  },
  {
    "path": "internal/engine/slurp.go",
    "chars": 999,
    "preview": "package engine\n\nimport (\n\t\"io\"\n\n\t\"github.com/antonmedv/fx/internal/jsonx\"\n)\n\nfunc Slurp(parser Parser, writeErr func(str"
  },
  {
    "path": "internal/engine/stdlib.js",
    "chars": 4390,
    "preview": "'use strict'\n\nconst console = {\n  log: function (...args) {\n    const parts = []\n    for (const arg of args) {\n      if "
  },
  {
    "path": "internal/engine/stdlib_test.go",
    "chars": 33553,
    "preview": "package engine_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/dop251/goja\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
  },
  {
    "path": "internal/engine/stringify.go",
    "chars": 2113,
    "preview": "package engine\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"math/big\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dop251/goja\"\n)\n\nfunc Str"
  },
  {
    "path": "internal/engine/transpile.go",
    "chars": 1802,
    "preview": "package engine\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc JS(args []string) string {\n\tvar code strings.Bui"
  },
  {
    "path": "internal/engine/transpile_test.go",
    "chars": 933,
    "preview": "package engine\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTranspile(t *testing.T) {\n\ttests := []struct {\n\t\tcode string\n\t\twant stri"
  },
  {
    "path": "internal/engine/utils.go",
    "chars": 734,
    "preview": "package engine\n\nimport (\n\t\"math/big\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/dop251/goja\"\n)\n\nvar (\n\tbigIntType   = re"
  },
  {
    "path": "internal/engine/vm.go",
    "chars": 2000,
    "preview": "package engine\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/dop251/goja\"\n\t\"github.com/goccy/go-yaml\"\n)\n\n// Fi"
  },
  {
    "path": "internal/fuzzy/algo.go",
    "chars": 12018,
    "preview": "package fuzzy\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\nvar delimiterChars = \".\"\n\nconst whiteChars = \""
  },
  {
    "path": "internal/fuzzy/chars.go",
    "chars": 6596,
    "preview": "package fuzzy\n\nimport (\n\t\"bytes\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\t\"unsafe\"\n)\n\nconst (\n\toverflow64 uint64 = 0x8080808080808080"
  },
  {
    "path": "internal/fuzzy/chars_test.go",
    "chars": 2125,
    "preview": "package fuzzy\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestToCharsAscii(t *testing.T) {\n\tchars := ToChars([]byte(\"foobar\"))\n\t"
  },
  {
    "path": "internal/fuzzy/find.go",
    "chars": 535,
    "preview": "package fuzzy\n\ntype Match struct {\n\tIndex int\n\tStr   string\n\tScore int\n\tPos   []int\n}\n\nfunc Find(pattern []rune, array ["
  },
  {
    "path": "internal/fuzzy/fuzzy_test.go",
    "chars": 10439,
    "preview": "package fuzzy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc"
  },
  {
    "path": "internal/fuzzy/normalize.go",
    "chars": 21646,
    "preview": "// Normalization of latin script letters\n// Reference: http://www.unicode.org/Public/UCD/latest/ucd/Index.txt\n\npackage f"
  },
  {
    "path": "internal/fuzzy/utils.go",
    "chars": 865,
    "preview": "package fuzzy\n\nimport (\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/rivo/uniseg\"\n)\n\nfunc AsUint16(val int) uint16 {\n\tif val > math."
  },
  {
    "path": "internal/ident/ident.go",
    "chars": 575,
    "preview": "package ident\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar Ident = \"  \"\nvar IdentBytes []byte\nvar IdentWidth int\n\nfunc i"
  },
  {
    "path": "internal/jsonpath/path.go",
    "chars": 3017,
    "preview": "package jsonpath\n\nimport (\n\t\"regexp\"\n\t\"strconv\"\n\t\"unicode\"\n)\n\ntype state int\n\nconst (\n\tstart state = iota\n\tunknown\n\tprop"
  },
  {
    "path": "internal/jsonpath/path_test.go",
    "chars": 2838,
    "preview": "package jsonpath_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/antonmedv/fx/internal/j"
  },
  {
    "path": "internal/jsonpath/ref.go",
    "chars": 771,
    "preview": "package jsonpath\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n)\n\nfunc ParseSchemaRef(ref string) ([]any, bool) {\n\t// Must start with "
  },
  {
    "path": "internal/jsonpath/ref_test.go",
    "chars": 1417,
    "preview": "package jsonpath_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/antonmedv/fx/internal/jsonpath\"\n)\n\nfunc TestParseSch"
  },
  {
    "path": "internal/jsonx/delete.go",
    "chars": 2345,
    "preview": "package jsonx\n\n// DeleteNode removes the node at from the linked structure and returns a node to select next.\n// It retu"
  },
  {
    "path": "internal/jsonx/delete_test.go",
    "chars": 2927,
    "preview": "package jsonx_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t. \"github.com/antonmedv/fx/internal/js"
  },
  {
    "path": "internal/jsonx/format_err.go",
    "chars": 1653,
    "preview": "package jsonx\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"unicode/utf8\"\n\n\t\"github.com/charmbracelet/x/term\"\n\t\"github.com/mattn/g"
  },
  {
    "path": "internal/jsonx/json.go",
    "chars": 11865,
    "preview": "package jsonx\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/antonmedv/fx/internal/utils"
  },
  {
    "path": "internal/jsonx/jsonx_test.go",
    "chars": 6261,
    "preview": "package jsonx_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/"
  },
  {
    "path": "internal/jsonx/line.go",
    "chars": 805,
    "preview": "package jsonx\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype LineParser struct {\n\tbuf        *bufio.Reader\n\teof "
  },
  {
    "path": "internal/jsonx/node.go",
    "chars": 5900,
    "preview": "package jsonx\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/antonmedv/fx/internal/jsonpath\"\n)\n\ntype Kind byte\n\nconst (\n\tErr Kind = "
  },
  {
    "path": "internal/jsonx/node_test.go",
    "chars": 2085,
    "preview": "package jsonx\n\nimport (\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/requi"
  },
  {
    "path": "internal/jsonx/string.go",
    "chars": 565,
    "preview": "package jsonx\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\tcurlyBracketOpen   = \"{\"\n\tcurlyBracketClose  = \"}\"\n\tcurlyBracketPair   = "
  },
  {
    "path": "internal/jsonx/to_value.go",
    "chars": 2434,
    "preview": "package jsonx\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"math/big\"\n\t\"strconv\"\n\n\t\"github.com/dop251/goja\"\n\n\t\"github.com/antonmedv/fx/inte"
  },
  {
    "path": "internal/jsonx/wrap.go",
    "chars": 1531,
    "preview": "package jsonx\n\nimport (\n\t\"unicode/utf8\"\n\n\t\"github.com/mattn/go-runewidth\"\n\n\t\"github.com/antonmedv/fx/internal/ident\"\n)\n\n"
  },
  {
    "path": "internal/pretty/inlineable.go",
    "chars": 2709,
    "preview": "package pretty\n\nimport (\n\t\"github.com/antonmedv/fx/internal/jsonx\"\n)\n\nfunc isInlineable(n *jsonx.Node) bool {\n\tif n.Kind"
  },
  {
    "path": "internal/pretty/inlineable_test.go",
    "chars": 11150,
    "preview": "package pretty_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify"
  },
  {
    "path": "internal/pretty/pretty_print.go",
    "chars": 2117,
    "preview": "package pretty\n\nimport (\n\t\"strings\"\n\n\t\"github.com/antonmedv/fx/internal/ident\"\n\t\"github.com/antonmedv/fx/internal/jsonx\""
  },
  {
    "path": "internal/pretty/pretty_print_test.go",
    "chars": 11025,
    "preview": "package pretty_test\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/"
  },
  {
    "path": "internal/shlex/shlex.go",
    "chars": 9995,
    "preview": "/*\nCopyright 2012 Google Inc. All Rights Reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou "
  },
  {
    "path": "internal/shlex/shlex_test.go",
    "chars": 3174,
    "preview": "/*\nCopyright 2012 Google Inc. All Rights Reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou "
  },
  {
    "path": "internal/theme/theme.go",
    "chars": 11101,
    "preview": "package theme\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"sort\"\n\n\t\"github.com/charmbracelet/lipgloss\"\n\t\"github.c"
  },
  {
    "path": "internal/toml/toml.go",
    "chars": 7138,
    "preview": "package toml\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"strings\"\n\n\ttoml \"github.com/pelletier/go-toml/v2\"\n\t\"github.com/"
  },
  {
    "path": "internal/toml/toml_test.go",
    "chars": 3109,
    "preview": "package toml\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// helper: comp"
  },
  {
    "path": "internal/utils/image.go",
    "chars": 1376,
    "preview": "package utils\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"image\"\n\t_ \"image/gif\"\n\t_ \"image/jpeg\"\n\t_ \"image/png\"\n\t\"io\"\n\n\t\"github.com/charm"
  },
  {
    "path": "internal/utils/life.go",
    "chars": 3193,
    "preview": "package utils\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/x/term\"\n)\n\nf"
  },
  {
    "path": "internal/utils/utils.go",
    "chars": 499,
    "preview": "package utils\n\nimport (\n\t\"encoding/json\"\n)\n\nfunc IsHexDigit(ch byte) bool {\n\treturn (ch >= '0' && ch <= '9') || (ch >= '"
  },
  {
    "path": "internal/utils/utils_test.go",
    "chars": 2802,
    "preview": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestIsHexDigit(t *testing.T) {\n\ttests "
  },
  {
    "path": "keymap.go",
    "chars": 6101,
    "preview": "package main\n\nimport \"github.com/charmbracelet/bubbles/key\"\n\ntype KeyMap struct {\n\tUp                  key.Binding `cate"
  },
  {
    "path": "main.go",
    "chars": 30199,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path"
  },
  {
    "path": "main_test.go",
    "chars": 2648,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/bubbles/textinput\"\n\ttea \"gith"
  },
  {
    "path": "npm/README.md",
    "chars": 3038,
    "preview": "# fx\n\nA non-interactive, JavaScript version of the [**fx**](https://fx.wtf). \nShort for _Function eXecution_ or _f(x)_.\n"
  },
  {
    "path": "npm/index.js",
    "chars": 153451,
    "preview": "#!/usr/bin/env node\n'use strict'\n\nvoid async function main() {\n  const os = await import('node:os')\n  const fs = await i"
  },
  {
    "path": "npm/package.json",
    "chars": 334,
    "preview": "{\n  \"name\": \"fx\",\n  \"version\": \"39.2.0\",\n  \"bin\": {\n    \"fx\": \"index.js\"\n  },\n  \"files\": [\n    \"index.js\"\n  ],\n  \"script"
  },
  {
    "path": "npm/test.js",
    "chars": 7577,
    "preview": "async function test(name, fn) {\n  try {\n    await fn(await import('node:assert/strict'))\n    console.log(`✓ ${name}`)\n  "
  },
  {
    "path": "preview.go",
    "chars": 5377,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/bubbles/key\"\n\ttea \"github.com/charmbracel"
  },
  {
    "path": "scripts/build.mjs",
    "chars": 741,
    "preview": "$.verbose = true\n\nconst goos = [\n  'linux',\n  'darwin',\n  'windows',\n]\nconst goarch = [\n  'amd64',\n  'arm64',\n]\n\nconst n"
  },
  {
    "path": "search.go",
    "chars": 5152,
    "preview": "package main\n\nimport (\n\t\"regexp\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\n\t. \"github.com/antonmedv/fx/internal/jsonx\""
  },
  {
    "path": "search_test.go",
    "chars": 18953,
    "preview": "package main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t. \"git"
  },
  {
    "path": "snap/snapcraft.yaml",
    "chars": 510,
    "preview": "name: fx\nversion: 39.2.0\nsummary: Terminal JSON viewer\ndescription: Terminal JSON viewer\nbase: core20\ngrade: stable\nconf"
  },
  {
    "path": "testdata/TestCollapseRecursive.golden",
    "chars": 887,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[7m{\u001b[0m\u001b[K\r\n  \"title\": \"Lorem ipsum\",\u001b[K\r\n  \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing"
  },
  {
    "path": "testdata/TestCollapseRecursiveWithSizes.golden",
    "chars": 915,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[7m{\u001b[0m (6 keys)\u001b[K\r\n  \"title\": \"Lorem ipsum\",\u001b[K\r\n  \"text\": \"Lorem ipsum dolor sit amet, consectetur a"
  },
  {
    "path": "testdata/TestGotoLine.golden",
    "chars": 1163,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[90m 1\u001b[0m  {\u001b[K\r\n\u001b[90m 2\u001b[0m    \"title\": \"Lorem ipsum\",\u001b[K\r\n\u001b[90m 3\u001b[0m    \"text\": \"Lorem ipsum dolor s"
  },
  {
    "path": "testdata/TestGotoLineCollapsed.golden",
    "chars": 1094,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[90m 1\u001b[0m  {\u001b[K\r\n\u001b[90m 2\u001b[0m    \"title\": \"Lorem ipsum\",\u001b[K\r\n\u001b[90m 3\u001b[0m    \"text\": \"Lorem ipsum dolor s"
  },
  {
    "path": "testdata/TestGotoLineInputGreaterThanTotalLines.golden",
    "chars": 1163,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[90m 1\u001b[0m  {\u001b[K\r\n\u001b[90m 2\u001b[0m    \"title\": \"Lorem ipsum\",\u001b[K\r\n\u001b[90m 3\u001b[0m    \"text\": \"Lorem ipsum dolor s"
  },
  {
    "path": "testdata/TestGotoLineInputInvalid.golden",
    "chars": 1013,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[90m 1\u001b[0m  {\u001b[K\r\n\u001b[90m 2\u001b[0m    \"title\": \"Lorem ipsum\",\u001b[K\r\n\u001b[90m 3\u001b[0m    \"text\": \"Lorem ipsum dolor s"
  },
  {
    "path": "testdata/TestGotoLineInputLessThanOne.golden",
    "chars": 1163,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[90m 1\u001b[0m  \u001b[7m{\u001b[0m\u001b[K\r\n\u001b[90m 2\u001b[0m    \"title\": \"Lorem ipsum\",\u001b[K\r\n\u001b[90m 3\u001b[0m    \"text\": \"Lorem ipsum"
  },
  {
    "path": "testdata/TestGotoLineKeepsHistory.golden",
    "chars": 1163,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[90m 1\u001b[0m  {\u001b[K\r\n\u001b[90m 2\u001b[0m    \"title\": \"Lorem ipsum\",\u001b[K\r\n\u001b[90m 3\u001b[0m    \"text\": \"Lorem ipsum dolor s"
  },
  {
    "path": "testdata/TestNavigation.golden",
    "chars": 946,
    "preview": "\u001b[?25l\u001b[?2004h\r{\u001b[K\r\n  \"title\": \"Lorem ipsum\",\u001b[K\r\n  \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, s"
  },
  {
    "path": "testdata/TestOutput.golden",
    "chars": 946,
    "preview": "\u001b[?25l\u001b[?2004h\r\u001b[7m{\u001b[0m\u001b[K\r\n  \"title\": \"Lorem ipsum\",\u001b[K\r\n  \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing"
  },
  {
    "path": "testdata/blog.json",
    "chars": 1123,
    "preview": "{\n  \"title\": \"Lorem ipsum\",\n  \"body\": \"# Lorem Ipsum Dolor Sit Amet\\n\\n## Consectetur Adipiscing Elit\\n\\nLorem ipsum dol"
  },
  {
    "path": "testdata/example.json",
    "chars": 644,
    "preview": "{\n  \"title\": \"Lorem ipsum\",\n  \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor in"
  },
  {
    "path": "utils.go",
    "chars": 2231,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/goc"
  },
  {
    "path": "version.go",
    "chars": 39,
    "preview": "package main\n\nconst version = \"39.2.0\"\n"
  },
  {
    "path": "view.go",
    "chars": 6202,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/antonmedv/fx/internal/ident\"\n\t. \"github.com/a"
  },
  {
    "path": "vim.go",
    "chars": 851,
    "preview": "package main\n\nimport (\n\t\"strconv\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\n\t. \"github.com/antonmedv/fx/internal/jsonx"
  },
  {
    "path": "vim_test.go",
    "chars": 3550,
    "preview": "package main\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n\t\"github.com/charmbracelet/lipgloss"
  }
]

About this extraction

This page contains the full source code of the antonmedv/fx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 103 files (518.4 KB), approximately 168.0k tokens, and a symbol index with 840 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!