@@ -13,14 +13,75 @@ type editorCmp struct {
textarea textarea.Model
}
+type focusedEditorKeyMaps struct {
+ Send key.Binding
+ Blur key.Binding
+}
+
+type bluredEditorKeyMaps struct {
+ Send key.Binding
+ Focus key.Binding
+}
+
+var focusedKeyMaps = focusedEditorKeyMaps{
+ Send: key.NewBinding(
+ key.WithKeys("ctrl+s"),
+ key.WithHelp("ctrl+s", "send message"),
+ ),
+ Blur: key.NewBinding(
+ key.WithKeys("esc"),
+ key.WithHelp("esc", "blur editor"),
+ ),
+}
+
+var bluredKeyMaps = bluredEditorKeyMaps{
+ Send: key.NewBinding(
+ key.WithKeys("ctrl+s", "enter"),
+ key.WithHelp("ctrl+s/enter", "send message"),
+ ),
+ Focus: key.NewBinding(
+ key.WithKeys("i"),
+ key.WithHelp("i", "focus editor"),
+ ),
+}
+
func (m *editorCmp) Init() tea.Cmd {
return textarea.Blink
}
func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
- m.textarea, cmd = m.textarea.Update(msg)
- return m, cmd
+ if m.textarea.Focused() {
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ if key.Matches(msg, focusedKeyMaps.Send) {
+ // TODO: send message
+ m.textarea.Reset()
+ m.textarea.Blur()
+ return m, nil
+ }
+ if key.Matches(msg, focusedKeyMaps.Blur) {
+ m.textarea.Blur()
+ return m, nil
+ }
+ }
+ m.textarea, cmd = m.textarea.Update(msg)
+ return m, cmd
+ }
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ if key.Matches(msg, bluredKeyMaps.Send) {
+ // TODO: send message
+ m.textarea.Reset()
+ return m, nil
+ }
+ if key.Matches(msg, bluredKeyMaps.Focus) {
+ m.textarea.Focus()
+ return m, textarea.Blink
+ }
+ }
+
+ return m, nil
}
func (m *editorCmp) View() string {
@@ -39,7 +100,13 @@ func (m *editorCmp) GetSize() (int, int) {
}
func (m *editorCmp) BindingKeys() []key.Binding {
- return layout.KeyMapToSlice(m.textarea.KeyMap)
+ bindings := layout.KeyMapToSlice(m.textarea.KeyMap)
+ if m.textarea.Focused() {
+ bindings = append(bindings, layout.KeyMapToSlice(focusedKeyMaps)...)
+ } else {
+ bindings = append(bindings, layout.KeyMapToSlice(bluredKeyMaps)...)
+ }
+ return bindings
}
func NewEditorCmp() tea.Model {
@@ -1,8 +1,18 @@
package chat
-import tea "github.com/charmbracelet/bubbletea"
+import (
+ "fmt"
-type sidebarCmp struct{}
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/kujtimiihoxha/termai/internal/config"
+ "github.com/kujtimiihoxha/termai/internal/tui/styles"
+ "github.com/kujtimiihoxha/termai/internal/version"
+)
+
+type sidebarCmp struct {
+ width, height int
+}
func (m *sidebarCmp) Init() tea.Cmd {
return nil
@@ -13,7 +23,174 @@ func (m *sidebarCmp) Update(tea.Msg) (tea.Model, tea.Cmd) {
}
func (m *sidebarCmp) View() string {
- return "Sidebar"
+ return styles.BaseStyle.Width(m.width).Render(
+ lipgloss.JoinVertical(
+ lipgloss.Top,
+ m.header(),
+ " ",
+ m.session(),
+ " ",
+ m.modifiedFiles(),
+ " ",
+ m.lspsConfigured(),
+ ),
+ )
+}
+
+func (m *sidebarCmp) session() string {
+ sessionKey := styles.BaseStyle.Foreground(styles.PrimaryColor).Render("Session")
+ sessionValue := styles.BaseStyle.
+ Foreground(styles.Forground).
+ Width(m.width - lipgloss.Width(sessionKey)).
+ Render(": New Session")
+ return lipgloss.JoinHorizontal(
+ lipgloss.Left,
+ sessionKey,
+ sessionValue,
+ )
+}
+
+func (m *sidebarCmp) modifiedFile(filePath string, additions, removals int) string {
+ stats := ""
+ if additions > 0 && removals > 0 {
+ stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf("%d additions and %d removals", additions, removals))
+ } else if additions > 0 {
+ stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf("%d additions", additions))
+ } else if removals > 0 {
+ stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf("%d removals", removals))
+ }
+ filePathStr := styles.BaseStyle.Foreground(styles.Forground).Render(filePath)
+
+ return styles.BaseStyle.
+ Width(m.width).
+ Render(
+ lipgloss.JoinHorizontal(
+ lipgloss.Left,
+ filePathStr,
+ " ",
+ stats,
+ ),
+ )
+}
+
+func (m *sidebarCmp) lspsConfigured() string {
+ lsps := styles.BaseStyle.Width(m.width).Foreground(styles.PrimaryColor).Render("LSP Configuration:")
+ lspsConfigured := []struct {
+ name string
+ path string
+ }{
+ {"golsp", "path/to/lsp1"},
+ {"vtsls", "path/to/lsp2"},
+ }
+
+ var lspViews []string
+ for _, lsp := range lspsConfigured {
+ lspName := styles.BaseStyle.Foreground(styles.Forground).Render(
+ fmt.Sprintf("• %s", lsp.name),
+ )
+ lspPath := styles.BaseStyle.Foreground(styles.ForgroundDim).Render(
+ fmt.Sprintf("(%s)", lsp.path),
+ )
+ lspViews = append(lspViews,
+ styles.BaseStyle.
+ Width(m.width).
+ Render(
+ lipgloss.JoinHorizontal(
+ lipgloss.Left,
+ lspName,
+ " ",
+ lspPath,
+ ),
+ ),
+ )
+
+ }
+ return styles.BaseStyle.
+ Width(m.width).
+ Render(
+ lipgloss.JoinVertical(
+ lipgloss.Left,
+ lsps,
+ lipgloss.JoinVertical(
+ lipgloss.Left,
+ lspViews...,
+ ),
+ ),
+ )
+}
+
+func (m *sidebarCmp) modifiedFiles() string {
+ modifiedFiles := styles.BaseStyle.Width(m.width).Foreground(styles.PrimaryColor).Render("Modified Files:")
+ files := []struct {
+ path string
+ additions int
+ removals int
+ }{
+ {"file1.txt", 10, 5},
+ {"file2.txt", 20, 0},
+ {"file3.txt", 0, 15},
+ }
+ var fileViews []string
+ for _, file := range files {
+ fileViews = append(fileViews, m.modifiedFile(file.path, file.additions, file.removals))
+ }
+
+ return styles.BaseStyle.
+ Width(m.width).
+ Render(
+ lipgloss.JoinVertical(
+ lipgloss.Top,
+ modifiedFiles,
+ lipgloss.JoinVertical(
+ lipgloss.Left,
+ fileViews...,
+ ),
+ ),
+ )
+}
+
+func (m *sidebarCmp) logo() string {
+ logo := fmt.Sprintf("%s %s", styles.OpenCodeIcon, "OpenCode")
+
+ version := styles.BaseStyle.Foreground(styles.ForgroundDim).Render(version.Version)
+
+ return styles.BaseStyle.
+ Bold(true).
+ Width(m.width).
+ Render(
+ lipgloss.JoinHorizontal(
+ lipgloss.Left,
+ logo,
+ " ",
+ version,
+ ),
+ )
+}
+
+func (m *sidebarCmp) header() string {
+ header := lipgloss.JoinVertical(
+ lipgloss.Top,
+ m.logo(),
+ m.cwd(),
+ )
+ return header
+}
+
+func (m *sidebarCmp) cwd() string {
+ cwd := fmt.Sprintf("cwd: %s", config.WorkingDirectory())
+ return styles.BaseStyle.
+ Foreground(styles.ForgroundDim).
+ Width(m.width).
+ Render(cwd)
+}
+
+func (m *sidebarCmp) SetSize(width, height int) {
+ m.width = width
+ m.height = height
+}
+
+func (m *sidebarCmp) GetSize() (int, int) {
+ return m.width, m.height
}
func NewSidebarCmp() tea.Model {
@@ -16,6 +16,10 @@ var (
Dark: "#212121",
Light: "#212121",
}
+ BackgroundDim = lipgloss.AdaptiveColor{
+ Dark: "#2c2c2c",
+ Light: "#2c2c2c",
+ }
BackgroundDarker = lipgloss.AdaptiveColor{
Dark: "#181818",
Light: "#181818",
@@ -24,6 +28,25 @@ var (
Dark: "#4b4c5c",
Light: "#4b4c5c",
}
+
+ Forground = lipgloss.AdaptiveColor{
+ Dark: "#d3d3d3",
+ Light: "#d3d3d3",
+ }
+
+ ForgroundDim = lipgloss.AdaptiveColor{
+ Dark: "#737373",
+ Light: "#737373",
+ }
+
+ BaseStyle = lipgloss.NewStyle().
+ Background(Background).
+ Foreground(Forground)
+
+ PrimaryColor = lipgloss.AdaptiveColor{
+ Dark: "#fab283",
+ Light: "#fab283",
+ }
)
var (