feat: implement starting ui for autocomplete

Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
cpendery
2023-09-08 19:51:44 -04:00
parent 788037d45e
commit 3aecd9cdff
5 changed files with 159 additions and 25 deletions
+12
View File
@@ -0,0 +1,12 @@
package autocomplete
import "strings"
func LoadSuggestions(cmd string) []string {
params := strings.Split(cmd, " ")
lastParam := params[len(params)-1]
if lastParam == "git" {
return []string{"go", "get"}
}
return []string{}
}
+8
View File
@@ -1,13 +1,21 @@
package main
import (
"fmt"
"log"
"os"
tea "github.com/charmbracelet/bubbletea"
"github.com/cpendery/clac/ui"
)
func main() {
f, err := tea.LogToFile("debug.log", "debug")
if err != nil {
fmt.Println("fatal:", err)
os.Exit(1)
}
defer f.Close()
p := tea.NewProgram(ui.Start())
if _, err := p.Run(); err != nil {
log.Fatal(err)
+106
View File
@@ -0,0 +1,106 @@
package suggestions
import (
"log"
"strings"
"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"
)
type Model struct {
cursor int
suggestions []string
keyMap KeyMap
}
const (
SuggestionWidth = 50
BorderOffset = 2
)
type KeyMap struct {
LineUp key.Binding
LineDown key.Binding
PageUp key.Binding
PageDown key.Binding
}
func New() Model {
keyBindings := KeyMap{
LineUp: key.NewBinding(
key.WithKeys("up"),
),
LineDown: key.NewBinding(
key.WithKeys("down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup"),
),
PageDown: key.NewBinding(
key.WithKeys("pgdown"),
),
}
return Model{
cursor: 0,
suggestions: []string{"div", "table"},
keyMap: keyBindings,
}
}
func (m *Model) cursorUp() {
m.cursor = utils.Clamp(m.cursor-1, 0, len(m.suggestions)-1)
}
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) Update(msg tea.Msg, command string) 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()
}
}
m.suggestions = autocomplete.LoadSuggestions(command)
return m
}
func (m Model) renderSuggestion(suggestion string, position int) string {
if position == m.cursor {
return lipgloss.NewStyle().Background(lipgloss.Color("#7D56F4")).Width(SuggestionWidth).Render(suggestion)
}
return lipgloss.NewStyle().Render(suggestion)
}
func (m Model) View() string {
if len(m.suggestions) == 0 {
return ""
}
var style = lipgloss.NewStyle().
Bold(true).
Width(SuggestionWidth).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("63"))
r := make([]string, len(m.suggestions))
for idx, suggestion := range m.suggestions {
r[idx] = m.renderSuggestion(suggestion, idx)
}
return style.Render(strings.Join(r, "\n"))
}
+25 -25
View File
@@ -6,21 +6,31 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/cpendery/clac/ui/suggestions"
"github.com/cpendery/clac/ui/utils"
)
type model struct {
textInput textinput.Model
suggestions suggestions.Model
windowHeight int
windowWidth int
}
const (
prompt = "> "
promptOffset = len(prompt)
cursorOffset = 1
widthOffset = promptOffset + cursorOffset
)
func Start() model {
ti := textinput.New()
ti.Cursor.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
ti.Focus()
return model{textInput: ti}
sug := suggestions.New()
return model{textInput: ti, suggestions: sug}
}
func (m model) Init() tea.Cmd {
@@ -32,44 +42,34 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
// return m, tea.ClearScreen
return m, tea.Quit
case tea.KeyTab:
activeSuggestion := m.suggestions.ActiveSuggestion()
s := m.textInput.Value() + activeSuggestion
m.textInput.SetValue(s)
m.textInput.SetCursor(len(s))
return m, nil
}
case tea.WindowSizeMsg:
m.windowHeight = msg.Height
m.windowWidth = msg.Width
m.textInput.Width = m.windowWidth - widthOffset
}
var cmd tea.Cmd
m.suggestions = m.suggestions.Update(msg, m.textInput.Value())
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}
func (m model) View() string {
sugWidth := 22
marginLeft := clamp(len(m.textInput.Value())+len(m.textInput.Prompt), 0, m.windowWidth-sugWidth)
var style = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
PaddingTop(2).
PaddingLeft(4).
Width(sugWidth).
MarginLeft(marginLeft)
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(),
style.Render("Hello, kitty"),
suggestionsSpacing.Render(m.suggestions.View()),
)
}
// work
func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}
+8
View File
@@ -0,0 +1,8 @@
package utils
func Clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}