├── exp ├── maps │ ├── go.sum │ ├── go.mod │ ├── maps.go │ └── maps_test.go ├── open │ ├── go.sum │ ├── go.mod │ ├── cmd │ │ └── main.go │ ├── open_windows.go │ ├── open_unix.go │ └── open.go ├── ordered │ ├── go.sum │ ├── go.mod │ └── ordered.go ├── charmtone │ ├── go.sum │ ├── go.mod │ └── charmtone_test.go ├── golden │ ├── testdata │ │ ├── TestTypes │ │ │ ├── String.golden │ │ │ └── SliceOfBytes.golden │ │ ├── TestRequireEqualUpdate.golden │ │ ├── TestRequireEqualNoUpdate.golden │ │ └── TestRequireWithLineBreaks.golden │ ├── go.mod │ ├── go.sum │ └── golden_test.go ├── teatest │ ├── testdata │ │ ├── TestRequireEqualOutputUpdate.golden │ │ ├── TestAppSendToOtherProgram.golden │ │ └── TestApp.golden │ ├── v2 │ │ ├── testdata │ │ │ ├── TestRequireEqualOutputUpdate.golden │ │ │ ├── TestAppSendToOtherProgram.golden │ │ │ └── TestApp.golden │ │ ├── go.mod │ │ └── teatest_test.go │ ├── go.mod │ └── teatest_test.go ├── slice │ └── go.mod ├── strings │ └── go.mod ├── higherorder │ ├── go.mod │ └── higherorder.go └── toner │ ├── go.mod │ └── go.sum ├── gitignore ├── go.sum ├── go.mod └── matcher.go ├── pony ├── examples │ ├── buttons │ │ ├── .gitignore │ │ ├── go.mod │ │ └── README.md │ ├── interactive-form │ │ ├── .gitignore │ │ └── go.mod │ ├── custom │ │ ├── .gitignore │ │ └── go.mod │ ├── dynamic │ │ ├── .gitignore │ │ └── go.mod │ ├── hello │ │ ├── .gitignore │ │ ├── main.go │ │ └── go.mod │ ├── helpers │ │ ├── .gitignore │ │ └── go.mod │ ├── layout │ │ ├── .gitignore │ │ └── go.mod │ ├── styled │ │ ├── .gitignore │ │ └── go.mod │ ├── alignment │ │ ├── .gitignore │ │ └── go.mod │ ├── bubbletea │ │ ├── .gitignore │ │ └── go.mod │ ├── components │ │ ├── .gitignore │ │ └── go.mod │ ├── scrolling │ │ ├── .gitignore │ │ └── go.mod │ ├── stateful │ │ ├── .gitignore │ │ └── go.mod │ ├── simple-bubbletea │ │ ├── .gitignore │ │ └── go.mod │ ├── advanced │ │ └── go.mod │ ├── scrolling-markup │ │ └── go.mod │ └── scrolling-with-clicks │ │ └── go.mod ├── testdata │ ├── TestHStackValign.golden │ ├── TestRender.golden │ ├── TestSlotBasic │ │ ├── without_slot.golden │ │ └── with_slot_filled.golden │ ├── TestSlotMissing.golden │ ├── TestTemplateFunctions │ │ ├── add_function.golden │ │ ├── lower_function.golden │ │ ├── upper_function.golden │ │ └── printf_function.golden │ ├── TestCustomComponentInMarkup.golden │ ├── TestRenderBox.golden │ ├── TestSlotInBox.golden │ ├── TestTemplateConditional │ │ ├── condition_false.golden │ │ └── condition_true.golden │ ├── TestTemplateVariables.golden │ ├── TestBadgeComponent │ │ ├── badge_with_child_text.golden │ │ ├── badge_with_text_attribute.golden │ │ └── badge_with_style.golden │ ├── TestHelperRoundTrip.golden │ ├── TestTemplateRange.golden │ ├── TestRenderWithStyle.golden │ ├── TestTemplateWithStyle.golden │ ├── TestVStackAlignment.golden │ ├── TestRenderVStack.golden │ ├── TestCustomComponentWithChildren.golden │ ├── TestProgressComponent │ │ ├── progress_with_custom_width.golden │ │ ├── progress_with_style.golden │ │ └── progress_with_value.golden │ ├── TestScrollViewInMarkup.golden │ ├── TestTextAlignment │ │ ├── center_aligned.golden │ │ ├── left_aligned.golden │ │ └── right_aligned.golden │ ├── TestMultipleSlots.golden │ ├── TestSlotsWithTemplateData.golden │ ├── TestBoxPadding.golden │ ├── TestZStackBasic.golden │ ├── TestRenderBoxWithBorderStyle.golden │ ├── TestFlexibleSpacerInHStack.golden │ ├── TestFlexGrow.golden │ ├── TestBoxMargin.golden │ ├── TestBoxWithWidth.golden │ ├── TestFlexibleSpacerBasic.golden │ ├── TestBoxMarginSides.golden │ ├── TestFlexVStack.golden │ ├── TestMultipleFlexibleSpacers.golden │ ├── TestScrollViewBasic.golden │ ├── TestSlotWithComplexElement.golden │ ├── TestPositionedBasic.golden │ ├── TestCombinedFeatures.golden │ ├── TestPositionedCorners.golden │ ├── TestAdvancedLayoutCombined.golden │ ├── TestScrollViewWithSlot.golden │ └── TestRenderComplexLayout.golden ├── .gitattributes ├── .gitignore ├── .golangci.yml ├── go.mod ├── constants.go ├── slot.go ├── spacer.go └── Taskfile.yaml ├── etag ├── go.mod └── etag.go ├── json ├── go.mod └── json.go ├── editor └── go.mod ├── errors ├── go.mod ├── join.go └── join_test.go ├── ansi ├── fixtures │ └── demo.vte ├── sixel │ ├── .gitattributes │ ├── repeat.go │ └── sixel_bench_test.go ├── ascii.go ├── paste.go ├── reset.go ├── doc.go ├── focus.go ├── ansi.go ├── go.mod ├── iterm2.go ├── urxvt.go ├── cwd_test.go ├── parser_sync.go ├── inband.go ├── parser_apc_test.go ├── parser_esc_test.go ├── hyperlink_test.go ├── title_test.go ├── keypad.go ├── hyperlink.go ├── cwd.go ├── notification.go ├── urxvt_test.go ├── termcap.go ├── palette.go ├── go.sum ├── clipboard_test.go ├── graphics_test.go ├── gen.go ├── background_test.go ├── palette_test.go ├── passthrough_test.go ├── title.go ├── kitty │ └── encoder.go ├── notification_test.go ├── style_test.go ├── charset.go └── winop.go ├── term ├── go.mod ├── go.sum ├── terminal.go ├── term_unix_other.go ├── term_unix_bsd.go ├── term_test.go ├── util.go ├── term_other.go └── term.go ├── conpty ├── go.mod ├── go.sum ├── doc.go └── conpty.go ├── termios ├── go.mod ├── doc.go ├── go.sum ├── bit_darwin.go ├── syscalls_darwin.go ├── bit_bsd.go ├── bit_other.go ├── syscalls_bsd.go ├── termios_linux.go ├── syscalls_linux.go ├── termios_bsd.go ├── termios_other.go └── termios_solaris.go ├── windows ├── go.mod ├── go.sum ├── doc.go └── syscall_windows.go ├── sshkey ├── _examples │ ├── key.pub │ ├── main.go │ └── key └── go.mod ├── vt ├── vt.go ├── csi_sgr.go ├── cursor.go ├── csi_screen.go ├── focus.go ├── esc.go ├── go.mod ├── charset.go ├── dcs.go ├── csi.go ├── mode.go └── terminal.go ├── examples ├── cellbuf │ ├── winsize_windows.go │ └── winsize_other.go ├── mosaic │ ├── pekinas.jpg │ └── main.go ├── JetBrainsMono-Regular.ttf ├── toner │ └── main.go ├── pen │ └── main.go └── img2term │ └── main.go ├── .gitattributes ├── fixtures ├── charm-wish.png └── JigokudaniMonkeyPark.png ├── vcr ├── doc.go ├── go.mod ├── marshaler.go ├── go.sum └── hooks.go ├── powernap ├── go.mod ├── go.sum └── pkg │ └── lsp │ ├── protocol │ ├── tables.go │ └── LICENSE │ ├── protocol.go │ └── types.go ├── cellbuf ├── errors.go ├── link.go ├── utils.go ├── geom.go ├── style.go └── go.mod ├── wcwidth ├── go.mod ├── go.sum └── wcwidth.go ├── xpty ├── conpty_other.go ├── go.mod ├── pty_other.go ├── go.sum ├── conpty.go ├── conpty_windows.go └── pty_unix.go ├── input ├── cursor.go ├── focus.go ├── cancelreader_other.go ├── doc.go ├── mode.go ├── paste.go ├── da1.go ├── focus_test.go ├── driver_other.go ├── driver_test.go ├── go.mod ├── clipboard.go ├── mod.go ├── input.go ├── termcap.go ├── xterm.go └── parse_test.go ├── .editorconfig ├── .github ├── CODEOWNERS └── workflows │ ├── input-fuzz.yml │ ├── generate.yml │ ├── windows-generate.yml │ ├── term-plan9.yml │ ├── vt.yml │ ├── vcr.yml │ └── ansi.yml ├── .gitignore ├── mosaic ├── go.mod └── go.sum ├── .golangci.yml ├── colors └── go.mod ├── scripts └── dependabot ├── LICENSE └── Taskfile.yaml /exp/maps/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exp/open/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exp/ordered/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gitignore/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exp/charmtone/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pony/examples/buttons/.gitignore: -------------------------------------------------------------------------------- 1 | go.sum 2 | -------------------------------------------------------------------------------- /pony/testdata/TestHStackValign.golden: -------------------------------------------------------------------------------- 1 | AB -------------------------------------------------------------------------------- /exp/golden/testdata/TestTypes/String.golden: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /pony/testdata/TestRender.golden: -------------------------------------------------------------------------------- 1 | Hello, World! -------------------------------------------------------------------------------- /pony/testdata/TestSlotBasic/without_slot.golden: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exp/golden/testdata/TestRequireEqualUpdate.golden: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /exp/golden/testdata/TestTypes/SliceOfBytes.golden: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /pony/examples/interactive-form/.gitignore: -------------------------------------------------------------------------------- 1 | go.sum 2 | -------------------------------------------------------------------------------- /pony/testdata/TestSlotMissing.golden: -------------------------------------------------------------------------------- 1 | Before 2 | After -------------------------------------------------------------------------------- /exp/golden/testdata/TestRequireEqualNoUpdate.golden: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /pony/testdata/TestTemplateFunctions/add_function.golden: -------------------------------------------------------------------------------- 1 | 8 -------------------------------------------------------------------------------- /exp/teatest/testdata/TestRequireEqualOutputUpdate.golden: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /exp/teatest/v2/testdata/TestRequireEqualOutputUpdate.golden: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /pony/testdata/TestCustomComponentInMarkup.golden: -------------------------------------------------------------------------------- 1 | Custom: Hello -------------------------------------------------------------------------------- /pony/testdata/TestRenderBox.golden: -------------------------------------------------------------------------------- 1 | ╭────╮ 2 | │Test│ 3 | ╰────╯ -------------------------------------------------------------------------------- /pony/testdata/TestSlotBasic/with_slot_filled.golden: -------------------------------------------------------------------------------- 1 | Slot Content -------------------------------------------------------------------------------- /pony/testdata/TestSlotInBox.golden: -------------------------------------------------------------------------------- 1 | ╭────╮ 2 | │Test│ 3 | ╰────╯ -------------------------------------------------------------------------------- /pony/testdata/TestTemplateConditional/condition_false.golden: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pony/testdata/TestTemplateFunctions/lower_function.golden: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /pony/testdata/TestTemplateFunctions/upper_function.golden: -------------------------------------------------------------------------------- 1 | HELLO -------------------------------------------------------------------------------- /pony/testdata/TestTemplateVariables.golden: -------------------------------------------------------------------------------- 1 | Hello from template! -------------------------------------------------------------------------------- /pony/.gitattributes: -------------------------------------------------------------------------------- 1 | *.golden linguist-generated=true -text 2 | 3 | -------------------------------------------------------------------------------- /pony/testdata/TestBadgeComponent/badge_with_child_text.golden: -------------------------------------------------------------------------------- 1 | [ALPHA] -------------------------------------------------------------------------------- /pony/testdata/TestHelperRoundTrip.golden: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /pony/testdata/TestTemplateConditional/condition_true.golden: -------------------------------------------------------------------------------- 1 | Visible -------------------------------------------------------------------------------- /pony/testdata/TestTemplateFunctions/printf_function.golden: -------------------------------------------------------------------------------- 1 | Count: 42 -------------------------------------------------------------------------------- /pony/testdata/TestTemplateRange.golden: -------------------------------------------------------------------------------- 1 | Item 1 2 | Item 2 3 | Item 3 -------------------------------------------------------------------------------- /etag/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/etag 2 | 3 | go 1.25.5 4 | -------------------------------------------------------------------------------- /json/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/json 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /pony/testdata/TestBadgeComponent/badge_with_text_attribute.golden: -------------------------------------------------------------------------------- 1 | [NEW] -------------------------------------------------------------------------------- /pony/testdata/TestRenderWithStyle.golden: -------------------------------------------------------------------------------- 1 | Styled Text -------------------------------------------------------------------------------- /editor/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/editor 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /errors/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/errors 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /exp/golden/testdata/TestRequireWithLineBreaks.golden: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | baz 4 | -------------------------------------------------------------------------------- /exp/maps/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/maps 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /exp/open/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/open 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /exp/slice/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/slice 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /gitignore/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/gitignore 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /pony/testdata/TestBadgeComponent/badge_with_style.golden: -------------------------------------------------------------------------------- 1 | [BETA] -------------------------------------------------------------------------------- /pony/testdata/TestTemplateWithStyle.golden: -------------------------------------------------------------------------------- 1 | Dynamic styled text -------------------------------------------------------------------------------- /pony/testdata/TestVStackAlignment.golden: -------------------------------------------------------------------------------- 1 | Short 2 | A bit longer text -------------------------------------------------------------------------------- /exp/ordered/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/ordered 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /exp/strings/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/strings 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /pony/testdata/TestRenderVStack.golden: -------------------------------------------------------------------------------- 1 | Line 1 2 | 3 | Line 2 4 | 5 | Line 3 -------------------------------------------------------------------------------- /exp/charmtone/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/charmtone 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /ansi/fixtures/demo.vte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charmbracelet/x/HEAD/ansi/fixtures/demo.vte -------------------------------------------------------------------------------- /ansi/sixel/.gitattributes: -------------------------------------------------------------------------------- 1 | ansi/fixtures/graphics/*.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /exp/higherorder/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/higherorder 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /pony/testdata/TestCustomComponentWithChildren.golden: -------------------------------------------------------------------------------- 1 | ╭───────╮ 2 | │Child 1│ 3 | │Child 2│ 4 | ╰───────╯ -------------------------------------------------------------------------------- /pony/testdata/TestProgressComponent/progress_with_custom_width.golden: -------------------------------------------------------------------------------- 1 | ██████████████████████░░░░░░░░ -------------------------------------------------------------------------------- /pony/testdata/TestProgressComponent/progress_with_style.golden: -------------------------------------------------------------------------------- 1 | ████████████████████ -------------------------------------------------------------------------------- /pony/testdata/TestProgressComponent/progress_with_value.golden: -------------------------------------------------------------------------------- 1 | ████████████████████░░░░░░░░░░░░░░░░░░░░ -------------------------------------------------------------------------------- /pony/testdata/TestScrollViewInMarkup.golden: -------------------------------------------------------------------------------- 1 | Line 1 2 | Line 2 3 | Line 3 4 | Line 4 5 | Line 5 6 | Line 6 7 | Line 7 -------------------------------------------------------------------------------- /term/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/term 2 | 3 | go 1.24.0 4 | 5 | require golang.org/x/sys v0.39.0 6 | -------------------------------------------------------------------------------- /conpty/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/conpty 2 | 3 | go 1.24.0 4 | 5 | require golang.org/x/sys v0.39.0 6 | -------------------------------------------------------------------------------- /pony/testdata/TestTextAlignment/center_aligned.golden: -------------------------------------------------------------------------------- 1 | ┌──────────────────┐ 2 | │ Center │ 3 | └──────────────────┘ -------------------------------------------------------------------------------- /pony/testdata/TestTextAlignment/left_aligned.golden: -------------------------------------------------------------------------------- 1 | ┌──────────────────┐ 2 | │Left │ 3 | └──────────────────┘ -------------------------------------------------------------------------------- /pony/testdata/TestTextAlignment/right_aligned.golden: -------------------------------------------------------------------------------- 1 | ┌──────────────────┐ 2 | │ Right│ 3 | └──────────────────┘ -------------------------------------------------------------------------------- /termios/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/termios 2 | 3 | go 1.24.0 4 | 5 | require golang.org/x/sys v0.39.0 6 | -------------------------------------------------------------------------------- /windows/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/windows 2 | 3 | go 1.24.0 4 | 5 | require golang.org/x/sys v0.39.0 6 | -------------------------------------------------------------------------------- /pony/testdata/TestMultipleSlots.golden: -------------------------------------------------------------------------------- 1 | Header Content 2 | 3 | Body Content 4 | 5 | Footer Content -------------------------------------------------------------------------------- /sshkey/_examples/key.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINPsF1vqqjVMhsYg0wn2w7KXSkjn9OyY1p1WjCpl2k5t carlos@darkstar 2 | -------------------------------------------------------------------------------- /termios/doc.go: -------------------------------------------------------------------------------- 1 | // Package termios provides low-level terminal I/O control for Unix-like 2 | // systems. 3 | package termios 4 | -------------------------------------------------------------------------------- /vt/vt.go: -------------------------------------------------------------------------------- 1 | // Package vt is a virtual terminal emulator that can be used to emulate a 2 | // modern terminal application. 3 | package vt 4 | -------------------------------------------------------------------------------- /examples/cellbuf/winsize_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | func listenForResize(func()) {} 7 | -------------------------------------------------------------------------------- /exp/golden/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/golden 2 | 3 | go 1.24.0 4 | 5 | require github.com/aymanbagabas/go-udiff v0.3.1 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ttf filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | *.jpg filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /pony/testdata/TestSlotsWithTemplateData.golden: -------------------------------------------------------------------------------- 1 | Header from data 2 | 3 | Slot from element 4 | 5 | Footer from data -------------------------------------------------------------------------------- /pony/testdata/TestBoxPadding.golden: -------------------------------------------------------------------------------- 1 | ┌───────────┐ 2 | │ │ 3 | │ │ 4 | │ Content │ 5 | │ │ 6 | │ │ 7 | └───────────┘ -------------------------------------------------------------------------------- /pony/testdata/TestZStackBasic.golden: -------------------------------------------------------------------------------- 1 | ╭──────────────────╮ 2 | │BackOverlay │ 3 | │ │ 4 | │ │ 5 | ╰──────────────────╯ -------------------------------------------------------------------------------- /fixtures/charm-wish.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b6beed501cdf17c8423644486f2010b69303e6228464652d83fe9b99c7aad7e9 3 | size 34322 4 | -------------------------------------------------------------------------------- /pony/testdata/TestRenderBoxWithBorderStyle.golden: -------------------------------------------------------------------------------- 1 | ╭───────╮ 2 | │Content│ 3 | ╰───────╯ -------------------------------------------------------------------------------- /examples/mosaic/pekinas.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:73b4eabeff12c4b09f1306ffa489a5b6d30ad2a1b8dc2bce882020c3be341665 3 | size 279891 4 | -------------------------------------------------------------------------------- /term/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 2 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 3 | -------------------------------------------------------------------------------- /conpty/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 2 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 3 | -------------------------------------------------------------------------------- /examples/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1f376439c75ab33392eb7a2f5ec999809493b378728d723327655c9fcb45cea9 3 | size 171876 4 | -------------------------------------------------------------------------------- /fixtures/JigokudaniMonkeyPark.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e43df98a45ede3b2bc5ec4f686b0b2953da933f9483607ebbdd2fac43fadf0f0 3 | size 698785 4 | -------------------------------------------------------------------------------- /termios/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 2 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 3 | -------------------------------------------------------------------------------- /windows/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 2 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 3 | -------------------------------------------------------------------------------- /ansi/ascii.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | const ( 4 | // SP is the space character (Char: \x20). 5 | SP = 0x20 6 | // DEL is the delete character (Caret: ^?, Char: \x7f). 7 | DEL = 0x7F 8 | ) 9 | -------------------------------------------------------------------------------- /exp/teatest/v2/testdata/TestAppSendToOtherProgram.golden: -------------------------------------------------------------------------------- 1 | [?2004h[>4;1m[?4m[>1u[?u[?25l All pings: 2 | from m1 3 | from m1 4 | from m2 5 | from m2 6 | from m2 7 | from m2 [?25h[?2004l -------------------------------------------------------------------------------- /vcr/doc.go: -------------------------------------------------------------------------------- 1 | // Package vcr provides utilities for recording and replaying HTTP interactions. 2 | // It's a wrapper around the go-vcr library with some opininated defaults. 3 | package vcr 4 | -------------------------------------------------------------------------------- /powernap/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/powernap 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/mitchellh/mapstructure v1.5.0 7 | github.com/sourcegraph/jsonrpc2 v0.2.1 8 | ) 9 | -------------------------------------------------------------------------------- /cellbuf/errors.go: -------------------------------------------------------------------------------- 1 | package cellbuf 2 | 3 | import "errors" 4 | 5 | // ErrOutOfBounds is returned when the given x, y position is out of bounds. 6 | var ErrOutOfBounds = errors.New("out of bounds") 7 | -------------------------------------------------------------------------------- /termios/bit_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package termios 5 | 6 | func speed(b uint32) uint64 { return uint64(b) } 7 | func bit(b uint32) uint64 { return uint64(b) } 8 | -------------------------------------------------------------------------------- /termios/syscalls_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | // +build darwin 3 | 4 | package termios 5 | 6 | import "syscall" 7 | 8 | func init() { 9 | allLineOpts[IUTF8] = syscall.IUTF8 10 | } 11 | -------------------------------------------------------------------------------- /vcr/go.mod: -------------------------------------------------------------------------------- 1 | module charm.land/x/vcr 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/google/go-cmp v0.7.0 7 | go.yaml.in/yaml/v4 v4.0.0-rc.3 8 | gopkg.in/dnaeon/go-vcr.v4 v4.0.6 9 | ) 10 | -------------------------------------------------------------------------------- /wcwidth/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/wcwidth 2 | 3 | go 1.24.0 4 | 5 | require github.com/mattn/go-runewidth v0.0.19 6 | 7 | require github.com/clipperhouse/uax29/v2 v2.2.0 // indirect 8 | -------------------------------------------------------------------------------- /exp/golden/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= 2 | github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= 3 | -------------------------------------------------------------------------------- /termios/bit_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build netbsd || openbsd 2 | // +build netbsd openbsd 3 | 4 | package termios 5 | 6 | func speed(b uint32) int32 { return int32(b) } 7 | func bit(b uint32) uint32 { return b } 8 | -------------------------------------------------------------------------------- /exp/teatest/v2/testdata/TestApp.golden: -------------------------------------------------------------------------------- 1 | [?2004h[>4;1m[?4m[>1u[?u[?25l Hi. This program will exit in 10 seconds. To quit sooner press any key 9 seconds. To quit sooner press any key. 2 | [?25h[?2004l -------------------------------------------------------------------------------- /xpty/conpty_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package xpty 5 | 6 | import "os/exec" 7 | 8 | func (c *ConPty) start(*exec.Cmd) error { 9 | return ErrUnsupported 10 | } 11 | -------------------------------------------------------------------------------- /pony/testdata/TestFlexibleSpacerInHStack.golden: -------------------------------------------------------------------------------- 1 | Left Right 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /windows/doc.go: -------------------------------------------------------------------------------- 1 | // Package windows provides Windows-specific system utilities. 2 | package windows 3 | 4 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 5 | -------------------------------------------------------------------------------- /pony/testdata/TestFlexGrow.golden: -------------------------------------------------------------------------------- 1 | ╭────────╮╭──────────────╮╭──────────────────────────────╮ 2 | │Fixed ││Flex 1 ││Flex 2 │ 3 | ╰────────╯╰──────────────╯╰──────────────────────────────╯ -------------------------------------------------------------------------------- /exp/teatest/testdata/TestAppSendToOtherProgram.golden: -------------------------------------------------------------------------------- 1 | [?25l[?2004h All pings: 2 | from m1 3 | from m1 4 | from m2 5 | from m2 6 | from m2 7 | from m2 [?2004l[?25h[?1002l[?1003l[?1006l -------------------------------------------------------------------------------- /input/cursor.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import "image" 4 | 5 | // CursorPositionEvent represents a cursor position event. Where X is the 6 | // zero-based column and Y is the zero-based row. 7 | type CursorPositionEvent image.Point 8 | -------------------------------------------------------------------------------- /termios/bit_other.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !netbsd && !openbsd && !windows 2 | // +build !darwin,!netbsd,!openbsd,!windows 3 | 4 | package termios 5 | 6 | func speed(b uint32) uint32 { return b } 7 | func bit(b uint32) uint32 { return b } 8 | -------------------------------------------------------------------------------- /exp/teatest/testdata/TestApp.golden: -------------------------------------------------------------------------------- 1 | [?25l[?2004h Hi. This program will exit in 10 seconds. To quit sooner press any key 2 | Hi. This program will exit in 9 seconds. To quit sooner press any key. 3 |  [?2004l[?25h[?1002l[?1003l[?1006l -------------------------------------------------------------------------------- /pony/testdata/TestBoxMargin.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | ╭───────────╮ 4 | │With margin│ 5 | ╰───────────╯ 6 | 7 | 8 | ╭─────────╮ 9 | │No margin│ 10 | ╰─────────╯ -------------------------------------------------------------------------------- /term/terminal.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // File represents a file that has a file descriptor and can be read from, 8 | // written to, and closed. 9 | type File interface { 10 | io.ReadWriteCloser 11 | Fd() uintptr 12 | } 13 | -------------------------------------------------------------------------------- /term/term_unix_other.go: -------------------------------------------------------------------------------- 1 | //go:build aix || linux || solaris || zos 2 | // +build aix linux solaris zos 3 | 4 | package term 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | const ( 9 | ioctlReadTermios = unix.TCGETS 10 | ioctlWriteTermios = unix.TCSETS 11 | ) 12 | -------------------------------------------------------------------------------- /pony/testdata/TestBoxWithWidth.golden: -------------------------------------------------------------------------------- 1 | ┌─────────────────────┐┌─────────────────────────────────────┐ 2 | │30% ││70% │ 3 | └─────────────────────┘└─────────────────────────────────────┘ -------------------------------------------------------------------------------- /termios/syscalls_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && netbsd && freebsd && netbsd 2 | // +build darwin,netbsd,freebsd,netbsd 3 | 4 | package term 5 | 6 | import "syscall" 7 | 8 | func init() { 9 | allCcOpts[STATUS] = syscall.VSTATUS 10 | allCcOpts[DSUSP] = syscall.VDSUSP 11 | } 12 | -------------------------------------------------------------------------------- /ansi/paste.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // BracketedPasteStart is the control sequence to enable bracketed paste mode. 4 | const BracketedPasteStart = "\x1b[200~" 5 | 6 | // BracketedPasteEnd is the control sequence to disable bracketed paste mode. 7 | const BracketedPasteEnd = "\x1b[201~" 8 | -------------------------------------------------------------------------------- /ansi/reset.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // ResetInitialState (RIS) resets the terminal to its initial state. 4 | // 5 | // ESC c 6 | // 7 | // See: https://vt100.net/docs/vt510-rm/RIS.html 8 | const ( 9 | ResetInitialState = "\x1bc" 10 | RIS = ResetInitialState 11 | ) 12 | -------------------------------------------------------------------------------- /exp/open/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Package main demonstrates open package usage. 2 | package main 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | 8 | "github.com/charmbracelet/x/exp/open" 9 | ) 10 | 11 | func main() { 12 | fmt.Println(open.Open(context.Background(), "https://charm.sh")) 13 | } 14 | -------------------------------------------------------------------------------- /pony/testdata/TestFlexibleSpacerBasic.golden: -------------------------------------------------------------------------------- 1 | Top 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Bottom -------------------------------------------------------------------------------- /input/focus.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | // FocusEvent represents a terminal focus event. 4 | // This occurs when the terminal gains focus. 5 | type FocusEvent struct{} 6 | 7 | // BlurEvent represents a terminal blur event. 8 | // This occurs when the terminal loses focus. 9 | type BlurEvent struct{} 10 | -------------------------------------------------------------------------------- /xpty/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/xpty 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/charmbracelet/x/conpty v0.2.0 7 | github.com/charmbracelet/x/term v0.2.2 8 | github.com/charmbracelet/x/termios v0.1.1 9 | github.com/creack/pty v1.1.24 10 | golang.org/x/sys v0.39.0 11 | ) 12 | -------------------------------------------------------------------------------- /ansi/doc.go: -------------------------------------------------------------------------------- 1 | // Package ansi defines common ANSI escape sequences based on the ECMA-48 2 | // specs. 3 | // 4 | // All sequences use 7-bit C1 control codes, which are supported by most 5 | // terminal emulators. OSC sequences are terminated by a BEL for wider 6 | // compatibility with terminals. 7 | package ansi 8 | -------------------------------------------------------------------------------- /term/term_unix_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 2 | // +build darwin dragonfly freebsd netbsd openbsd 3 | 4 | package term 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | const ( 9 | ioctlReadTermios = unix.TIOCGETA 10 | ioctlWriteTermios = unix.TIOCSETA 11 | ) 12 | -------------------------------------------------------------------------------- /termios/termios_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package termios 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | const ( 9 | ioctlGets = unix.TCGETS 10 | ioctlSets = unix.TCSETS 11 | ioctlGetWinSize = unix.TIOCGWINSZ 12 | ioctlSetWinSize = unix.TIOCSWINSZ 13 | ) 14 | -------------------------------------------------------------------------------- /pony/testdata/TestBoxMarginSides.golden: -------------------------------------------------------------------------------- 1 | ╭──────────────────╮ 2 | │Horizontal margins│ 3 | ╰──────────────────╯ 4 | 5 | 6 | ╭────────────────╮ 7 | │Vertical margins│ 8 | ╰────────────────╯ 9 | -------------------------------------------------------------------------------- /input/cancelreader_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package input 5 | 6 | import ( 7 | "io" 8 | 9 | "github.com/muesli/cancelreader" 10 | ) 11 | 12 | func newCancelreader(r io.Reader, _ int) (cancelreader.CancelReader, error) { 13 | return cancelreader.NewReader(r) //nolint:wrapcheck 14 | } 15 | -------------------------------------------------------------------------------- /ansi/focus.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // Focus is an escape sequence to notify the terminal that it has focus. 4 | // This is used with [FocusEventMode]. 5 | const Focus = "\x1b[I" 6 | 7 | // Blur is an escape sequence to notify the terminal that it has lost focus. 8 | // This is used with [FocusEventMode]. 9 | const Blur = "\x1b[O" 10 | -------------------------------------------------------------------------------- /cellbuf/link.go: -------------------------------------------------------------------------------- 1 | package cellbuf 2 | 3 | import ( 4 | "github.com/charmbracelet/colorprofile" 5 | ) 6 | 7 | // ConvertLink converts a hyperlink to respect the given color profile. 8 | func ConvertLink(h Link, p colorprofile.Profile) Link { 9 | if p == colorprofile.NoTTY { 10 | return Link{} 11 | } 12 | 13 | return h 14 | } 15 | -------------------------------------------------------------------------------- /sshkey/_examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/charmbracelet/x/sshkey" 7 | ) 8 | 9 | func main() { 10 | // password is "asd". 11 | signer, err := sshkey.Open("./key") 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | if signer != nil { 17 | fmt.Println("Key opened!") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/cellbuf/winsize_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | func listenForResize(fn func()) { 13 | sig := make(chan os.Signal, 1) 14 | signal.Notify(sig, syscall.SIGWINCH) 15 | 16 | for range sig { 17 | fn() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /termios/syscalls_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package termios 5 | 6 | import "syscall" 7 | 8 | func init() { 9 | allCcOpts[SWTCH] = syscall.VSWTC 10 | allInputOpts[IUCLC] = syscall.IUCLC 11 | allLineOpts[IUTF8] = syscall.IUTF8 12 | allLineOpts[XCASE] = syscall.XCASE 13 | allOutputOpts[OLCUC] = syscall.OLCUC 14 | } 15 | -------------------------------------------------------------------------------- /ansi/ansi.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import "io" 4 | 5 | // Execute is a function that "execute" the given escape sequence by writing it 6 | // to the provided output writter. 7 | // 8 | // This is a syntactic sugar over [io.WriteString]. 9 | func Execute(w io.Writer, s string) (int, error) { 10 | return io.WriteString(w, s) //nolint:wrapcheck 11 | } 12 | -------------------------------------------------------------------------------- /input/doc.go: -------------------------------------------------------------------------------- 1 | // Package input provides a set of utilities for handling input events in a 2 | // terminal environment. It includes support for reading input events, parsing 3 | // escape sequences, and handling clipboard events. 4 | // The package is designed to work with various terminal types and supports 5 | // customization through flags and options. 6 | package input 7 | -------------------------------------------------------------------------------- /pony/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | 9 | # Test binary 10 | *.test 11 | 12 | # Output of the go coverage tool 13 | *.out 14 | 15 | # Go workspace file 16 | go.work 17 | go.work.sum 18 | 19 | # IDE 20 | .vscode/ 21 | .idea/ 22 | *.swp 23 | *.swo 24 | *~ 25 | 26 | # OS 27 | .DS_Store 28 | Thumbs.db 29 | -------------------------------------------------------------------------------- /pony/testdata/TestFlexVStack.golden: -------------------------------------------------------------------------------- 1 | Header 2 | ╭───────────────╮ 3 | │Growing content│ 4 | │ │ 5 | │ │ 6 | │ │ 7 | │ │ 8 | │ │ 9 | │ │ 10 | │ │ 11 | │ │ 12 | │ │ 13 | │ │ 14 | ╰───────────────╯ 15 | Footer -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.go] 13 | indent_style = tab 14 | indent_size = 4 15 | 16 | [*.golden] 17 | insert_final_newline = false 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /vt/csi_sgr.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import ( 4 | uv "github.com/charmbracelet/ultraviolet" 5 | "github.com/charmbracelet/x/ansi" 6 | ) 7 | 8 | // handleSgr handles SGR escape sequences. 9 | // handleSgr handles Select Graphic Rendition (SGR) escape sequences. 10 | func (e *Emulator) handleSgr(params ansi.Params) { 11 | uv.ReadStyle(params, &e.scr.cur.Pen) 12 | } 13 | -------------------------------------------------------------------------------- /wcwidth/go.sum: -------------------------------------------------------------------------------- 1 | github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= 2 | github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= 3 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 4 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 5 | -------------------------------------------------------------------------------- /termios/termios_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly 2 | // +build darwin netbsd freebsd openbsd dragonfly 3 | 4 | package termios 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | const ( 9 | ioctlGets = unix.TIOCGETA 10 | ioctlSets = unix.TIOCSETA 11 | ioctlGetWinSize = unix.TIOCGWINSZ 12 | ioctlSetWinSize = unix.TIOCSWINSZ 13 | ) 14 | -------------------------------------------------------------------------------- /pony/examples/custom/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/dynamic/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/hello/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/helpers/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/layout/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/styled/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /input/mode.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | // ModeReportEvent is a message that represents a mode report event (DECRPM). 6 | // 7 | // See: https://vt100.net/docs/vt510-rm/DECRPM.html 8 | type ModeReportEvent struct { 9 | // Mode is the mode number. 10 | Mode ansi.Mode 11 | 12 | // Value is the mode value. 13 | Value ansi.ModeSetting 14 | } 15 | -------------------------------------------------------------------------------- /pony/examples/alignment/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/bubbletea/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/components/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/scrolling/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /pony/examples/stateful/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /ansi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/ansi 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/bits-and-blooms/bitset v1.24.4 7 | github.com/clipperhouse/displaywidth v0.6.2 8 | github.com/clipperhouse/uax29/v2 v2.3.0 9 | github.com/lucasb-eyer/go-colorful v1.3.0 10 | github.com/mattn/go-runewidth v0.0.19 11 | ) 12 | 13 | require github.com/clipperhouse/stringish v0.1.1 // indirect 14 | -------------------------------------------------------------------------------- /pony/examples/simple-bubbletea/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | example 8 | custom 9 | 10 | # Test binary 11 | *.test 12 | 13 | # Output of the go coverage tool 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | go.work.sum 19 | 20 | # IDE 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | *~ 26 | 27 | # OS 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /xpty/pty_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris 2 | // +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris 3 | 4 | package xpty 5 | 6 | func (p *UnixPty) setWinsize(int, int, int, int) error { 7 | return ErrUnsupported 8 | } 9 | 10 | func (*UnixPty) size() (int, int, error) { 11 | return 0, 0, ErrUnsupported 12 | } 13 | -------------------------------------------------------------------------------- /exp/open/open_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package open 5 | 6 | import ( 7 | "context" 8 | "os/exec" 9 | ) 10 | 11 | func buildCmd(ctx context.Context, app, path string) *exec.Cmd { 12 | if app != "" { 13 | return exec.Command("cmd", "/C", "start", "", app, path) 14 | } 15 | return exec.CommandContext(ctx, "rundll32", "url.dll,FileProtocolHandler", path) 16 | } 17 | -------------------------------------------------------------------------------- /pony/testdata/TestMultipleFlexibleSpacers.golden: -------------------------------------------------------------------------------- 1 | First 2 | 3 | 4 | 5 | 6 | 7 | 8 | Middle 9 | 10 | 11 | 12 | 13 | 14 | 15 | Last -------------------------------------------------------------------------------- /vcr/marshaler.go: -------------------------------------------------------------------------------- 1 | package vcr 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "go.yaml.in/yaml/v4" 8 | ) 9 | 10 | func customMarshaler(in any) ([]byte, error) { 11 | var buff bytes.Buffer 12 | enc := yaml.NewEncoder(&buff) 13 | enc.SetIndent(2) 14 | enc.CompactSeqIndent() 15 | if err := enc.Encode(in); err != nil { 16 | return nil, fmt.Errorf("vcr: unable to encode to yaml: %w", err) 17 | } 18 | return buff.Bytes(), nil 19 | } 20 | -------------------------------------------------------------------------------- /cellbuf/utils.go: -------------------------------------------------------------------------------- 1 | package cellbuf 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Height returns the height of a string. 8 | func Height(s string) int { 9 | return strings.Count(s, "\n") + 1 10 | } 11 | 12 | func clamp(v, low, high int) int { 13 | if high < low { 14 | low, high = high, low 15 | } 16 | return min(high, max(low, v)) 17 | } 18 | 19 | func abs(a int) int { 20 | if a < 0 { 21 | return -a 22 | } 23 | return a 24 | } 25 | -------------------------------------------------------------------------------- /pony/testdata/TestScrollViewBasic.golden: -------------------------------------------------------------------------------- 1 | A █ 2 | B █ 3 | C █ 4 | D ░ 5 | E ░ -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /ansi @aymanbagabas 2 | /conpty @aymanbagabas 3 | /editor @caarlos0 4 | /errors @caarlos0 5 | /exp/golden @caarlos0 6 | /exp/higherorder @meowgorithm 7 | /exp/ordered @meowgorithm 8 | /exp/slice @meowgorithm 9 | /exp/strings @meowgorithm 10 | /exp/teatest @caarlos0 11 | /input @aymanbagabas 12 | /scripts @caarlos0 13 | /term @aymanbagabas 14 | /termios @caarlos0 @aymanbagabas 15 | /windows @aymanbagabas 16 | /xpty @aymanbagabas 17 | -------------------------------------------------------------------------------- /pony/testdata/TestSlotWithComplexElement.golden: -------------------------------------------------------------------------------- 1 | ╭──────────────────────────────────────────────────────────────────────────────╮ 2 | │Title │ 3 | │──────────────────────────────────────────────────────────────────────────────│ 4 | │Content │ 5 | ╰──────────────────────────────────────────────────────────────────────────────╯ -------------------------------------------------------------------------------- /conpty/doc.go: -------------------------------------------------------------------------------- 1 | // Package conpty implements Windows Console Pseudo-terminal support. 2 | // 3 | // https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session 4 | package conpty 5 | 6 | import "errors" 7 | 8 | // ErrUnsupported is returned when the current platform is not supported. 9 | var ErrUnsupported = errors.New("conpty: unsupported platform") 10 | 11 | // Default size. 12 | const ( 13 | DefaultWidth = 80 14 | DefaultHeight = 25 15 | ) 16 | -------------------------------------------------------------------------------- /input/paste.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | // PasteEvent is an message that is emitted when a terminal receives pasted text 4 | // using bracketed-paste. 5 | type PasteEvent string 6 | 7 | // PasteStartEvent is an message that is emitted when the terminal starts the 8 | // bracketed-paste text. 9 | type PasteStartEvent struct{} 10 | 11 | // PasteEndEvent is an message that is emitted when the terminal ends the 12 | // bracketed-paste text. 13 | type PasteEndEvent struct{} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !go.work 2 | *.png 3 | **/.crush/** 4 | .DS_Store 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | /examples/charmtone/charmtone 13 | 14 | # Test binary, built with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Allow graphics used for benchmarks 21 | !ansi/fixtures/graphics/*.png 22 | !mosaic/fixtures/*.png 23 | 24 | go.work 25 | go.work.sum 26 | -------------------------------------------------------------------------------- /examples/toner/main.go: -------------------------------------------------------------------------------- 1 | // Package main demonstrates usage. 2 | package main 3 | 4 | import ( 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "github.com/charmbracelet/x/exp/toner" 10 | ) 11 | 12 | func main() { 13 | bts, err := io.ReadAll(os.Stdin) 14 | if err != nil { 15 | log.Fatalf("failed to read from stdin: %v", err) 16 | } 17 | 18 | w := toner.Writer{Writer: os.Stdout} 19 | if _, err := w.Write(bts); err != nil { 20 | log.Fatalf("failed to write to stdout: %v", err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mosaic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/mosaic 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/charmbracelet/x/ansi v0.11.3 7 | golang.org/x/image v0.34.0 8 | ) 9 | 10 | require ( 11 | github.com/clipperhouse/displaywidth v0.6.1 // indirect 12 | github.com/clipperhouse/stringish v0.1.1 // indirect 13 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 14 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 15 | github.com/mattn/go-runewidth v0.0.19 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /pony/testdata/TestPositionedBasic.golden: -------------------------------------------------------------------------------- 1 | ╭──────────────────────────────────────╮ 2 | │Background │ 3 | │ At (5,2) │ 4 | │ │ 5 | │ │ 6 | │ At (20,5) │ 7 | │ │ 8 | │ │ 9 | │ │ 10 | ╰──────────────────────────────────────╯ -------------------------------------------------------------------------------- /cellbuf/geom.go: -------------------------------------------------------------------------------- 1 | package cellbuf 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | // Position represents an x, y position. 8 | type Position = image.Point 9 | 10 | // Pos is a shorthand for Position{X: x, Y: y}. 11 | func Pos(x, y int) Position { 12 | return image.Pt(x, y) 13 | } 14 | 15 | // Rectangle represents a rectangle. 16 | type Rectangle = image.Rectangle 17 | 18 | // Rect is a shorthand for Rectangle. 19 | func Rect(x, y, w, h int) Rectangle { 20 | return image.Rect(x, y, x+w, y+h) 21 | } 22 | -------------------------------------------------------------------------------- /sshkey/_examples/key: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCPsyZVkz 3 | HWCy4ydV8FbCRAAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAINPsF1vqqjVMhsYg 4 | 0wn2w7KXSkjn9OyY1p1WjCpl2k5tAAAAoB7O0zxl192uyIc5IRyCP8M+p2eLaQoYKMH52Y 5 | HcrvatVb06mJf8RkSVkZxht4iaO6qwUyF437UttUMIs5pUvLppHU6WPc2n8bdO8H5eQSGn 6 | tBcHH22x+cg/k52X0srqcjvBU5bzviz0b7Az5rJWhwb3Nl5n+HSVggD5r7X5Sqbc1DZl/A 7 | JOJHD9QIpPw+v8kfwevT9SZWRPHtEOzcFxbfY= 8 | -----END OPENSSH PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /vt/cursor.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import uv "github.com/charmbracelet/ultraviolet" 4 | 5 | // CursorStyle represents a cursor style. 6 | type CursorStyle int 7 | 8 | // Cursor styles. 9 | const ( 10 | CursorBlock CursorStyle = iota 11 | CursorUnderline 12 | CursorBar 13 | ) 14 | 15 | // Cursor represents a cursor in a terminal. 16 | type Cursor struct { 17 | Pen uv.Style 18 | Link uv.Link 19 | 20 | uv.Position 21 | 22 | Style CursorStyle 23 | Steady bool // Not blinking 24 | Hidden bool 25 | } 26 | -------------------------------------------------------------------------------- /ansi/iterm2.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import "fmt" 4 | 5 | // ITerm2 returns a sequence that uses the iTerm2 proprietary protocol. Use the 6 | // iterm2 package for a more convenient API. 7 | // 8 | // OSC 1337 ; key = value ST 9 | // 10 | // Example: 11 | // 12 | // ITerm2(iterm2.File{...}) 13 | // 14 | // See https://iterm2.com/documentation-escape-codes.html 15 | // See https://iterm2.com/documentation-images.html 16 | func ITerm2(data any) string { 17 | return "\x1b]1337;" + fmt.Sprint(data) + "\x07" 18 | } 19 | -------------------------------------------------------------------------------- /examples/pen/main.go: -------------------------------------------------------------------------------- 1 | // Package main demonstrates usage. 2 | package main 3 | 4 | import ( 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "github.com/charmbracelet/x/ansi" 10 | "github.com/charmbracelet/x/cellbuf" 11 | ) 12 | 13 | func main() { 14 | pw := cellbuf.NewPenWriter(os.Stdout) 15 | defer pw.Close() //nolint:errcheck 16 | 17 | data, err := io.ReadAll(os.Stdin) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | io.WriteString(pw, ansi.Wrap(string(data), 10, "")) //nolint:errcheck,gosec 23 | } 24 | -------------------------------------------------------------------------------- /input/da1.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | // PrimaryDeviceAttributesEvent is an event that represents the terminal 6 | // primary device attributes. 7 | type PrimaryDeviceAttributesEvent []int 8 | 9 | func parsePrimaryDevAttrs(params ansi.Params) Event { 10 | // Primary Device Attributes 11 | da1 := make(PrimaryDeviceAttributesEvent, len(params)) 12 | for i, p := range params { 13 | if !p.HasMore() { 14 | da1[i] = p.Param(0) 15 | } 16 | } 17 | return da1 18 | } 19 | -------------------------------------------------------------------------------- /vcr/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 2 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 3 | go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= 4 | go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= 5 | gopkg.in/dnaeon/go-vcr.v4 v4.0.6 h1:PiJkrakkmzc5s7EfBnZOnyiLwi7o7A9fwPzN0X2uwe0= 6 | gopkg.in/dnaeon/go-vcr.v4 v4.0.6/go.mod h1:sbq5oMEcM4PXngbcNbHhzfCP9OdZodLhrbRYoyg09HY= 7 | -------------------------------------------------------------------------------- /vt/csi_screen.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import ( 4 | uv "github.com/charmbracelet/ultraviolet" 5 | ) 6 | 7 | // eraseCharacter erases n characters starting from the cursor position. It 8 | // does not move the cursor. This is equivalent to [ansi.ECH]. 9 | func (e *Emulator) eraseCharacter(n int) { 10 | if n <= 0 { 11 | n = 1 12 | } 13 | x, y := e.scr.CursorPosition() 14 | rect := uv.Rect(x, y, n, 1) 15 | e.scr.FillArea(e.scr.blankCell(), rect) 16 | e.atPhantom = false 17 | // ECH does not move the cursor. 18 | } 19 | -------------------------------------------------------------------------------- /input/focus_test.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFocus(t *testing.T) { 8 | var p Parser 9 | _, e := p.parseSequence([]byte("\x1b[I")) 10 | switch e.(type) { 11 | case FocusEvent: 12 | // ok 13 | default: 14 | t.Error("invalid sequence") 15 | } 16 | } 17 | 18 | func TestBlur(t *testing.T) { 19 | var p Parser 20 | _, e := p.parseSequence([]byte("\x1b[O")) 21 | switch e.(type) { 22 | case BlurEvent: 23 | // ok 24 | default: 25 | t.Error("invalid sequence") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /exp/charmtone/charmtone_test.go: -------------------------------------------------------------------------------- 1 | package charmtone 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestValidateHexes(t *testing.T) { 10 | for _, key := range Keys() { 11 | hex := strings.TrimPrefix(key.Hex(), "#") 12 | if len(hex) != 6 && len(hex) != 3 { 13 | t.Errorf("Key %s: invalid hex length %d for %s", key, len(hex), key.Hex()) 14 | } 15 | if _, err := strconv.ParseUint(hex, 16, 32); err != nil { 16 | t.Errorf("Key %s: invalid hex value %s", key, key.Hex()) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /termios/termios_other.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || netbsd || freebsd || openbsd || linux || dragonfly 2 | // +build darwin netbsd freebsd openbsd linux dragonfly 3 | 4 | package termios 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | func setSpeed(term *unix.Termios, ispeed, ospeed uint32) { 9 | term.Ispeed = speed(ispeed) 10 | term.Ospeed = speed(ospeed) 11 | } 12 | 13 | func getSpeed(term *unix.Termios) (uint32, uint32) { //nolint:unused 14 | return uint32(term.Ispeed), uint32(term.Ospeed) //nolint:gosec,unconvert,nolintlint 15 | } 16 | -------------------------------------------------------------------------------- /conpty/conpty.go: -------------------------------------------------------------------------------- 1 | package conpty 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | type pty interface { 8 | Close() error 9 | Fd() uintptr 10 | InPipeReadFd() uintptr 11 | InPipeWriteFd() uintptr 12 | OutPipeReadFd() uintptr 13 | OutPipeWriteFd() uintptr 14 | Read(p []byte) (n int, err error) 15 | Resize(w int, h int) error 16 | Size() (w int, h int, err error) 17 | Spawn(name string, args []string, attr *syscall.ProcAttr) (pid int, handle uintptr, err error) 18 | Write(p []byte) (n int, err error) 19 | } 20 | 21 | var _ pty = &ConPty{} 22 | -------------------------------------------------------------------------------- /exp/toner/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/toner 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/charmbracelet/x/ansi v0.11.3 7 | github.com/charmbracelet/x/exp/charmtone v0.0.0-20250602192518-9e722df69bbb 8 | ) 9 | 10 | require ( 11 | github.com/clipperhouse/displaywidth v0.6.1 // indirect 12 | github.com/clipperhouse/stringish v0.1.1 // indirect 13 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 14 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 15 | github.com/mattn/go-runewidth v0.0.19 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /powernap/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 2 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 3 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 4 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 5 | github.com/sourcegraph/jsonrpc2 v0.2.1 h1:2GtljixMQYUYCmIg7W9aF2dFmniq/mOr2T9tFRh6zSQ= 6 | github.com/sourcegraph/jsonrpc2 v0.2.1/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= 7 | -------------------------------------------------------------------------------- /exp/maps/maps.go: -------------------------------------------------------------------------------- 1 | // Package maps provides utility functions for working with maps. 2 | package maps 3 | 4 | import ( 5 | "cmp" 6 | "slices" 7 | ) 8 | 9 | // SortedKeys returns the keys of the map m. 10 | // The keys will be sorted. 11 | func SortedKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K { 12 | r := Keys(m) 13 | slices.Sort(r) 14 | return r 15 | } 16 | 17 | // Keys returns the keys of the map m. 18 | func Keys[M ~map[K]V, K cmp.Ordered, V any](m M) []K { 19 | r := make([]K, 0, len(m)) 20 | for k := range m { 21 | r = append(r, k) 22 | } 23 | return r 24 | } 25 | -------------------------------------------------------------------------------- /input/driver_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package input 5 | 6 | // ReadEvents reads input events from the terminal. 7 | // 8 | // It reads the events available in the input buffer and returns them. 9 | func (d *Reader) ReadEvents() ([]Event, error) { 10 | return d.readEvents() 11 | } 12 | 13 | // parseWin32InputKeyEvent parses a Win32 input key events. This function is 14 | // only available on Windows. 15 | func (p *Parser) parseWin32InputKeyEvent(*win32InputState, uint16, uint16, rune, bool, uint32, uint16) Event { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /pony/testdata/TestCombinedFeatures.golden: -------------------------------------------------------------------------------- 1 | ╭───────────────────────────────────╮ ┌────────────────┐ 2 | │ │ │ │ 3 | │ Centered │ │ │ 4 | │ │ │ Right │ 5 | ╰───────────────────────────────────╯ │ │ 6 | │ │ 7 | └────────────────┘ -------------------------------------------------------------------------------- /ansi/urxvt.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // URxvtExt returns an escape sequence for calling a URxvt perl extension with 9 | // the given name and parameters. 10 | // 11 | // OSC 777 ; extension_name ; param1 ; param2 ; ... ST 12 | // OSC 777 ; extension_name ; param1 ; param2 ; ... BEL 13 | // 14 | // See: https://man.archlinux.org/man/extra/rxvt-unicode/urxvt.7.en#XTerm_Operating_System_Commands 15 | func URxvtExt(extension string, params ...string) string { 16 | return fmt.Sprintf("\x1b]777;%s;%s\x07", extension, strings.Join(params, ";")) 17 | } 18 | -------------------------------------------------------------------------------- /pony/testdata/TestPositionedCorners.golden: -------------------------------------------------------------------------------- 1 | ╭──────────────────────────────────────╮ 2 | │TLin Content TR│ 3 | │ │ 4 | │ │ 5 | │ │ 6 | │ │ 7 | │ │ 8 | │ │ 9 | │ │ 10 | │ │ 11 | │BL BR│ 12 | ╰──────────────────────────────────────╯ -------------------------------------------------------------------------------- /input/driver_test.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkDriver(b *testing.B) { 10 | input := "\x1b\x1b[Ztest\x00\x1b]10;1234/1234/1234\x07\x1b[27;2;27~" 11 | rdr := strings.NewReader(input) 12 | drv, err := NewReader(rdr, "dumb", 0) 13 | if err != nil { 14 | b.Fatalf("could not create driver: %v", err) 15 | } 16 | 17 | b.ReportAllocs() 18 | b.ResetTimer() 19 | for i := 0; i < b.N; i++ { 20 | rdr.Reset(input) 21 | if _, err := drv.ReadEvents(); err != nil && err != io.EOF { 22 | b.Errorf("error reading input: %v", err) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ansi/cwd_test.go: -------------------------------------------------------------------------------- 1 | package ansi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | ) 8 | 9 | func TestNotifyWorkingDirectory_LocalFile(t *testing.T) { 10 | h := ansi.NotifyWorkingDirectory("localhost", "path", "to", "file") 11 | if h != "\x1b]7;file://localhost/path/to/file\x07" { 12 | t.Errorf("Unexpected url: %s", h) 13 | } 14 | } 15 | 16 | func TestNotifyWorkingDirectory_RemoteFile(t *testing.T) { 17 | h := ansi.NotifyWorkingDirectory("example.com", "path", "to", "file") 18 | if h != "\x1b]7;file://example.com/path/to/file\x07" { 19 | t.Errorf("Unexpected url: %s", h) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wcwidth/wcwidth.go: -------------------------------------------------------------------------------- 1 | // Package wcwidth provides utilities for calculating character width. 2 | package wcwidth 3 | 4 | import ( 5 | "github.com/mattn/go-runewidth" 6 | ) 7 | 8 | // RuneWidth returns fixed-width width of rune. 9 | // 10 | // Deprecated: this is now a wrapper around go-runewidth. Use go-runewidth 11 | // directly. 12 | func RuneWidth(r rune) int { 13 | return runewidth.RuneWidth(r) 14 | } 15 | 16 | // StringWidth returns fixed-width width of string. 17 | // 18 | // Deprecated: this is now a wrapper around go-runewidth. Use go-runewidth 19 | // directly. 20 | func StringWidth(s string) (n int) { 21 | return runewidth.StringWidth(s) 22 | } 23 | -------------------------------------------------------------------------------- /ansi/parser_sync.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/charmbracelet/x/ansi/parser" 7 | ) 8 | 9 | var parserPool = sync.Pool{ 10 | New: func() any { 11 | p := NewParser() 12 | p.SetParamsSize(parser.MaxParamsSize) 13 | p.SetDataSize(1024 * 1024 * 4) // 4MB of data buffer 14 | return p 15 | }, 16 | } 17 | 18 | // GetParser returns a parser from a sync pool. 19 | func GetParser() *Parser { 20 | return parserPool.Get().(*Parser) 21 | } 22 | 23 | // PutParser returns a parser to a sync pool. The parser is reset 24 | // automatically. 25 | func PutParser(p *Parser) { 26 | p.Reset() 27 | p.dataLen = 0 28 | parserPool.Put(p) 29 | } 30 | -------------------------------------------------------------------------------- /exp/open/open_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package open 5 | 6 | import ( 7 | "context" 8 | "os/exec" 9 | ) 10 | 11 | func buildCmd(ctx context.Context, app, path string) *exec.Cmd { 12 | if _, err := exec.LookPath("open"); err == nil { 13 | var arg []string 14 | if app != "" { 15 | arg = append(arg, "-a", app) 16 | } 17 | arg = append(arg, path) 18 | return exec.CommandContext(ctx, "open", arg...) 19 | } 20 | if app != "" { 21 | return exec.CommandContext(ctx, app, path) 22 | } 23 | if _, err := exec.LookPath("xdg-open"); err == nil { 24 | return exec.CommandContext(ctx, "xdg-open", path) 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /cellbuf/style.go: -------------------------------------------------------------------------------- 1 | package cellbuf 2 | 3 | import ( 4 | "github.com/charmbracelet/colorprofile" 5 | ) 6 | 7 | // ConvertStyle converts a style to respect the given color profile. 8 | func ConvertStyle(s Style, p colorprofile.Profile) Style { 9 | switch p { //nolint:exhaustive 10 | case colorprofile.TrueColor: 11 | return s 12 | case colorprofile.Ascii: 13 | s.Fg = nil 14 | s.Bg = nil 15 | s.Ul = nil 16 | case colorprofile.NoTTY: 17 | return Style{} 18 | } 19 | 20 | if s.Fg != nil { 21 | s.Fg = p.Convert(s.Fg) 22 | } 23 | if s.Bg != nil { 24 | s.Bg = p.Convert(s.Bg) 25 | } 26 | if s.Ul != nil { 27 | s.Ul = p.Convert(s.Ul) 28 | } 29 | 30 | return s 31 | } 32 | -------------------------------------------------------------------------------- /exp/golden/golden_test.go: -------------------------------------------------------------------------------- 1 | package golden 2 | 3 | import "testing" 4 | 5 | func TestRequireEqualUpdate(t *testing.T) { 6 | *update = true 7 | RequireEqual(t, []byte("test")) 8 | } 9 | 10 | func TestRequireEqualNoUpdate(t *testing.T) { 11 | *update = false 12 | RequireEqual(t, []byte("test")) 13 | } 14 | 15 | func TestRequireWithLineBreaks(t *testing.T) { 16 | *update = false 17 | RequireEqual(t, []byte("foo\nbar\nbaz\n")) 18 | } 19 | 20 | func TestTypes(t *testing.T) { 21 | *update = false 22 | 23 | t.Run("SliceOfBytes", func(t *testing.T) { 24 | RequireEqual(t, []byte("test")) 25 | }) 26 | t.Run("String", func(t *testing.T) { 27 | RequireEqual(t, "test") 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /exp/ordered/ordered.go: -------------------------------------------------------------------------------- 1 | // Package ordered provides utility functions for ordered types. 2 | package ordered 3 | 4 | import "cmp" 5 | 6 | // Clamp returns a value clamped between the given low and high values. 7 | func Clamp[T cmp.Ordered](n, low, high T) T { 8 | if low > high { 9 | low, high = high, low 10 | } 11 | return min(high, max(low, n)) 12 | } 13 | 14 | // First returns the first non-default value of a fixed number of 15 | // arguments of [cmp.Ordered] types. 16 | func First[T cmp.Ordered](x T, y ...T) T { 17 | var empty T 18 | if x != empty { 19 | return x 20 | } 21 | for _, s := range y { 22 | if s != empty { 23 | return s 24 | } 25 | } 26 | return empty 27 | } 28 | -------------------------------------------------------------------------------- /ansi/inband.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import "fmt" 4 | 5 | // InBandResize encodes an in-band terminal resize event sequence. 6 | // 7 | // CSI 48 ; height_cells ; widht_cells ; height_pixels ; width_pixels t 8 | // 9 | // See https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83 10 | func InBandResize(heightCells, widthCells, heightPixels, widthPixels int) string { 11 | if heightCells < 0 { 12 | heightCells = 0 13 | } 14 | if widthCells < 0 { 15 | widthCells = 0 16 | } 17 | if heightPixels < 0 { 18 | heightPixels = 0 19 | } 20 | if widthPixels < 0 { 21 | widthPixels = 0 22 | } 23 | return fmt.Sprintf("\x1b[48;%d;%d;%d;%dt", heightCells, widthCells, heightPixels, widthPixels) 24 | } 25 | -------------------------------------------------------------------------------- /exp/maps/maps_test.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | ) 7 | 8 | func TestSortedKeys(t *testing.T) { 9 | m := map[string]int{ 10 | "foo": 1, 11 | "bar": 10, 12 | "aaaaa": 11, 13 | } 14 | 15 | keys := SortedKeys(m) 16 | if slices.Compare(keys, []string{"aaaaa", "bar", "foo"}) != 0 { 17 | t.Fatalf("unexpected keys order: %v", keys) 18 | } 19 | } 20 | 21 | func TestKeys(t *testing.T) { 22 | m := map[string]int{ 23 | "foo": 1, 24 | "bar": 10, 25 | "aaaaa": 11, 26 | } 27 | 28 | keys := Keys(m) 29 | for _, s := range []string{"aaaaa", "bar", "foo"} { 30 | if !slices.Contains(keys, s) { 31 | t.Fatalf("unexpected keys: %v", keys) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ansi/parser_apc_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSosPmApcSequence(t *testing.T) { 8 | cases := []testCase{ 9 | { 10 | name: "apc7", 11 | input: "\x1b_Gf=24,s=10,v=20,o=z;aGVsbG8gd29ybGQ=\x1b\\", 12 | expected: []any{ 13 | []byte("Gf=24,s=10,v=20,o=z;aGVsbG8gd29ybGQ="), 14 | Cmd('\\'), 15 | }, 16 | }, 17 | } 18 | 19 | for _, c := range cases { 20 | t.Run(c.name, func(t *testing.T) { 21 | dispatcher := &testDispatcher{} 22 | parser := testParser(dispatcher) 23 | parser.Parse([]byte(c.input)) 24 | assertEqual(t, len(c.expected), len(dispatcher.dispatched)) 25 | assertEqual(t, c.expected, dispatcher.dispatched) 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vt/focus.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | ) 8 | 9 | // Focus sends the terminal a focus event if focus events mode is enabled. 10 | // This is the opposite of [Blur]. 11 | func (e *Emulator) Focus() { 12 | e.focus(true) 13 | } 14 | 15 | // Blur sends the terminal a blur event if focus events mode is enabled. 16 | // This is the opposite of [Focus]. 17 | func (e *Emulator) Blur() { 18 | e.focus(false) 19 | } 20 | 21 | func (e *Emulator) focus(focus bool) { 22 | if mode, ok := e.modes[ansi.FocusEventMode]; ok && mode.IsSet() { 23 | if focus { 24 | _, _ = io.WriteString(e.pw, ansi.Focus) 25 | } else { 26 | _, _ = io.WriteString(e.pw, ansi.Blur) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /input/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/input 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/charmbracelet/x/ansi v0.11.3 7 | github.com/charmbracelet/x/windows v0.2.2 8 | github.com/muesli/cancelreader v0.2.2 9 | github.com/rivo/uniseg v0.4.7 10 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e 11 | golang.org/x/sys v0.39.0 12 | ) 13 | 14 | require ( 15 | github.com/clipperhouse/displaywidth v0.6.1 // indirect 16 | github.com/clipperhouse/stringish v0.1.1 // indirect 17 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 18 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 19 | github.com/mattn/go-runewidth v0.0.19 // indirect 20 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /ansi/parser_esc_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/charmbracelet/x/ansi/parser" 7 | ) 8 | 9 | func TestEscSequence(t *testing.T) { 10 | cases := []testCase{ 11 | { 12 | name: "reset", 13 | input: "\x1b[3;1\x1b(A", 14 | expected: []any{ 15 | Cmd('A' | '('< 14 | 15 | Hello, World! 16 | 17 | Welcome to pony - a declarative markup language for terminal UIs. 18 | 19 | 20 | Left 21 | Right 22 | 23 | 24 | ` 25 | 26 | // Get terminal size 27 | width, height, err := term.GetSize(os.Stdout.Fd()) 28 | if err != nil { 29 | width, height = 80, 24 // fallback 30 | } 31 | 32 | t := pony.MustParse[any](tmpl) 33 | output := t.Render(nil, width, height) 34 | fmt.Print(output) 35 | } 36 | -------------------------------------------------------------------------------- /vcr/hooks.go: -------------------------------------------------------------------------------- 1 | package vcr 2 | 3 | import ( 4 | "strings" 5 | 6 | "gopkg.in/dnaeon/go-vcr.v4/pkg/cassette" 7 | "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" 8 | ) 9 | 10 | var headersToKeep = map[string]struct{}{ 11 | "accept": {}, 12 | "content-type": {}, 13 | "user-agent": {}, 14 | } 15 | 16 | func hookRemoveHeaders(keepAll bool) recorder.HookFunc { 17 | return func(i *cassette.Interaction) error { 18 | if keepAll { 19 | return nil 20 | } 21 | for k := range i.Request.Headers { 22 | if _, ok := headersToKeep[strings.ToLower(k)]; !ok { 23 | delete(i.Request.Headers, k) 24 | } 25 | } 26 | for k := range i.Response.Headers { 27 | if _, ok := headersToKeep[strings.ToLower(k)]; !ok { 28 | delete(i.Response.Headers, k) 29 | } 30 | } 31 | return nil 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | tests: false 4 | linters: 5 | enable: 6 | - bodyclose 7 | - exhaustive 8 | - goconst 9 | - godot 10 | - godox 11 | - gomoddirectives 12 | - goprintffuncname 13 | - gosec 14 | - misspell 15 | - nakedret 16 | - nestif 17 | - nilerr 18 | - noctx 19 | - nolintlint 20 | - prealloc 21 | - revive 22 | - rowserrcheck 23 | - sqlclosecheck 24 | - tparallel 25 | - unconvert 26 | - unparam 27 | - whitespace 28 | - wrapcheck 29 | exclusions: 30 | generated: lax 31 | presets: 32 | - common-false-positives 33 | issues: 34 | max-issues-per-linter: 0 35 | max-same-issues: 0 36 | formatters: 37 | enable: 38 | - gofumpt 39 | - goimports 40 | exclusions: 41 | generated: lax 42 | -------------------------------------------------------------------------------- /examples/mosaic/main.go: -------------------------------------------------------------------------------- 1 | // Package main demonstrates usage. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "image" 7 | "image/jpeg" 8 | "os" 9 | 10 | "github.com/charmbracelet/lipgloss" 11 | "github.com/charmbracelet/x/mosaic" 12 | ) 13 | 14 | func main() { 15 | dogImg, err := loadImage("./pekinas.jpg") 16 | if err != nil { 17 | fmt.Print(err) 18 | os.Exit(1) 19 | } 20 | 21 | m := mosaic.New().Width(80).Height(40) 22 | 23 | fmt.Println(lipgloss.JoinVertical(lipgloss.Right, lipgloss.JoinHorizontal(lipgloss.Center, m.Render(dogImg)))) 24 | } 25 | 26 | func loadImage(path string) (image.Image, error) { 27 | f, err := os.Open(path) 28 | defer f.Close() //nolint:errcheck,staticcheck 29 | if err != nil { 30 | return nil, err //nolint:wrapcheck 31 | } 32 | return jpeg.Decode(f) //nolint:wrapcheck 33 | } 34 | -------------------------------------------------------------------------------- /ansi/keypad.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // Keypad Application Mode (DECKPAM) is a mode that determines whether the 4 | // keypad sends application sequences or ANSI sequences. 5 | // 6 | // This works like enabling [DECNKM]. 7 | // Use [NumericKeypadMode] to set the numeric keypad mode. 8 | // 9 | // ESC = 10 | // 11 | // See: https://vt100.net/docs/vt510-rm/DECKPAM.html 12 | const ( 13 | KeypadApplicationMode = "\x1b=" 14 | DECKPAM = KeypadApplicationMode 15 | ) 16 | 17 | // Keypad Numeric Mode (DECKPNM) is a mode that determines whether the keypad 18 | // sends application sequences or ANSI sequences. 19 | // 20 | // This works the same as disabling [DECNKM]. 21 | // 22 | // ESC > 23 | // 24 | // See: https://vt100.net/docs/vt510-rm/DECKPNM.html 25 | const ( 26 | KeypadNumericMode = "\x1b>" 27 | DECKPNM = KeypadNumericMode 28 | ) 29 | -------------------------------------------------------------------------------- /input/clipboard.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | // ClipboardSelection represents a clipboard selection. The most common 6 | // clipboard selections are "system" and "primary" and selections. 7 | type ClipboardSelection = byte 8 | 9 | // Clipboard selections. 10 | const ( 11 | SystemClipboard ClipboardSelection = ansi.SystemClipboard 12 | PrimaryClipboard ClipboardSelection = ansi.PrimaryClipboard 13 | ) 14 | 15 | // ClipboardEvent is a clipboard read message event. This message is emitted when 16 | // a terminal receives an OSC52 clipboard read message event. 17 | type ClipboardEvent struct { 18 | Content string 19 | Selection ClipboardSelection 20 | } 21 | 22 | // String returns the string representation of the clipboard message. 23 | func (e ClipboardEvent) String() string { 24 | return e.Content 25 | } 26 | -------------------------------------------------------------------------------- /ansi/hyperlink.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import "strings" 4 | 5 | // SetHyperlink returns a sequence for starting a hyperlink. 6 | // 7 | // OSC 8 ; Params ; Uri ST 8 | // OSC 8 ; Params ; Uri BEL 9 | // 10 | // To reset the hyperlink, omit the URI. 11 | // 12 | // See: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda 13 | func SetHyperlink(uri string, params ...string) string { 14 | var p string 15 | if len(params) > 0 { 16 | p = strings.Join(params, ":") 17 | } 18 | return "\x1b]8;" + p + ";" + uri + "\x07" 19 | } 20 | 21 | // ResetHyperlink returns a sequence for resetting the hyperlink. 22 | // 23 | // This is equivalent to SetHyperlink("", params...). 24 | // 25 | // See: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda 26 | func ResetHyperlink(params ...string) string { 27 | return SetHyperlink("", params...) 28 | } 29 | -------------------------------------------------------------------------------- /pony/testdata/TestScrollViewWithSlot.golden: -------------------------------------------------------------------------------- 1 | Line 6 ░ 2 | Line 7 ░ 3 | Line 8 █ 4 | Line 9 █ 5 | Line 10 █ 6 | Line 11 █ 7 | Line 12 █ 8 | Line 13 ░ 9 | Line 14 ░ 10 | Line 15 ░ -------------------------------------------------------------------------------- /ansi/cwd.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "net/url" 5 | "path" 6 | ) 7 | 8 | // NotifyWorkingDirectory returns a sequence that notifies the terminal 9 | // of the current working directory. 10 | // 11 | // OSC 7 ; Pt BEL 12 | // 13 | // Where Pt is a URL in the format "file://[host]/[path]". 14 | // Set host to "localhost" if this is a path on the local computer. 15 | // 16 | // See: https://wezfurlong.org/wezterm/shell-integration.html#osc-7-escape-sequence-to-set-the-working-directory 17 | // See: https://iterm2.com/documentation-escape-codes.html#:~:text=RemoteHost%20and%20CurrentDir%3A-,OSC%207,-%3B%20%5BPs%5D%20ST 18 | func NotifyWorkingDirectory(host string, paths ...string) string { 19 | path := path.Join(paths...) 20 | u := &url.URL{ 21 | Scheme: "file", 22 | Host: host, 23 | Path: path, 24 | } 25 | return "\x1b]7;" + u.String() + "\x07" 26 | } 27 | -------------------------------------------------------------------------------- /termios/termios_solaris.go: -------------------------------------------------------------------------------- 1 | //go:build solaris 2 | // +build solaris 3 | 4 | package termios 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | // see https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c 9 | // see https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h 10 | const ( 11 | ioctlSets = unix.TCSETA 12 | ioctlGets = unix.TCGETA 13 | ioctlSetWinSize = (int('T') << 8) | 103 14 | ioctlGetWinSize = (int('T') << 8) | 104 15 | ) 16 | 17 | func setSpeed(*unix.Termios, uint32, uint32) { 18 | // TODO: support setting speed on Solaris? 19 | // see cfgetospeed(3C) and cfsetospeed(3C) 20 | // see cfgetispeed(3C) and cfsetispeed(3C) 21 | // https://github.com/omniti-labs/illumos-omnios/blob/master/usr/src/uts/common/sys/termios.h#L103 22 | } 23 | 24 | func getSpeed(*unix.Termios) (uint32, uint32) { 25 | return 0, 0 26 | } 27 | -------------------------------------------------------------------------------- /xpty/go.sum: -------------------------------------------------------------------------------- 1 | github.com/charmbracelet/x/conpty v0.2.0 h1:eKtA2hm34qNfgJCDp/M6Dc0gLy7e07YEK4qAdNGOvVY= 2 | github.com/charmbracelet/x/conpty v0.2.0/go.mod h1:fexgUnVrZgw8scD49f6VSi0Ggj9GWYIrpedRthAwW/8= 3 | github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 4 | github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= 5 | github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= 6 | github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= 7 | github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= 8 | github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 9 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 10 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 11 | -------------------------------------------------------------------------------- /xpty/conpty.go: -------------------------------------------------------------------------------- 1 | package xpty 2 | 3 | import ( 4 | "os/exec" 5 | 6 | "github.com/charmbracelet/x/conpty" 7 | ) 8 | 9 | // ConPty is a Windows console pty. 10 | type ConPty struct { 11 | *conpty.ConPty 12 | } 13 | 14 | var _ Pty = &ConPty{} 15 | 16 | // NewConPty creates a new ConPty. 17 | func NewConPty(width, height int, opts ...PtyOption) (*ConPty, error) { 18 | var opt Options 19 | for _, o := range opts { 20 | o(opt) 21 | } 22 | 23 | c, err := conpty.New(width, height, opt.Flags) 24 | if err != nil { 25 | return nil, err //nolint:wrapcheck 26 | } 27 | 28 | return &ConPty{c}, nil 29 | } 30 | 31 | // Name returns the name of the ConPty. 32 | func (c *ConPty) Name() string { 33 | return "windows-pty" 34 | } 35 | 36 | // Start starts a command on the ConPty. 37 | // This is a wrapper around conpty.Spawn. 38 | func (c *ConPty) Start(cmd *exec.Cmd) error { 39 | return c.start(cmd) 40 | } 41 | -------------------------------------------------------------------------------- /powernap/pkg/lsp/protocol/tables.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | var TableKindMap = map[SymbolKind]string{ 4 | File: "File", 5 | Module: "Module", 6 | Namespace: "Namespace", 7 | Package: "Package", 8 | Class: "Class", 9 | Method: "Method", 10 | Property: "Property", 11 | Field: "Field", 12 | Constructor: "Constructor", 13 | Enum: "Enum", 14 | Interface: "Interface", 15 | Function: "Function", 16 | Variable: "Variable", 17 | Constant: "Constant", 18 | String: "String", 19 | Number: "Number", 20 | Boolean: "Boolean", 21 | Array: "Array", 22 | Object: "Object", 23 | Key: "Key", 24 | Null: "Null", 25 | EnumMember: "EnumMember", 26 | Struct: "Struct", 27 | Event: "Event", 28 | Operator: "Operator", 29 | TypeParameter: "TypeParameter", 30 | } 31 | -------------------------------------------------------------------------------- /colors/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/colors 2 | 3 | go 1.24.0 4 | 5 | require github.com/charmbracelet/lipgloss v1.1.0 6 | 7 | require ( 8 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 9 | github.com/charmbracelet/colorprofile v0.3.2 // indirect 10 | github.com/charmbracelet/x/ansi v0.10.1 // indirect 11 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect 12 | github.com/charmbracelet/x/term v0.2.1 // indirect 13 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 14 | github.com/mattn/go-isatty v0.0.20 // indirect 15 | github.com/mattn/go-runewidth v0.0.16 // indirect 16 | github.com/muesli/termenv v0.16.0 // indirect 17 | github.com/rivo/uniseg v0.4.7 // indirect 18 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 19 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 20 | golang.org/x/sys v0.35.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /input/mod.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | // KeyMod represents modifier keys. 4 | type KeyMod int 5 | 6 | // Modifier keys. 7 | const ( 8 | ModShift KeyMod = 1 << iota 9 | ModAlt 10 | ModCtrl 11 | ModMeta 12 | 13 | // These modifiers are used with the Kitty protocol. 14 | // XXX: Meta and Super are swapped in the Kitty protocol, 15 | // this is to preserve compatibility with XTerm modifiers. 16 | 17 | ModHyper 18 | ModSuper // Windows/Command keys 19 | 20 | // These are key lock states. 21 | 22 | ModCapsLock 23 | ModNumLock 24 | ModScrollLock // Defined in Windows API only 25 | ) 26 | 27 | // Contains reports whether m contains the given modifiers. 28 | // 29 | // Example: 30 | // 31 | // m := ModAlt | ModCtrl 32 | // m.Contains(ModCtrl) // true 33 | // m.Contains(ModAlt | ModCtrl) // true 34 | // m.Contains(ModAlt | ModCtrl | ModShift) // false 35 | func (m KeyMod) Contains(mods KeyMod) bool { 36 | return m&mods == mods 37 | } 38 | -------------------------------------------------------------------------------- /ansi/notification.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Notify sends a desktop notification using iTerm's OSC 9. 9 | // 10 | // OSC 9 ; Mc ST 11 | // OSC 9 ; Mc BEL 12 | // 13 | // Where Mc is the notification body. 14 | // 15 | // See: https://iterm2.com/documentation-escape-codes.html 16 | func Notify(s string) string { 17 | return "\x1b]9;" + s + "\x07" 18 | } 19 | 20 | // DesktopNotification sends a desktop notification based on the extensible OSC 21 | // 99 escape code. 22 | // 23 | // OSC 99 ; ; ST 24 | // OSC 99 ; ; BEL 25 | // 26 | // Where is a colon-separated list of key-value pairs, and 27 | // is the notification body. 28 | // 29 | // See: https://sw.kovidgoyal.net/kitty/desktop-notifications/ 30 | func DesktopNotification(payload string, metadata ...string) string { 31 | return fmt.Sprintf("\x1b]99;%s;%s\x07", strings.Join(metadata, ":"), payload) 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/windows-generate.yml: -------------------------------------------------------------------------------- 1 | name: windows-generate 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - windows/** 9 | - .github/workflows/windows-generate.yml 10 | workflow_dispatch: {} 11 | 12 | permissions: 13 | contents: write 14 | actions: write 15 | 16 | jobs: 17 | generate: 18 | runs-on: windows-latest 19 | defaults: 20 | run: 21 | working-directory: ./windows 22 | steps: 23 | - uses: actions/checkout@v6 24 | - uses: actions/setup-go@v6 25 | with: 26 | go-version-file: ./windows/go.mod 27 | cache: true 28 | cache-dependency-path: ./windows/go.sum 29 | - run: go generate ./... 30 | - uses: stefanzweifel/git-auto-commit-action@v7 31 | with: 32 | commit_message: "ci: generate windows syscalls" 33 | branch: main 34 | commit_user_name: actions-user 35 | commit_user_email: actions@github.com 36 | -------------------------------------------------------------------------------- /ansi/urxvt_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import "testing" 4 | 5 | func TestUrxvtExt(t *testing.T) { 6 | tests := []struct { 7 | extension string 8 | params []string 9 | expected string 10 | }{ 11 | { 12 | extension: "foo", 13 | params: []string{"bar", "baz"}, 14 | expected: "\x1b]777;foo;bar;baz\x07", 15 | }, 16 | { 17 | extension: "test", 18 | params: []string{}, 19 | expected: "\x1b]777;test;\x07", 20 | }, 21 | { 22 | extension: "example", 23 | params: []string{"param1"}, 24 | expected: "\x1b]777;example;param1\x07", 25 | }, 26 | { 27 | extension: "notify", 28 | params: []string{"message", "info"}, 29 | expected: "\x1b]777;notify;message;info\x07", 30 | }, 31 | } 32 | 33 | for _, tt := range tests { 34 | result := URxvtExt(tt.extension, tt.params...) 35 | if result != tt.expected { 36 | t.Errorf("URxvtExt(%q, %v) = %q; want %q", tt.extension, tt.params, result, tt.expected) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pony/.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | - bodyclose 5 | - exhaustive 6 | - goconst 7 | - godot 8 | - godox 9 | - gomoddirectives 10 | - goprintffuncname 11 | - gosec 12 | - misspell 13 | - nakedret 14 | - nilerr 15 | - noctx 16 | - nolintlint 17 | - prealloc 18 | - revive 19 | - rowserrcheck 20 | - sqlclosecheck 21 | - staticcheck 22 | - tparallel 23 | - unconvert 24 | - unparam 25 | - whitespace 26 | - wrapcheck 27 | disable: 28 | - errcheck 29 | - ineffassign 30 | - unused 31 | - nestif 32 | exclusions: 33 | generated: lax 34 | presets: 35 | - common-false-positives 36 | rules: 37 | - text: '(slog|log)\.\w+' 38 | linters: 39 | - noctx 40 | issues: 41 | max-issues-per-linter: 0 42 | max-same-issues: 0 43 | formatters: 44 | enable: 45 | - gofumpt 46 | - goimports 47 | exclusions: 48 | generated: lax 49 | -------------------------------------------------------------------------------- /xpty/conpty_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package xpty 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "syscall" 11 | 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | func (c *ConPty) start(cmd *exec.Cmd) error { 16 | pid, proc, err := c.Spawn(cmd.Path, cmd.Args, &syscall.ProcAttr{ 17 | Dir: cmd.Dir, 18 | Env: cmd.Env, 19 | Sys: cmd.SysProcAttr, 20 | }) 21 | if err != nil { 22 | return err //nolint:wrapcheck 23 | } 24 | 25 | cmd.Process, err = os.FindProcess(pid) 26 | if err != nil { 27 | // If we can't find the process via os.FindProcess, terminate the 28 | // process as that's what we rely on for all further operations on the 29 | // object. 30 | if tErr := windows.TerminateProcess(windows.Handle(proc), 1); tErr != nil { 31 | return fmt.Errorf("failed to terminate process after process not found: %w", tErr) 32 | } 33 | return fmt.Errorf("failed to find process after starting: %w", err) 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /term/term_test.go: -------------------------------------------------------------------------------- 1 | package term_test 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/charmbracelet/x/term" 9 | ) 10 | 11 | func TestIsTerminalTempFile(t *testing.T) { 12 | file, err := os.CreateTemp("", "TestIsTerminalTempFile") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | defer os.Remove(file.Name()) 17 | defer file.Close() 18 | 19 | if term.IsTerminal(file.Fd()) { 20 | t.Fatalf("IsTerminal unexpectedly returned true for temporary file %s", file.Name()) 21 | } 22 | } 23 | 24 | func TestIsTerminalTerm(t *testing.T) { 25 | var name string 26 | switch runtime.GOOS { 27 | case "linux": 28 | name = "/dev/ptmx" 29 | case "plan9": 30 | name = "/dev/cons" 31 | default: 32 | t.Skipf("unknown terminal path for GOOS %v", runtime.GOOS) 33 | } 34 | 35 | file, err := os.OpenFile(name, os.O_RDWR, 0) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | defer file.Close() 40 | 41 | if !term.IsTerminal(file.Fd()) { 42 | t.Fatalf("IsTerminal unexpectedly returned false for terminal file %s", file.Name()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /vt/esc.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import ( 4 | "github.com/charmbracelet/x/ansi" 5 | "github.com/charmbracelet/x/ansi/parser" 6 | ) 7 | 8 | // handleEsc handles an escape sequence. 9 | func (e *Emulator) handleEsc(cmd ansi.Cmd) { 10 | e.flushGrapheme() // Flush any pending grapheme before handling ESC sequences. 11 | if !e.handlers.handleEsc(int(cmd)) { 12 | var str string 13 | if inter := cmd.Intermediate(); inter != 0 { 14 | str += string(inter) + " " 15 | } 16 | if final := cmd.Final(); final != 0 { 17 | str += string(final) 18 | } 19 | e.logf("unhandled sequence: ESC %q", str) 20 | } 21 | } 22 | 23 | // fullReset performs a full terminal reset as in [ansi.RIS]. 24 | func (e *Emulator) fullReset() { 25 | e.scrs[0].Reset() 26 | e.scrs[1].Reset() 27 | e.resetTabStops() 28 | 29 | // XXX: Do we reset all modes here? Investigate. 30 | e.resetModes() 31 | 32 | e.gl, e.gr = 0, 1 33 | e.gsingle = 0 34 | e.charsets = [4]CharSet{} 35 | e.atPhantom = false 36 | e.grapheme = e.grapheme[:0] 37 | e.lastChar = 0 38 | e.lastState = parser.GroundState 39 | } 40 | -------------------------------------------------------------------------------- /ansi/termcap.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | ) 7 | 8 | // XTGETTCAP (RequestTermcap) requests Termcap/Terminfo strings. 9 | // 10 | // DCS + q ST 11 | // 12 | // Where is a list of Termcap/Terminfo capabilities, encoded in 2-digit 13 | // hexadecimals, separated by semicolons. 14 | // 15 | // See: https://man7.org/linux/man-pages/man5/terminfo.5.html 16 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands 17 | func XTGETTCAP(caps ...string) string { 18 | if len(caps) == 0 { 19 | return "" 20 | } 21 | 22 | s := "\x1bP+q" 23 | for i, c := range caps { 24 | if i > 0 { 25 | s += ";" 26 | } 27 | s += strings.ToUpper(hex.EncodeToString([]byte(c))) 28 | } 29 | 30 | return s + "\x1b\\" 31 | } 32 | 33 | // RequestTermcap is an alias for [XTGETTCAP]. 34 | func RequestTermcap(caps ...string) string { 35 | return XTGETTCAP(caps...) 36 | } 37 | 38 | // RequestTerminfo is an alias for [XTGETTCAP]. 39 | func RequestTerminfo(caps ...string) string { 40 | return XTGETTCAP(caps...) 41 | } 42 | -------------------------------------------------------------------------------- /pony/testdata/TestRenderComplexLayout.golden: -------------------------------------------------------------------------------- 1 | ╔═════╗ 2 | ║Title║ 3 | ╚═════╝ 4 | 5 | ╭────────────────────────────╮ ╭────────────╮ 6 | │Left │ │Right │ 7 | ╰────────────────────────────╯ ╰────────────╯ 8 | 9 | ──────────────────────────────────────────────────────────────────────────────── 10 | 11 | Footer -------------------------------------------------------------------------------- /ansi/palette.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | ) 7 | 8 | // SetPalette sets the palette color for the given index. The index is a 16 9 | // color index between 0 and 15. The color is a 24-bit RGB color. 10 | // 11 | // OSC P n rrggbb BEL 12 | // 13 | // Where n is the color index in hex (0-f), and rrggbb is the color in 14 | // hexadecimal format (e.g., ff0000 for red). 15 | // 16 | // This sequence is specific to the Linux Console and may not work in other 17 | // terminal emulators. 18 | // 19 | // See https://man7.org/linux/man-pages/man4/console_codes.4.html 20 | func SetPalette(i int, c color.Color) string { 21 | if c == nil || i < 0 || i > 15 { 22 | return "" 23 | } 24 | r, g, b, _ := c.RGBA() 25 | return fmt.Sprintf("\x1b]P%x%02x%02x%02x\x07", i, r>>8, g>>8, b>>8) 26 | } 27 | 28 | // ResetPalette resets the color palette to the default values. 29 | // 30 | // This sequence is specific to the Linux Console and may not work in other 31 | // terminal emulators. 32 | // 33 | // See https://man7.org/linux/man-pages/man4/console_codes.4.html 34 | const ResetPalette = "\x1b]R\x07" 35 | -------------------------------------------------------------------------------- /scripts/dependabot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo '# auto-generated by scripts/dependabot. DO NOT EDIT. 4 | 5 | version: 2 6 | 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | day: "monday" 13 | time: "05:00" 14 | timezone: "America/New_York" 15 | labels: 16 | - "dependencies" 17 | commit-message: 18 | prefix: "chore" 19 | include: "scope" 20 | groups: 21 | all: 22 | patterns: 23 | - "*"' >./.github/dependabot.yml 24 | 25 | find . -type f -name go.mod | cut -f2- -d'/' | sort | while read -r mod; do 26 | echo ' 27 | - package-ecosystem: "gomod" 28 | directory: "/'"$(dirname "$mod")"'" 29 | schedule: 30 | interval: "weekly" 31 | day: "monday" 32 | time: "05:00" 33 | timezone: "America/New_York" 34 | labels: 35 | - "dependencies" 36 | commit-message: 37 | prefix: "chore" 38 | include: "scope" 39 | groups: 40 | all: 41 | patterns: 42 | - "*"' >>./.github/dependabot.yml 43 | done 44 | -------------------------------------------------------------------------------- /exp/teatest/v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/teatest/v2 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250617194119-3f1d09f7d826 7 | github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f 8 | ) 9 | 10 | require ( 11 | github.com/aymanbagabas/go-udiff v0.3.1 // indirect 12 | github.com/charmbracelet/colorprofile v0.3.2 // indirect 13 | github.com/charmbracelet/x/ansi v0.10.1 // indirect 14 | github.com/charmbracelet/x/cellbuf v0.0.14-0.20250505150409-97991a1f17d1 // indirect 15 | github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 // indirect 16 | github.com/charmbracelet/x/term v0.2.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.1 // indirect 18 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 19 | github.com/mattn/go-runewidth v0.0.16 // indirect 20 | github.com/muesli/cancelreader v0.2.2 // indirect 21 | github.com/rivo/uniseg v0.4.7 // indirect 22 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 23 | golang.org/x/sync v0.16.0 // indirect 24 | golang.org/x/sys v0.35.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /vt/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/vt 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/charmbracelet/ultraviolet v0.0.0-20251106193841-7889546fc720 7 | github.com/charmbracelet/x/ansi v0.11.3 8 | github.com/charmbracelet/x/exp/ordered v0.1.0 9 | ) 10 | 11 | require ( 12 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 13 | github.com/charmbracelet/x/term v0.2.2 // indirect 14 | github.com/charmbracelet/x/termios v0.1.1 // indirect 15 | github.com/charmbracelet/x/windows v0.2.2 // indirect 16 | github.com/clipperhouse/displaywidth v0.6.1 // indirect 17 | github.com/clipperhouse/stringish v0.1.1 // indirect 18 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 19 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 20 | github.com/mattn/go-runewidth v0.0.19 // indirect 21 | github.com/muesli/cancelreader v0.2.2 // indirect 22 | github.com/rivo/uniseg v0.4.7 // indirect 23 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 24 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 25 | golang.org/x/sync v0.17.0 // indirect 26 | golang.org/x/sys v0.37.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /vt/charset.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | // CharSet represents a character set designator. 4 | // This can be used to select a character set for G0 or G1 and others. 5 | type CharSet map[byte]string 6 | 7 | // Character sets. 8 | var ( 9 | UK = CharSet{ 10 | '$': "£", // U+00A3 11 | } 12 | SpecialDrawing = CharSet{ 13 | '`': "◆", // U+25C6 14 | 'a': "▒", // U+2592 15 | 'b': "␉", // U+2409 16 | 'c': "␌", // U+240C 17 | 'd': "␍", // U+240D 18 | 'e': "␊", // U+240A 19 | 'f': "°", // U+00B0 20 | 'g': "±", // U+00B1 21 | 'h': "␤", // U+2424 22 | 'i': "␋", // U+240B 23 | 'j': "┘", // U+2518 24 | 'k': "┐", // U+2510 25 | 'l': "┌", // U+250C 26 | 'm': "└", // U+2514 27 | 'n': "┼", // U+253C 28 | 'o': "⎺", // U+23BA 29 | 'p': "⎻", // U+23BB 30 | 'q': "─", // U+2500 31 | 'r': "⎼", // U+23BC 32 | 's': "⎽", // U+23BD 33 | 't': "├", // U+251C 34 | 'u': "┤", // U+2524 35 | 'v': "┴", // U+2534 36 | 'w': "┬", // U+252C 37 | 'x': "│", // U+2502 38 | 'y': "⩽", // U+2A7D 39 | 'z': "⩾", // U+2A7E 40 | '{': "π", // U+03C0 41 | '|': "≠", // U+2260 42 | '}': "£", // U+00A3 43 | '~': "·", // U+00B7 44 | } 45 | ) 46 | -------------------------------------------------------------------------------- /ansi/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= 2 | github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 3 | github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo= 4 | github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 5 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 6 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 7 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 8 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 9 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 10 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 11 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 12 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 13 | -------------------------------------------------------------------------------- /examples/img2term/main.go: -------------------------------------------------------------------------------- 1 | // Package main demonstrates usage. 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "flag" 7 | "image" 8 | "io" 9 | "log" 10 | "os" 11 | 12 | _ "image/jpeg" 13 | _ "image/png" 14 | 15 | "github.com/charmbracelet/x/ansi" 16 | "github.com/charmbracelet/x/ansi/sixel" 17 | ) 18 | 19 | // $ go run . ../../fixtures/JigokudaniMonkeyPark.png 20 | func main() { 21 | flag.Parse() 22 | args := flag.Args() 23 | if len(args) == 0 { 24 | flag.Usage() 25 | os.Exit(1) 26 | } 27 | 28 | f, err := os.Open(args[0]) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | defer f.Close() //nolint:errcheck 34 | img, _, err := image.Decode(f) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | if _, err := writeSixel(os.Stdout, img); err != nil { 40 | log.Fatal(err) 41 | } 42 | } 43 | 44 | func writeSixel(w io.Writer, img image.Image) (int, error) { 45 | var buf bytes.Buffer 46 | var e sixel.Encoder 47 | if err := e.Encode(&buf, img); err != nil { 48 | return 0, err //nolint:wrapcheck 49 | } 50 | 51 | return io.WriteString(w, ansi.SixelGraphics(0, 1, 0, buf.Bytes())) //nolint:wrapcheck 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Charmbracelet, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ansi/clipboard_test.go: -------------------------------------------------------------------------------- 1 | package ansi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | ) 8 | 9 | func TestClipboardNewClipboard(t *testing.T) { 10 | tt := []struct { 11 | name byte 12 | data string 13 | expect string 14 | }{ 15 | {'c', "Hello Test", "\x1b]52;c;SGVsbG8gVGVzdA==\x07"}, 16 | {'p', "Ansi Test", "\x1b]52;p;QW5zaSBUZXN0\x07"}, 17 | {'c', "", "\x1b]52;c;\x07"}, 18 | {'p', "?", "\x1b]52;p;Pw==\x07"}, 19 | {ansi.SystemClipboard, "test", "\x1b]52;c;dGVzdA==\x07"}, 20 | } 21 | for _, tp := range tt { 22 | cb := ansi.SetClipboard(tp.name, tp.data) 23 | if cb != tp.expect { 24 | t.Errorf("SetClipboard(%q, %q) = %q, want %q", tp.name, tp.data, cb, tp.expect) 25 | } 26 | } 27 | } 28 | 29 | func TestClipboardReset(t *testing.T) { 30 | cb := ansi.ResetClipboard(ansi.PrimaryClipboard) 31 | if cb != "\x1b]52;p;\x07" { 32 | t.Errorf("Unexpected clipboard reset: %q", cb) 33 | } 34 | } 35 | 36 | func TestClipboardRequest(t *testing.T) { 37 | cb := ansi.RequestClipboard(ansi.PrimaryClipboard) 38 | if cb != "\x1b]52;p;?\x07" { 39 | t.Errorf("Unexpected clipboard request: %q", cb) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pony/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/pony 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 7 | github.com/charmbracelet/x/ansi v0.11.1 8 | github.com/charmbracelet/x/exp/golden v0.0.0-20251118172736-77d017256798 9 | github.com/lucasb-eyer/go-colorful v1.3.0 10 | golang.org/x/text v0.31.0 11 | ) 12 | 13 | require ( 14 | github.com/aymanbagabas/go-udiff v0.3.1 // indirect 15 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 16 | github.com/charmbracelet/x/term v0.2.2 // indirect 17 | github.com/charmbracelet/x/termios v0.1.1 // indirect 18 | github.com/charmbracelet/x/windows v0.2.2 // indirect 19 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 20 | github.com/clipperhouse/stringish v0.1.1 // indirect 21 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /pony/examples/custom/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/ultraviolet v0.0.0-20251124142205-05635edc50ab 9 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 10 | github.com/charmbracelet/x/term v0.2.2 11 | ) 12 | 13 | require ( 14 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /term/util.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import ( 4 | "io" 5 | "runtime" 6 | ) 7 | 8 | // readPasswordLine reads from reader until it finds \n or io.EOF. 9 | // The slice returned does not include the \n. 10 | // readPasswordLine also ignores any \r it finds. 11 | // Windows uses \r as end of line. So, on Windows, readPasswordLine 12 | // reads until it finds \r and ignores any \n it finds during processing. 13 | func readPasswordLine(reader io.Reader) ([]byte, error) { 14 | var buf [1]byte 15 | var ret []byte 16 | 17 | for { 18 | n, err := reader.Read(buf[:]) 19 | if n > 0 { 20 | switch buf[0] { 21 | case '\b': 22 | if len(ret) > 0 { 23 | ret = ret[:len(ret)-1] 24 | } 25 | case '\n': 26 | if runtime.GOOS != "windows" { 27 | return ret, nil 28 | } 29 | // otherwise ignore \n 30 | case '\r': 31 | if runtime.GOOS == "windows" { 32 | return ret, nil 33 | } 34 | // otherwise ignore \r 35 | default: 36 | ret = append(ret, buf[0]) 37 | } 38 | continue 39 | } 40 | if err != nil { 41 | if err == io.EOF && len(ret) > 0 { 42 | return ret, nil 43 | } 44 | return ret, err //nolint:wrapcheck 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pony/examples/helpers/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 11 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 12 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 13 | github.com/charmbracelet/x/term v0.2.2 // indirect 14 | github.com/charmbracelet/x/termios v0.1.1 // indirect 15 | github.com/charmbracelet/x/windows v0.2.2 // indirect 16 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 17 | github.com/clipperhouse/stringish v0.1.1 // indirect 18 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 19 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 20 | github.com/mattn/go-runewidth v0.0.19 // indirect 21 | github.com/muesli/cancelreader v0.2.2 // indirect 22 | github.com/rivo/uniseg v0.4.7 // indirect 23 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 24 | golang.org/x/sync v0.18.0 // indirect 25 | golang.org/x/sys v0.38.0 // indirect 26 | golang.org/x/text v0.31.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /pony/examples/advanced/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 8 | 9 | require ( 10 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 11 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 12 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 13 | github.com/charmbracelet/x/term v0.2.2 // indirect 14 | github.com/charmbracelet/x/termios v0.1.1 // indirect 15 | github.com/charmbracelet/x/windows v0.2.2 // indirect 16 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 17 | github.com/clipperhouse/stringish v0.1.1 // indirect 18 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 19 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 20 | github.com/mattn/go-runewidth v0.0.19 // indirect 21 | github.com/muesli/cancelreader v0.2.2 // indirect 22 | github.com/rivo/uniseg v0.4.7 // indirect 23 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 24 | golang.org/x/sync v0.18.0 // indirect 25 | golang.org/x/sys v0.38.0 // indirect 26 | golang.org/x/text v0.31.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /pony/examples/alignment/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 9 | github.com/charmbracelet/x/term v0.2.2 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /pony/examples/dynamic/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 9 | github.com/charmbracelet/x/term v0.2.2 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /pony/examples/hello/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 9 | github.com/charmbracelet/x/term v0.2.2 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /pony/examples/layout/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 9 | github.com/charmbracelet/x/term v0.2.2 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /pony/examples/styled/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 9 | github.com/charmbracelet/x/term v0.2.2 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /pony/examples/components/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 9 | github.com/charmbracelet/x/term v0.2.2 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/termios v0.1.1 // indirect 17 | github.com/charmbracelet/x/windows v0.2.2 // indirect 18 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 19 | github.com/clipperhouse/stringish v0.1.1 // indirect 20 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 21 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 22 | github.com/mattn/go-runewidth v0.0.19 // indirect 23 | github.com/muesli/cancelreader v0.2.2 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sync v0.18.0 // indirect 27 | golang.org/x/sys v0.38.0 // indirect 28 | golang.org/x/text v0.31.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /xpty/pty_unix.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 2 | // +build darwin dragonfly freebsd linux netbsd openbsd solaris 3 | 4 | package xpty 5 | 6 | import ( 7 | "github.com/charmbracelet/x/termios" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | // setWinsize sets window size for the PTY. 12 | func (p *UnixPty) setWinsize(width, height, x, y int) error { 13 | var rErr error 14 | if err := p.Control(func(fd uintptr) { 15 | rErr = termios.SetWinsize(int(fd), &unix.Winsize{ 16 | Row: uint16(height), //nolint:gosec 17 | Col: uint16(width), //nolint:gosec 18 | Xpixel: uint16(x), //nolint:gosec 19 | Ypixel: uint16(y), //nolint:gosec 20 | }) 21 | }); err != nil { 22 | rErr = err 23 | } 24 | return rErr 25 | } 26 | 27 | // size returns the size of the PTY. 28 | func (p *UnixPty) size() (width, height int, err error) { 29 | var rErr error 30 | if err := p.Control(func(fd uintptr) { 31 | ws, err := termios.GetWinsize(int(fd)) 32 | if err != nil { 33 | rErr = err 34 | return 35 | } 36 | width = int(ws.Col) 37 | height = int(ws.Row) 38 | }); err != nil { 39 | rErr = err 40 | } 41 | 42 | return width, height, rErr 43 | } 44 | -------------------------------------------------------------------------------- /pony/examples/bubbletea/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | charm.land/bubbletea/v2 v2.0.0-rc.2 9 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/term v0.2.2 // indirect 17 | github.com/charmbracelet/x/termios v0.1.1 // indirect 18 | github.com/charmbracelet/x/windows v0.2.2 // indirect 19 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 20 | github.com/clipperhouse/stringish v0.1.1 // indirect 21 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 22 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 23 | github.com/mattn/go-runewidth v0.0.19 // indirect 24 | github.com/muesli/cancelreader v0.2.2 // indirect 25 | github.com/rivo/uniseg v0.4.7 // indirect 26 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 27 | golang.org/x/sync v0.18.0 // indirect 28 | golang.org/x/sys v0.38.0 // indirect 29 | golang.org/x/text v0.31.0 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /pony/examples/scrolling/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | charm.land/bubbletea/v2 v2.0.0-rc.2 9 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/term v0.2.2 // indirect 17 | github.com/charmbracelet/x/termios v0.1.1 // indirect 18 | github.com/charmbracelet/x/windows v0.2.2 // indirect 19 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 20 | github.com/clipperhouse/stringish v0.1.1 // indirect 21 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 22 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 23 | github.com/mattn/go-runewidth v0.0.19 // indirect 24 | github.com/muesli/cancelreader v0.2.2 // indirect 25 | github.com/rivo/uniseg v0.4.7 // indirect 26 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 27 | golang.org/x/sync v0.18.0 // indirect 28 | golang.org/x/sys v0.38.0 // indirect 29 | golang.org/x/text v0.31.0 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /pony/examples/stateful/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | charm.land/bubbletea/v2 v2.0.0-rc.2 9 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/term v0.2.2 // indirect 17 | github.com/charmbracelet/x/termios v0.1.1 // indirect 18 | github.com/charmbracelet/x/windows v0.2.2 // indirect 19 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 20 | github.com/clipperhouse/stringish v0.1.1 // indirect 21 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 22 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 23 | github.com/mattn/go-runewidth v0.0.19 // indirect 24 | github.com/muesli/cancelreader v0.2.2 // indirect 25 | github.com/rivo/uniseg v0.4.7 // indirect 26 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 27 | golang.org/x/sync v0.18.0 // indirect 28 | golang.org/x/sys v0.38.0 // indirect 29 | golang.org/x/text v0.31.0 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /pony/examples/simple-bubbletea/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | charm.land/bubbletea/v2 v2.0.0-rc.2 9 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 14 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 15 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 16 | github.com/charmbracelet/x/term v0.2.2 // indirect 17 | github.com/charmbracelet/x/termios v0.1.1 // indirect 18 | github.com/charmbracelet/x/windows v0.2.2 // indirect 19 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 20 | github.com/clipperhouse/stringish v0.1.1 // indirect 21 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 22 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 23 | github.com/mattn/go-runewidth v0.0.19 // indirect 24 | github.com/muesli/cancelreader v0.2.2 // indirect 25 | github.com/rivo/uniseg v0.4.7 // indirect 26 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 27 | golang.org/x/sync v0.18.0 // indirect 28 | golang.org/x/sys v0.38.0 // indirect 29 | golang.org/x/text v0.31.0 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /powernap/pkg/lsp/protocol.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // Message represents a JSON-RPC 2.0 message 8 | type Message struct { 9 | JSONRPC string `json:"jsonrpc"` 10 | ID int32 `json:"id,omitempty"` 11 | Method string `json:"method,omitempty"` 12 | Params json.RawMessage `json:"params,omitempty"` 13 | Result json.RawMessage `json:"result,omitempty"` 14 | Error *ResponseError `json:"error,omitempty"` 15 | } 16 | 17 | // ResponseError represents a JSON-RPC 2.0 error 18 | type ResponseError struct { 19 | Code int `json:"code"` 20 | Message string `json:"message"` 21 | } 22 | 23 | func NewRequest(id int32, method string, params any) (*Message, error) { 24 | paramsJSON, err := json.Marshal(params) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return &Message{ 30 | JSONRPC: "2.0", 31 | ID: id, 32 | Method: method, 33 | Params: paramsJSON, 34 | }, nil 35 | } 36 | 37 | func NewNotification(method string, params any) (*Message, error) { 38 | paramsJSON, err := json.Marshal(params) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &Message{ 44 | JSONRPC: "2.0", 45 | Method: method, 46 | Params: paramsJSON, 47 | }, nil 48 | } 49 | -------------------------------------------------------------------------------- /exp/higherorder/higherorder.go: -------------------------------------------------------------------------------- 1 | // Package higherorder provides higher-order functions for Go. 2 | package higherorder 3 | 4 | // Foldl applies a function to each element of a list, starting from the left. 5 | // A single value is returned. 6 | func Foldl[A any](f func(x, y A) A, start A, list []A) A { 7 | for _, v := range list { 8 | start = f(start, v) 9 | } 10 | return start 11 | } 12 | 13 | // Foldr applies a function to each element of a list, starting from the right. 14 | // A single value is returned. 15 | func Foldr[A any](f func(x, y A) A, start A, list []A) A { 16 | for i := len(list) - 1; i >= 0; i-- { 17 | start = f(start, list[i]) 18 | } 19 | return start 20 | } 21 | 22 | // Map applies a given function to each element of a list, returning a new list. 23 | func Map[A, B any](f func(A) B, list []A) []B { 24 | res := make([]B, len(list)) 25 | for i, v := range list { 26 | res[i] = f(v) 27 | } 28 | return res 29 | } 30 | 31 | // Filter applies a function to each element of a list, if the function returns false those elements are removed, returning a new list. 32 | func Filter[A any](f func(A) bool, list []A) []A { 33 | res := make([]A, 0) 34 | for _, v := range list { 35 | if f(v) { 36 | res = append(res, v) 37 | } 38 | } 39 | return res 40 | } 41 | -------------------------------------------------------------------------------- /vt/dcs.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | // handleDcs handles a DCS escape sequence. 6 | func (e *Emulator) handleDcs(cmd ansi.Cmd, params ansi.Params, data []byte) { 7 | e.flushGrapheme() // Flush any pending grapheme before handling DCS sequences. 8 | if !e.handlers.handleDcs(cmd, params, data) { 9 | e.logf("unhandled sequence: DCS %q %q", paramsString(cmd, params), data) 10 | } 11 | } 12 | 13 | // handleApc handles an APC escape sequence. 14 | func (e *Emulator) handleApc(data []byte) { 15 | e.flushGrapheme() // Flush any pending grapheme before handling APC sequences. 16 | if !e.handlers.handleApc(data) { 17 | e.logf("unhandled sequence: APC %q", data) 18 | } 19 | } 20 | 21 | // handleSos handles an SOS escape sequence. 22 | func (e *Emulator) handleSos(data []byte) { 23 | e.flushGrapheme() // Flush any pending grapheme before handling SOS sequences. 24 | if !e.handlers.handleSos(data) { 25 | e.logf("unhandled sequence: SOS %q", data) 26 | } 27 | } 28 | 29 | // handlePm handles a PM escape sequence. 30 | func (e *Emulator) handlePm(data []byte) { 31 | e.flushGrapheme() // Flush any pending grapheme before handling PM sequences. 32 | if !e.handlers.handlePm(data) { 33 | e.logf("unhandled sequence: PM %q", data) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/term-plan9.yml: -------------------------------------------------------------------------------- 1 | name: term-plan9 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | paths: 9 | - term/** 10 | - .github/workflows/term-plan9.yml 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | working-directory: ./term 18 | steps: 19 | - uses: actions/checkout@v6 20 | - uses: actions/setup-go@v6 21 | with: 22 | go-version-file: ./term/go.mod 23 | cache: true 24 | cache-dependency-path: ./term/go.sum 25 | - run: env GOOS=plan9 GOARCH=amd64 go build -v ./... 26 | 27 | dependabot: 28 | needs: [build] 29 | runs-on: ubuntu-latest 30 | permissions: 31 | pull-requests: write 32 | contents: write 33 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'}} 34 | steps: 35 | - id: metadata 36 | uses: dependabot/fetch-metadata@v2 37 | with: 38 | github-token: "${{ secrets.GITHUB_TOKEN }}" 39 | - run: | 40 | gh pr review --approve "$PR_URL" 41 | gh pr merge --squash --auto "$PR_URL" 42 | env: 43 | PR_URL: ${{github.event.pull_request.html_url}} 44 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 45 | -------------------------------------------------------------------------------- /input/input.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Event represents a terminal event. 9 | type Event any 10 | 11 | // UnknownEvent represents an unknown event. 12 | type UnknownEvent string 13 | 14 | // String returns a string representation of the unknown event. 15 | func (e UnknownEvent) String() string { 16 | return fmt.Sprintf("%q", string(e)) 17 | } 18 | 19 | // MultiEvent represents multiple messages event. 20 | type MultiEvent []Event 21 | 22 | // String returns a string representation of the multiple messages event. 23 | func (e MultiEvent) String() string { 24 | var sb strings.Builder 25 | for _, ev := range e { 26 | sb.WriteString(fmt.Sprintf("%v\n", ev)) 27 | } 28 | return sb.String() 29 | } 30 | 31 | // WindowSizeEvent is used to report the terminal size. Note that Windows does 32 | // not have support for reporting resizes via SIGWINCH signals and relies on 33 | // the Windows Console API to report window size changes. 34 | type WindowSizeEvent struct { 35 | Width int 36 | Height int 37 | } 38 | 39 | // WindowOpEvent is a window operation (XTWINOPS) report event. This is used to 40 | // report various window operations such as reporting the window size or cell 41 | // size. 42 | type WindowOpEvent struct { 43 | Op int 44 | Args []int 45 | } 46 | -------------------------------------------------------------------------------- /mosaic/go.sum: -------------------------------------------------------------------------------- 1 | github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI= 2 | github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI= 3 | github.com/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY= 4 | github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 5 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 6 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 7 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 8 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 9 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 10 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 11 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 12 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 13 | golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= 14 | golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= 15 | -------------------------------------------------------------------------------- /pony/examples/buttons/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | // Pin to PR #1549 which adds View callback support 9 | charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251120234600-e78528df7958 10 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 11 | ) 12 | 13 | require ( 14 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 15 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 16 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 17 | github.com/charmbracelet/x/term v0.2.2 // indirect 18 | github.com/charmbracelet/x/termios v0.1.1 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 24 | github.com/mattn/go-runewidth v0.0.19 // indirect 25 | github.com/muesli/cancelreader v0.2.2 // indirect 26 | github.com/rivo/uniseg v0.4.7 // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | golang.org/x/sync v0.18.0 // indirect 29 | golang.org/x/sys v0.38.0 // indirect 30 | golang.org/x/text v0.31.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /pony/examples/scrolling-markup/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | // Pin to PR #1549 which adds View callback support 9 | charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251120234600-e78528df7958 10 | github.com/charmbracelet/ultraviolet v0.0.0-20251124142205-05635edc50ab 11 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 12 | ) 13 | 14 | require ( 15 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 16 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 17 | github.com/charmbracelet/x/term v0.2.2 // indirect 18 | github.com/charmbracelet/x/termios v0.1.1 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 24 | github.com/mattn/go-runewidth v0.0.19 // indirect 25 | github.com/muesli/cancelreader v0.2.2 // indirect 26 | github.com/rivo/uniseg v0.4.7 // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | golang.org/x/sync v0.18.0 // indirect 29 | golang.org/x/sys v0.38.0 // indirect 30 | golang.org/x/text v0.31.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /ansi/graphics_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestKittyGraphics(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | payload []byte 11 | opts []string 12 | want string 13 | }{ 14 | { 15 | name: "empty payload no options", 16 | payload: []byte{}, 17 | opts: nil, 18 | want: "\x1b_G\x1b\\", 19 | }, 20 | { 21 | name: "with payload no options", 22 | payload: []byte("test"), 23 | opts: nil, 24 | want: "\x1b_G;test\x1b\\", 25 | }, 26 | { 27 | name: "with payload and options", 28 | payload: []byte("test"), 29 | opts: []string{"a=t", "f=100"}, 30 | want: "\x1b_Ga=t,f=100;test\x1b\\", 31 | }, 32 | { 33 | name: "multiple options no payload", 34 | payload: []byte{}, 35 | opts: []string{"q=2", "C=1", "f=24"}, 36 | want: "\x1b_Gq=2,C=1,f=24\x1b\\", 37 | }, 38 | { 39 | name: "with special characters in payload", 40 | payload: []byte("\x1b_G"), 41 | opts: []string{"a=t"}, 42 | want: "\x1b_Ga=t;\x1b_G\x1b\\", 43 | }, 44 | } 45 | 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | got := KittyGraphics(tt.payload, tt.opts...) 49 | if got != tt.want { 50 | t.Errorf("KittyGraphics() = %q, want %q", got, tt.want) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /input/termcap.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "strings" 7 | ) 8 | 9 | // CapabilityEvent represents a Termcap/Terminfo response event. Termcap 10 | // responses are generated by the terminal in response to RequestTermcap 11 | // (XTGETTCAP) requests. 12 | // 13 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands 14 | type CapabilityEvent string 15 | 16 | func parseTermcap(data []byte) CapabilityEvent { 17 | // XTGETTCAP 18 | if len(data) == 0 { 19 | return CapabilityEvent("") 20 | } 21 | 22 | var tc strings.Builder 23 | split := bytes.Split(data, []byte{';'}) 24 | for _, s := range split { 25 | parts := bytes.SplitN(s, []byte{'='}, 2) 26 | if len(parts) == 0 { 27 | return CapabilityEvent("") 28 | } 29 | 30 | name, err := hex.DecodeString(string(parts[0])) 31 | if err != nil || len(name) == 0 { 32 | continue 33 | } 34 | 35 | var value []byte 36 | if len(parts) > 1 { 37 | value, err = hex.DecodeString(string(parts[1])) 38 | if err != nil { 39 | continue 40 | } 41 | } 42 | 43 | if tc.Len() > 0 { 44 | tc.WriteByte(';') 45 | } 46 | tc.WriteString(string(name)) 47 | if len(value) > 0 { 48 | tc.WriteByte('=') 49 | tc.WriteString(string(value)) 50 | } 51 | } 52 | 53 | return CapabilityEvent(tc.String()) 54 | } 55 | -------------------------------------------------------------------------------- /pony/examples/interactive-form/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | // Pin to PR #1549 which adds View callback support 9 | charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251120234600-e78528df7958 10 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 11 | ) 12 | 13 | require ( 14 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 15 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 16 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 17 | github.com/charmbracelet/x/term v0.2.2 // indirect 18 | github.com/charmbracelet/x/termios v0.1.1 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 24 | github.com/mattn/go-runewidth v0.0.19 // indirect 25 | github.com/muesli/cancelreader v0.2.2 // indirect 26 | github.com/rivo/uniseg v0.4.7 // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | golang.org/x/sync v0.18.0 // indirect 29 | golang.org/x/sys v0.38.0 // indirect 30 | golang.org/x/text v0.31.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /ansi/gen.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "go/format" 10 | "log" 11 | "os" 12 | 13 | . "github.com/charmbracelet/x/ansi/parser" 14 | ) 15 | 16 | func main() { 17 | var f bytes.Buffer 18 | table := GenerateTransitionTable() 19 | _, _ = f.WriteString(`package parser 20 | 21 | // Code generated by gen.go. DO NOT EDIT. 22 | 23 | // Table is a DEC ANSI transition table. 24 | var Table = TransitionTable{ 25 | `) 26 | for i, v := range table { 27 | code := i & 0xFF 28 | state := v & TransitionStateMask 29 | action := v >> TransitionActionShift 30 | next := v & TransitionStateMask 31 | 32 | format := "\t%s< GroundState { 35 | format += " | %s" 36 | args = append(args, StateNames[next]) 37 | } 38 | format += "," 39 | fmt.Fprintf(&f, format, args...) 40 | fmt.Fprintf(&f, " // %d: %s << IndexStateShift | 0x%02x", i, StateNames[state], code) 41 | fmt.Fprintln(&f) 42 | } 43 | 44 | fmt.Fprintln(&f, "}") 45 | content, err := format.Source(f.Bytes()) 46 | if err != nil { 47 | log.Fatalf("formatting source: %v", err) 48 | } 49 | 50 | if err := os.WriteFile("parser/table.go", content, os.ModePerm); err != nil { 51 | log.Fatalf("writing file: %v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pony/examples/scrolling-with-clicks/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.25.4 4 | 5 | replace github.com/charmbracelet/x/pony => ../.. 6 | 7 | require ( 8 | // Pin to PR #1549 which adds View callback support 9 | charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251120234600-e78528df7958 10 | github.com/charmbracelet/x/pony v0.0.0-00010101000000-000000000000 11 | ) 12 | 13 | require ( 14 | github.com/charmbracelet/colorprofile v0.3.3 // indirect 15 | github.com/charmbracelet/ultraviolet v0.0.0-20251120225753-26363bddd922 // indirect 16 | github.com/charmbracelet/x/ansi v0.11.1 // indirect 17 | github.com/charmbracelet/x/term v0.2.2 // indirect 18 | github.com/charmbracelet/x/termios v0.1.1 // indirect 19 | github.com/charmbracelet/x/windows v0.2.2 // indirect 20 | github.com/clipperhouse/displaywidth v0.5.0 // indirect 21 | github.com/clipperhouse/stringish v0.1.1 // indirect 22 | github.com/clipperhouse/uax29/v2 v2.3.0 // indirect 23 | github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 24 | github.com/mattn/go-runewidth v0.0.19 // indirect 25 | github.com/muesli/cancelreader v0.2.2 // indirect 26 | github.com/rivo/uniseg v0.4.7 // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | golang.org/x/sync v0.18.0 // indirect 29 | golang.org/x/sys v0.38.0 // indirect 30 | golang.org/x/text v0.31.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /errors/join.go: -------------------------------------------------------------------------------- 1 | // Package errors provides error handling utilities. 2 | package errors 3 | 4 | import "strings" 5 | 6 | // Join returns an error that wraps the given errors. 7 | // Any nil error values are discarded. 8 | // Join returns nil if every value in errs is nil. 9 | // The error formats as the concatenation of the strings obtained 10 | // by calling the Error method of each element of errs, with a newline 11 | // between each string. 12 | // 13 | // A non-nil error returned by Join implements the Unwrap() []error method. 14 | // 15 | // This is copied from Go 1.20 errors.Unwrap, with some tuning to avoid using unsafe. 16 | // The main goal is to have this available in older Go versions. 17 | func Join(errs ...error) error { 18 | var nonNil []error //nolint:prealloc 19 | for _, err := range errs { 20 | if err == nil { 21 | continue 22 | } 23 | nonNil = append(nonNil, err) 24 | } 25 | if len(nonNil) == 0 { 26 | return nil 27 | } 28 | return &joinError{ 29 | errs: nonNil, 30 | } 31 | } 32 | 33 | type joinError struct { 34 | errs []error 35 | } 36 | 37 | func (e *joinError) Error() string { 38 | strs := make([]string, 0, len(e.errs)) 39 | for _, err := range e.errs { 40 | strs = append(strs, err.Error()) 41 | } 42 | return strings.Join(strs, "\n") 43 | } 44 | 45 | func (e *joinError) Unwrap() []error { 46 | return e.errs 47 | } 48 | -------------------------------------------------------------------------------- /pony/constants.go: -------------------------------------------------------------------------------- 1 | package pony 2 | 3 | // Border styles. 4 | const ( 5 | BorderNone = "none" 6 | BorderNormal = "normal" 7 | BorderRounded = "rounded" 8 | BorderThick = "thick" 9 | BorderDouble = "double" 10 | BorderHidden = "hidden" 11 | ) 12 | 13 | // Alignment constants. 14 | const ( 15 | AlignmentLeading = "leading" // Horizontal: left side 16 | AlignmentCenter = "center" // Horizontal and vertical: center 17 | AlignmentTrailing = "trailing" // Horizontal: right side 18 | AlignmentTop = "top" // Vertical: top 19 | AlignmentBottom = "bottom" // Vertical: bottom 20 | ) 21 | 22 | // Size constraint units. 23 | const ( 24 | UnitAuto = "auto" 25 | UnitMin = "min" 26 | UnitMax = "max" 27 | UnitPercent = "%" 28 | ) 29 | 30 | // Underline styles (matching UV). 31 | const ( 32 | UnderlineNone = "none" 33 | UnderlineSingle = "single" 34 | UnderlineDouble = "double" 35 | UnderlineCurly = "curly" 36 | UnderlineDotted = "dotted" 37 | UnderlineDashed = "dashed" 38 | UnderlineSolid = "solid" 39 | ) 40 | 41 | // Text decoration styles. 42 | const ( 43 | DecorationUnderline = "underline" 44 | DecorationStrikethrough = "strikethrough" 45 | ) 46 | 47 | // Font weight values. 48 | const ( 49 | FontWeightBold = "bold" 50 | ) 51 | 52 | // Font style values. 53 | const ( 54 | FontStyleItalic = "italic" 55 | ) 56 | -------------------------------------------------------------------------------- /pony/slot.go: -------------------------------------------------------------------------------- 1 | package pony 2 | 3 | import ( 4 | uv "github.com/charmbracelet/ultraviolet" 5 | ) 6 | 7 | // Slot represents a placeholder for dynamic content. 8 | // Slots are filled with Elements passed via RenderWithSlots. 9 | type Slot struct { 10 | BaseElement 11 | Name string 12 | element Element // The actual element to render (filled during render) 13 | } 14 | 15 | var _ Element = (*Slot)(nil) 16 | 17 | // NewSlot creates a new slot element. 18 | func NewSlot(name string) *Slot { 19 | return &Slot{Name: name} 20 | } 21 | 22 | // Draw renders the slot's element if it exists. 23 | func (s *Slot) Draw(scr uv.Screen, area uv.Rectangle) { 24 | s.SetBounds(area) 25 | 26 | if s.element != nil { 27 | s.element.Draw(scr, area) 28 | } 29 | } 30 | 31 | // Layout calculates the slot's element size if it exists. 32 | func (s *Slot) Layout(constraints Constraints) Size { 33 | if s.element != nil { 34 | return s.element.Layout(constraints) 35 | } 36 | return Size{Width: 0, Height: 0} 37 | } 38 | 39 | // Children returns the slot's element children if it exists. 40 | func (s *Slot) Children() []Element { 41 | if s.element != nil { 42 | return []Element{s.element} 43 | } 44 | return nil 45 | } 46 | 47 | // setElement sets the element for this slot (used internally during rendering). 48 | func (s *Slot) setElement(elem Element) { 49 | s.element = elem 50 | } 51 | -------------------------------------------------------------------------------- /vt/csi.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "github.com/charmbracelet/x/ansi" 9 | ) 10 | 11 | func (e *Emulator) handleCsi(cmd ansi.Cmd, params ansi.Params) { 12 | e.flushGrapheme() // Flush any pending grapheme before handling CSI sequences. 13 | if !e.handlers.handleCsi(cmd, params) { 14 | e.logf("unhandled sequence: CSI %q", paramsString(cmd, params)) 15 | } 16 | } 17 | 18 | func (e *Emulator) handleRequestMode(params ansi.Params, isAnsi bool) { 19 | n, _, ok := params.Param(0, 0) 20 | if !ok || n == 0 { 21 | return 22 | } 23 | 24 | var mode ansi.Mode = ansi.DECMode(n) 25 | if isAnsi { 26 | mode = ansi.ANSIMode(n) 27 | } 28 | 29 | setting := e.modes[mode] 30 | _, _ = io.WriteString(e.pw, ansi.ReportMode(mode, setting)) 31 | } 32 | 33 | func paramsString(cmd ansi.Cmd, params ansi.Params) string { 34 | var s strings.Builder 35 | if mark := cmd.Prefix(); mark != 0 { 36 | s.WriteByte(mark) 37 | } 38 | params.ForEach(-1, func(i, p int, more bool) { 39 | s.WriteString(fmt.Sprintf("%d", p)) 40 | if i < len(params)-1 { 41 | if more { 42 | s.WriteByte(':') 43 | } else { 44 | s.WriteByte(';') 45 | } 46 | } 47 | }) 48 | if inter := cmd.Intermediate(); inter != 0 { 49 | s.WriteByte(inter) 50 | } 51 | if final := cmd.Final(); final != 0 { 52 | s.WriteByte(final) 53 | } 54 | return s.String() 55 | } 56 | -------------------------------------------------------------------------------- /pony/spacer.go: -------------------------------------------------------------------------------- 1 | package pony 2 | 3 | import uv "github.com/charmbracelet/ultraviolet" 4 | 5 | // Spacer represents empty space that can grow to fill available space. 6 | type Spacer struct { 7 | BaseElement 8 | fixedSize int // fixed size, 0 means flexible 9 | } 10 | 11 | var _ Element = (*Spacer)(nil) 12 | 13 | // NewSpacer creates a new spacer element. 14 | func NewSpacer() *Spacer { 15 | return &Spacer{} 16 | } 17 | 18 | // NewFixedSpacer creates a new spacer with fixed size. 19 | func NewFixedSpacer(size int) *Spacer { 20 | return &Spacer{fixedSize: size} 21 | } 22 | 23 | // FixedSize sets the size and returns the spacer for chaining. 24 | func (s *Spacer) FixedSize(size int) *Spacer { 25 | s.fixedSize = size 26 | return s 27 | } 28 | 29 | // Draw renders the spacer (nothing to draw). 30 | func (s *Spacer) Draw(_ uv.Screen, area uv.Rectangle) { 31 | s.SetBounds(area) 32 | // Spacers are invisible 33 | } 34 | 35 | // Layout calculates the spacer size. 36 | func (s *Spacer) Layout(constraints Constraints) Size { 37 | if s.fixedSize > 0 { 38 | return constraints.Constrain(Size{Width: s.fixedSize, Height: s.fixedSize}) 39 | } 40 | // Flexible spacer - take all available space 41 | return Size{Width: constraints.MaxWidth, Height: constraints.MaxHeight} 42 | } 43 | 44 | // Children returns nil for spacers. 45 | func (s *Spacer) Children() []Element { 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /etag/etag.go: -------------------------------------------------------------------------------- 1 | // Package etag provides utilities for generating and handling ETag headers in 2 | // HTTP requests and responses. 3 | package etag 4 | 5 | import ( 6 | "crypto/sha256" 7 | "fmt" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | // Of returns the etag for the given data. 13 | func Of(data []byte) string { 14 | hash := sha256.Sum256(data) 15 | return fmt.Sprintf(`%x`, hash[:16]) 16 | } 17 | 18 | // Request sets the `If-None-Match` header in the given request, appropriately 19 | // quoting the etag value. 20 | func Request(req *http.Request, etag string) { 21 | if etag == "" { 22 | return 23 | } 24 | req.Header.Add("If-None-Match", fmt.Sprintf(`"%s"`, etag)) 25 | } 26 | 27 | // Response sets the `ETag` header in the given response writer, appropriately 28 | // quoting the etag value. 29 | func Response(w http.ResponseWriter, etag string) { 30 | if etag == "" { 31 | return 32 | } 33 | w.Header().Set("ETag", fmt.Sprintf(`"%s"`, etag)) 34 | } 35 | 36 | // Matches checks if the given request has `If-None-Match` header matching the 37 | // given etag. 38 | func Matches(r *http.Request, etag string) bool { 39 | header := r.Header.Get("If-None-Match") 40 | if header == "" || etag == "" { 41 | return false 42 | } 43 | return unquote(header) == unquote(etag) 44 | } 45 | 46 | func unquote(s string) string { 47 | return strings.TrimSuffix(strings.TrimPrefix(s, `"`), `"`) 48 | } 49 | -------------------------------------------------------------------------------- /gitignore/matcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The go-git authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // This file was originally part of https://github.com/go-git/go-git/blob/main/plumbing/format/gitattributes 16 | // and has been modified to provide a simplified matcher that works with a single pattern. 17 | 18 | package gitignore 19 | 20 | // Matcher defines a matcher for a single gitignore pattern 21 | type Matcher struct { 22 | pattern Pattern 23 | } 24 | 25 | // NewMatcher constructs a new simple matcher for a single pattern 26 | func NewMatcher(pattern Pattern) *Matcher { 27 | return &Matcher{pattern: pattern} 28 | } 29 | 30 | // Match matches the given path against the single pattern 31 | func (m *Matcher) Match(path []string, isDir bool) bool { 32 | match := m.pattern.Match(path, isDir) 33 | return match == Exclude 34 | } 35 | -------------------------------------------------------------------------------- /exp/toner/go.sum: -------------------------------------------------------------------------------- 1 | github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI= 2 | github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI= 3 | github.com/charmbracelet/x/exp/charmtone v0.0.0-20250602192518-9e722df69bbb h1:oTM8tZxV7FY0ehvYjFuICouuhzE08UZYNqUIp/lDQdY= 4 | github.com/charmbracelet/x/exp/charmtone v0.0.0-20250602192518-9e722df69bbb/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0= 5 | github.com/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY= 6 | github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 7 | github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= 8 | github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= 9 | github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= 10 | github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 11 | github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= 12 | github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 13 | github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 14 | github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 15 | -------------------------------------------------------------------------------- /term/term_other.go: -------------------------------------------------------------------------------- 1 | //go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !zos && !windows && !solaris && !plan9 2 | // +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!zos,!windows,!solaris,!plan9 3 | 4 | package term 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | ) 10 | 11 | type state struct{} 12 | 13 | func isTerminal(fd uintptr) bool { 14 | return false 15 | } 16 | 17 | func makeRaw(fd uintptr) (*State, error) { 18 | return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 19 | } 20 | 21 | func getState(fd uintptr) (*State, error) { 22 | return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 23 | } 24 | 25 | func restore(fd uintptr, state *State) error { 26 | return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 27 | } 28 | 29 | func getSize(fd uintptr) (width, height int, err error) { 30 | return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 31 | } 32 | 33 | func setState(fd uintptr, state *State) error { 34 | return fmt.Errorf("terminal: SetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 35 | } 36 | 37 | func readPassword(fd uintptr) ([]byte, error) { 38 | return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 39 | } 40 | -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | 3 | version: "3" 4 | 5 | vars: 6 | PACKAGES: 7 | [ 8 | ansi, 9 | cellbuf, 10 | colors, 11 | conpty, 12 | editor, 13 | errors, 14 | examples, 15 | exp/golden, 16 | exp/higherorder, 17 | exp/maps, 18 | exp/open, 19 | exp/ordered, 20 | exp/slice, 21 | exp/strings, 22 | exp/teatest, 23 | exp/teatest/v2, 24 | input, 25 | json, 26 | sshkey, 27 | term, 28 | termios, 29 | vt, 30 | wcwidth, 31 | windows, 32 | xpty, 33 | ] 34 | 35 | tasks: 36 | fmt: 37 | desc: Run gofumpt for all packages 38 | cmds: 39 | - for: { var: PACKAGES } 40 | cmd: cd {{.ITEM}} && gofmt -s -w . 41 | 42 | modernize: 43 | desc: Run modernize for all packages 44 | cmds: 45 | - for: { var: PACKAGES } 46 | cmd: cd {{.ITEM}} && modernize -fix ./... 47 | 48 | lint: 49 | desc: Run base linters for all packages 50 | cmds: 51 | - for: { var: PACKAGES } 52 | cmd: cd {{.ITEM}} && golangci-lint run 53 | 54 | test: 55 | desc: Run tests for all packages 56 | cmds: 57 | - for: { var: PACKAGES } 58 | cmd: cd {{.ITEM}} && go test ./... {{.CLI_ARGS}} 59 | 60 | tidy: 61 | desc: Run `go mod tidy` for all packages 62 | cmds: 63 | - for: { var: PACKAGES } 64 | cmd: cd {{.ITEM}} && go mod tidy 65 | -------------------------------------------------------------------------------- /vt/mode.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import "github.com/charmbracelet/x/ansi" 4 | 5 | // resetModes resets all modes to their default values. 6 | func (e *Emulator) resetModes() { 7 | e.modes = ansi.Modes{ 8 | // Recognized modes and their default values. 9 | ansi.ModeCursorKeys: ansi.ModeReset, // ?1 10 | ansi.ModeOrigin: ansi.ModeReset, // ?6 11 | ansi.ModeAutoWrap: ansi.ModeSet, // ?7 12 | ansi.ModeMouseX10: ansi.ModeReset, // ?9 13 | ansi.ModeLineFeedNewLine: ansi.ModeReset, // ?20 14 | ansi.ModeTextCursorEnable: ansi.ModeSet, // ?25 15 | ansi.ModeNumericKeypad: ansi.ModeReset, // ?66 16 | ansi.ModeLeftRightMargin: ansi.ModeReset, // ?69 17 | ansi.ModeMouseNormal: ansi.ModeReset, // ?1000 18 | ansi.ModeMouseHighlight: ansi.ModeReset, // ?1001 19 | ansi.ModeMouseButtonEvent: ansi.ModeReset, // ?1002 20 | ansi.ModeMouseAnyEvent: ansi.ModeReset, // ?1003 21 | ansi.ModeFocusEvent: ansi.ModeReset, // ?1004 22 | ansi.ModeMouseExtSgr: ansi.ModeReset, // ?1006 23 | ansi.ModeAltScreen: ansi.ModeReset, // ?1047 24 | ansi.ModeSaveCursor: ansi.ModeReset, // ?1048 25 | ansi.ModeAltScreenSaveCursor: ansi.ModeReset, // ?1049 26 | ansi.ModeBracketedPaste: ansi.ModeReset, // ?2004 27 | } 28 | 29 | // Set mode effects. 30 | for mode, setting := range e.modes { 31 | e.setMode(mode, setting) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ansi/background_test.go: -------------------------------------------------------------------------------- 1 | package ansi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | "github.com/lucasb-eyer/go-colorful" 8 | ) 9 | 10 | func TestSetForegroundColorNil(t *testing.T) { 11 | s := ansi.SetForegroundColor("") 12 | if s != "\x1b]10;\x07" { 13 | t.Errorf("Unexpected string for SetForegroundColor: got %q", s) 14 | } 15 | } 16 | 17 | func TestStringImplementations(t *testing.T) { 18 | brightMagenta, ok := colorful.MakeColor(ansi.BrightMagenta) 19 | if !ok { 20 | t.Fatalf("Failed to create color for BrightMagenta: %v", ansi.BrightMagenta) 21 | } 22 | color255, ok := colorful.MakeColor(ansi.ExtendedColor(255)) 23 | if !ok { 24 | t.Fatalf("Failed to create color for ExtendedColor(255): %v", ansi.ExtendedColor(255)) 25 | } 26 | hexColor := ansi.HexColor("#ffeeaa") 27 | foregroundColor := ansi.SetForegroundColor(brightMagenta.Hex()) 28 | backgroundColor := ansi.SetBackgroundColor(color255.Hex()) 29 | cursorColor := ansi.SetCursorColor(hexColor.Hex()) 30 | 31 | if foregroundColor != "\x1b]10;#ff00ff\x07" { 32 | t.Errorf("Unexpected string for SetForegroundColor: got %q", 33 | foregroundColor) 34 | } 35 | if backgroundColor != "\x1b]11;#eeeeee\x07" { 36 | t.Errorf("Unexpected string for SetBackgroundColor: got %q", 37 | backgroundColor) 38 | } 39 | if cursorColor != "\x1b]12;#ffeeaa\x07" { 40 | t.Errorf("Unexpected string for SetCursorColor: got %q", 41 | cursorColor) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /exp/teatest/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/exp/teatest 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/charmbracelet/bubbletea v1.3.5 7 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a 8 | ) 9 | 10 | require ( 11 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 | github.com/aymanbagabas/go-udiff v0.3.1 // indirect 13 | github.com/charmbracelet/colorprofile v0.3.2 // indirect 14 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 15 | github.com/charmbracelet/x/ansi v0.10.1 // indirect 16 | github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect 17 | github.com/charmbracelet/x/term v0.2.1 // indirect 18 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 19 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/mattn/go-localereader v0.0.1 // indirect 22 | github.com/mattn/go-runewidth v0.0.16 // indirect 23 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 24 | github.com/muesli/cancelreader v0.2.2 // indirect 25 | github.com/muesli/termenv v0.16.0 // indirect 26 | github.com/rivo/uniseg v0.4.7 // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 29 | golang.org/x/sync v0.16.0 // indirect 30 | golang.org/x/sys v0.35.0 // indirect 31 | golang.org/x/text v0.28.0 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /input/xterm.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "github.com/charmbracelet/x/ansi" 5 | ) 6 | 7 | func parseXTermModifyOtherKeys(params ansi.Params) Event { 8 | // XTerm modify other keys starts with ESC [ 27 ; ; ~ 9 | xmod, _, _ := params.Param(1, 1) 10 | xrune, _, _ := params.Param(2, 1) 11 | mod := KeyMod(xmod - 1) 12 | r := rune(xrune) 13 | 14 | switch r { 15 | case ansi.BS: 16 | return KeyPressEvent{Mod: mod, Code: KeyBackspace} 17 | case ansi.HT: 18 | return KeyPressEvent{Mod: mod, Code: KeyTab} 19 | case ansi.CR: 20 | return KeyPressEvent{Mod: mod, Code: KeyEnter} 21 | case ansi.ESC: 22 | return KeyPressEvent{Mod: mod, Code: KeyEscape} 23 | case ansi.DEL: 24 | return KeyPressEvent{Mod: mod, Code: KeyBackspace} 25 | } 26 | 27 | // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys 28 | k := KeyPressEvent{Code: r, Mod: mod} 29 | if k.Mod <= ModShift { 30 | k.Text = string(r) 31 | } 32 | 33 | return k 34 | } 35 | 36 | // TerminalVersionEvent is a message that represents the terminal version. 37 | type TerminalVersionEvent string 38 | 39 | // ModifyOtherKeysEvent represents a modifyOtherKeys event. 40 | // 41 | // 0: disable 42 | // 1: enable mode 1 43 | // 2: enable mode 2 44 | // 45 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ 46 | // See: https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyOtherKeys 47 | type ModifyOtherKeysEvent uint8 48 | -------------------------------------------------------------------------------- /powernap/pkg/lsp/types.go: -------------------------------------------------------------------------------- 1 | package lsp 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/charmbracelet/x/powernap/pkg/lsp/protocol" 8 | "github.com/charmbracelet/x/powernap/pkg/transport" 9 | ) 10 | 11 | // OffsetEncoding represents the character encoding used for text document offsets. 12 | type OffsetEncoding int 13 | 14 | const ( 15 | // UTF8 encoding - bytes 16 | UTF8 OffsetEncoding = iota 17 | // UTF16 encoding - default for LSP 18 | UTF16 19 | // UTF32 encoding - codepoints 20 | UTF32 21 | ) 22 | 23 | // Client represents an LSP client connection to a language server. 24 | type Client struct { 25 | ID string 26 | Name string 27 | conn *transport.Connection 28 | ctx context.Context 29 | cancel context.CancelFunc 30 | initialized bool 31 | shutdown bool 32 | capabilities protocol.ServerCapabilities 33 | offsetEncoding OffsetEncoding 34 | rootURI string 35 | workspaceFolders []protocol.WorkspaceFolder 36 | config map[string]any 37 | initOptions map[string]any 38 | } 39 | 40 | // ClientConfig represents the configuration for creating a new LSP client. 41 | type ClientConfig struct { 42 | Command string 43 | Args []string 44 | RootURI string 45 | WorkspaceFolders []protocol.WorkspaceFolder 46 | InitOptions map[string]any 47 | Settings map[string]any 48 | Environment map[string]string 49 | Timeout time.Duration 50 | } 51 | -------------------------------------------------------------------------------- /errors/join_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestJoin(t *testing.T) { 9 | t.Run("nil", func(t *testing.T) { 10 | err := Join(nil, nil, nil) 11 | if err != nil { 12 | t.Errorf("expected nil, got %v", err) 13 | } 14 | }) 15 | t.Run("one err", func(t *testing.T) { 16 | expected := fmt.Errorf("fake") 17 | err := Join(nil, expected, nil) 18 | je := err.(*joinError) 19 | un := je.Unwrap() 20 | if len(un) != 1 { 21 | t.Fatalf("expected 1 err, got %d", len(un)) 22 | } 23 | if s := un[0].Error(); s != expected.Error() { 24 | t.Errorf("expected %v, got %v", expected, un[0]) 25 | } 26 | if s := err.Error(); s != expected.Error() { 27 | t.Errorf("expected %s, got %s", expected, err) 28 | } 29 | }) 30 | t.Run("many errs", func(t *testing.T) { 31 | expected1 := fmt.Errorf("fake 1") 32 | expected2 := fmt.Errorf("fake 2") 33 | err := Join(nil, expected1, nil, nil, expected2, nil) 34 | je := err.(*joinError) 35 | un := je.Unwrap() 36 | if len(un) != 2 { 37 | t.Fatalf("expected 2 err, got %d", len(un)) 38 | } 39 | if s := un[0].Error(); s != expected1.Error() { 40 | t.Errorf("expected %v, got %v", expected1, un[0]) 41 | } 42 | if s := un[1].Error(); s != expected2.Error() { 43 | t.Errorf("expected %v, got %v", expected2, un[1]) 44 | } 45 | expectedS := expected1.Error() + "\n" + expected2.Error() 46 | if s := err.Error(); s != expectedS { 47 | t.Errorf("expected %s, got %s", expectedS, err) 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /input/parse_test.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "image/color" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/charmbracelet/x/ansi" 9 | ) 10 | 11 | func TestParseSequence_Events(t *testing.T) { 12 | input := []byte("\x1b\x1b[Ztest\x00\x1b]10;rgb:1234/1234/1234\x07\x1b[27;2;27~\x1b[?1049;2$y\x1b[4;1$y") 13 | want := []Event{ 14 | KeyPressEvent{Code: KeyTab, Mod: ModShift | ModAlt}, 15 | KeyPressEvent{Code: 't', Text: "t"}, 16 | KeyPressEvent{Code: 'e', Text: "e"}, 17 | KeyPressEvent{Code: 's', Text: "s"}, 18 | KeyPressEvent{Code: 't', Text: "t"}, 19 | KeyPressEvent{Code: KeySpace, Mod: ModCtrl}, 20 | ForegroundColorEvent{color.RGBA{R: 0x12, G: 0x12, B: 0x12, A: 0xff}}, 21 | KeyPressEvent{Code: KeyEscape, Mod: ModShift}, 22 | ModeReportEvent{Mode: ansi.AltScreenSaveCursorMode, Value: ansi.ModeReset}, 23 | ModeReportEvent{Mode: ansi.InsertReplaceMode, Value: ansi.ModeSet}, 24 | } 25 | 26 | var p Parser 27 | for i := 0; len(input) != 0; i++ { 28 | if i >= len(want) { 29 | t.Fatalf("reached end of want events") 30 | } 31 | n, got := p.parseSequence(input) 32 | if !reflect.DeepEqual(got, want[i]) { 33 | t.Errorf("got %#v (%T), want %#v (%T)", got, got, want[i], want[i]) 34 | } 35 | input = input[n:] 36 | } 37 | } 38 | 39 | func BenchmarkParseSequence(b *testing.B) { 40 | var p Parser 41 | input := []byte("\x1b\x1b[Ztest\x00\x1b]10;1234/1234/1234\x07\x1b[27;2;27~") 42 | b.ReportAllocs() 43 | b.ResetTimer() 44 | for i := 0; i < b.N; i++ { 45 | p.parseSequence(input) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pony/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev 2 | 3 | version: "3" 4 | 5 | tasks: 6 | default: 7 | desc: List all available tasks 8 | cmds: 9 | - task --list 10 | 11 | lint:install: 12 | desc: Install golangci-lint 13 | cmds: 14 | - go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest 15 | 16 | lint: 17 | desc: Run base linters 18 | cmds: 19 | - golangci-lint run --path-mode=abs --config=".golangci.yml" --timeout=5m 20 | 21 | lint:fix: 22 | desc: Run base linters and fix issues 23 | cmds: 24 | - golangci-lint run --path-mode=abs --config=".golangci.yml" --timeout=5m --fix 25 | 26 | fmt: 27 | desc: Format code 28 | cmds: 29 | - gofumpt -l -w . 30 | - goimports -l -w . 31 | 32 | test: 33 | desc: Run tests 34 | cmds: 35 | - go test -v -race ./... 36 | 37 | test:coverage: 38 | desc: Run tests with coverage 39 | cmds: 40 | - go test -v -race -coverprofile=coverage.out -covermode=atomic ./... 41 | - go tool cover -html=coverage.out -o coverage.html 42 | - echo "Coverage report generated at coverage.html" 43 | 44 | test:update: 45 | desc: Update golden test files 46 | cmds: 47 | - go test -update ./... 48 | 49 | clean: 50 | desc: Clean build artifacts and test cache 51 | cmds: 52 | - go clean -testcache 53 | - rm -f coverage.out coverage.html 54 | - find ./examples -type f -executable -delete 55 | 56 | tidy: 57 | desc: Tidy go.mod 58 | cmds: 59 | - go mod tidy 60 | -------------------------------------------------------------------------------- /ansi/palette_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "image/color" 5 | "testing" 6 | ) 7 | 8 | func TestSetPalette(t *testing.T) { 9 | cases := []struct { 10 | index int 11 | color color.Color 12 | want string 13 | }{ 14 | {-1, color.RGBA{255, 0, 0, 255}, ""}, 15 | {0, nil, ""}, 16 | {0, color.RGBA{255, 0, 0, 255}, "\x1b]P0ff0000\x07"}, 17 | {1, color.RGBA{0, 255, 0, 255}, "\x1b]P100ff00\x07"}, 18 | {2, color.RGBA{0, 0, 255, 255}, "\x1b]P20000ff\x07"}, 19 | {3, color.RGBA{255, 255, 0, 255}, "\x1b]P3ffff00\x07"}, 20 | {4, color.RGBA{255, 0, 255, 255}, "\x1b]P4ff00ff\x07"}, 21 | {5, color.RGBA{0, 255, 255, 255}, "\x1b]P500ffff\x07"}, 22 | {6, color.RGBA{192, 192, 192, 255}, "\x1b]P6c0c0c0\x07"}, 23 | {7, color.RGBA{128, 128, 128, 255}, "\x1b]P7808080\x07"}, 24 | {8, color.RGBA{255, 128, 128, 255}, "\x1b]P8ff8080\x07"}, 25 | {9, color.RGBA{128, 255, 128, 255}, "\x1b]P980ff80\x07"}, 26 | {10, color.RGBA{128, 128, 255, 255}, "\x1b]Pa8080ff\x07"}, 27 | {11, color.RGBA{255, 255, 128, 255}, "\x1b]Pbffff80\x07"}, 28 | {12, color.RGBA{255, 128, 255, 255}, "\x1b]Pcff80ff\x07"}, 29 | {13, color.RGBA{128, 255, 255, 255}, "\x1b]Pd80ffff\x07"}, 30 | {14, color.RGBA{192, 192, 192, 255}, "\x1b]Pec0c0c0\x07"}, 31 | {15, color.RGBA{0, 0, 0, 255}, "\x1b]Pf000000\x07"}, 32 | {16, color.RGBA{255, 0, 0, 255}, ""}, 33 | {256, nil, ""}, 34 | } 35 | for _, c := range cases { 36 | got := SetPalette(c.index, c.color) 37 | if got != c.want { 38 | t.Errorf("SetPalette(%d, %v) = %q; want %q", c.index, c.color, got, c.want) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /powernap/pkg/lsp/protocol/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /ansi/passthrough_test.go: -------------------------------------------------------------------------------- 1 | package ansi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/charmbracelet/x/ansi" 7 | ) 8 | 9 | var passthroughCases = []struct { 10 | name string 11 | seq string 12 | limit int 13 | screen string 14 | tmux string 15 | }{ 16 | { 17 | name: "empty", 18 | seq: "", 19 | screen: "\x1bP\x1b\\", 20 | tmux: "\x1bPtmux;\x1b\\", 21 | }, 22 | { 23 | name: "short", 24 | seq: "hello", 25 | screen: "\x1bPhello\x1b\\", 26 | tmux: "\x1bPtmux;hello\x1b\\", 27 | }, 28 | { 29 | name: "limit", 30 | seq: "foobarbaz", 31 | limit: 3, 32 | screen: "\x1bPfoo\x1b\\\x1bPbar\x1b\\\x1bPbaz\x1b\\", 33 | tmux: "\x1bPtmux;foobarbaz\x1b\\", 34 | }, 35 | { 36 | name: "escaped", 37 | seq: "\x1b]52;c;Zm9vYmFy\x07", 38 | screen: "\x1bP\x1b]52;c;Zm9vYmFy\x07\x1b\\", 39 | tmux: "\x1bPtmux;\x1b\x1b]52;c;Zm9vYmFy\x07\x1b\\", 40 | }, 41 | } 42 | 43 | func TestScreenPassthrough(t *testing.T) { 44 | for i, tt := range passthroughCases { 45 | t.Run(tt.name, func(t *testing.T) { 46 | got := ansi.ScreenPassthrough(tt.seq, tt.limit) 47 | if got != tt.screen { 48 | t.Errorf("case: %d, ScreenPassthrough() = %q, want %q", i+1, got, tt.screen) 49 | } 50 | }) 51 | } 52 | } 53 | 54 | func TestTmuxPassthrough(t *testing.T) { 55 | for i, tt := range passthroughCases { 56 | t.Run(tt.name, func(t *testing.T) { 57 | got := ansi.TmuxPassthrough(tt.seq) 58 | if got != tt.tmux { 59 | t.Errorf("case: %d, TmuxPassthrough() = %q, want %q", i+1, got, tt.tmux) 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ansi/title.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // SetIconNameWindowTitle returns a sequence for setting the icon name and 4 | // window title. 5 | // 6 | // OSC 0 ; title ST 7 | // OSC 0 ; title BEL 8 | // 9 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands 10 | func SetIconNameWindowTitle(s string) string { 11 | return "\x1b]0;" + s + "\x07" 12 | } 13 | 14 | // SetIconName returns a sequence for setting the icon name. 15 | // 16 | // OSC 1 ; title ST 17 | // OSC 1 ; title BEL 18 | // 19 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands 20 | func SetIconName(s string) string { 21 | return "\x1b]1;" + s + "\x07" 22 | } 23 | 24 | // SetWindowTitle returns a sequence for setting the window title. 25 | // 26 | // OSC 2 ; title ST 27 | // OSC 2 ; title BEL 28 | // 29 | // See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands 30 | func SetWindowTitle(s string) string { 31 | return "\x1b]2;" + s + "\x07" 32 | } 33 | 34 | // DECSWT is a sequence for setting the window title. 35 | // 36 | // This is an alias for [SetWindowTitle]("1;"). 37 | // See: EK-VT520-RM 5–156 https://vt100.net/dec/ek-vt520-rm.pdf 38 | func DECSWT(name string) string { 39 | return SetWindowTitle("1;" + name) 40 | } 41 | 42 | // DECSIN is a sequence for setting the icon name. 43 | // 44 | // This is an alias for [SetWindowTitle]("L;"). 45 | // See: EK-VT520-RM 5–134 https://vt100.net/dec/ek-vt520-rm.pdf 46 | func DECSIN(name string) string { 47 | return SetWindowTitle("L;" + name) 48 | } 49 | -------------------------------------------------------------------------------- /exp/teatest/teatest_test.go: -------------------------------------------------------------------------------- 1 | package teatest 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | "testing/iotest" 8 | "time" 9 | 10 | tea "github.com/charmbracelet/bubbletea" 11 | ) 12 | 13 | func TestWaitForErrorReader(t *testing.T) { 14 | err := doWaitFor(iotest.ErrReader(fmt.Errorf("fake")), func(bts []byte) bool { 15 | return true 16 | }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) 17 | if err == nil { 18 | t.Fatal("expected an error, got nil") 19 | } 20 | if err.Error() != "WaitFor: fake" { 21 | t.Fatalf("unexpected error: %s", err.Error()) 22 | } 23 | } 24 | 25 | func TestWaitForTimeout(t *testing.T) { 26 | err := doWaitFor(strings.NewReader("nope"), func(bts []byte) bool { 27 | return false 28 | }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) 29 | if err == nil { 30 | t.Fatal("expected an error, got nil") 31 | } 32 | if err.Error() != "WaitFor: condition not met after 1ms. Last output:\nnope" { 33 | t.Fatalf("unexpected error: %s", err.Error()) 34 | } 35 | } 36 | 37 | type m string 38 | 39 | func (m m) Init() tea.Cmd { return nil } 40 | func (m m) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil } 41 | func (m m) View() string { return string(m) } 42 | 43 | func TestWaitFinishedWithTimeoutFn(t *testing.T) { 44 | tm := NewTestModel(t, m("a")) 45 | var timedOut bool 46 | tm.WaitFinished(t, WithFinalTimeout(time.Nanosecond), WithTimeoutFn(func(testing.TB) { 47 | timedOut = true 48 | })) 49 | if !timedOut { 50 | t.Fatal("expected timedOut to be set") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /exp/teatest/v2/teatest_test.go: -------------------------------------------------------------------------------- 1 | package teatest 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | "testing/iotest" 8 | "time" 9 | 10 | tea "github.com/charmbracelet/bubbletea/v2" 11 | ) 12 | 13 | func TestWaitForErrorReader(t *testing.T) { 14 | err := doWaitFor(iotest.ErrReader(fmt.Errorf("fake")), func(bts []byte) bool { 15 | return true 16 | }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) 17 | if err == nil { 18 | t.Fatal("expected an error, got nil") 19 | } 20 | if err.Error() != "WaitFor: fake" { 21 | t.Fatalf("unexpected error: %s", err.Error()) 22 | } 23 | } 24 | 25 | func TestWaitForTimeout(t *testing.T) { 26 | err := doWaitFor(strings.NewReader("nope"), func(bts []byte) bool { 27 | return false 28 | }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) 29 | if err == nil { 30 | t.Fatal("expected an error, got nil") 31 | } 32 | if err.Error() != "WaitFor: condition not met after 1ms. Last output:\nnope" { 33 | t.Fatalf("unexpected error: %s", err.Error()) 34 | } 35 | } 36 | 37 | type m string 38 | 39 | func (m m) Init() tea.Cmd { return nil } 40 | func (m m) Update(tea.Msg) (tea.Model, tea.Cmd) { return m, nil } 41 | func (m m) View() string { return string(m) } 42 | 43 | func TestWaitFinishedWithTimeoutFn(t *testing.T) { 44 | tm := NewTestModel(t, m("a")) 45 | var timedOut bool 46 | tm.WaitFinished(t, WithFinalTimeout(time.Nanosecond), WithTimeoutFn(func(testing.TB) { 47 | timedOut = true 48 | })) 49 | if !timedOut { 50 | t.Fatal("expected timedOut to be set") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /json/json.go: -------------------------------------------------------------------------------- 1 | // Package json provides functions to facilitate dealing with JSON. 2 | package json 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | ) 11 | 12 | // Reader takes an input, marshal it to JSON and returns a io.Reader of it. 13 | func Reader[T any](v T) io.Reader { 14 | bts, err := json.Marshal(v) 15 | if err != nil { 16 | return &ErrorReader{err} 17 | } 18 | return bytes.NewReader(bts) 19 | } 20 | 21 | // ErrorReader is a reader that always errors with the given error. 22 | type ErrorReader struct { 23 | err error 24 | } 25 | 26 | func (r *ErrorReader) Read(_ []byte) (int, error) { 27 | return 0, r.err 28 | } 29 | 30 | // From parses a io.Reader with JSON. 31 | func From[T any](r io.Reader, t T) (T, error) { 32 | bts, err := io.ReadAll(r) 33 | if err != nil { 34 | return t, fmt.Errorf("failed to read response: %w", err) 35 | } 36 | if err := json.Unmarshal(bts, &t); err != nil { 37 | return t, fmt.Errorf("failed to parse body: %w: %s", err, bts) 38 | } 39 | return t, nil 40 | } 41 | 42 | // Write writes the given data as JSON. 43 | func Write(w http.ResponseWriter, data any) error { 44 | bts, err := json.Marshal(data) 45 | if err != nil { 46 | return err //nolint:wrapcheck 47 | } 48 | w.Header().Add("Content-Type", "application/json") 49 | _, err = w.Write(bts) 50 | return err //nolint:wrapcheck 51 | } 52 | 53 | // IsValid checks if the given data is valid JSON. 54 | func IsValid[T string | []byte](data T) bool { 55 | if len(data) == 0 { // hot path 56 | return false 57 | } 58 | var m json.RawMessage 59 | err := json.Unmarshal([]byte(data), &m) 60 | return err == nil 61 | } 62 | -------------------------------------------------------------------------------- /ansi/kitty/encoder.go: -------------------------------------------------------------------------------- 1 | package kitty 2 | 3 | import ( 4 | "compress/zlib" 5 | "fmt" 6 | "image" 7 | "image/png" 8 | "io" 9 | ) 10 | 11 | // Encoder is an encoder for the Kitty graphics protocol. It supports encoding 12 | // images in the 24-bit [RGB], 32-bit [RGBA], and [PNG] formats, and 13 | // compressing the data using zlib. 14 | // The default format is 32-bit [RGBA]. 15 | type Encoder struct { 16 | // Uses zlib compression. 17 | Compress bool 18 | 19 | // Can be one of [RGBA], [RGB], or [PNG]. 20 | Format int 21 | } 22 | 23 | // Encode encodes the image data in the specified format and writes it to w. 24 | func (e *Encoder) Encode(w io.Writer, m image.Image) error { 25 | if m == nil { 26 | return nil 27 | } 28 | 29 | if e.Compress { 30 | zw := zlib.NewWriter(w) 31 | defer zw.Close() //nolint:errcheck 32 | w = zw 33 | } 34 | 35 | if e.Format == 0 { 36 | e.Format = RGBA 37 | } 38 | 39 | switch e.Format { 40 | case RGBA, RGB: 41 | bounds := m.Bounds() 42 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 43 | for x := bounds.Min.X; x < bounds.Max.X; x++ { 44 | r, g, b, a := m.At(x, y).RGBA() 45 | switch e.Format { 46 | case RGBA: 47 | w.Write([]byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)}) //nolint:errcheck,gosec 48 | case RGB: 49 | w.Write([]byte{byte(r >> 8), byte(g >> 8), byte(b >> 8)}) //nolint:errcheck,gosec 50 | } 51 | } 52 | } 53 | 54 | case PNG: 55 | if err := png.Encode(w, m); err != nil { 56 | return fmt.Errorf("failed to encode PNG: %w", err) 57 | } 58 | 59 | default: 60 | return fmt.Errorf("unsupported format: %d", e.Format) 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /ansi/sixel/repeat.go: -------------------------------------------------------------------------------- 1 | package sixel 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | ) 8 | 9 | // ErrInvalidRepeat is returned when a Repeat is invalid. 10 | var ErrInvalidRepeat = fmt.Errorf("invalid repeat") 11 | 12 | // WriteRepeat writes a Repeat to a writer. A repeat character is in the range 13 | // of '?' (0x3F) to '~' (0x7E). 14 | func WriteRepeat(w io.Writer, count int, char byte) (int, error) { 15 | return fmt.Fprintf(w, "%c%d%c", RepeatIntroducer, count, char) //nolint:wrapcheck 16 | } 17 | 18 | // Repeat represents a Sixel repeat introducer. 19 | type Repeat struct { 20 | Count int 21 | Char byte 22 | } 23 | 24 | // WriteTo writes a Repeat to a writer. 25 | func (r Repeat) WriteTo(w io.Writer) (int64, error) { 26 | n, err := WriteRepeat(w, r.Count, r.Char) 27 | return int64(n), err 28 | } 29 | 30 | // String returns the Repeat as a string. 31 | func (r Repeat) String() string { 32 | var b strings.Builder 33 | r.WriteTo(&b) //nolint:errcheck,gosec 34 | return b.String() 35 | } 36 | 37 | // DecodeRepeat decodes a Repeat from a byte slice. It returns the Repeat and 38 | // the number of bytes read. 39 | func DecodeRepeat(data []byte) (r Repeat, n int) { 40 | if len(data) == 0 || data[0] != RepeatIntroducer { 41 | return r, n 42 | } 43 | 44 | if len(data) < 3 { // The minimum length is 3: the introducer, a digit, and a character. 45 | return r, n 46 | } 47 | 48 | for n = 1; n < len(data); n++ { 49 | if data[n] >= '0' && data[n] <= '9' { 50 | r.Count = r.Count*10 + int(data[n]-'0') 51 | } else { 52 | r.Char = data[n] 53 | n++ // Include the character in the count. 54 | break 55 | } 56 | } 57 | 58 | return r, n 59 | } 60 | -------------------------------------------------------------------------------- /ansi/notification_test.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import "testing" 4 | 5 | func TestNotify(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | s string 9 | want string 10 | }{ 11 | { 12 | name: "basic", 13 | s: "Hello, World!", 14 | want: "\x1b]9;Hello, World!\x07", 15 | }, 16 | { 17 | name: "empty", 18 | s: "", 19 | want: "\x1b]9;\x07", 20 | }, 21 | { 22 | name: "special characters", 23 | s: "Line1\nLine2\tTabbed", 24 | want: "\x1b]9;Line1\nLine2\tTabbed\x07", 25 | }, 26 | } 27 | for _, tt := range tests { 28 | t.Run(tt.name, func(t *testing.T) { 29 | if got := Notify(tt.s); got != tt.want { 30 | t.Errorf("Notify() = %q, want %q", got, tt.want) 31 | } 32 | }) 33 | } 34 | } 35 | 36 | func TestDesktopNotification(t *testing.T) { 37 | tests := []struct { 38 | name string 39 | payload string 40 | metadata []string 41 | want string 42 | }{ 43 | { 44 | name: "basic", 45 | payload: "Task Completed", 46 | metadata: []string{}, 47 | want: "\x1b]99;;Task Completed\x07", 48 | }, 49 | { 50 | name: "with metadata", 51 | payload: "New Message", 52 | metadata: []string{"i=1", "a=focus"}, 53 | want: "\x1b]99;i=1:a=focus;New Message\x07", 54 | }, 55 | { 56 | name: "empty payload", 57 | payload: "", 58 | metadata: []string{"i=2"}, 59 | want: "\x1b]99;i=2;\x07", 60 | }, 61 | } 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | if got := DesktopNotification(tt.payload, tt.metadata...); got != tt.want { 65 | t.Errorf("DesktopNotification() = %q, want %q", got, tt.want) 66 | } 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ansi/style_test.go: -------------------------------------------------------------------------------- 1 | package ansi_test 2 | 3 | import ( 4 | "image/color" 5 | "testing" 6 | 7 | "github.com/charmbracelet/x/ansi" 8 | ) 9 | 10 | func TestReset(t *testing.T) { 11 | var s ansi.Style 12 | if s.String() != "\x1b[m" { 13 | t.Errorf("Unexpected reset sequence: %q", ansi.ResetStyle) 14 | } 15 | } 16 | 17 | func TestBold(t *testing.T) { 18 | var s ansi.Style 19 | s = s.Bold() 20 | if s.String() != "\x1b[1m" { 21 | t.Errorf("Unexpected bold sequence: %q", s) 22 | } 23 | } 24 | 25 | func TestDefaultBackground(t *testing.T) { 26 | var s ansi.Style 27 | s = s.DefaultBackgroundColor() 28 | if s.String() != "\x1b[49m" { 29 | t.Errorf("Unexpected default background sequence: %q", s) 30 | } 31 | } 32 | 33 | func TestSequence(t *testing.T) { 34 | var s ansi.Style 35 | s = s.Bold().Underline(true).ForegroundColor(ansi.ExtendedColor(255)) 36 | if s.String() != "\x1b[1;4;38;5;255m" { 37 | t.Errorf("Unexpected sequence: %q", s) 38 | } 39 | } 40 | 41 | func TestColorColor(t *testing.T) { 42 | s := ansi.NewStyle().Bold().Underline(true).ForegroundColor(color.Black) 43 | if s.String() != "\x1b[1;4;38;2;0;0;0m" { 44 | t.Errorf("Unexpected sequence: %q", s) 45 | } 46 | } 47 | 48 | func TestNilColors(t *testing.T) { 49 | var s ansi.Style 50 | s = s.ForegroundColor(nil).BackgroundColor(nil).UnderlineColor(nil) 51 | if s.String() != "\x1b[39;49;59m" { 52 | t.Errorf("Unexpected sequence: %q", s) 53 | } 54 | } 55 | 56 | func BenchmarkStyle(b *testing.B) { 57 | b.ReportAllocs() 58 | for i := 0; i < b.N; i++ { 59 | _ = ansi.NewStyle(). 60 | Bold(). 61 | UnderlineStyle(ansi.UnderlineStyleDouble). 62 | ForegroundColor(color.RGBA{255, 255, 255, 255}). 63 | String() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ansi/sixel/sixel_bench_test.go: -------------------------------------------------------------------------------- 1 | package sixel 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/png" 8 | "io" 9 | "os" 10 | "testing" 11 | 12 | "github.com/charmbracelet/x/ansi" 13 | // gosixel "github.com/mattn/go-sixel" 14 | ) 15 | 16 | // func BenchmarkEncodingGoSixel(b *testing.B) { 17 | // for i := 0; i < b.N; i++ { 18 | // raw, err := loadImage("../../fixtures/JigokudaniMonkeyPark.png") 19 | // if err != nil { 20 | // os.Exit(1) 21 | // } 22 | 23 | // b := bytes.NewBuffer(nil) 24 | // enc := gosixel.NewEncoder(b) 25 | // if err := enc.Encode(raw); err != nil { 26 | // fmt.Fprintln(os.Stderr, err) 27 | // os.Exit(1) 28 | // } 29 | 30 | // // fmt.Println(b) 31 | // } 32 | // } 33 | 34 | func writeSixelGraphics(w io.Writer, m image.Image) error { 35 | e := &Encoder{} 36 | 37 | data := bytes.NewBuffer(nil) 38 | if err := e.Encode(data, m); err != nil { 39 | return fmt.Errorf("failed to encode sixel image: %w", err) 40 | } 41 | 42 | _, err := io.WriteString(w, ansi.SixelGraphics(0, 1, 0, data.Bytes())) 43 | return err //nolint:wrapcheck 44 | } 45 | 46 | func BenchmarkEncodingXSixel(b *testing.B) { 47 | for i := 0; i < b.N; i++ { 48 | raw, err := loadImage("../../fixtures/JigokudaniMonkeyPark.png") 49 | if err != nil { 50 | os.Exit(1) 51 | } 52 | 53 | b := bytes.NewBuffer(nil) 54 | if err := writeSixelGraphics(b, raw); err != nil { 55 | fmt.Fprintln(os.Stderr, err) 56 | os.Exit(1) 57 | } 58 | 59 | // fmt.Println(b) 60 | } 61 | } 62 | 63 | func loadImage(path string) (image.Image, error) { 64 | f, err := os.Open(path) 65 | if err != nil { 66 | return nil, err //nolint:wrapcheck 67 | } 68 | return png.Decode(f) //nolint:wrapcheck 69 | } 70 | -------------------------------------------------------------------------------- /pony/examples/buttons/README.md: -------------------------------------------------------------------------------- 1 | # Button Click Example 2 | 3 | This example demonstrates mouse click handling in pony using Bubble Tea's View callback feature. 4 | 5 | ## Features 6 | 7 | - **Interactive Buttons**: Click buttons with your mouse 8 | - **Hit Testing**: Automatically detects which element was clicked 9 | - **Hover Detection**: Shows which element is under the cursor 10 | - **Stateless View**: View function remains pure using callbacks 11 | 12 | ## How It Works 13 | 14 | 1. **BoundsMap**: During rendering, every element records where it was drawn 15 | 2. **View Callback**: The View returns a callback that has access to the BoundsMap via closure 16 | 3. **Hit Testing**: When mouse events arrive, the callback uses `HitTest()` to find the clicked element 17 | 4. **Event Messages**: The callback returns commands that emit custom messages with element IDs 18 | 5. **Update**: The Update function handles these messages to update model state 19 | 20 | ## Requirements 21 | 22 | This example uses Bubble Tea PR #1549 which adds View callback support. The go.mod pins to the specific commit that includes this feature. 23 | 24 | ## Running 25 | 26 | ```bash 27 | go run main.go 28 | ``` 29 | 30 | Click the buttons to see mouse interaction in action! 31 | 32 | ## Architecture 33 | 34 | ``` 35 | View() renders -> returns (screen, boundsMap) 36 | -> creates View with callback that captures boundsMap 37 | -> callback does hit testing on mouse events 38 | -> returns Cmd with element ID 39 | Update() receives element ID message 40 | -> updates model based on which button was clicked 41 | ``` 42 | 43 | This keeps View() completely pure - no model mutation, just returning a callback closure. 44 | -------------------------------------------------------------------------------- /sshkey/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/charmbracelet/x/sshkey 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/charmbracelet/huh v0.8.0 7 | golang.org/x/crypto v0.46.0 8 | ) 9 | 10 | require ( 11 | github.com/atotto/clipboard v0.1.4 // indirect 12 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 13 | github.com/catppuccin/go v0.3.0 // indirect 14 | github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect 15 | github.com/charmbracelet/bubbletea v1.3.6 // indirect 16 | github.com/charmbracelet/colorprofile v0.3.2 // indirect 17 | github.com/charmbracelet/lipgloss v1.1.0 // indirect 18 | github.com/charmbracelet/x/ansi v0.10.1 // indirect 19 | github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 20 | github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect 21 | github.com/charmbracelet/x/term v0.2.1 // indirect 22 | github.com/dustin/go-humanize v1.0.1 // indirect 23 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 24 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 25 | github.com/mattn/go-isatty v0.0.20 // indirect 26 | github.com/mattn/go-localereader v0.0.1 // indirect 27 | github.com/mattn/go-runewidth v0.0.16 // indirect 28 | github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect 29 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 30 | github.com/muesli/cancelreader v0.2.2 // indirect 31 | github.com/muesli/termenv v0.16.0 // indirect 32 | github.com/rivo/uniseg v0.4.7 // indirect 33 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 34 | golang.org/x/sync v0.19.0 // indirect 35 | golang.org/x/sys v0.39.0 // indirect 36 | golang.org/x/text v0.32.0 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /vt/terminal.go: -------------------------------------------------------------------------------- 1 | package vt 2 | 3 | import ( 4 | "image/color" 5 | "io" 6 | 7 | uv "github.com/charmbracelet/ultraviolet" 8 | ) 9 | 10 | // Terminal represents a virtual terminal interface. 11 | type Terminal interface { 12 | BackgroundColor() color.Color 13 | Blur() 14 | Bounds() uv.Rectangle 15 | CellAt(x int, y int) *uv.Cell 16 | Close() error 17 | CursorColor() color.Color 18 | CursorPosition() uv.Position 19 | Draw(scr uv.Screen, area uv.Rectangle) 20 | Focus() 21 | ForegroundColor() color.Color 22 | Height() int 23 | IndexedColor(i int) color.Color 24 | InputPipe() io.Writer 25 | Paste(text string) 26 | Read(p []byte) (n int, err error) 27 | RegisterApcHandler(handler ApcHandler) 28 | RegisterCsiHandler(cmd int, handler CsiHandler) 29 | RegisterDcsHandler(cmd int, handler DcsHandler) 30 | RegisterEscHandler(cmd int, handler EscHandler) 31 | RegisterOscHandler(cmd int, handler OscHandler) 32 | RegisterPmHandler(handler PmHandler) 33 | RegisterSosHandler(handler SosHandler) 34 | Render() string 35 | Resize(width int, height int) 36 | SendKey(k uv.KeyEvent) 37 | SendKeys(keys ...uv.KeyEvent) 38 | SendMouse(m Mouse) 39 | SendText(text string) 40 | SetBackgroundColor(c color.Color) 41 | SetCallbacks(cb Callbacks) 42 | SetCell(x int, y int, c *uv.Cell) 43 | SetCursorColor(c color.Color) 44 | SetDefaultBackgroundColor(c color.Color) 45 | SetDefaultCursorColor(c color.Color) 46 | SetDefaultForegroundColor(c color.Color) 47 | SetForegroundColor(c color.Color) 48 | SetIndexedColor(i int, c color.Color) 49 | SetLogger(l Logger) 50 | String() string 51 | Touched() []*uv.LineData 52 | Width() int 53 | WidthMethod() uv.WidthMethod 54 | Write(p []byte) (n int, err error) 55 | WriteString(s string) (n int, err error) 56 | } 57 | -------------------------------------------------------------------------------- /term/term.go: -------------------------------------------------------------------------------- 1 | // Package term provides a platform-independent interfaces for interacting with 2 | // Terminal and TTY devices. 3 | package term 4 | 5 | // State contains platform-specific state of a terminal. 6 | type State struct { 7 | state 8 | } 9 | 10 | // IsTerminal returns whether the given file descriptor is a terminal. 11 | func IsTerminal(fd uintptr) bool { 12 | return isTerminal(fd) 13 | } 14 | 15 | // MakeRaw puts the terminal connected to the given file descriptor into raw 16 | // mode and returns the previous state of the terminal so that it can be 17 | // restored. 18 | func MakeRaw(fd uintptr) (*State, error) { 19 | return makeRaw(fd) 20 | } 21 | 22 | // GetState returns the current state of a terminal which may be useful to 23 | // restore the terminal after a signal. 24 | func GetState(fd uintptr) (*State, error) { 25 | return getState(fd) 26 | } 27 | 28 | // SetState sets the given state of the terminal. 29 | func SetState(fd uintptr, state *State) error { 30 | return setState(fd, state) 31 | } 32 | 33 | // Restore restores the terminal connected to the given file descriptor to a 34 | // previous state. 35 | func Restore(fd uintptr, oldState *State) error { 36 | return restore(fd, oldState) 37 | } 38 | 39 | // GetSize returns the visible dimensions of the given terminal. 40 | // 41 | // These dimensions don't include any scrollback buffer height. 42 | func GetSize(fd uintptr) (width, height int, err error) { 43 | return getSize(fd) 44 | } 45 | 46 | // ReadPassword reads a line of input from a terminal without local echo. This 47 | // is commonly used for inputting passwords and other sensitive data. The slice 48 | // returned does not include the \n. 49 | func ReadPassword(fd uintptr) ([]byte, error) { 50 | return readPassword(fd) 51 | } 52 | -------------------------------------------------------------------------------- /ansi/charset.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | // SelectCharacterSet sets the G-set character designator to the specified 4 | // character set. 5 | // 6 | // ESC Ps Pd 7 | // 8 | // Where Ps is the G-set character designator, and Pd is the identifier. 9 | // For 94-character sets, the designator can be one of: 10 | // - ( G0 11 | // - ) G1 12 | // - * G2 13 | // - + G3 14 | // 15 | // For 96-character sets, the designator can be one of: 16 | // - - G1 17 | // - . G2 18 | // - / G3 19 | // 20 | // Some common 94-character sets are: 21 | // - 0 DEC Special Drawing Set 22 | // - A United Kingdom (UK) 23 | // - B United States (USASCII) 24 | // 25 | // Examples: 26 | // 27 | // ESC ( B Select character set G0 = United States (USASCII) 28 | // ESC ( 0 Select character set G0 = Special Character and Line Drawing Set 29 | // ESC ) 0 Select character set G1 = Special Character and Line Drawing Set 30 | // ESC * A Select character set G2 = United Kingdom (UK) 31 | // 32 | // See: https://vt100.net/docs/vt510-rm/SCS.html 33 | func SelectCharacterSet(gset byte, charset byte) string { 34 | return "\x1b" + string(gset) + string(charset) 35 | } 36 | 37 | // SCS is an alias for SelectCharacterSet. 38 | func SCS(gset byte, charset byte) string { 39 | return SelectCharacterSet(gset, charset) 40 | } 41 | 42 | // LS1R (Locking Shift 1 Right) shifts G1 into GR character set. 43 | const LS1R = "\x1b~" 44 | 45 | // LS2 (Locking Shift 2) shifts G2 into GL character set. 46 | const LS2 = "\x1bn" 47 | 48 | // LS2R (Locking Shift 2 Right) shifts G2 into GR character set. 49 | const LS2R = "\x1b}" 50 | 51 | // LS3 (Locking Shift 3) shifts G3 into GL character set. 52 | const LS3 = "\x1bo" 53 | 54 | // LS3R (Locking Shift 3 Right) shifts G3 into GR character set. 55 | const LS3R = "\x1b|" 56 | -------------------------------------------------------------------------------- /ansi/winop.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | // ResizeWindowWinOp is a window operation that resizes the terminal 10 | // window. 11 | // 12 | // Deprecated: Use constant number directly with [WindowOp]. 13 | ResizeWindowWinOp = 4 14 | 15 | // RequestWindowSizeWinOp is a window operation that requests a report of 16 | // the size of the terminal window in pixels. The response is in the form: 17 | // CSI 4 ; height ; width t 18 | // 19 | // Deprecated: Use constant number directly with [WindowOp]. 20 | RequestWindowSizeWinOp = 14 21 | 22 | // RequestCellSizeWinOp is a window operation that requests a report of 23 | // the size of the terminal cell size in pixels. The response is in the form: 24 | // CSI 6 ; height ; width t 25 | // 26 | // Deprecated: Use constant number directly with [WindowOp]. 27 | RequestCellSizeWinOp = 16 28 | ) 29 | 30 | // WindowOp (XTWINOPS) is a sequence that manipulates the terminal window. 31 | // 32 | // CSI Ps ; Ps ; Ps t 33 | // 34 | // Ps is a semicolon-separated list of parameters. 35 | // See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps;Ps;Ps-t.1EB0 36 | func WindowOp(p int, ps ...int) string { 37 | if p <= 0 { 38 | return "" 39 | } 40 | 41 | if len(ps) == 0 { 42 | return "\x1b[" + strconv.Itoa(p) + "t" 43 | } 44 | 45 | params := make([]string, 0, len(ps)+1) 46 | params = append(params, strconv.Itoa(p)) 47 | for _, p := range ps { 48 | if p >= 0 { 49 | params = append(params, strconv.Itoa(p)) 50 | } 51 | } 52 | 53 | return "\x1b[" + strings.Join(params, ";") + "t" 54 | } 55 | 56 | // XTWINOPS is an alias for [WindowOp]. 57 | func XTWINOPS(p int, ps ...int) string { 58 | return WindowOp(p, ps...) 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/vt.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by scripts/builds. DO NOT EDIT. 2 | name: vt 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | paths: 10 | - vt/** 11 | - .github/workflows/vt.yml 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | runs-on: ${{ matrix.os }} 19 | defaults: 20 | run: 21 | working-directory: ./vt 22 | steps: 23 | - uses: actions/checkout@v6 24 | with: 25 | lfs: true 26 | - uses: actions/setup-go@v6 27 | with: 28 | go-version-file: ./vt/go.mod 29 | cache: true 30 | cache-dependency-path: ./vt/go.sum 31 | - run: go build -v ./... 32 | - run: | 33 | go test -race -covermode=atomic -coverprofile='coverage.txt' ./... 34 | - uses: codecov/codecov-action@v5 35 | with: 36 | file: ./coverage.txt 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | dependabot: 40 | needs: [build] 41 | runs-on: ubuntu-latest 42 | permissions: 43 | pull-requests: write 44 | contents: write 45 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'}} 46 | steps: 47 | - id: metadata 48 | uses: dependabot/fetch-metadata@v2 49 | with: 50 | github-token: "${{ secrets.GITHUB_TOKEN }}" 51 | - run: | 52 | gh pr review --approve "$PR_URL" 53 | gh pr merge --squash --auto "$PR_URL" 54 | env: 55 | PR_URL: ${{github.event.pull_request.html_url}} 56 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 57 | 58 | lint: 59 | uses: charmbracelet/meta/.github/workflows/lint.yml@main 60 | with: 61 | directory: vt 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/workflows/vcr.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by scripts/builds. DO NOT EDIT. 2 | name: vcr 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | paths: 10 | - vcr/** 11 | - .github/workflows/vcr.yml 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | runs-on: ${{ matrix.os }} 19 | defaults: 20 | run: 21 | working-directory: ./vcr 22 | steps: 23 | - uses: actions/checkout@v6 24 | with: 25 | lfs: true 26 | - uses: actions/setup-go@v6 27 | with: 28 | go-version-file: ./vcr/go.mod 29 | cache: true 30 | cache-dependency-path: ./vcr/go.sum 31 | - run: go build -v ./... 32 | - run: | 33 | go test -race -covermode=atomic -coverprofile='coverage.txt' ./... 34 | - uses: codecov/codecov-action@v5 35 | with: 36 | file: ./coverage.txt 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | dependabot: 40 | needs: [build] 41 | runs-on: ubuntu-latest 42 | permissions: 43 | pull-requests: write 44 | contents: write 45 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'}} 46 | steps: 47 | - id: metadata 48 | uses: dependabot/fetch-metadata@v2 49 | with: 50 | github-token: "${{ secrets.GITHUB_TOKEN }}" 51 | - run: | 52 | gh pr review --approve "$PR_URL" 53 | gh pr merge --squash --auto "$PR_URL" 54 | env: 55 | PR_URL: ${{github.event.pull_request.html_url}} 56 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 57 | 58 | lint: 59 | uses: charmbracelet/meta/.github/workflows/lint.yml@main 60 | with: 61 | directory: vcr 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/workflows/ansi.yml: -------------------------------------------------------------------------------- 1 | # auto-generated by scripts/builds. DO NOT EDIT. 2 | name: ansi 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | paths: 10 | - ansi/** 11 | - .github/workflows/ansi.yml 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | runs-on: ${{ matrix.os }} 19 | defaults: 20 | run: 21 | working-directory: ./ansi 22 | steps: 23 | - uses: actions/checkout@v6 24 | with: 25 | lfs: true 26 | - uses: actions/setup-go@v6 27 | with: 28 | go-version-file: ./ansi/go.mod 29 | cache: true 30 | cache-dependency-path: ./ansi/go.sum 31 | - run: go build -v ./... 32 | - run: | 33 | go test -race -covermode=atomic -coverprofile='coverage.txt' ./... 34 | - uses: codecov/codecov-action@v5 35 | with: 36 | file: ./coverage.txt 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | 39 | dependabot: 40 | needs: [build] 41 | runs-on: ubuntu-latest 42 | permissions: 43 | pull-requests: write 44 | contents: write 45 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'}} 46 | steps: 47 | - id: metadata 48 | uses: dependabot/fetch-metadata@v2 49 | with: 50 | github-token: "${{ secrets.GITHUB_TOKEN }}" 51 | - run: | 52 | gh pr review --approve "$PR_URL" 53 | gh pr merge --squash --auto "$PR_URL" 54 | env: 55 | PR_URL: ${{github.event.pull_request.html_url}} 56 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 57 | 58 | lint: 59 | uses: charmbracelet/meta/.github/workflows/lint.yml@main 60 | with: 61 | directory: ansi 62 | 63 | 64 | --------------------------------------------------------------------------------