feat: linewrap support (#35)
* feat: linewrap support * feat: support Terminal.app * fix: fix corner case of renderCompletion * refactor: remove verbose method * refactor: inlining to stringWidth
This commit is contained in:
parent
a34adaee8d
commit
5f0f837e16
142
render.go
142
render.go
@ -1,9 +1,5 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Render to render prompt information from state of Buffer.
|
||||
type Render struct {
|
||||
out ConsoleWriter
|
||||
@ -12,6 +8,9 @@ type Render struct {
|
||||
title string
|
||||
row uint16
|
||||
col uint16
|
||||
|
||||
previousCursor int
|
||||
|
||||
// colors,
|
||||
prefixTextColor Color
|
||||
prefixBGColor Color
|
||||
@ -88,48 +87,46 @@ func (r *Render) renderWindowTooSmall() {
|
||||
}
|
||||
|
||||
func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
suggestions := completions.GetSuggestions()
|
||||
if len(completions.GetSuggestions()) == 0 {
|
||||
return
|
||||
}
|
||||
prefix := r.getCurrentPrefix()
|
||||
formatted, width := formatSuggestions(
|
||||
suggestions,
|
||||
int(r.col)-len(prefix)-1, // -1 means a width of scrollbar
|
||||
)
|
||||
// +1 means a width of scrollbar.
|
||||
width += 1
|
||||
|
||||
windowHeight := len(completions.tmp)
|
||||
if windowHeight > int(completions.max) {
|
||||
windowHeight = int(completions.max)
|
||||
}
|
||||
formatted = formatted[completions.verticalScroll : completions.verticalScroll+windowHeight]
|
||||
r.prepareArea(windowHeight)
|
||||
|
||||
cursor := len(prefix) + len(buf.Document().TextBeforeCursor())
|
||||
x, _ := r.toPos(cursor)
|
||||
if x+width >= int(r.col) {
|
||||
r.out.CursorBackward(x + width - int(r.col))
|
||||
}
|
||||
|
||||
contentHeight := len(completions.tmp)
|
||||
|
||||
fractionVisible := float64(windowHeight) / float64(contentHeight)
|
||||
fractionAbove := float64(completions.verticalScroll) / float64(contentHeight)
|
||||
|
||||
scrollbarHeight := int(math.Min(float64(windowHeight), math.Max(1, float64(windowHeight)*fractionVisible)))
|
||||
scrollbarHeight := int(clamp(float64(windowHeight), 1, float64(windowHeight)*fractionVisible))
|
||||
scrollbarTop := int(float64(windowHeight) * fractionAbove)
|
||||
|
||||
isScrollThumb := func(row int) bool {
|
||||
return scrollbarTop <= row && row <= scrollbarTop+scrollbarHeight
|
||||
}
|
||||
|
||||
suggestions := completions.GetSuggestions()
|
||||
if l := len(completions.GetSuggestions()); l == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
prefix := r.getCurrentPrefix()
|
||||
formatted, width := formatSuggestions(
|
||||
suggestions,
|
||||
int(r.col)-len(prefix)-1, // -1 means a width of scrollbar
|
||||
)
|
||||
formatted = formatted[completions.verticalScroll : completions.verticalScroll+windowHeight]
|
||||
l := len(formatted)
|
||||
r.prepareArea(windowHeight)
|
||||
|
||||
// +1 means a width of scrollbar.
|
||||
d := (len(prefix) + len(buf.Document().TextBeforeCursor()) + 1) % int(r.col)
|
||||
if d == 0 { // the cursor is on right end.
|
||||
r.out.CursorBackward(width)
|
||||
} else if d+width > int(r.col) {
|
||||
r.out.CursorBackward(d + width - int(r.col))
|
||||
}
|
||||
|
||||
selected := completions.selected - completions.verticalScroll
|
||||
|
||||
r.out.SetColor(White, Cyan, false)
|
||||
for i := 0; i < l; i++ {
|
||||
for i := 0; i < windowHeight; i++ {
|
||||
r.out.CursorDown(1)
|
||||
if i == selected {
|
||||
r.out.SetColor(r.selectedSuggestionTextColor, r.selectedSuggestionBGColor, true)
|
||||
@ -151,17 +148,14 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
r.out.SetColor(DefaultColor, r.scrollbarBGColor, false)
|
||||
}
|
||||
r.out.WriteStr(" ")
|
||||
// +1 means a width of scrollbar.
|
||||
r.out.CursorBackward(width + 1)
|
||||
}
|
||||
if d == 0 && len(prefix)+len(buf.Text()) != 0 { // the cursor is on right end.
|
||||
// DON'T CURSOR DOWN HERE. Because the line doesn't erase properly.
|
||||
r.out.CursorForward(width + 1)
|
||||
} else if d+width > int(r.col) {
|
||||
r.out.CursorForward(d + width - int(r.col))
|
||||
r.out.CursorBackward(width)
|
||||
}
|
||||
|
||||
r.out.CursorUp(l)
|
||||
if x+width >= int(r.col) {
|
||||
r.out.CursorForward(x + width - int(r.col))
|
||||
}
|
||||
|
||||
r.out.CursorUp(windowHeight)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
return
|
||||
}
|
||||
@ -170,16 +164,18 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
line := buffer.Text()
|
||||
prefix := r.getCurrentPrefix()
|
||||
cursor := len(prefix) + len(line)
|
||||
|
||||
// In situations where a psuedo tty is allocated (e.g. within a docker container),
|
||||
// window size via TIOCGWINSZ is not immediately available and will result in 0,0 dimensions.
|
||||
if r.col > 0 {
|
||||
// Erasing
|
||||
r.out.CursorBackward(int(r.col) + len(line) + len(prefix))
|
||||
r.out.EraseDown()
|
||||
r.clear(r.previousCursor)
|
||||
|
||||
// prepare area
|
||||
h := ((len(prefix) + len(line)) / int(r.col)) + 1 + int(completion.max)
|
||||
_, y := r.toPos(cursor)
|
||||
|
||||
h := y + 1 + int(completion.max)
|
||||
if h > int(r.row) || completionMargin > int(r.col) {
|
||||
r.renderWindowTooSmall()
|
||||
return
|
||||
@ -188,31 +184,81 @@ func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
|
||||
// Rendering
|
||||
r.renderPrefix()
|
||||
|
||||
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
|
||||
r.out.WriteStr(line)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
r.out.CursorBackward(len([]rune(line)) - buffer.CursorPosition)
|
||||
|
||||
cursor = r.backward(cursor, len(line)-buffer.CursorPosition)
|
||||
|
||||
r.renderCompletion(buffer, completion)
|
||||
if suggest, ok := completion.GetSelectedSuggestion(); ok {
|
||||
r.out.CursorBackward(len([]rune(buffer.Document().GetWordBeforeCursor())))
|
||||
cursor = r.backward(cursor, len(buffer.Document().GetWordBeforeCursor()))
|
||||
|
||||
r.out.SetColor(r.previewSuggestionTextColor, r.previewSuggestionBGColor, false)
|
||||
r.out.WriteStr(suggest.Text)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
|
||||
cursor += len(suggest.Text)
|
||||
}
|
||||
r.out.Flush()
|
||||
|
||||
r.previousCursor = cursor
|
||||
}
|
||||
|
||||
// BreakLine to break line.
|
||||
func (r *Render) BreakLine(buffer *Buffer) {
|
||||
// CR
|
||||
prefix := r.getCurrentPrefix()
|
||||
r.out.CursorBackward(int(r.col) + len(buffer.Text()) + len(prefix))
|
||||
// Erasing and Render
|
||||
r.out.EraseDown()
|
||||
cursor := len(buffer.Document().TextBeforeCursor()) + len(r.getCurrentPrefix())
|
||||
r.clear(cursor)
|
||||
r.renderPrefix()
|
||||
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
|
||||
r.out.WriteStr(buffer.Document().Text + "\n")
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
r.out.Flush()
|
||||
|
||||
r.previousCursor = 0
|
||||
}
|
||||
|
||||
func (r *Render) clear(cursor int) {
|
||||
r.backward(cursor, cursor)
|
||||
r.out.EraseDown()
|
||||
}
|
||||
|
||||
func (r *Render) backward(from, n int) int {
|
||||
return r.move(from, from-n)
|
||||
}
|
||||
|
||||
func (r *Render) move(from, to int) int {
|
||||
_, fromY := r.toPos(from)
|
||||
toX, toY := r.toPos(to)
|
||||
|
||||
r.out.CursorUp(fromY - toY)
|
||||
r.out.WriteRaw([]byte{'\r'})
|
||||
r.out.CursorForward(toX)
|
||||
return to
|
||||
}
|
||||
|
||||
// toPos returns the relative position from the beginning of the string.
|
||||
// the coordinate system with the beginning of the string as (0,0) and the width as r.col.
|
||||
// the cursor points to the next character, but it points to that character only at the right end (x == r.col - 1).
|
||||
// x will not return 0 except for the first row.
|
||||
func (r *Render) toPos(cursor int) (x, y int) {
|
||||
col := int(r.col)
|
||||
|
||||
if cursor > 0 && cursor%col == 0 {
|
||||
return col - 1, cursor/col - 1
|
||||
}
|
||||
|
||||
return cursor % col, cursor / col
|
||||
}
|
||||
|
||||
func clamp(high, low, x float64) float64 {
|
||||
switch {
|
||||
case high < x:
|
||||
return high
|
||||
case x < low:
|
||||
return low
|
||||
default:
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user