summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-06-16 15:58:46 -0500
committeradamdottv <[email protected]>2025-06-16 15:58:52 -0500
commitd6d45bdc6388dcc7e5e5248c6b4645d90159bb18 (patch)
tree011b8409874bcda6d39ae10cfb3bdd2c0bbc956c /packages
parent13a83721b076a1201a049ca1e4cebae8896da55b (diff)
downloadopencode-d6d45bdc6388dcc7e5e5248c6b4645d90159bb18.tar.gz
opencode-d6d45bdc6388dcc7e5e5248c6b4645d90159bb18.zip
feat: share and init commands
Diffstat (limited to 'packages')
-rw-r--r--packages/tui/internal/app/app.go18
-rw-r--r--packages/tui/internal/commands/command.go22
-rw-r--r--packages/tui/internal/completions/commands.go40
-rw-r--r--packages/tui/internal/completions/files-folders.go4
-rw-r--r--packages/tui/internal/components/chat/editor.go18
-rw-r--r--packages/tui/internal/components/dialog/complete.go8
-rw-r--r--packages/tui/internal/components/list/list.go5
-rw-r--r--packages/tui/internal/tui/tui.go8
8 files changed, 102 insertions, 21 deletions
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 658dbcbdf..f7481f9f4 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -170,16 +170,17 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
cmds = append(cmds, util.CmdHandler(state.SessionSelectedMsg(session)))
go func() {
- // TODO: Handle no provider or model setup, yet
response, err := a.Client.PostSessionInitialize(ctx, client.PostSessionInitializeJSONRequestBody{
SessionID: a.Session.Id,
ProviderID: a.Provider.Id,
ModelID: a.Model.Id,
})
if err != nil {
+ slog.Error("Failed to initialize project", "error", err)
// status.Error(err.Error())
}
if response != nil && response.StatusCode != 200 {
+ slog.Error("Failed to initialize project", "error", response.StatusCode)
// status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
}
}()
@@ -187,6 +188,21 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
return tea.Batch(cmds...)
}
+func (a *App) CompactSession(ctx context.Context) tea.Cmd {
+ response, err := a.Client.PostSessionSummarizeWithResponse(ctx, client.PostSessionSummarizeJSONRequestBody{
+ SessionID: a.Session.Id,
+ ProviderID: a.Provider.Id,
+ ModelID: a.Model.Id,
+ })
+ if err != nil {
+ slog.Error("Failed to compact session", "error", err)
+ }
+ if response != nil && response.StatusCode() != 200 {
+ slog.Error("Failed to compact session", "error", response.StatusCode)
+ }
+ return nil
+}
+
func (a *App) MarkProjectInitialized(ctx context.Context) error {
response, err := a.Client.PostAppInitialize(ctx)
if err != nil {
diff --git a/packages/tui/internal/commands/command.go b/packages/tui/internal/commands/command.go
index e71e2d299..792ab9464 100644
--- a/packages/tui/internal/commands/command.go
+++ b/packages/tui/internal/commands/command.go
@@ -59,6 +59,27 @@ func NewCommandRegistry() Registry {
key.WithKeys("f5", "super+t"),
),
},
+ "share": {
+ Name: "share",
+ Description: "create shareable link",
+ KeyBinding: key.NewBinding(
+ key.WithKeys("f6"),
+ ),
+ },
+ "init": {
+ Name: "init",
+ Description: "create or update AGENTS.md",
+ KeyBinding: key.NewBinding(
+ key.WithKeys("f7"),
+ ),
+ },
+ // "compact": {
+ // Name: "compact",
+ // Description: "compact the session",
+ // KeyBinding: key.NewBinding(
+ // key.WithKeys("f8"),
+ // ),
+ // },
"quit": {
Name: "quit",
Description: "quit",
@@ -68,4 +89,3 @@ func NewCommandRegistry() Registry {
},
}
}
-
diff --git a/packages/tui/internal/completions/commands.go b/packages/tui/internal/completions/commands.go
index 3bb28ac9b..00e8847f4 100644
--- a/packages/tui/internal/completions/commands.go
+++ b/packages/tui/internal/completions/commands.go
@@ -2,10 +2,14 @@ package completions
import (
"sort"
+ "strings"
+ "github.com/charmbracelet/lipgloss/v2"
"github.com/lithammer/fuzzysearch/fuzzy"
"github.com/sst/opencode/internal/app"
+ "github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/components/dialog"
+ "github.com/sst/opencode/internal/theme"
)
type CommandCompletionProvider struct {
@@ -27,15 +31,36 @@ func (c *CommandCompletionProvider) GetEntry() dialog.CompletionItemI {
})
}
+func (c *CommandCompletionProvider) GetEmptyMessage() string {
+ return "no matching commands"
+}
+
+func getCommandCompletionItem(cmd commands.Command, space int) dialog.CompletionItemI {
+ t := theme.CurrentTheme()
+ spacer := strings.Repeat(" ", space)
+ title := " /" + cmd.Name + lipgloss.NewStyle().Foreground(t.TextMuted()).Render(spacer+cmd.Description)
+ value := "/" + cmd.Name
+ return dialog.NewCompletionItem(dialog.CompletionItem{
+ Title: title,
+ Value: value,
+ })
+}
+
func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.CompletionItemI, error) {
+ space := 1
+ for _, cmd := range c.app.Commands {
+ if lipgloss.Width(cmd.Name) > space {
+ space = lipgloss.Width(cmd.Name)
+ }
+ }
+ space += 2
+
if query == "" {
// If no query, return all commands
items := []dialog.CompletionItemI{}
for _, cmd := range c.app.Commands {
- items = append(items, dialog.NewCompletionItem(dialog.CompletionItem{
- Title: " /" + cmd.Name,
- Value: "/" + cmd.Name,
- }))
+ space := space - lipgloss.Width(cmd.Name)
+ items = append(items, getCommandCompletionItem(cmd, space))
}
return items, nil
}
@@ -45,11 +70,9 @@ func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.Comp
commandMap := make(map[string]dialog.CompletionItemI)
for _, cmd := range c.app.Commands {
+ space := space - lipgloss.Width(cmd.Name)
commandNames = append(commandNames, cmd.Name)
- commandMap[cmd.Name] = dialog.NewCompletionItem(dialog.CompletionItem{
- Title: " /" + cmd.Name,
- Value: "/" + cmd.Name,
- })
+ commandMap[cmd.Name] = getCommandCompletionItem(cmd, space)
}
// Find fuzzy matches
@@ -68,4 +91,3 @@ func (c *CommandCompletionProvider) GetChildEntries(query string) ([]dialog.Comp
return items, nil
}
-
diff --git a/packages/tui/internal/completions/files-folders.go b/packages/tui/internal/completions/files-folders.go
index 0e6a4e4b6..491b67aac 100644
--- a/packages/tui/internal/completions/files-folders.go
+++ b/packages/tui/internal/completions/files-folders.go
@@ -24,6 +24,10 @@ func (cg *filesAndFoldersContextGroup) GetEntry() dialog.CompletionItemI {
})
}
+func (cg *filesAndFoldersContextGroup) GetEmptyMessage() string {
+ return "no matching files"
+}
+
func (cg *filesAndFoldersContextGroup) getFiles(query string) ([]string, error) {
response, err := cg.app.Client.PostFileSearchWithResponse(context.Background(), client.PostFileSearchJSONRequestBody{
Query: query,
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 3c005a32e..349087fbe 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -101,6 +101,8 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case dialog.ThemeChangedMsg:
m.textarea = createTextArea(&m.textarea)
+ m.spinner = createSpinner()
+ return m, m.spinner.Tick
case dialog.CompletionSelectedMsg:
if msg.IsCommand {
// Execute the command directly
@@ -421,12 +423,8 @@ func createTextArea(existing *textarea.Model) textarea.Model {
return ta
}
-func (m *editorComponent) GetValue() string {
- return m.textarea.Value()
-}
-
-func NewEditorComponent(app *app.App) layout.ModelWithView {
- s := spinner.New(
+func createSpinner() spinner.Model {
+ return spinner.New(
spinner.WithSpinner(spinner.Ellipsis),
spinner.WithStyle(
styles.
@@ -434,6 +432,14 @@ func NewEditorComponent(app *app.App) layout.ModelWithView {
Background(theme.CurrentTheme().Background()).
Width(3)),
)
+}
+
+func (m *editorComponent) GetValue() string {
+ return m.textarea.Value()
+}
+
+func NewEditorComponent(app *app.App) layout.ModelWithView {
+ s := createSpinner()
ta := createTextArea(nil)
return &editorComponent{
diff --git a/packages/tui/internal/components/dialog/complete.go b/packages/tui/internal/components/dialog/complete.go
index 7a15c8c70..d87a331cf 100644
--- a/packages/tui/internal/components/dialog/complete.go
+++ b/packages/tui/internal/components/dialog/complete.go
@@ -13,7 +13,6 @@ import (
)
type CompletionItem struct {
- title string
Title string
Value string
}
@@ -35,8 +34,7 @@ func (ci *CompletionItem) Render(selected bool, width int) string {
if selected {
itemStyle = itemStyle.
- Foreground(t.Primary()).
- Bold(true)
+ Foreground(t.Primary())
}
title := itemStyle.Render(
@@ -62,6 +60,7 @@ type CompletionProvider interface {
GetId() string
GetEntry() CompletionItemI
GetChildEntries(query string) ([]CompletionItemI, error)
+ GetEmptyMessage() string
}
type CompletionSelectedMsg struct {
@@ -250,6 +249,7 @@ func (c *completionDialogComponent) IsEmpty() bool {
func (c *completionDialogComponent) SetProvider(provider CompletionProvider) {
if c.completionProvider.GetId() != provider.GetId() {
c.completionProvider = provider
+ c.list.SetEmptyMessage(" " + provider.GetEmptyMessage())
}
}
@@ -259,7 +259,7 @@ func NewCompletionDialogComponent(completionProvider CompletionProvider) Complet
li := list.NewListComponent(
[]CompletionItemI{},
7,
- "No matches",
+ completionProvider.GetEmptyMessage(),
false,
)
diff --git a/packages/tui/internal/components/list/list.go b/packages/tui/internal/components/list/list.go
index 8be3c7b01..fea77e902 100644
--- a/packages/tui/internal/components/list/list.go
+++ b/packages/tui/internal/components/list/list.go
@@ -18,6 +18,7 @@ type List[T ListItem] interface {
SetItems(items []T)
GetItems() []T
SetSelectedIndex(idx int)
+ SetEmptyMessage(msg string)
IsEmpty() bool
}
@@ -100,6 +101,10 @@ func (c *listComponent[T]) GetItems() []T {
return c.items
}
+func (c *listComponent[T]) SetEmptyMessage(msg string) {
+ c.fallbackMsg = msg
+}
+
func (c *listComponent[T]) IsEmpty() bool {
return len(c.items) == 0
}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index d9ce0180c..1c93f116b 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -145,6 +145,14 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "theme":
themeDialog := dialog.NewThemeDialog()
a.modal = themeDialog
+ case "share":
+ a.app.Client.PostSessionShareWithResponse(context.Background(), client.PostSessionShareJSONRequestBody{
+ SessionID: a.app.Session.Id,
+ })
+ case "init":
+ return a, a.app.InitializeProject(context.Background())
+ // case "compact":
+ // return a, a.app.CompactSession(context.Background())
case "help":
var helpBindings []key.Binding
for _, cmd := range a.app.Commands {