pax_global_header 0000666 0000000 0000000 00000000064 14656474022 0014524 g ustar 00root root 0000000 0000000 52 comment=d6a19f0eb5a983610bd65a1647f5955abe3ee69e
bubbletea-0.27.0/ 0000775 0000000 0000000 00000000000 14656474022 0013537 5 ustar 00root root 0000000 0000000 bubbletea-0.27.0/.gitattributes 0000664 0000000 0000000 00000000017 14656474022 0016430 0 ustar 00root root 0000000 0000000 *.golden -text
bubbletea-0.27.0/.github/ 0000775 0000000 0000000 00000000000 14656474022 0015077 5 ustar 00root root 0000000 0000000 bubbletea-0.27.0/.github/CODEOWNERS 0000664 0000000 0000000 00000000036 14656474022 0016471 0 ustar 00root root 0000000 0000000 * @meowgorithm @aymanbagabas
bubbletea-0.27.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14656474022 0017262 5 ustar 00root root 0000000 0000000 bubbletea-0.27.0/.github/ISSUE_TEMPLATE/bug.yml 0000664 0000000 0000000 00000003251 14656474022 0020563 0 ustar 00root root 0000000 0000000 name: Bug Report
description: File a bug report
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please fill the form below.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
validations:
required: true
- type: textarea
id: reproducible
attributes:
label: How can we reproduce this?
description: |
Please share a code snippet, gist, or public repository that reproduces the issue.
Make sure to make the reproducible as concise as possible,
with only the minimum required code to reproduce the issue.
validations:
required: true
- type: textarea
id: version
attributes:
label: Which version of bubbletea are you using?
description: ''
render: bash
validations:
required: true
- type: textarea
id: terminaal
attributes:
label: Which terminals did you reproduce this with?
description: |
Other helpful information:
was it over SSH?
On tmux?
Which version of said terminal?
validations:
required: true
- type: checkboxes
id: search
attributes:
label: Search
options:
- label: |
I searched for other open and closed issues and pull requests before opening this,
and didn't find anything that seems related.
required: true
- type: textarea
id: ctx
attributes:
label: Additional context
description: Anything else you would like to add
validations:
required: false
bubbletea-0.27.0/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000001474 14656474022 0021762 0 ustar 00root root 0000000 0000000 ---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Setup**
Please complete the following information along with version numbers, if applicable.
- OS [e.g. Ubuntu, macOS]
- Shell [e.g. zsh, fish]
- Terminal Emulator [e.g. kitty, iterm]
- Terminal Multiplexer [e.g. tmux]
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Source Code**
Please include source code if needed to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
Add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
bubbletea-0.27.0/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000170 14656474022 0021250 0 ustar 00root root 0000000 0000000 blank_issues_enabled: true
contact_links:
- name: Discord
url: https://charm.sh/discord
about: Chat on our Discord.
bubbletea-0.27.0/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000001134 14656474022 0023006 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
bubbletea-0.27.0/.github/dependabot.yml 0000664 0000000 0000000 00000001436 14656474022 0017733 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: "gomod"
directory: "/examples"
schedule:
interval: "daily"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: "gomod"
directory: "/tutorials"
schedule:
interval: "daily"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
bubbletea-0.27.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14656474022 0017134 5 ustar 00root root 0000000 0000000 bubbletea-0.27.0/.github/workflows/build.yml 0000664 0000000 0000000 00000001736 14656474022 0020765 0 ustar 00root root 0000000 0000000 name: build
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
go-version: [~1.18, ^1]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
GO111MODULE: "on"
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Download Go modules
run: go mod download
- name: Build
run: |
go mod tidy
go build -v ./...
- name: Test
run: go test ./...
- name: Build examples
run: |
go mod tidy
go build -v ./...
working-directory: ./examples
- name: Test examples
run: go test -v ./...
working-directory: ./examples
- name: Build tutorials
run: |
go mod tidy
go build -v ./...
working-directory: ./tutorials
bubbletea-0.27.0/.github/workflows/coverage.yml 0000664 0000000 0000000 00000001267 14656474022 0021460 0 ustar 00root root 0000000 0000000 name: coverage
on: [push, pull_request]
jobs:
coverage:
strategy:
matrix:
go-version: [^1]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
env:
GO111MODULE: "on"
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Coverage
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go test -race -covermode atomic -coverprofile=profile.cov ./...
go install github.com/mattn/goveralls@latest
goveralls -coverprofile=profile.cov -service=github
bubbletea-0.27.0/.github/workflows/examples.yml 0000664 0000000 0000000 00000001561 14656474022 0021500 0 ustar 00root root 0000000 0000000 name: examples
on:
push:
branches:
- 'master'
paths:
- '.github/workflows/examples.yml'
- './examples/go.mod'
- './examples/go.sum'
- './tutorials/go.mod'
- './tutorials/go.sum'
- './go.mod'
- './go.sum'
workflow_dispatch: {}
jobs:
tidy:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '^1'
cache: true
- shell: bash
run: |
(cd ./examples && go mod tidy)
(cd ./tutorials && go mod tidy)
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: go mod tidy tutorials and examples"
branch: master
commit_user_name: actions-user
commit_user_email: actions@github.com
bubbletea-0.27.0/.github/workflows/lint-soft.yml 0000664 0000000 0000000 00000001336 14656474022 0021601 0 ustar 00root root 0000000 0000000 name: lint-soft
on:
push:
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
pull-requests: read
jobs:
golangci:
name: lint-soft
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ^1
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
# Optional: golangci-lint command line arguments.
args: --config .golangci-soft.yml --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
bubbletea-0.27.0/.github/workflows/lint.yml 0000664 0000000 0000000 00000001244 14656474022 0020626 0 ustar 00root root 0000000 0000000 name: lint
on:
push:
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ^1
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
# Optional: golangci-lint command line arguments.
#args:
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
bubbletea-0.27.0/.github/workflows/release.yml 0000664 0000000 0000000 00000002122 14656474022 0021274 0 ustar 00root root 0000000 0000000 name: goreleaser
on:
push:
tags:
- v*.*.*
concurrency:
group: goreleaser
cancel-in-progress: true
jobs:
goreleaser:
uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main
secrets:
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
docker_token: ${{ secrets.DOCKERHUB_TOKEN }}
gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
twitter_consumer_key: ${{ secrets.TWITTER_CONSUMER_KEY }}
twitter_consumer_secret: ${{ secrets.TWITTER_CONSUMER_SECRET }}
twitter_access_token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
twitter_access_token_secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
mastodon_client_id: ${{ secrets.MASTODON_CLIENT_ID }}
mastodon_client_secret: ${{ secrets.MASTODON_CLIENT_SECRET }}
mastodon_access_token: ${{ secrets.MASTODON_ACCESS_TOKEN }}
discord_webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
discord_webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
bubbletea-0.27.0/.gitignore 0000664 0000000 0000000 00000001007 14656474022 0015525 0 ustar 00root root 0000000 0000000 .DS_Store
.envrc
examples/fullscreen/fullscreen
examples/help/help
examples/http/http
examples/list-default/list-default
examples/list-fancy/list-fancy
examples/list-simple/list-simple
examples/mouse/mouse
examples/pager/pager
examples/progress-download/color_vortex.blend
examples/progress-download/progress-download
examples/simple/simple
examples/spinner/spinner
examples/textinput/textinput
examples/textinputs/textinputs
examples/views/views
tutorials/basics/basics
tutorials/commands/commands
.idea
coverage.txt
bubbletea-0.27.0/.golangci-soft.yml 0000664 0000000 0000000 00000001252 14656474022 0017074 0 ustar 00root root 0000000 0000000 run:
tests: false
issues:
include:
- EXC0001
- EXC0005
- EXC0011
- EXC0012
- EXC0013
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
# - dupl
- exhaustive
# - exhaustivestruct
- goconst
- godot
- godox
- gomnd
- gomoddirectives
- goprintffuncname
# - lll
- misspell
- nakedret
- nestif
- noctx
- nolintlint
- prealloc
- wrapcheck
# disable default linters, they are already enabled in .golangci.yml
disable:
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
bubbletea-0.27.0/.golangci.yml 0000664 0000000 0000000 00000000623 14656474022 0016124 0 ustar 00root root 0000000 0000000 run:
tests: false
issues:
include:
- EXC0001
- EXC0005
- EXC0011
- EXC0012
- EXC0013
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
- bodyclose
- exportloopref
- gofumpt
- goimports
- gosec
- nilerr
- predeclared
- revive
- rowserrcheck
- sqlclosecheck
- tparallel
- unconvert
- unparam
- whitespace
bubbletea-0.27.0/.goreleaser.yml 0000664 0000000 0000000 00000000237 14656474022 0016472 0 ustar 00root root 0000000 0000000 includes:
- from_url:
url: charmbracelet/meta/main/goreleaser-lib.yaml
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
bubbletea-0.27.0/CONTRIBUTING.md 0000664 0000000 0000000 00000000714 14656474022 0015772 0 ustar 00root root 0000000 0000000 # Contributing
Pull requests are welcome for any changes.
Consider opening an issue for larger changes to get feedback on the idea from the team.
If your change touches parts of the Bubble Tea renderer or internals, make sure
that all the examples in the `examples/` folder continue to run correctly.
For commit messages, please use conventional commits[^1] to make it easier to
generate release notes.
[^1]: https://www.conventionalcommits.org/en/v1.0.0
bubbletea-0.27.0/LICENSE 0000664 0000000 0000000 00000002070 14656474022 0014543 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2020-2023 Charmbracelet, Inc
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.
bubbletea-0.27.0/README.md 0000664 0000000 0000000 00000051133 14656474022 0015021 0 ustar 00root root 0000000 0000000 # Bubble Tea

The fun, functional and stateful way to build terminal apps. A Go framework
based on [The Elm Architecture][elm]. Bubble Tea is well-suited for simple and
complex terminal applications, either inline, full-window, or a mix of both.
Bubble Tea is in use in production and includes a number of features and
performance optimizations we’ve added along the way. Among those is a standard
framerate-based renderer, a renderer for high-performance scrollable
regions which works alongside the main renderer, and mouse support.
To get started, see the tutorial below, the [examples][examples], the
[docs][docs], the [video tutorials][youtube] and some common [resources](#libraries-we-use-with-bubble-tea).
[youtube]: https://charm.sh/yt
## By the way
Be sure to check out [Bubbles][bubbles], a library of common UI components for Bubble Tea.
***
## Tutorial
Bubble Tea is based on the functional design paradigms of [The Elm
Architecture][elm], which happens to work nicely with Go. It's a delightful way
to build applications.
This tutorial assumes you have a working knowledge of Go.
By the way, the non-annotated source code for this program is available
[on GitHub][tut-source].
[elm]: https://guide.elm-lang.org/architecture/
[tut-source]:https://github.com/charmbracelet/bubbletea/tree/master/tutorials/basics
### Enough! Let's get to it.
For this tutorial, we're making a shopping list.
To start we'll define our package and import some libraries. Our only external
import will be the Bubble Tea library, which we'll call `tea` for short.
```go
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
)
```
Bubble Tea programs are comprised of a **model** that describes the application
state and three simple methods on that model:
* **Init**, a function that returns an initial command for the application to run.
* **Update**, a function that handles incoming events and updates the model accordingly.
* **View**, a function that renders the UI based on the data in the model.
### The Model
So let's start by defining our model which will store our application's state.
It can be any type, but a `struct` usually makes the most sense.
```go
type model struct {
choices []string // items on the to-do list
cursor int // which to-do list item our cursor is pointing at
selected map[int]struct{} // which to-do items are selected
}
```
### Initialization
Next, we’ll define our application’s initial state. In this case, we’re defining
a function to return our initial model, however, we could just as easily define
the initial model as a variable elsewhere, too.
```go
func initialModel() model {
return model{
// Our to-do list is a grocery list
choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
// A map which indicates which choices are selected. We're using
// the map like a mathematical set. The keys refer to the indexes
// of the `choices` slice, above.
selected: make(map[int]struct{}),
}
}
```
Next, we define the `Init` method. `Init` can return a `Cmd` that could perform
some initial I/O. For now, we don't need to do any I/O, so for the command,
we'll just return `nil`, which translates to "no command."
```go
func (m model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
return nil
}
```
### The Update Method
Next up is the update method. The update function is called when ”things
happen.” Its job is to look at what has happened and return an updated model in
response. It can also return a `Cmd` to make more things happen, but for now
don't worry about that part.
In our case, when a user presses the down arrow, `Update`’s job is to notice
that the down arrow was pressed and move the cursor accordingly (or not).
The “something happened” comes in the form of a `Msg`, which can be any type.
Messages are the result of some I/O that took place, such as a keypress, timer
tick, or a response from a server.
We usually figure out which type of `Msg` we received with a type switch, but
you could also use a type assertion.
For now, we'll just deal with `tea.KeyMsg` messages, which are automatically
sent to the update function when keys are pressed.
```go
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
// Is it a key press?
case tea.KeyMsg:
// Cool, what was the actual key pressed?
switch msg.String() {
// These keys should exit the program.
case "ctrl+c", "q":
return m, tea.Quit
// The "up" and "k" keys move the cursor up
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
// The "down" and "j" keys move the cursor down
case "down", "j":
if m.cursor < len(m.choices)-1 {
m.cursor++
}
// The "enter" key and the spacebar (a literal space) toggle
// the selected state for the item that the cursor is pointing at.
case "enter", " ":
_, ok := m.selected[m.cursor]
if ok {
delete(m.selected, m.cursor)
} else {
m.selected[m.cursor] = struct{}{}
}
}
}
// Return the updated model to the Bubble Tea runtime for processing.
// Note that we're not returning a command.
return m, nil
}
```
You may have noticed that ctrl+c and q above return
a `tea.Quit` command with the model. That’s a special command which instructs
the Bubble Tea runtime to quit, exiting the program.
### The View Method
At last, it’s time to render our UI. Of all the methods, the view is the
simplest. We look at the model in its current state and use it to return
a `string`. That string is our UI!
Because the view describes the entire UI of your application, you don’t have to
worry about redrawing logic and stuff like that. Bubble Tea takes care of it
for you.
```go
func (m model) View() string {
// The header
s := "What should we buy at the market?\n\n"
// Iterate over our choices
for i, choice := range m.choices {
// Is the cursor pointing at this choice?
cursor := " " // no cursor
if m.cursor == i {
cursor = ">" // cursor!
}
// Is this choice selected?
checked := " " // not selected
if _, ok := m.selected[i]; ok {
checked = "x" // selected!
}
// Render the row
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
}
// The footer
s += "\nPress q to quit.\n"
// Send the UI for rendering
return s
}
```
### All Together Now
The last step is to simply run our program. We pass our initial model to
`tea.NewProgram` and let it rip:
```go
func main() {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}
```
## What’s Next?
This tutorial covers the basics of building an interactive terminal UI, but
in the real world you'll also need to perform I/O. To learn about that have a
look at the [Command Tutorial][cmd]. It's pretty simple.
There are also several [Bubble Tea examples][examples] available and, of course,
there are [Go Docs][docs].
[cmd]: http://github.com/charmbracelet/bubbletea/tree/master/tutorials/commands/
[examples]: http://github.com/charmbracelet/bubbletea/tree/master/examples
[docs]: https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc
## Debugging
### Debugging with Delve
Since Bubble Tea apps assume control of stdin and stdout, you’ll need to run
delve in headless mode and then connect to it:
```bash
# Start the debugger
$ dlv debug --headless --api-version=2 --listen=127.0.0.1:43000 .
API server listening at: 127.0.0.1:43000
# Connect to it from another terminal
$ dlv connect 127.0.0.1:43000
```
If you do not explicitly supply the `--listen` flag, the port used will vary
per run, so passing this in makes the debugger easier to use from a script
or your IDE of choice.
Additionally, we pass in `--api-version=2` because delve defaults to version 1
for backwards compatibility reasons. However, delve recommends using version 2
for all new development and some clients may no longer work with version 1.
For more information, see the [Delve documentation](https://github.com/go-delve/delve/tree/master/Documentation/api).
### Logging Stuff
You can’t really log to stdout with Bubble Tea because your TUI is busy
occupying that! You can, however, log to a file by including something like
the following prior to starting your Bubble Tea program:
```go
if len(os.Getenv("DEBUG")) > 0 {
f, err := tea.LogToFile("debug.log", "debug")
if err != nil {
fmt.Println("fatal:", err)
os.Exit(1)
}
defer f.Close()
}
```
To see what’s being logged in real time, run `tail -f debug.log` while you run
your program in another window.
## Libraries we use with Bubble Tea
* [Bubbles][bubbles]: Common Bubble Tea components such as text inputs, viewports, spinners and so on
* [Lip Gloss][lipgloss]: Style, format and layout tools for terminal applications
* [Harmonica][harmonica]: A spring animation library for smooth, natural motion
* [BubbleZone][bubblezone]: Easy mouse event tracking for Bubble Tea components
* [ntcharts][ntcharts]: A terminal charting library built for Bubble Tea and [Lip Gloss][lipgloss]
* [Termenv][termenv]: Advanced ANSI styling for terminal applications
* [Reflow][reflow]: Advanced ANSI-aware methods for working with text
[bubbles]: https://github.com/charmbracelet/bubbles
[lipgloss]: https://github.com/charmbracelet/lipgloss
[harmonica]: https://github.com/charmbracelet/harmonica
[bubblezone]: https://github.com/lrstanley/bubblezone
[ntcharts]: https://github.com/NimbleMarkets/ntcharts
[termenv]: https://github.com/muesli/termenv
[reflow]: https://github.com/muesli/reflow
## Bubble Tea in the Wild
For some Bubble Tea programs in production, see:
* [ASCII Movie](https://github.com/gabe565/ascii-movie): a Star Wars ASCII art movie player
* [AT CLI](https://github.com/daskycodes/at_cli): execute AT Commands via serial port connections
* [Aztify](https://github.com/Azure/aztfy): bring Microsoft Azure resources under Terraform
* [brows](https://github.com/rubysolo/brows): a GitHub release browser
* [Canard](https://github.com/mrusme/canard): an RSS client
* [charm](https://github.com/charmbracelet/charm): the official Charm user account manager
* [chatgpt-cli](https://github.com/j178/chatgpt): a CLI for ChatGPT
* [chatgpt-tui](https://github.com/tearingItUp786/chatgpt-tui): a TUI for ChatGPT with SQLite sessions
* [ChatGPTUI](https://github.com/dwisiswant0/chatgptui): a TUI for ChatGPT
* [chezmoi](https://github.com/twpayne/chezmoi): securely manage your dotfiles across multiple machines
* [chip-8](https://github.com/braheezy/chip-8): a CHIP-8 interpreter
* [chtop](https://github.com/chhetripradeep/chtop): monitor your ClickHouse node without leaving the terminal
* [circumflex](https://github.com/bensadeh/circumflex): read Hacker News in the terminal
* [clidle](https://github.com/ajeetdsouza/clidle): a Wordle clone
* [cLive](https://github.com/koki-develop/clive): automate terminal operations and view them live in a browser
* [container-canary](https://github.com/NVIDIA/container-canary): a container validator
* [countdown](https://github.com/aldernero/countdown): a multi-event countdown timer
* [CRT](https://github.com/BigJk/crt): a simple terminal emulator for running Bubble Tea in a dedicated window, with optional shaders
* [cueitup](https://github.com/dhth/cueitup): inspect messages in an AWS SQS queue in a simple and deliberate manner
* [Daytona](https://github.com/daytonaio/daytona): an development environment manager
* [dns53](https://github.com/purpleclay/dns53): dynamic DNS with Amazon Route53; expose your EC2 quickly, securely and privately
* [eks-node-viewer](https://github.com/awslabs/eks-node-viewer): a tool for visualizing dynamic node usage within an EKS cluster
* [End Of Eden](https://github.com/BigJk/end_of_eden): a "Slay the Spire"-like, roguelike deck-builder game
* [enola](https://github.com/sherlock-project/enola): find social media accounts by username across social networks
* [flapioca](https://github.com/kbrgl/flapioca): Flappy Bird on the CLI!
* [fm](https://github.com/knipferrc/fm): a terminal-based file manager
* [fork-cleaner](https://github.com/caarlos0/fork-cleaner): clean up old and inactive forks in your GitHub account
* [fractals-cli](https://github.com/MicheleFiladelfia/fractals-cli): a multiplatform terminal fractal explorer
* [fztea](https://github.com/jon4hz/fztea): a Flipper Zero TUI
* [gama](https://github.com/termkit/gama): manage GitHub Actions from the terminal
* [gambit](https://github.com/maaslalani/gambit): chess in the terminal
* [gembro](https://git.sr.ht/~rafael/gembro): a mouse-driven Gemini browser
* [gh-b](https://github.com/joaom00/gh-b): a GitHub CLI extension for managing branches
* [gh-dash](https://www.github.com/dlvhdr/gh-dash): a GitHub CLI extension for PRs and issues
* [gitflow-toolkit](https://github.com/mritd/gitflow-toolkit): a GitFlow submission tool
* [Glow](https://github.com/charmbracelet/glow): a markdown reader, browser, and online markdown stash
* [go-sweep](https://github.com/maxpaulus43/go-sweep): Minesweeper in the terminal
* [gocovsh](https://github.com/orlangure/gocovsh): explore Go coverage reports from the CLI
* [got](https://github.com/fedeztk/got): a simple translator and text-to-speech app built on simplytranslate's APIs
* [gum](https://github.com/charmbracelet/gum): interactivity and styling for shells and shell scripts
* [hiSHtory](https://github.com/ddworken/hishtory): your shell history in context: synced, and queryable
* [httpit](https://github.com/gonetx/httpit): a rapid http(s) benchmark tool
* [Huh?](https://github.com/charmbracelet/huh): an interactive prompt and form toolkit
* [IDNT](https://github.com/r-darwish/idnt): a batch software uninstaller
* [json-log-viewer](https://github.com/hedhyw/json-log-viewer): an interactive JSON log viewer
* [kboard](https://github.com/CamiloGarciaLaRotta/kboard): a typing game
* [kplay](https://github.com/dhth/kplay): inspect messages in a Kafka topic
* [laboon](https://github.com/arisnacg/laboon): a Docker-desktop-style container manager
* [mc](https://github.com/minio/mc): the official [MinIO](https://min.io) client
* [mergestat](https://github.com/mergestat/mergestat): run SQL queries on git repositories
* [meteor](https://github.com/stefanlogue/meteor): a highly customizable conventional commit message tool
* [mods](https://github.com/charmbracelet/mods): AI on the CLI, built for pipelines
* [nachrichten](https://github.com/zMoooooritz/nachrichten): access up-to-date news in German provided by the [Tagesschau](https://www.tagesschau.de/)
* [Neon Modem Overdrive](https://github.com/mrusme/neonmodem): a BBS-style TUI client for Discourse, Lemmy, Lobste.rs and Hacker News
* [nom](https://github.com/guyfedwards/nom): an RSS reader and manager
* [Noted](https://github.com/torbratsberg/noted): a note viewer and manager
* [outtasync](https://github.com/dhth/outtasync): identify CloudFormation stacks that are out of sync with their template files
* [pathos](https://github.com/chip/pathos): a PATH environment variable editor
* [Plandex](https://github.com/plandex-ai/plandex): a terminal-based AI coding engine for complex tasks
* [portal](https://github.com/ZinoKader/portal): secure transfers between computers
* [prs](https://github.com/dhth/prs): stay up to date with your PRs
* [puffin](https://github.com/siddhantac/puffin): a TUI for hledger to manage your finances
* [pug](https://github.com/leg100/pug): terraform task manager
* [punchout](https://github.com/dhth/punchout): takes the suck out of logging time on JIRA
* [redis-viewer](https://github.com/SaltFishPr/redis-viewer): a Redis database browser
* [redis_tui](https://github.com/mat2cc/redis_tui): a Redis database browser
* [schemas](https://github.com/dhth/schemas): lets you inspect postgres schemas in the terminal
* [scrabbler](https://github.com/wI2L/scrabbler): an automatic draw tool for your duplicate Scrabble games
* [sku](https://github.com/fedeztk/sku): Sudoku on the CLI
* [Slides](https://github.com/maaslalani/slides): a markdown-based presentation tool
* [SlurmCommander](https://github.com/CLIP-HPC/SlurmCommander): a Slurm workload manager
* [Soft Serve](https://github.com/charmbracelet/soft-serve): a command-line-first Git server that runs a TUI over SSH
* [solitaire-tui](https://github.com/brianstrauch/solitaire-tui): Klondike Solitaire for the terminal
* [StormForge Optimize Controller](https://github.com/thestormforge/optimize-controller): a tool for experimenting with application configurations in Kubernetes
* [Storydb](https://github.com/grrlopes/storydb): an improved bash/zsh-style ctrl+r command history finder
* [STTG](https://github.com/wille1101/sttg): a teletext client for SVT, Sweden’s national public television station
* [sttr](https://github.com/abhimanyu003/sttr): a general-purpose text transformer
* [superfile](https://github.com/MHNightCat/superfile) a fancy, modern terminal-based file manager
* [tasktimer](https://github.com/caarlos0/tasktimer): a dead-simple task timer
* [termdbms](https://github.com/mathaou/termdbms): a keyboard and mouse driven database browser
* [tgpt](https://github.com/aandrew-me/tgpt): conversational AI for the CLI; no API keys necessary
* [ticker](https://github.com/achannarasappa/ticker): a terminal stock viewer and stock position tracker
* [trainer](https://github.com/rusinikita/trainer): a Go concurrency coding interview simulator with learning materials
* [tran](https://github.com/abdfnx/tran): securely transfer stuff between computers (based on [portal](https://github.com/ZinoKader/portal))
* [Trufflehog](https://github.com/trufflesecurity/trufflehog): find leaked credentials
* [Typer](https://github.com/maaslalani/typer): a typing test
* [typioca](https://github.com/bloznelis/typioca): a typing test
* [tz](https://github.com/oz/tz): a scheduling aid for people in multiple time zones
* [ugm](https://github.com/ariasmn/ugm): a unix user and group browser
* [walk](https://github.com/antonmedv/walk): a terminal navigator
* [wander](https://github.com/robinovitch61/wander): a HashiCorp Nomad terminal client
* [WG Commander](https://github.com/AndrianBdn/wg-cmd): a TUI for a simple WireGuard VPN setup
* [wishlist](https://github.com/charmbracelet/wishlist): an SSH directory
## Feedback
We'd love to hear your thoughts on this project. Feel free to drop us a note!
* [Twitter](https://twitter.com/charmcli)
* [The Fediverse](https://mastodon.social/@charmcli)
* [Discord](https://charm.sh/chat)
## Acknowledgments
Bubble Tea is based on the paradigms of [The Elm Architecture][elm] by Evan
Czaplicki et alia and the excellent [go-tea][gotea] by TJ Holowaychuk. It’s
inspired by the many great [_Zeichenorientierte Benutzerschnittstellen_][zb]
of days past.
[elm]: https://guide.elm-lang.org/architecture/
[gotea]: https://github.com/tj/go-tea
[zb]: https://de.wikipedia.org/wiki/Zeichenorientierte_Benutzerschnittstelle
## License
[MIT](https://github.com/charmbracelet/bubbletea/raw/master/LICENSE)
***
Part of [Charm](https://charm.sh).
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
bubbletea-0.27.0/commands.go 0000664 0000000 0000000 00000012702 14656474022 0015671 0 ustar 00root root 0000000 0000000 package tea
import (
"time"
)
// Batch performs a bunch of commands concurrently with no ordering guarantees
// about the results. Use a Batch to return several commands.
//
// Example:
//
// func (m model) Init() Cmd {
// return tea.Batch(someCommand, someOtherCommand)
// }
func Batch(cmds ...Cmd) Cmd {
var validCmds []Cmd //nolint:prealloc
for _, c := range cmds {
if c == nil {
continue
}
validCmds = append(validCmds, c)
}
switch len(validCmds) {
case 0:
return nil
case 1:
return validCmds[0]
default:
return func() Msg {
return BatchMsg(validCmds)
}
}
}
// BatchMsg is a message used to perform a bunch of commands concurrently with
// no ordering guarantees. You can send a BatchMsg with Batch.
type BatchMsg []Cmd
// Sequence runs the given commands one at a time, in order. Contrast this with
// Batch, which runs commands concurrently.
func Sequence(cmds ...Cmd) Cmd {
return func() Msg {
return sequenceMsg(cmds)
}
}
// sequenceMsg is used internally to run the given commands in order.
type sequenceMsg []Cmd
// Every is a command that ticks in sync with the system clock. So, if you
// wanted to tick with the system clock every second, minute or hour you
// could use this. It's also handy for having different things tick in sync.
//
// Because we're ticking with the system clock the tick will likely not run for
// the entire specified duration. For example, if we're ticking for one minute
// and the clock is at 12:34:20 then the next tick will happen at 12:35:00, 40
// seconds later.
//
// To produce the command, pass a duration and a function which returns
// a message containing the time at which the tick occurred.
//
// type TickMsg time.Time
//
// cmd := Every(time.Second, func(t time.Time) Msg {
// return TickMsg(t)
// })
//
// Beginners' note: Every sends a single message and won't automatically
// dispatch messages at an interval. To do that, you'll want to return another
// Every command after receiving your tick message. For example:
//
// type TickMsg time.Time
//
// // Send a message every second.
// func tickEvery() Cmd {
// return Every(time.Second, func(t time.Time) Msg {
// return TickMsg(t)
// })
// }
//
// func (m model) Init() Cmd {
// // Start ticking.
// return tickEvery()
// }
//
// func (m model) Update(msg Msg) (Model, Cmd) {
// switch msg.(type) {
// case TickMsg:
// // Return your Every command again to loop.
// return m, tickEvery()
// }
// return m, nil
// }
//
// Every is analogous to Tick in the Elm Architecture.
func Every(duration time.Duration, fn func(time.Time) Msg) Cmd {
n := time.Now()
d := n.Truncate(duration).Add(duration).Sub(n)
t := time.NewTimer(d)
return func() Msg {
ts := <-t.C
t.Stop()
for len(t.C) > 0 {
<-t.C
}
return fn(ts)
}
}
// Tick produces a command at an interval independent of the system clock at
// the given duration. That is, the timer begins precisely when invoked,
// and runs for its entire duration.
//
// To produce the command, pass a duration and a function which returns
// a message containing the time at which the tick occurred.
//
// type TickMsg time.Time
//
// cmd := Tick(time.Second, func(t time.Time) Msg {
// return TickMsg(t)
// })
//
// Beginners' note: Tick sends a single message and won't automatically
// dispatch messages at an interval. To do that, you'll want to return another
// Tick command after receiving your tick message. For example:
//
// type TickMsg time.Time
//
// func doTick() Cmd {
// return Tick(time.Second, func(t time.Time) Msg {
// return TickMsg(t)
// })
// }
//
// func (m model) Init() Cmd {
// // Start ticking.
// return doTick()
// }
//
// func (m model) Update(msg Msg) (Model, Cmd) {
// switch msg.(type) {
// case TickMsg:
// // Return your Tick command again to loop.
// return m, doTick()
// }
// return m, nil
// }
func Tick(d time.Duration, fn func(time.Time) Msg) Cmd {
t := time.NewTimer(d)
return func() Msg {
ts := <-t.C
t.Stop()
for len(t.C) > 0 {
<-t.C
}
return fn(ts)
}
}
// Sequentially produces a command that sequentially executes the given
// commands.
// The Msg returned is the first non-nil message returned by a Cmd.
//
// func saveStateCmd() Msg {
// if err := save(); err != nil {
// return errMsg{err}
// }
// return nil
// }
//
// cmd := Sequentially(saveStateCmd, Quit)
//
// Deprecated: use Sequence instead.
func Sequentially(cmds ...Cmd) Cmd {
return func() Msg {
for _, cmd := range cmds {
if cmd == nil {
continue
}
if msg := cmd(); msg != nil {
return msg
}
}
return nil
}
}
// setWindowTitleMsg is an internal message used to set the window title.
type setWindowTitleMsg string
// SetWindowTitle produces a command that sets the terminal title.
//
// For example:
//
// func (m model) Init() Cmd {
// // Set title.
// return tea.SetWindowTitle("My App")
// }
func SetWindowTitle(title string) Cmd {
return func() Msg {
return setWindowTitleMsg(title)
}
}
type windowSizeMsg struct{}
// WindowSize is a command that queries the terminal for its current size. It
// delivers the results to Update via a [WindowSizeMsg]. Keep in mind that
// WindowSizeMsgs will automatically be delivered to Update when the [Program]
// starts and when the window dimensions change so in many cases you will not
// need to explicitly invoke this command.
func WindowSize() Cmd {
return func() Msg {
return windowSizeMsg{}
}
}
bubbletea-0.27.0/commands_test.go 0000664 0000000 0000000 00000004031 14656474022 0016724 0 ustar 00root root 0000000 0000000 package tea
import (
"fmt"
"testing"
"time"
)
func TestEvery(t *testing.T) {
expected := "every ms"
msg := Every(time.Millisecond, func(t time.Time) Msg {
return expected
})()
if expected != msg {
t.Fatalf("expected a msg %v but got %v", expected, msg)
}
}
func TestTick(t *testing.T) {
expected := "tick"
msg := Tick(time.Millisecond, func(t time.Time) Msg {
return expected
})()
if expected != msg {
t.Fatalf("expected a msg %v but got %v", expected, msg)
}
}
func TestSequentially(t *testing.T) {
expectedErrMsg := fmt.Errorf("some err")
expectedStrMsg := "some msg"
nilReturnCmd := func() Msg {
return nil
}
tests := []struct {
name string
cmds []Cmd
expected Msg
}{
{
name: "all nil",
cmds: []Cmd{nilReturnCmd, nilReturnCmd},
expected: nil,
},
{
name: "null cmds",
cmds: []Cmd{nil, nil},
expected: nil,
},
{
name: "one error",
cmds: []Cmd{
nilReturnCmd,
func() Msg {
return expectedErrMsg
},
nilReturnCmd,
},
expected: expectedErrMsg,
},
{
name: "some msg",
cmds: []Cmd{
nilReturnCmd,
func() Msg {
return expectedStrMsg
},
nilReturnCmd,
},
expected: expectedStrMsg,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if msg := Sequentially(test.cmds...)(); msg != test.expected {
t.Fatalf("expected a msg %v but got %v", test.expected, msg)
}
})
}
}
func TestBatch(t *testing.T) {
t.Run("nil cmd", func(t *testing.T) {
if b := Batch(nil); b != nil {
t.Fatalf("expected nil, got %+v", b)
}
})
t.Run("empty cmd", func(t *testing.T) {
if b := Batch(); b != nil {
t.Fatalf("expected nil, got %+v", b)
}
})
t.Run("single cmd", func(t *testing.T) {
b := Batch(Quit)()
if _, ok := b.(QuitMsg); !ok {
t.Fatalf("expected a QuitMsg, got %T", b)
}
})
t.Run("mixed nil cmds", func(t *testing.T) {
b := Batch(nil, Quit, nil, Quit, nil, nil)()
if l := len(b.(BatchMsg)); l != 2 {
t.Fatalf("expected a []Cmd with len 2, got %d", l)
}
})
}
bubbletea-0.27.0/examples/ 0000775 0000000 0000000 00000000000 14656474022 0015355 5 ustar 00root root 0000000 0000000 bubbletea-0.27.0/examples/README.md 0000664 0000000 0000000 00000017066 14656474022 0016646 0 ustar 00root root 0000000 0000000 # Examples
### Alt Screen Toggle
The `altscreen-toggle` example shows how to transition between the alternative
screen buffer and the normal screen buffer using Bubble Tea.
### Chat
The `chat` examples shows a basic chat application with a multi-line `textarea`
input.
### Composable Views
The `composable-views` example shows how to compose two bubble models (spinner
and timer) together in a single application and switch between them.
### Credit Card Form
The `credit-card-form` example demonstrates how to build a multi-step form with
`textinput`s bubbles and validation on the inputs.
### Debounce
The `debounce` example shows how to throttle key presses to avoid overloading
your Bubble Tea application.
### Exec
The `exec` example shows how to execute a running command during the execution
of a Bubble Tea application such as launching an `EDITOR`.
### Full Screen
The `fullscreen` example shows how to make a Bubble Tea application fullscreen.
### Glamour
The `glamour` example shows how to use [Glamour](https://github.com/charmbracelet/glamour) inside a viewport bubble.
### Help
The `help` example shows how to use the `help` bubble to display help to the
user of your application.
### Http
The `http` example shows how to make an `http` call within your Bubble Tea
application.
### Default List
The `list-default` example shows how to use the list bubble.
### Fancy List
The `list-fancy` example shows how to use the list bubble with extra customizations.
### Simple List
The `list-simple` example shows how to use the list and customize it to have a simpler, more compact, appearance.
### Mouse
The `mouse` example shows how to receive mouse events in a Bubble Tea
application.
Code
### Package Manager
The `package-manager` example shows how to build an interface for a package
manager using the `tea.Println` feature.
### Pager
The `pager` example shows how to build a simple pager application similar to
`less`.
### Paginator
The `paginator` example shows how to build a simple paginated list.
### Pipe
The `pipe` example demonstrates using shell pipes to communicate with Bubble
Tea applications.
### Animated Progress
The `progress-animated` example shows how to build a progress bar with an
animated progression.
### Download Progress
The `progress-download` example demonstrates how to download a file while
indicating download progress through Bubble Tea.
Code
### Static Progress
The `progress-static` example shows a progress bar with static incrementation
of progress.
### Real Time
The `realtime` example demonstrates the use of go channels to perform realtime
communication with a Bubble Tea application.
### Result
The `result` example shows a choice menu with the ability to select an option.
### Send Msg
The `send-msg` example demonstrates the usage of custom `tea.Msg`s.
### Sequence
The `sequence` example demonstrates the `tea.Sequence` command.
### Simple
The `simple` example shows a very simple Bubble Tea application.
### Spinner
The `spinner` example demonstrates a spinner bubble being used to indicate loading.
### Spinners
The `spinner` example shows various spinner types that are available.
### Split Editors
The `split-editors` example shows multiple `textarea`s being used in a single
application and being able to switch focus between them.
### Stop Watch
The `stopwatch` example shows a sample stop watch built with Bubble Tea.
### Table
The `table` example demonstrates the table bubble being used to display tabular
data.
### Tabs
The `tabs` example demonstrates tabbed navigation styled with [Lip Gloss](https://github.com/charmbracelet/lipgloss).
### Text Area
The `textarea` example demonstrates a simple Bubble Tea application using a
`textarea` bubble.
### Text Input
The `textinput` example demonstrates a simple Bubble Tea application using a `textinput` bubble.
### Multiple Text Inputs
The `textinputs` example shows multiple `textinputs` and being able to switch
focus between them as well as changing the cursor mode.
### Timer
The `timer` example shows a simple timer built with Bubble Tea.
### TUI Daemon
The `tui-daemon-combo` demonstrates building a text-user interface along with a
daemon mode using Bubble Tea.
### Views
The `views` example demonstrates how to build a Bubble Tea application with
multiple views and switch between them.
bubbletea-0.27.0/examples/altscreen-toggle/ 0000775 0000000 0000000 00000000000 14656474022 0020614 5 ustar 00root root 0000000 0000000 bubbletea-0.27.0/examples/altscreen-toggle/README.md 0000664 0000000 0000000 00000000106 14656474022 0022070 0 ustar 00root root 0000000 0000000 # Alt Screen Toggle
bubbletea-0.27.0/examples/altscreen-toggle/altscreen-toggle.gif 0000664 0000000 0000000 00000143562 14656474022 0024555 0 ustar 00root root 0000000 0000000 GIF89aX !/ ! 6!!!!!7"""###$#@$$$%%%&&&'&&'''((()&')))*'(***+'(+)U+++,+Y,,,---.().../(*///0001(*11122231n3334),4445556*-6667*.7778889*.97999:8:::;+/;9;;;<+/<<<=:===>>>?,1???@@@A-2AAABBBCCCD-3DDDEEEFCFFFG.5GGGH/5HHHI.5IIIJ/6JJJK/6KHKKKLHLLLM07MMMN07NNNOOOPPPQQQR09RRRSOSSSTPTTTUQUUUVVVW2P?Q@R@SAUBWCXCYE[F]F^G_H`IaIcJeKfMhNjMjOlPmOmPnQoQoRqSsTuUvVwUxWzX|X|Y~[[]]_!NETSCAPE2.0 ! , X !/ ! 6!!!!!7"""###$#@$$$%%%&&&'&&'''((()&')))*'(***+'(+)U+++,+Y,,,---.().../(*///0001(*11122231n3334),4445556*-6667*.7778889*.97999:8:::;+/;9;;;<+/<<<=:===>>>?,1???@@@A-2AAABBBCCCD-3DDDEEEFCFFFG.5GGGH/5HHHI.5IIIJ/6JJJK/6KHKKKLHLLLM07MMMN07NNNOOOPPPQQQR09RRRSOSSSTPTTTUQUUUVVVW2P?Q@R@SAUBWCXCYE[F]F^G_H`IaIcJeKfMhNjMjOlPmOmPnQoQoRqSsTuUvVwUxWzX|X|Y~[[]]_ H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@
JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻNȓ+_μУKNسkνËOӫ_Ͼ˟OϿ (h&6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄j衈&袌6裐F*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무:?檫Z1,ȶh,6ⲻ:+m: V+,r-݆kn䞫n.y~'4oj'vpHkput0$TK' o1HGs C Q?<&*wD4v5<GQ4HӖ0j40IAG'mk1APdvoq5=وspoGУnW)pP|n'7~G.Wgw砇.褗n騧ꬷ.n/o'7G/Wogw/o觯/o HL:'H
Z̠7z GH(L
W0gH8̡w@H"HL&:PH*ZX̢.z`H2hL6pH:x̣> IBL"F:$'IJZ̤&7Nz(GIRL*WV,gIZ̥.w^0IbL2f:Ќ4IjZ̦6nz8IrL:v~
@JЂMBІ:D'JъZͨF7юz
HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@
PJԢHMRԦ:PTJժZXͪVծz`
XJֲhMZֶp\Jxͫ^
`KMb:d'KZͬf7z
hGKҚMjWֺlgKͭnw
pKMr:ЍtKZͮvz
xKMz|Kͯ~ LN;'L
[ΰ7{ GL(NW0gL8αw@L"HN8 ! ,W ! ,z P H࿃&,p6Q"C-ĨPA=
a=DFgQ=R*IJBOކ %WʆS&CP}4ZMKsC/.kO%[╩T]:p=Ik
NA\ћ552Eoĕ,pGOᯏ譳X 7z(ef+ ZdiC ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.a,ř{G:>I
,^J9);%ZnP2=W 20A f
'iϳ-gO1^or'+(LO\*g*+))`Zw;
2fHёAG`%\ iSR6l۵5}2 ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$ɃKZrƓћ9SK,)͏8y$á?JQIG.%(ǣMUցU^j#֨N;["[fT3Gw:]ߙ~ըVק&z,lq ! ,W ! ,W ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"G$H+R&I3k^)0M6s'CI J^QNZ
T&=r?9⼚V;oZd?5lUpu(]+9ԩD>UkC
84俔,c֜I- ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.a$H dž30Fg;Wl'u= %)x%RB%Sq#g *ilMЍ30IO5IJ-4`96$sgQԂ W)
FQzRJ\Q .cKَa 2 Zcm2gli& ! ,W ! ,W ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.a,B38=y8Ow|JR/z` g!M%E3 PYb#>oJħƘ|0;&%Q`1z_q(uSÚOp0
KB,pf5c̔I6]ɀ ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.ah"͒-W23' "ɣWP$(:zSE*GBc])c
m"YUqU[ޫSUݴyu{G]Ό t=L9[%_ɀ ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.a,hř2 {G^Zμ %)P+\ w(EYU9{n ؈ɶ;F`p)
3Ř{DpE8y+
(3AKQ č)qTkMR/sa#NM쿷% ! ,W ! ,W ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.a,hř2 {G^Zμ %)P+\ w(EYU9{n ؈ɶ;F`p)
3Ř{DpE8y+
(3AKQ č)qTkMR/sa#NM쿷% ! ,W ! , P H࿃&,p6Q"C-ĨPA=
R$Ƀ"GL)?.adƕ-⩃ =z8\Y^