Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/better indentation #12

Merged
merged 4 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ This release aims to make the code a bit cleaner, fix a couple of bugs and provi
- <kbd>Tab</kbd> will insert a single indentation level when there are no suggestions
- <kbd>Shift</kbd> + <kbd>Tab</kbd> will delete a single indentation level when there are no suggestions and the line before the cursor consists only of indentation (spaces)
- Make `Completer` optional when creating a new `prompt.Prompt`. Change the signature of `prompt.New` from `func New(Executor, Completer, ...Option) *Prompt` to `func New(Executor, ...Option) *Prompt`
- Make `prefix` optional in `prompt.Input`. Change the signature of `prompt.Input` from `func Input(string, ...Option) string` to `func Input(...Option) string`.
- Make `prefix` and `completer` optional in `prompt.Input`. Change the signature of `prompt.Input` from `func Input(string, Completer, ...Option) string` to `func Input(...Option) string`.
- Rename `prompt.ConsoleParser` to `prompt.Reader` and make it embed `io.ReadCloser`
- Rename `prompt.ConsoleWriter` to `prompt.Writer` and make it embed `io.Writer` and `io.StringWriter`
- Rename `prompt.Render` to `prompt.Renderer`
- Rename `prompt.OptionTitle` to `prompt.WithTitle`
- Rename `prompt.OptionPrefix` to `prompt.WithPrefix`
- Rename `prompt.OptionInitialBufferText` to `prompt.WithInitialText`
Expand Down Expand Up @@ -79,7 +80,7 @@ This release aims to make the code a bit cleaner, fix a couple of bugs and provi

- Make pasting multiline text work properly
- Make pasting text with tabs work properly (tabs get replaced with indentation -- spaces)
- Introduce `strings.ByteNumber`, `strings.RuneNumber`, `strings.StringWidth` to reduce the ambiguity of when to use which of the three main units used by this library to measure string length and index parts of strings. Several subtle bugs (using the wrong unit) causing panics have been fixed this way.
- Introduce `strings.ByteNumber`, `strings.RuneNumber`, `strings.Width` to reduce the ambiguity of when to use which of the three main units used by this library to measure string length and index parts of strings. Several subtle bugs (using the wrong unit) causing panics have been fixed this way.
- Remove a `/dev/tty` leak in `prompt.PosixReader` (old `prompt.PosixParser`)

### Removed
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ func completer(d prompt.Document) []prompt.Suggest {

func main() {
fmt.Println("Please select table.")
t := prompt.Input("> ", completer)
t := prompt.Input(
prompt.WithPrefix("> "),
prompt.WithCompleter(completer),
)
fmt.Println("You selected " + t)
}
```
Expand Down
3 changes: 2 additions & 1 deletion _example/bang-executor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func main() {
p := prompt.New(
executor,
prompt.WithPrefix(">>> "),
prompt.WithExecuteOnEnterCallback(ExecuteOnEnter),
)

Expand All @@ -18,7 +19,7 @@ func main() {

func ExecuteOnEnter(input string, indentSize int) (int, bool) {
char, _ := utf8.DecodeLastRuneInString(input)
return 1, char == '!'
return 0, char == '!'
}

func executor(s string) {
Expand Down
2 changes: 1 addition & 1 deletion buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (b *Buffer) Document() (d *Document) {

// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
func (b *Buffer) DisplayCursorPosition(columns istrings.StringWidth) Position {
func (b *Buffer) DisplayCursorPosition(columns istrings.Width) Position {
return b.Document().DisplayCursorPosition(columns)
}

Expand Down
26 changes: 13 additions & 13 deletions completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const (
)

var (
leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
leftMargin = istrings.GetWidth(leftPrefix + leftSuffix)
rightMargin = istrings.GetWidth(rightPrefix + rightSuffix)
completionMargin = leftMargin + rightMargin
)

Expand Down Expand Up @@ -114,18 +114,18 @@ func deleteBreakLineCharacters(s string) string {
return s
}

func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) {
func formatTexts(o []string, max istrings.Width, prefix, suffix string) (new []string, width istrings.Width) {
l := len(o)
n := make([]string, l)

lenPrefix := runewidth.StringWidth(prefix)
lenSuffix := runewidth.StringWidth(suffix)
lenShorten := runewidth.StringWidth(shortenSuffix)
lenPrefix := istrings.GetWidth(prefix)
lenSuffix := istrings.GetWidth(suffix)
lenShorten := istrings.GetWidth(shortenSuffix)
min := lenPrefix + lenSuffix + lenShorten
for i := 0; i < l; i++ {
o[i] = deleteBreakLineCharacters(o[i])

w := runewidth.StringWidth(o[i])
w := istrings.GetWidth(o[i])
if width < w {
width = w
}
Expand All @@ -142,21 +142,21 @@ func formatTexts(o []string, max int, prefix, suffix string) (new []string, widt
}

for i := 0; i < l; i++ {
x := runewidth.StringWidth(o[i])
x := istrings.GetWidth(o[i])
if x <= width {
spaces := strings.Repeat(" ", width-x)
spaces := strings.Repeat(" ", int(width-x))
n[i] = prefix + o[i] + spaces + suffix
} else if x > width {
x := runewidth.Truncate(o[i], width, shortenSuffix)
x := runewidth.Truncate(o[i], int(width), shortenSuffix)
// When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..."
// But the length of this result is 10. So we need fill right using runewidth.FillRight.
n[i] = prefix + runewidth.FillRight(x, width) + suffix
n[i] = prefix + runewidth.FillRight(x, int(width)) + suffix
}
}
return n, lenPrefix + width + lenSuffix
}

func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width istrings.StringWidth) {
func formatSuggestions(suggests []Suggest, max istrings.Width) (new []Suggest, width istrings.Width) {
num := len(suggests)
new = make([]Suggest, num)

Expand All @@ -178,7 +178,7 @@ func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width istrin
for i := 0; i < num; i++ {
new[i] = Suggest{Text: left[i], Description: right[i]}
}
return new, istrings.StringWidth(leftWidth + rightWidth)
return new, istrings.Width(leftWidth + rightWidth)
}

// Constructor option for CompletionManager.
Expand Down
18 changes: 9 additions & 9 deletions completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ func TestFormatShortSuggestion(t *testing.T) {
var scenarioTable = []struct {
in []Suggest
expected []Suggest
max int
exWidth istrings.StringWidth
max istrings.Width
exWidth istrings.Width
}{
{
in: []Suggest{
Expand Down Expand Up @@ -40,7 +40,7 @@ func TestFormatShortSuggestion(t *testing.T) {
{Text: " coconut ", Description: " This is coconut. "},
},
max: 100,
exWidth: istrings.StringWidth(len(" apple " + " This is apple. ")),
exWidth: istrings.Width(len(" apple " + " This is apple. ")),
},
{
in: []Suggest{
Expand Down Expand Up @@ -84,7 +84,7 @@ func TestFormatShortSuggestion(t *testing.T) {
{Text: " --include-extended-apis ", Description: " --------------... "},
},
max: 50,
exWidth: istrings.StringWidth(len(" --include-extended-apis " + " ---------------...")),
exWidth: istrings.Width(len(" --include-extended-apis " + " ---------------...")),
},
{
in: []Suggest{
Expand All @@ -104,7 +104,7 @@ func TestFormatShortSuggestion(t *testing.T) {
{Text: " --include-extended-apis ", Description: " If true, include definitions of new APIs via calls to the API server. [default true] "},
},
max: 500,
exWidth: istrings.StringWidth(len(" --include-extended-apis " + " If true, include definitions of new APIs via calls to the API server. [default true] ")),
exWidth: istrings.Width(len(" --include-extended-apis " + " If true, include definitions of new APIs via calls to the API server. [default true] ")),
},
}

Expand All @@ -123,8 +123,8 @@ func TestFormatText(t *testing.T) {
var scenarioTable = []struct {
in []string
expected []string
max int
exWidth int
max istrings.Width
exWidth istrings.Width
}{
{
in: []string{
Expand Down Expand Up @@ -163,7 +163,7 @@ func TestFormatText(t *testing.T) {
"",
"",
},
max: len(" " + " " + shortenSuffix),
max: istrings.GetWidth(" " + " " + shortenSuffix),
exWidth: 0,
},
{
Expand All @@ -178,7 +178,7 @@ func TestFormatText(t *testing.T) {
" coconut ",
},
max: 100,
exWidth: len(" coconut "),
exWidth: istrings.GetWidth(" coconut "),
},
{
in: []string{
Expand Down
29 changes: 3 additions & 26 deletions constructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const DefaultIndentSize = 2
// that constitute a single indentation level.
func WithIndentSize(i int) Option {
return func(p *Prompt) error {
p.indentSize = i
p.renderer.indentSize = i
return nil
}
}
Expand Down Expand Up @@ -312,37 +312,14 @@ func DefaultPrefixCallback() string {

// New returns a Prompt with powerful auto-completion.
func New(executor Executor, opts ...Option) *Prompt {
defaultWriter := NewStdoutWriter()
registerWriter(defaultWriter)

pt := &Prompt{
reader: NewStdinReader(),
renderer: &Render{
out: defaultWriter,
prefixCallback: DefaultPrefixCallback,
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,
inputBGColor: DefaultColor,
previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White,
suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise,
descriptionTextColor: Black,
descriptionBGColor: Turquoise,
selectedDescriptionTextColor: White,
selectedDescriptionBGColor: Cyan,
scrollbarThumbColor: DarkGray,
scrollbarBGColor: Cyan,
},
reader: NewStdinReader(),
renderer: NewRenderer(),
buf: NewBuffer(),
executor: executor,
history: NewHistory(),
completion: NewCompletionManager(6),
executeOnEnterCallback: DefaultExecuteOnEnterCallback,
indentSize: DefaultIndentSize,
keyBindMode: EmacsKeyBind, // All the above assume that bash is running in the default Emacs setting
}

Expand Down
6 changes: 3 additions & 3 deletions document.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (d *Document) LastKeyStroke() Key {

// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
func (d *Document) DisplayCursorPosition(columns istrings.StringWidth) Position {
func (d *Document) DisplayCursorPosition(columns istrings.Width) Position {
str := utf8string.NewString(d.Text).Slice(0, int(d.cursorPosition))
return positionAtEndOfString(str, columns)
}
Expand Down Expand Up @@ -381,12 +381,12 @@ func (d *Document) GetCursorRightPosition(count istrings.RuneNumber) istrings.Ru
}

// Get the current cursor position.
func (d *Document) GetCursorPosition(columns istrings.StringWidth) Position {
func (d *Document) GetCursorPosition(columns istrings.Width) Position {
return positionAtEndOfString(d.TextBeforeCursor(), columns)
}

// Get the position of the end of the current text.
func (d *Document) GetEndOfTextPosition(columns istrings.StringWidth) Position {
func (d *Document) GetEndOfTextPosition(columns istrings.Width) Position {
return positionAtEndOfString(d.Text, columns)
}

Expand Down
10 changes: 5 additions & 5 deletions position.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// (0, 0) represents the top-left corner of the prompt,
// while (n, n) the bottom-right corner.
type Position struct {
X istrings.StringWidth
X istrings.Width
Y int
}

Expand Down Expand Up @@ -47,16 +47,16 @@ func (p Position) Subtract(other Position) Position {

// positionAtEndOfString calculates the position of the
// p at the end of the given string.
func positionAtEndOfString(str string, columns istrings.StringWidth) Position {
func positionAtEndOfString(str string, columns istrings.Width) Position {
pos := positionAtEndOfReader(strings.NewReader(str), columns)
return pos
}

// positionAtEndOfReader calculates the position of the
// p at the end of the given io.Reader.
func positionAtEndOfReader(reader io.RuneReader, columns istrings.StringWidth) Position {
func positionAtEndOfReader(reader io.RuneReader, columns istrings.Width) Position {
var down int
var right istrings.StringWidth
var right istrings.Width

charLoop:
for {
Expand All @@ -80,7 +80,7 @@ charLoop:
down++
right = 0
default:
right += istrings.StringWidth(runewidth.RuneWidth(char))
right += istrings.Width(runewidth.RuneWidth(char))
if right == columns {
right = 0
down++
Expand Down
2 changes: 1 addition & 1 deletion position_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
func TestPositionAtEndOfString(t *testing.T) {
tests := map[string]struct {
input string
columns istrings.StringWidth
columns istrings.Width
want Position
}{
"empty": {
Expand Down
Loading