-
Notifications
You must be signed in to change notification settings - Fork 282
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(textarea): Add multi-line text input
- Loading branch information
1 parent
42f85b4
commit 2fd583c
Showing
4 changed files
with
1,785 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package cursor | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
tea "github.com/charmbracelet/bubbletea" | ||
"github.com/charmbracelet/lipgloss" | ||
) | ||
|
||
const defaultBlinkSpeed = time.Millisecond * 530 | ||
|
||
// initialBlinkMsg initializes cursor blinking. | ||
type initialBlinkMsg struct{} | ||
|
||
// BlinkMsg signals that the cursor should blink. It contains metadata that | ||
// allows us to tell if the blink message is the one we're expecting. | ||
type BlinkMsg struct { | ||
id int | ||
tag int | ||
} | ||
|
||
// blinkCanceled is sent when a blink operation is canceled. | ||
type blinkCanceled struct{} | ||
|
||
// blinkCtx manages cursor blinking. | ||
type blinkCtx struct { | ||
ctx context.Context | ||
cancel context.CancelFunc | ||
} | ||
|
||
// Mode describes the behavior of the cursor. | ||
type Mode int | ||
|
||
// Available cursor modes. | ||
const ( | ||
CursorBlink Mode = iota | ||
CursorStatic | ||
CursorHide | ||
) | ||
|
||
// String returns the cursor mode in a human-readable format. This method is | ||
// provisional and for informational purposes only. | ||
func (c Mode) String() string { | ||
return [...]string{ | ||
"blink", | ||
"static", | ||
"hidden", | ||
}[c] | ||
} | ||
|
||
// Model is the Bubble Tea model for this cursor element. | ||
type Model struct { | ||
BlinkSpeed time.Duration | ||
// Style for styling the cursor block. | ||
Style lipgloss.Style | ||
// TextStyle is the style used for the cursor when it is hidden (when blinking). | ||
// I.e. displaying normal text. | ||
TextStyle lipgloss.Style | ||
|
||
// char is the character under the cursor | ||
char string | ||
// The ID of this Model as it relates to other cursors | ||
id int | ||
// focus indicates whether the containing input is focused | ||
focus bool | ||
// Cursor Blink state. | ||
Blink bool | ||
// Used to manage cursor blink | ||
blinkCtx *blinkCtx | ||
// The ID of the blink message we're expecting to receive. | ||
blinkTag int | ||
// cursorMode determines the behavior of the cursor | ||
cursorMode Mode | ||
} | ||
|
||
// New creates a new model with default settings. | ||
func New() Model { | ||
return Model{ | ||
BlinkSpeed: defaultBlinkSpeed, | ||
|
||
Blink: true, | ||
cursorMode: CursorBlink, | ||
|
||
blinkCtx: &blinkCtx{ | ||
ctx: context.Background(), | ||
}, | ||
} | ||
} | ||
|
||
// Update updates the cursor. | ||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||
switch msg := msg.(type) { | ||
case initialBlinkMsg: | ||
// We accept all initialBlinkMsgs generated by the Blink command. | ||
|
||
if m.cursorMode != CursorBlink || !m.focus { | ||
return m, nil | ||
} | ||
|
||
cmd := m.BlinkCmd() | ||
return m, cmd | ||
|
||
case BlinkMsg: | ||
// We're choosy about whether to accept blinkMsgs so that our cursor | ||
// only exactly when it should. | ||
|
||
// Is this model blink-able? | ||
if m.cursorMode != CursorBlink || !m.focus { | ||
return m, nil | ||
} | ||
|
||
// Were we expecting this blink message? | ||
if msg.id != m.id || msg.tag != m.blinkTag { | ||
return m, nil | ||
} | ||
|
||
var cmd tea.Cmd | ||
if m.cursorMode == CursorBlink { | ||
m.Blink = !m.Blink | ||
cmd = m.BlinkCmd() | ||
} | ||
return m, cmd | ||
|
||
case blinkCanceled: // no-op | ||
return m, nil | ||
} | ||
return m, nil | ||
} | ||
|
||
// CursorMode returns the model's cursor mode. For available cursor modes, see | ||
// type CursorMode. | ||
func (m Model) CursorMode() Mode { | ||
return m.cursorMode | ||
} | ||
|
||
// SetCursorMode sets the model's cursor mode. This method returns a command. | ||
// | ||
// For available cursor modes, see type CursorMode. | ||
func (m *Model) SetCursorMode(mode Mode) tea.Cmd { | ||
m.cursorMode = mode | ||
m.Blink = m.cursorMode == CursorHide || !m.focus | ||
if mode == CursorBlink { | ||
return Blink | ||
} | ||
return nil | ||
} | ||
|
||
// BlinkCmd is an command used to manage cursor blinking. | ||
func (m *Model) BlinkCmd() tea.Cmd { | ||
if m.cursorMode != CursorBlink { | ||
return nil | ||
} | ||
|
||
if m.blinkCtx != nil && m.blinkCtx.cancel != nil { | ||
m.blinkCtx.cancel() | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed) | ||
m.blinkCtx.cancel = cancel | ||
|
||
m.blinkTag++ | ||
|
||
return func() tea.Msg { | ||
defer cancel() | ||
<-ctx.Done() | ||
if ctx.Err() == context.DeadlineExceeded { | ||
return BlinkMsg{id: m.id, tag: m.blinkTag} | ||
} | ||
return blinkCanceled{} | ||
} | ||
} | ||
|
||
// Blink is a command used to initialize cursor blinking. | ||
func Blink() tea.Msg { | ||
return initialBlinkMsg{} | ||
} | ||
|
||
// Focus focuses the cursor to allow it to blink if desired. | ||
func (m *Model) Focus() tea.Cmd { | ||
m.focus = true | ||
m.Blink = m.cursorMode == CursorHide // show the cursor unless we've explicitly hidden it | ||
|
||
if m.cursorMode == CursorBlink && m.focus { | ||
return m.BlinkCmd() | ||
} | ||
return nil | ||
} | ||
|
||
// Blur blurs the cursor. | ||
func (m *Model) Blur() { | ||
m.focus = false | ||
m.Blink = true | ||
} | ||
|
||
// SetChar sets the character under the cursor. | ||
func (m *Model) SetChar(char string) { | ||
m.char = char | ||
} | ||
|
||
// View displays the cursor. | ||
func (m Model) View() string { | ||
if m.Blink { | ||
return m.TextStyle.Render(m.char) | ||
} | ||
return m.Style.Inline(true).Reverse(true).Render(m.char) | ||
} |
Oops, something went wrong.