mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
fix: update ui & add mock completion
Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
@@ -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{}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user