Skip to content

Commit bb450fb

Browse files
committed
feat(editor): rune-wrap text
Fork and vendor github.com/charmbracelet/bubbles/textarea.
1 parent 989b91a commit bb450fb

File tree

8 files changed

+3702
-5
lines changed

8 files changed

+3702
-5
lines changed

editor/editor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"fmt"
1010
"strings"
1111

12+
"dbohdan.com/pago/textarea"
1213
"github.com/charmbracelet/bubbles/cursor"
13-
"github.com/charmbracelet/bubbles/textarea"
1414
tea "github.com/charmbracelet/bubbletea"
1515
style "github.com/charmbracelet/lipgloss"
1616
)
@@ -85,6 +85,7 @@ func Edit(title, initial string, save bool) (string, error) {
8585
}
8686

8787
ta := textarea.New()
88+
ta.SetWrapMode(textarea.RuneWrap)
8889
ta.CharLimit = editorCharLimit
8990
ta.Cursor.SetMode(cursor.CursorStatic)
9091
ta.ShowLineNumbers = false

go.mod

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ go 1.24
55
require (
66
filippo.io/age v1.2.1
77
github.com/BurntSushi/toml v1.4.0
8+
github.com/MakeNowJust/heredoc v1.0.0
89
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
910
github.com/adrg/xdg v0.5.3
1011
github.com/alecthomas/kong v1.12.1
1112
github.com/alecthomas/repr v0.5.1
1213
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
14+
github.com/atotto/clipboard v0.1.4
15+
github.com/aymanbagabas/go-udiff v0.2.0
1316
github.com/charmbracelet/bubbles v0.21.0
1417
github.com/charmbracelet/bubbletea v1.3.6
1518
github.com/charmbracelet/lipgloss v1.1.0
19+
github.com/charmbracelet/x/ansi v0.10.1
1620
github.com/go-git/go-git/v5 v5.16.2
1721
github.com/ktr0731/go-fuzzyfinder v0.9.0
22+
github.com/mattn/go-runewidth v0.0.16
1823
github.com/pquerna/otp v1.5.0
24+
github.com/rivo/uniseg v0.4.7
1925
github.com/tidwall/redcon v1.6.2
2026
github.com/valkey-io/valkey-go v1.0.64
2127
github.com/xlab/treeprint v1.2.0
@@ -28,11 +34,9 @@ require (
2834
filippo.io/edwards25519 v1.1.0 // indirect
2935
github.com/Microsoft/go-winio v0.6.2 // indirect
3036
github.com/ProtonMail/go-crypto v1.3.0 // indirect
31-
github.com/atotto/clipboard v0.1.4 // indirect
3237
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
3338
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
3439
github.com/charmbracelet/colorprofile v0.3.2 // indirect
35-
github.com/charmbracelet/x/ansi v0.10.1 // indirect
3640
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
3741
github.com/charmbracelet/x/term v0.2.1 // indirect
3842
github.com/cloudflare/circl v1.6.1 // indirect
@@ -51,14 +55,12 @@ require (
5155
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
5256
github.com/mattn/go-isatty v0.0.20 // indirect
5357
github.com/mattn/go-localereader v0.0.1 // indirect
54-
github.com/mattn/go-runewidth v0.0.16 // indirect
5558
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
5659
github.com/muesli/cancelreader v0.2.2 // indirect
5760
github.com/muesli/termenv v0.16.0 // indirect
5861
github.com/nsf/termbox-go v1.1.1 // indirect
5962
github.com/pjbgf/sha1cd v0.4.0 // indirect
6063
github.com/pkg/errors v0.9.1 // indirect
61-
github.com/rivo/uniseg v0.4.7 // indirect
6264
github.com/sergi/go-diff v1.4.0 // indirect
6365
github.com/skeema/knownhosts v1.3.1 // indirect
6466
github.com/tidwall/btree v1.8.1 // indirect

textarea/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020-2025 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.

textarea/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Text Area
2+
3+
This [Bubble Tea](https://github.com/charmbracelet/bubbletea) component is forked from [github.com/charmbracelet/bubbles/textarea](https://github.com/charmbracelet/bubbles/tree/master/textarea) to add rune wrap (as opposed to word wrap).
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Package memoization is an internal package that provides a simple memoization
2+
// for text area.
3+
package memoization
4+
5+
import (
6+
"container/list"
7+
"crypto/sha256"
8+
"fmt"
9+
"sync"
10+
)
11+
12+
// Hasher is an interface that requires a Hash method. The Hash method is
13+
// expected to return a string representation of the hash of the object.
14+
type Hasher interface {
15+
Hash() string
16+
}
17+
18+
// entry is a struct that holds a key-value pair. It is used as an element
19+
// in the evictionList of the MemoCache.
20+
type entry[T any] struct {
21+
key string
22+
value T
23+
}
24+
25+
// MemoCache is a struct that represents a cache with a set capacity. It
26+
// uses an LRU (Least Recently Used) eviction policy. It is safe for
27+
// concurrent use.
28+
type MemoCache[H Hasher, T any] struct {
29+
capacity int
30+
mutex sync.Mutex
31+
cache map[string]*list.Element // The cache holding the results
32+
evictionList *list.List // A list to keep track of the order for LRU
33+
hashableItems map[string]T // This map keeps track of the original hashable items (optional)
34+
}
35+
36+
// NewMemoCache is a function that creates a new MemoCache with a given
37+
// capacity. It returns a pointer to the created MemoCache.
38+
func NewMemoCache[H Hasher, T any](capacity int) *MemoCache[H, T] {
39+
return &MemoCache[H, T]{
40+
capacity: capacity,
41+
cache: make(map[string]*list.Element),
42+
evictionList: list.New(),
43+
hashableItems: make(map[string]T),
44+
}
45+
}
46+
47+
// Capacity is a method that returns the capacity of the MemoCache.
48+
func (m *MemoCache[H, T]) Capacity() int {
49+
return m.capacity
50+
}
51+
52+
// Size is a method that returns the current size of the MemoCache. It is
53+
// the number of items currently stored in the cache.
54+
func (m *MemoCache[H, T]) Size() int {
55+
m.mutex.Lock()
56+
defer m.mutex.Unlock()
57+
return m.evictionList.Len()
58+
}
59+
60+
// Get is a method that returns the value associated with the given
61+
// hashable item in the MemoCache. If there is no corresponding value, the
62+
// method returns nil.
63+
func (m *MemoCache[H, T]) Get(h H) (T, bool) {
64+
m.mutex.Lock()
65+
defer m.mutex.Unlock()
66+
67+
hashedKey := h.Hash()
68+
if element, found := m.cache[hashedKey]; found {
69+
m.evictionList.MoveToFront(element)
70+
return element.Value.(*entry[T]).value, true
71+
}
72+
var result T
73+
return result, false
74+
}
75+
76+
// Set is a method that sets the value for the given hashable item in the
77+
// MemoCache. If the cache is at capacity, it evicts the least recently
78+
// used item before adding the new item.
79+
func (m *MemoCache[H, T]) Set(h H, value T) {
80+
m.mutex.Lock()
81+
defer m.mutex.Unlock()
82+
83+
hashedKey := h.Hash()
84+
if element, found := m.cache[hashedKey]; found {
85+
m.evictionList.MoveToFront(element)
86+
element.Value.(*entry[T]).value = value
87+
return
88+
}
89+
90+
// Check if the cache is at capacity
91+
if m.evictionList.Len() >= m.capacity {
92+
// Evict the least recently used item from the cache
93+
toEvict := m.evictionList.Back()
94+
if toEvict != nil {
95+
evictedEntry := m.evictionList.Remove(toEvict).(*entry[T])
96+
delete(m.cache, evictedEntry.key)
97+
delete(m.hashableItems, evictedEntry.key) // if you're keeping track of original items
98+
}
99+
}
100+
101+
// Add the value to the cache and the evictionList
102+
newEntry := &entry[T]{
103+
key: hashedKey,
104+
value: value,
105+
}
106+
element := m.evictionList.PushFront(newEntry)
107+
m.cache[hashedKey] = element
108+
m.hashableItems[hashedKey] = value // if you're keeping track of original items
109+
}
110+
111+
// HString is a type that implements the Hasher interface for strings.
112+
type HString string
113+
114+
// Hash is a method that returns the hash of the string.
115+
func (h HString) Hash() string {
116+
return fmt.Sprintf("%x", sha256.Sum256([]byte(h)))
117+
}
118+
119+
// HInt is a type that implements the Hasher interface for integers.
120+
type HInt int
121+
122+
// Hash is a method that returns the hash of the integer.
123+
func (h HInt) Hash() string {
124+
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d", h))))
125+
}

0 commit comments

Comments
 (0)