fix: update ui & add mock completion

Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
cpendery
2023-09-09 01:28:40 -04:00
parent 3aecd9cdff
commit b0314187c7
5 changed files with 134 additions and 39 deletions
+8 -3
View File
@@ -2,11 +2,16 @@ package autocomplete
import "strings"
func LoadSuggestions(cmd string) []string {
type Suggestion struct {
Name string
Description string
}
func LoadSuggestions(cmd string) []Suggestion {
params := strings.Split(cmd, " ")
lastParam := params[len(params)-1]
if lastParam == "git" {
return []string{"go", "get"}
return []Suggestion{{"go", "the only way to go"}, {"get", "the right way to get"}, {"gone", "here today, bye tomorrow"}}
}
return []string{}
return []Suggestion{}
}
+3 -3
View File
@@ -6,6 +6,9 @@ require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.8.0
github.com/mattn/go-runewidth v0.0.14
github.com/muesli/reflow v0.3.0
golang.org/x/term v0.6.0
)
require (
@@ -15,14 +18,11 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
+94 -27
View File
@@ -1,25 +1,33 @@
package suggestions
import (
"log"
"runtime"
"strings"
"github.com/charmbracelet/bubbles/cursor"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/cpendery/clac/autocomplete"
"github.com/cpendery/clac/ui/utils"
"github.com/mattn/go-runewidth"
"github.com/muesli/reflow/wordwrap"
"github.com/muesli/reflow/wrap"
)
type Model struct {
cursor int
suggestions []string
keyMap KeyMap
cursor int
userInputCursorLocation int
suggestions []autocomplete.Suggestion
keyMap KeyMap
windowWidth int
windowHeight int
}
const (
SuggestionWidth = 50
BorderOffset = 2
SuggestionWidth = 40
DescriptionWidth = 30
BorderOffset = 2
)
type KeyMap struct {
@@ -45,9 +53,8 @@ func New() Model {
),
}
return Model{
cursor: 0,
suggestions: []string{"div", "table"},
keyMap: keyBindings,
cursor: 0,
keyMap: keyBindings,
}
}
@@ -59,31 +66,75 @@ func (m *Model) cursorDown() {
m.cursor = utils.Clamp(m.cursor+1, 0, len(m.suggestions)-1)
}
func (m Model) ActiveSuggestion() string {
return m.suggestions[m.cursor]
func (m Model) HasActiveSuggestion() bool {
return len(m.suggestions) > 0
}
func (m Model) Update(msg tea.Msg, command string) Model {
func (m Model) ActiveSuggestion() string {
return m.suggestions[m.cursor].Name
}
func (m Model) Update(msg tea.Msg, command string, userInputCursorLocation int) Model {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keyMap.LineUp):
log.Println("up")
m.cursorUp()
case key.Matches(msg, m.keyMap.LineDown):
log.Println("down")
m.cursorDown()
}
case cursor.BlinkMsg:
if runtime.GOOS == "windows" {
m.windowWidth, m.windowHeight = utils.GetWindowSize()
}
case tea.WindowSizeMsg:
m.windowHeight = msg.Height
m.windowWidth = msg.Width
}
m.userInputCursorLocation = userInputCursorLocation
m.suggestions = autocomplete.LoadSuggestions(command)
return m
}
func (m Model) renderSuggestion(suggestion string, position int) string {
func (m Model) renderSuggestion(suggestion string, position int, width int) string {
content := wordTrunc(suggestion, width)
if position == m.cursor {
return lipgloss.NewStyle().Background(lipgloss.Color("#7D56F4")).Width(SuggestionWidth).Render(suggestion)
return lipgloss.NewStyle().Background(lipgloss.Color("#7D56F4")).Width(width).Render(content)
}
return lipgloss.NewStyle().Render(suggestion)
return lipgloss.NewStyle().Render(content)
}
func wordWrap(content string, width int) string {
return wrap.String(wordwrap.String(content, width), width)
}
func wordTrunc(content string, width int) string {
return runewidth.Truncate(content, width, "…")
}
func (m Model) renderSuggestions(width int) string {
var style = lipgloss.NewStyle().
Bold(true).
Width(width).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("63"))
r := make([]string, len(m.suggestions))
for idx, suggestion := range m.suggestions {
r[idx] = m.renderSuggestion(suggestion.Name, idx, width)
}
return style.Render(strings.Join(r, "\n"))
}
func (m Model) renderDescription(width int) string {
var style = lipgloss.NewStyle().
Bold(true).
Width(width).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("63"))
return style.Render(wordWrap(m.suggestions[m.cursor].Description, width))
}
func (m Model) View() string {
@@ -91,16 +142,32 @@ func (m Model) View() string {
return ""
}
var style = lipgloss.NewStyle().
Bold(true).
Width(SuggestionWidth).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("63"))
suggestionWidth := min(m.windowWidth-BorderOffset, SuggestionWidth)
descriptionWidth := min(m.windowWidth-BorderOffset, DescriptionWidth)
r := make([]string, len(m.suggestions))
for idx, suggestion := range m.suggestions {
r[idx] = m.renderSuggestion(suggestion, idx)
suggestionsView := m.renderSuggestions(suggestionWidth)
descriptionView := m.renderDescription(descriptionWidth)
maxLeftPadding := m.windowWidth - suggestionWidth - BorderOffset
maxLeftPaddingWhenJoined := maxLeftPadding - BorderOffset - descriptionWidth
showDescriptionRight := m.userInputCursorLocation+suggestionWidth+descriptionWidth+BorderOffset*2 <= m.windowWidth
showDescriptionLeft := !showDescriptionRight && (suggestionWidth+descriptionWidth+BorderOffset*2 <= m.windowWidth)
if showDescriptionRight {
return lipgloss.
NewStyle().
MarginLeft(utils.Clamp(m.userInputCursorLocation, 0, maxLeftPaddingWhenJoined)).
Render(lipgloss.JoinHorizontal(lipgloss.Top, suggestionsView, descriptionView))
} else if showDescriptionLeft {
return lipgloss.
NewStyle().
MarginLeft(utils.Clamp(m.userInputCursorLocation-DescriptionWidth-BorderOffset, 0, maxLeftPaddingWhenJoined)).
Render(lipgloss.JoinHorizontal(lipgloss.Top, descriptionView, suggestionsView))
} else {
return lipgloss.
NewStyle().
MarginLeft(utils.Clamp(m.userInputCursorLocation, 0, maxLeftPadding)).
Render(lipgloss.JoinVertical(lipgloss.Left, suggestionsView, descriptionView))
}
return style.Render(strings.Join(r, "\n"))
}
+15 -6
View File
@@ -2,7 +2,9 @@ package ui
import (
"fmt"
"runtime"
"github.com/charmbracelet/bubbles/cursor"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@@ -44,12 +46,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
case tea.KeyTab:
if !m.suggestions.HasActiveSuggestion() {
return m, nil
}
activeSuggestion := m.suggestions.ActiveSuggestion()
s := m.textInput.Value() + activeSuggestion
m.textInput.SetValue(s)
m.textInput.SetCursor(len(s))
return m, nil
}
case cursor.BlinkMsg:
if runtime.GOOS == "windows" {
m.windowWidth, m.windowHeight = utils.GetWindowSize()
m.textInput.Width = m.windowWidth - widthOffset
}
case tea.WindowSizeMsg:
m.windowHeight = msg.Height
m.windowWidth = msg.Width
@@ -57,19 +67,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
var cmd tea.Cmd
m.suggestions = m.suggestions.Update(msg, m.textInput.Value())
m.textInput, cmd = m.textInput.Update(msg)
cursorLocation := len(m.textInput.Value()) + len(m.textInput.Prompt)
m.suggestions = m.suggestions.Update(msg, m.textInput.Value(), cursorLocation)
return m, cmd
}
func (m model) View() string {
suggestionsSpacing := lipgloss.NewStyle().MarginLeft(
utils.Clamp(len(m.textInput.Value())+len(m.textInput.Prompt), 0, m.windowWidth-suggestions.SuggestionWidth-suggestions.BorderOffset),
)
return fmt.Sprintf(
"%s\n%s",
m.textInput.View(),
suggestionsSpacing.Render(m.suggestions.View()),
m.suggestions.View(),
)
}
+14
View File
@@ -1,8 +1,22 @@
package utils
import (
"os"
"golang.org/x/term"
)
func Clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}
func GetWindowSize() (width int, height int) {
fd := int(os.Stdout.Fd())
if term.IsTerminal(fd) {
width, height, _ = term.GetSize(fd)
}
return
}