Răsfoiți Sursa

wip: refactoring tui

adamdottv 8 luni în urmă
părinte
comite
b7f06bbc1f

+ 0 - 264
packages/tui/internal/components/dialog/arguments.go

@@ -1,264 +0,0 @@
-package dialog
-
-import (
-	"fmt"
-	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/bubbles/v2/textinput"
-	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/charmbracelet/lipgloss/v2"
-
-	"github.com/sst/opencode/internal/styles"
-	"github.com/sst/opencode/internal/theme"
-	"github.com/sst/opencode/internal/util"
-)
-
-type argumentsDialogKeyMap struct {
-	Enter  key.Binding
-	Escape key.Binding
-}
-
-// ShortHelp implements key.Map.
-func (k argumentsDialogKeyMap) ShortHelp() []key.Binding {
-	return []key.Binding{
-		key.NewBinding(
-			key.WithKeys("enter"),
-			key.WithHelp("enter", "confirm"),
-		),
-		key.NewBinding(
-			key.WithKeys("esc"),
-			key.WithHelp("esc", "cancel"),
-		),
-	}
-}
-
-// FullHelp implements key.Map.
-func (k argumentsDialogKeyMap) FullHelp() [][]key.Binding {
-	return [][]key.Binding{k.ShortHelp()}
-}
-
-// ShowMultiArgumentsDialogMsg is a message that is sent to show the multi-arguments dialog.
-type ShowMultiArgumentsDialogMsg struct {
-	CommandID string
-	Content   string
-	ArgNames  []string
-}
-
-// CloseMultiArgumentsDialogMsg is a message that is sent when the multi-arguments dialog is closed.
-type CloseMultiArgumentsDialogMsg struct {
-	Submit    bool
-	CommandID string
-	Content   string
-	Args      map[string]string
-}
-
-// MultiArgumentsDialogCmp is a component that asks the user for multiple command arguments.
-type MultiArgumentsDialogCmp struct {
-	width, height int
-	inputs        []textinput.Model
-	focusIndex    int
-	keys          argumentsDialogKeyMap
-	commandID     string
-	content       string
-	argNames      []string
-}
-
-// NewMultiArgumentsDialogCmp creates a new MultiArgumentsDialogCmp.
-func NewMultiArgumentsDialogCmp(commandID, content string, argNames []string) MultiArgumentsDialogCmp {
-	t := theme.CurrentTheme()
-	inputs := make([]textinput.Model, len(argNames))
-
-	for i, name := range argNames {
-		ti := textinput.New()
-		ti.Placeholder = fmt.Sprintf("Enter value for %s...", name)
-		ti.SetWidth(40)
-		ti.Prompt = ""
-		ti.Styles.Blurred.Placeholder = ti.Styles.Blurred.Placeholder.Background(t.Background())
-		ti.Styles.Blurred.Text = ti.Styles.Blurred.Text.Background(t.Background())
-		ti.Styles.Blurred.Prompt = ti.Styles.Blurred.Prompt.Foreground(t.Primary())
-
-		ti.Styles.Focused.Placeholder = ti.Styles.Focused.Placeholder.Background(t.Background())
-		ti.Styles.Focused.Text = ti.Styles.Focused.Text.Background(t.Background())
-		ti.Styles.Focused.Prompt = ti.Styles.Focused.Prompt.Foreground(t.Primary())
-
-		// ti.PromptStyle = ti.PromptStyle.Background(t.Background())
-		// ti.TextStyle = ti.TextStyle.Background(t.Background())
-
-		// Only focus the first input initially
-		if i == 0 {
-			ti.Focus()
-			// ti.PromptStyle = ti.PromptStyle.Foreground(t.Primary())
-			// ti.TextStyle = ti.TextStyle.Foreground(t.Primary())
-		} else {
-			ti.Blur()
-		}
-
-		inputs[i] = ti
-	}
-
-	return MultiArgumentsDialogCmp{
-		inputs:     inputs,
-		keys:       argumentsDialogKeyMap{},
-		commandID:  commandID,
-		content:    content,
-		argNames:   argNames,
-		focusIndex: 0,
-	}
-}
-
-// Init implements tea.Model.
-func (m MultiArgumentsDialogCmp) Init() tea.Cmd {
-	// Make sure only the first input is focused
-	for i := range m.inputs {
-		if i == 0 {
-			m.inputs[i].Focus()
-		} else {
-			m.inputs[i].Blur()
-		}
-	}
-
-	return textinput.Blink
-}
-
-// Update implements tea.Model.
-func (m MultiArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	var cmds []tea.Cmd
-	// t := theme.CurrentTheme()
-
-	switch msg := msg.(type) {
-	case tea.KeyMsg:
-		switch {
-		case key.Matches(msg, key.NewBinding(key.WithKeys("esc"))):
-			return m, util.CmdHandler(CloseMultiArgumentsDialogMsg{
-				Submit:    false,
-				CommandID: m.commandID,
-				Content:   m.content,
-				Args:      nil,
-			})
-		case key.Matches(msg, key.NewBinding(key.WithKeys("enter"))):
-			// If we're on the last input, submit the form
-			if m.focusIndex == len(m.inputs)-1 {
-				args := make(map[string]string)
-				for i, name := range m.argNames {
-					args[name] = m.inputs[i].Value()
-				}
-				return m, util.CmdHandler(CloseMultiArgumentsDialogMsg{
-					Submit:    true,
-					CommandID: m.commandID,
-					Content:   m.content,
-					Args:      args,
-				})
-			}
-			// Otherwise, move to the next input
-			m.inputs[m.focusIndex].Blur()
-			m.focusIndex++
-			m.inputs[m.focusIndex].Focus()
-			// m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary())
-			// m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary())
-		case key.Matches(msg, key.NewBinding(key.WithKeys("tab"))):
-			// Move to the next input
-			m.inputs[m.focusIndex].Blur()
-			m.focusIndex = (m.focusIndex + 1) % len(m.inputs)
-			m.inputs[m.focusIndex].Focus()
-			// m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary())
-			// m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary())
-		case key.Matches(msg, key.NewBinding(key.WithKeys("shift+tab"))):
-			// Move to the previous input
-			m.inputs[m.focusIndex].Blur()
-			m.focusIndex = (m.focusIndex - 1 + len(m.inputs)) % len(m.inputs)
-			m.inputs[m.focusIndex].Focus()
-			// m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary())
-			// m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary())
-		}
-	case tea.WindowSizeMsg:
-		m.width = msg.Width
-		m.height = msg.Height
-	}
-
-	// Update the focused input
-	var cmd tea.Cmd
-	m.inputs[m.focusIndex], cmd = m.inputs[m.focusIndex].Update(msg)
-	cmds = append(cmds, cmd)
-
-	return m, tea.Batch(cmds...)
-}
-
-// View implements tea.Model.
-func (m MultiArgumentsDialogCmp) View() string {
-	t := theme.CurrentTheme()
-	baseStyle := styles.BaseStyle()
-
-	// Calculate width needed for content
-	maxWidth := 60 // Width for explanation text
-
-	title := lipgloss.NewStyle().
-		Foreground(t.Primary()).
-		Bold(true).
-		Width(maxWidth).
-		Padding(0, 1).
-		Background(t.Background()).
-		Render("Command Arguments")
-
-	explanation := lipgloss.NewStyle().
-		Foreground(t.Text()).
-		Width(maxWidth).
-		Padding(0, 1).
-		Background(t.Background()).
-		Render("This command requires multiple arguments. Please enter values for each:")
-
-	// Create input fields for each argument
-	inputFields := make([]string, len(m.inputs))
-	for i, input := range m.inputs {
-		// Highlight the label of the focused input
-		labelStyle := lipgloss.NewStyle().
-			Width(maxWidth).
-			Padding(1, 1, 0, 1).
-			Background(t.Background())
-
-		if i == m.focusIndex {
-			labelStyle = labelStyle.Foreground(t.Primary()).Bold(true)
-		} else {
-			labelStyle = labelStyle.Foreground(t.TextMuted())
-		}
-
-		label := labelStyle.Render(m.argNames[i] + ":")
-
-		field := lipgloss.NewStyle().
-			Foreground(t.Text()).
-			Width(maxWidth).
-			Padding(0, 1).
-			Background(t.Background()).
-			Render(input.View())
-
-		inputFields[i] = lipgloss.JoinVertical(lipgloss.Left, label, field)
-	}
-
-	maxWidth = min(maxWidth, m.width-10)
-
-	// Join all elements vertically
-	elements := []string{title, explanation}
-	elements = append(elements, inputFields...)
-
-	content := lipgloss.JoinVertical(
-		lipgloss.Left,
-		elements...,
-	)
-
-	return baseStyle.Padding(1, 2).
-		Border(lipgloss.RoundedBorder()).
-		BorderBackground(t.Background()).
-		BorderForeground(t.TextMuted()).
-		Background(t.Background()).
-		Width(lipgloss.Width(content) + 4).
-		Render(content)
-}
-
-// SetSize sets the size of the component.
-func (m *MultiArgumentsDialogCmp) SetSize(width, height int) {
-	m.width = width
-	m.height = height
-}
-
-// Bindings implements layout.Bindings.
-func (m MultiArgumentsDialogCmp) Bindings() []key.Binding {
-	return m.keys.ShortHelp()
-}

+ 0 - 180
packages/tui/internal/components/dialog/commands.go

@@ -1,180 +0,0 @@
-package dialog
-
-import (
-	"github.com/charmbracelet/bubbles/v2/key"
-	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/charmbracelet/lipgloss/v2"
-	utilComponents "github.com/sst/opencode/internal/components/util"
-	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/styles"
-	"github.com/sst/opencode/internal/theme"
-	"github.com/sst/opencode/internal/util"
-)
-
-// Command represents a command that can be executed
-type Command struct {
-	ID          string
-	Title       string
-	Description string
-	Handler     func(cmd Command) tea.Cmd
-}
-
-func (ci Command) Render(selected bool, width int) string {
-	t := theme.CurrentTheme()
-	baseStyle := styles.BaseStyle()
-
-	descStyle := baseStyle.Width(width).Foreground(t.TextMuted())
-	itemStyle := baseStyle.Width(width).
-		Foreground(t.Text()).
-		Background(t.Background())
-
-	if selected {
-		itemStyle = itemStyle.
-			Background(t.Primary()).
-			Foreground(t.Background()).
-			Bold(true)
-		descStyle = descStyle.
-			Background(t.Primary()).
-			Foreground(t.Background())
-	}
-
-	title := itemStyle.Padding(0, 1).Render(ci.Title)
-	if ci.Description != "" {
-		description := descStyle.Padding(0, 1).Render(ci.Description)
-		return lipgloss.JoinVertical(lipgloss.Left, title, description)
-	}
-	return title
-}
-
-// CommandSelectedMsg is sent when a command is selected
-type CommandSelectedMsg struct {
-	Command Command
-}
-
-// CloseCommandDialogMsg is sent when the command dialog is closed
-type CloseCommandDialogMsg struct{}
-
-// CommandDialog interface for the command selection dialog
-type CommandDialog interface {
-	layout.ModelWithView
-	layout.Bindings
-	SetCommands(commands []Command)
-}
-
-type commandDialogComponent struct {
-	listView utilComponents.SimpleList[Command]
-	width    int
-	height   int
-}
-
-type commandKeyMap struct {
-	Enter  key.Binding
-	Escape key.Binding
-}
-
-var commandKeys = commandKeyMap{
-	Enter: key.NewBinding(
-		key.WithKeys("enter"),
-		key.WithHelp("enter", "select command"),
-	),
-	Escape: key.NewBinding(
-		key.WithKeys("esc"),
-		key.WithHelp("esc", "close"),
-	),
-}
-
-func (c *commandDialogComponent) Init() tea.Cmd {
-	return c.listView.Init()
-}
-
-func (c *commandDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	var cmds []tea.Cmd
-	switch msg := msg.(type) {
-	case tea.KeyMsg:
-		switch {
-		case key.Matches(msg, commandKeys.Enter):
-			selectedItem, idx := c.listView.GetSelectedItem()
-			if idx != -1 {
-				return c, util.CmdHandler(CommandSelectedMsg{
-					Command: selectedItem,
-				})
-			}
-		case key.Matches(msg, commandKeys.Escape):
-			return c, util.CmdHandler(CloseCommandDialogMsg{})
-		}
-	case tea.WindowSizeMsg:
-		c.width = msg.Width
-		c.height = msg.Height
-	}
-
-	u, cmd := c.listView.Update(msg)
-	c.listView = u.(utilComponents.SimpleList[Command])
-	cmds = append(cmds, cmd)
-
-	return c, tea.Batch(cmds...)
-}
-
-func (c *commandDialogComponent) View() string {
-	t := theme.CurrentTheme()
-	baseStyle := styles.BaseStyle()
-
-	maxWidth := 40
-
-	commands := c.listView.GetItems()
-
-	for _, cmd := range commands {
-		if len(cmd.Title) > maxWidth-4 {
-			maxWidth = len(cmd.Title) + 4
-		}
-		if cmd.Description != "" {
-			if len(cmd.Description) > maxWidth-4 {
-				maxWidth = len(cmd.Description) + 4
-			}
-		}
-	}
-
-	c.listView.SetMaxWidth(maxWidth)
-
-	title := baseStyle.
-		Foreground(t.Primary()).
-		Bold(true).
-		Width(maxWidth).
-		Padding(0, 1).
-		Render("Commands")
-
-	content := lipgloss.JoinVertical(
-		lipgloss.Left,
-		title,
-		baseStyle.Width(maxWidth).Render(""),
-		baseStyle.Width(maxWidth).Render(c.listView.View()),
-		baseStyle.Width(maxWidth).Render(""),
-	)
-
-	return baseStyle.Padding(1, 2).
-		Border(lipgloss.RoundedBorder()).
-		BorderBackground(t.Background()).
-		BorderForeground(t.TextMuted()).
-		Width(lipgloss.Width(content) + 4).
-		Render(content)
-}
-
-func (c *commandDialogComponent) BindingKeys() []key.Binding {
-	return layout.KeyMapToSlice(commandKeys)
-}
-
-func (c *commandDialogComponent) SetCommands(commands []Command) {
-	c.listView.SetItems(commands)
-}
-
-// NewCommandDialogCmp creates a new command selection dialog
-func NewCommandDialogCmp() CommandDialog {
-	listView := utilComponents.NewSimpleList[Command](
-		[]Command{},
-		10,
-		"No commands available",
-		true,
-	)
-	return &commandDialogComponent{
-		listView: listView,
-	}
-}

+ 0 - 155
packages/tui/internal/components/dialog/custom_commands.go

@@ -1,155 +0,0 @@
-package dialog
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strings"
-
-	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/sst/opencode/internal/app"
-	"github.com/sst/opencode/internal/util"
-)
-
-// Command prefix constants
-const (
-	UserCommandPrefix    = "user:"
-	ProjectCommandPrefix = "project:"
-)
-
-// namedArgPattern is a regex pattern to find named arguments in the format $NAME
-var namedArgPattern = regexp.MustCompile(`\$([A-Z][A-Z0-9_]*)`)
-
-// LoadCustomCommands loads custom commands from both XDG_CONFIG_HOME and project data directory
-func LoadCustomCommands() ([]Command, error) {
-	var commands []Command
-
-	homeCommandsDir := filepath.Join(app.Info.Path.Config, "commands")
-	homeCommands, err := loadCommandsFromDir(homeCommandsDir, UserCommandPrefix)
-	if err != nil {
-		// Log error but continue - we'll still try to load other commands
-		fmt.Printf("Warning: failed to load home commands: %v\n", err)
-	} else {
-		commands = append(commands, homeCommands...)
-	}
-
-	projectCommandsDir := filepath.Join(app.Info.Path.Root, ".opencode", "commands")
-	projectCommands, err := loadCommandsFromDir(projectCommandsDir, ProjectCommandPrefix)
-	if err != nil {
-		// Log error but return what we have so far
-		fmt.Printf("Warning: failed to load project commands: %v\n", err)
-	} else {
-		commands = append(commands, projectCommands...)
-	}
-
-	return commands, nil
-}
-
-// loadCommandsFromDir loads commands from a specific directory with the given prefix
-func loadCommandsFromDir(commandsDir string, prefix string) ([]Command, error) {
-	// Check if the commands directory exists
-	if _, err := os.Stat(commandsDir); os.IsNotExist(err) {
-		// Create the commands directory if it doesn't exist
-		if err := os.MkdirAll(commandsDir, 0755); err != nil {
-			return nil, fmt.Errorf("failed to create commands directory %s: %w", commandsDir, err)
-		}
-		// Return empty list since we just created the directory
-		return []Command{}, nil
-	}
-
-	var commands []Command
-
-	// Walk through the commands directory and load all .md files
-	err := filepath.Walk(commandsDir, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		// Skip directories
-		if info.IsDir() {
-			return nil
-		}
-
-		// Only process markdown files
-		if !strings.HasSuffix(strings.ToLower(info.Name()), ".md") {
-			return nil
-		}
-
-		// Read the file content
-		content, err := os.ReadFile(path)
-		if err != nil {
-			return fmt.Errorf("failed to read command file %s: %w", path, err)
-		}
-
-		// Get the command ID from the file name without the .md extension
-		commandID := strings.TrimSuffix(info.Name(), filepath.Ext(info.Name()))
-
-		// Get relative path from commands directory
-		relPath, err := filepath.Rel(commandsDir, path)
-		if err != nil {
-			return fmt.Errorf("failed to get relative path for %s: %w", path, err)
-		}
-
-		// Create the command ID from the relative path
-		// Replace directory separators with colons
-		commandIDPath := strings.ReplaceAll(filepath.Dir(relPath), string(filepath.Separator), ":")
-		if commandIDPath != "." {
-			commandID = commandIDPath + ":" + commandID
-		}
-
-		// Create a command
-		command := Command{
-			ID:          prefix + commandID,
-			Title:       prefix + commandID,
-			Description: fmt.Sprintf("Custom command from %s", relPath),
-			Handler: func(cmd Command) tea.Cmd {
-				commandContent := string(content)
-
-				// Check for named arguments
-				matches := namedArgPattern.FindAllStringSubmatch(commandContent, -1)
-				if len(matches) > 0 {
-					// Extract unique argument names
-					argNames := make([]string, 0)
-					argMap := make(map[string]bool)
-
-					for _, match := range matches {
-						argName := match[1] // Group 1 is the name without $
-						if !argMap[argName] {
-							argMap[argName] = true
-							argNames = append(argNames, argName)
-						}
-					}
-
-					// Show multi-arguments dialog for all named arguments
-					return util.CmdHandler(ShowMultiArgumentsDialogMsg{
-						CommandID: cmd.ID,
-						Content:   commandContent,
-						ArgNames:  argNames,
-					})
-				}
-
-				// No arguments needed, run command directly
-				return util.CmdHandler(CommandRunCustomMsg{
-					Content: commandContent,
-					Args:    nil, // No arguments
-				})
-			},
-		}
-
-		commands = append(commands, command)
-		return nil
-	})
-
-	if err != nil {
-		return nil, fmt.Errorf("failed to load custom commands from %s: %w", commandsDir, err)
-	}
-
-	return commands, nil
-}
-
-// CommandRunCustomMsg is sent when a custom command is executed
-type CommandRunCustomMsg struct {
-	Content string
-	Args    map[string]string // Map of argument names to values
-}

+ 0 - 106
packages/tui/internal/components/dialog/custom_commands_test.go

@@ -1,106 +0,0 @@
-package dialog
-
-import (
-	"testing"
-	"regexp"
-)
-
-func TestNamedArgPattern(t *testing.T) {
-	testCases := []struct {
-		input    string
-		expected []string
-	}{
-		{
-			input:    "This is a test with $ARGUMENTS placeholder",
-			expected: []string{"ARGUMENTS"},
-		},
-		{
-			input:    "This is a test with $FOO and $BAR placeholders",
-			expected: []string{"FOO", "BAR"},
-		},
-		{
-			input:    "This is a test with $FOO_BAR and $BAZ123 placeholders",
-			expected: []string{"FOO_BAR", "BAZ123"},
-		},
-		{
-			input:    "This is a test with no placeholders",
-			expected: []string{},
-		},
-		{
-			input:    "This is a test with $FOO appearing twice: $FOO",
-			expected: []string{"FOO"},
-		},
-		{
-			input:    "This is a test with $1INVALID placeholder",
-			expected: []string{},
-		},
-	}
-
-	for _, tc := range testCases {
-		matches := namedArgPattern.FindAllStringSubmatch(tc.input, -1)
-		
-		// Extract unique argument names
-		argNames := make([]string, 0)
-		argMap := make(map[string]bool)
-		
-		for _, match := range matches {
-			argName := match[1] // Group 1 is the name without $
-			if !argMap[argName] {
-				argMap[argName] = true
-				argNames = append(argNames, argName)
-			}
-		}
-		
-		// Check if we got the expected number of arguments
-		if len(argNames) != len(tc.expected) {
-			t.Errorf("Expected %d arguments, got %d for input: %s", len(tc.expected), len(argNames), tc.input)
-			continue
-		}
-		
-		// Check if we got the expected argument names
-		for _, expectedArg := range tc.expected {
-			found := false
-			for _, actualArg := range argNames {
-				if actualArg == expectedArg {
-					found = true
-					break
-				}
-			}
-			if !found {
-				t.Errorf("Expected argument %s not found in %v for input: %s", expectedArg, argNames, tc.input)
-			}
-		}
-	}
-}
-
-func TestRegexPattern(t *testing.T) {
-	pattern := regexp.MustCompile(`\$([A-Z][A-Z0-9_]*)`)
-	
-	validMatches := []string{
-		"$FOO",
-		"$BAR",
-		"$FOO_BAR",
-		"$BAZ123",
-		"$ARGUMENTS",
-	}
-	
-	invalidMatches := []string{
-		"$foo",
-		"$1BAR",
-		"$_FOO",
-		"FOO",
-		"$",
-	}
-	
-	for _, valid := range validMatches {
-		if !pattern.MatchString(valid) {
-			t.Errorf("Expected %s to match, but it didn't", valid)
-		}
-	}
-	
-	for _, invalid := range invalidMatches {
-		if pattern.MatchString(invalid) {
-			t.Errorf("Expected %s not to match, but it did", invalid)
-		}
-	}
-}

+ 0 - 178
packages/tui/internal/components/dialog/tools.go

@@ -1,178 +0,0 @@
-package dialog
-
-import (
-	"github.com/charmbracelet/bubbles/v2/key"
-	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/charmbracelet/lipgloss/v2"
-	utilComponents "github.com/sst/opencode/internal/components/util"
-	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/styles"
-	"github.com/sst/opencode/internal/theme"
-)
-
-const (
-	maxToolsDialogWidth = 60
-	maxVisibleTools     = 15
-)
-
-// ToolsDialog interface for the tools list dialog
-type ToolsDialog interface {
-	layout.ModelWithView
-	layout.Bindings
-	SetTools(tools []string)
-}
-
-// ShowToolsDialogMsg is sent to show the tools dialog
-type ShowToolsDialogMsg struct {
-	Show bool
-}
-
-// CloseToolsDialogMsg is sent when the tools dialog is closed
-type CloseToolsDialogMsg struct{}
-
-type toolItem struct {
-	name string
-}
-
-func (t toolItem) Render(selected bool, width int) string {
-	th := theme.CurrentTheme()
-	baseStyle := styles.BaseStyle().
-		Width(width).
-		Background(th.Background())
-
-	if selected {
-		baseStyle = baseStyle.
-			Background(th.Primary()).
-			Foreground(th.Background()).
-			Bold(true)
-	} else {
-		baseStyle = baseStyle.
-			Foreground(th.Text())
-	}
-
-	return baseStyle.Render(t.name)
-}
-
-type toolsDialogComponent struct {
-	tools  []toolItem
-	width  int
-	height int
-	list   utilComponents.SimpleList[toolItem]
-}
-
-type toolsKeyMap struct {
-	Up     key.Binding
-	Down   key.Binding
-	Escape key.Binding
-	J      key.Binding
-	K      key.Binding
-}
-
-var toolsKeys = toolsKeyMap{
-	Up: key.NewBinding(
-		key.WithKeys("up"),
-		key.WithHelp("↑", "previous tool"),
-	),
-	Down: key.NewBinding(
-		key.WithKeys("down"),
-		key.WithHelp("↓", "next tool"),
-	),
-	Escape: key.NewBinding(
-		key.WithKeys("esc"),
-		key.WithHelp("esc", "close"),
-	),
-	J: key.NewBinding(
-		key.WithKeys("j"),
-		key.WithHelp("j", "next tool"),
-	),
-	K: key.NewBinding(
-		key.WithKeys("k"),
-		key.WithHelp("k", "previous tool"),
-	),
-}
-
-func (m *toolsDialogComponent) Init() tea.Cmd {
-	return nil
-}
-
-func (m *toolsDialogComponent) SetTools(tools []string) {
-	var toolItems []toolItem
-	for _, name := range tools {
-		toolItems = append(toolItems, toolItem{name: name})
-	}
-
-	m.tools = toolItems
-	m.list.SetItems(toolItems)
-}
-
-func (m *toolsDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	switch msg := msg.(type) {
-	case tea.KeyMsg:
-		switch {
-		case key.Matches(msg, toolsKeys.Escape):
-			return m, func() tea.Msg { return CloseToolsDialogMsg{} }
-		// Pass other key messages to the list component
-		default:
-			var cmd tea.Cmd
-			listModel, cmd := m.list.Update(msg)
-			m.list = listModel.(utilComponents.SimpleList[toolItem])
-			return m, cmd
-		}
-	case tea.WindowSizeMsg:
-		m.width = msg.Width
-		m.height = msg.Height
-	}
-
-	// For non-key messages
-	var cmd tea.Cmd
-	listModel, cmd := m.list.Update(msg)
-	m.list = listModel.(utilComponents.SimpleList[toolItem])
-	return m, cmd
-}
-
-func (m *toolsDialogComponent) View() string {
-	t := theme.CurrentTheme()
-	baseStyle := styles.BaseStyle().Background(t.Background())
-
-	title := baseStyle.
-		Foreground(t.Primary()).
-		Bold(true).
-		Width(maxToolsDialogWidth).
-		Padding(0, 0, 1).
-		Render("Available Tools")
-
-	// Calculate dialog width based on content
-	dialogWidth := min(maxToolsDialogWidth, m.width/2)
-	m.list.SetMaxWidth(dialogWidth)
-
-	content := lipgloss.JoinVertical(
-		lipgloss.Left,
-		title,
-		m.list.View(),
-	)
-
-	return baseStyle.Padding(1, 2).
-		Border(lipgloss.RoundedBorder()).
-		BorderBackground(t.Background()).
-		BorderForeground(t.TextMuted()).
-		Background(t.Background()).
-		Width(lipgloss.Width(content) + 4).
-		Render(content)
-}
-
-func (m *toolsDialogComponent) BindingKeys() []key.Binding {
-	return layout.KeyMapToSlice(toolsKeys)
-}
-
-func NewToolsDialogCmp() ToolsDialog {
-	list := utilComponents.NewSimpleList[toolItem](
-		[]toolItem{},
-		maxVisibleTools,
-		"No tools available",
-		true,
-	)
-
-	return &toolsDialogComponent{
-		list: list,
-	}
-}

+ 0 - 25
packages/tui/internal/page/chat.go

@@ -2,7 +2,6 @@ package page
 
 import (
 	"context"
-	"strings"
 
 	"github.com/charmbracelet/bubbles/v2/key"
 	tea "github.com/charmbracelet/bubbletea/v2"
@@ -12,7 +11,6 @@ import (
 	"github.com/sst/opencode/internal/components/chat"
 	"github.com/sst/opencode/internal/components/dialog"
 	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/util"
 )
 
@@ -67,29 +65,6 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		if cmd != nil {
 			return p, cmd
 		}
-	case dialog.CommandRunCustomMsg:
-		// Check if the agent is busy before executing custom commands
-		if p.app.IsBusy() {
-			status.Warn("Agent is busy, please wait before executing a command...")
-			return p, nil
-		}
-
-		// Process the command content with arguments if any
-		content := msg.Content
-		if msg.Args != nil {
-			// Replace all named arguments with their values
-			for name, value := range msg.Args {
-				placeholder := "$" + name
-				content = strings.ReplaceAll(content, placeholder, value)
-			}
-		}
-
-		// Handle custom command execution
-		cmd := p.sendMessage(content, nil)
-		if cmd != nil {
-			return p, cmd
-		}
-
 	case dialog.CompletionDialogCloseMsg:
 		p.showCompletionDialog = false
 		p.app.SetCompletionDialogOpen(false)

+ 0 - 45
packages/tui/internal/tui/tui.go

@@ -2,7 +2,6 @@ package tui
 
 import (
 	"context"
-	"log/slog"
 
 	"github.com/charmbracelet/bubbles/v2/cursor"
 	"github.com/charmbracelet/bubbles/v2/key"
@@ -17,7 +16,6 @@ import (
 	"github.com/sst/opencode/internal/layout"
 	"github.com/sst/opencode/internal/page"
 	"github.com/sst/opencode/internal/state"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
 	"github.com/sst/opencode/internal/util"
@@ -69,7 +67,6 @@ type appModel struct {
 	status        core.StatusComponent
 	app           *app.App
 	modal         layout.Modal
-	commands      []dialog.Command
 }
 
 func (a appModel) Init() tea.Cmd {
@@ -348,11 +345,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	return a, tea.Batch(cmds...)
 }
 
-// RegisterCommand adds a command to the command dialog
-func (a *appModel) RegisterCommand(cmd dialog.Command) {
-	a.commands = append(a.commands, cmd)
-}
-
 func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
 	var cmds []tea.Cmd
 	if _, ok := a.loadedPages[pageID]; !ok {
@@ -391,47 +383,10 @@ func NewModel(app *app.App) tea.Model {
 		loadedPages: make(map[page.PageID]bool),
 		status:      core.NewStatusCmp(app),
 		app:         app,
-		commands:    []dialog.Command{},
 		pages: map[page.PageID]layout.ModelWithView{
 			page.ChatPage: page.NewChatPage(app),
 		},
 	}
 
-	model.RegisterCommand(dialog.Command{
-		ID:          "init",
-		Title:       "Initialize Project",
-		Description: "Create/Update the AGENTS.md memory file",
-		Handler: func(cmd dialog.Command) tea.Cmd {
-			return app.InitializeProject(context.Background())
-		},
-	})
-
-	model.RegisterCommand(dialog.Command{
-		ID:          "compact_conversation",
-		Title:       "Compact Conversation",
-		Description: "Summarize the current session to save tokens",
-		Handler: func(cmd dialog.Command) tea.Cmd {
-			// Get the current session from the appModel
-			if model.currentPage != page.ChatPage {
-				status.Warn("Please navigate to a chat session first.")
-				return nil
-			}
-
-			// Return a message that will be handled by the chat page
-			status.Info("Compacting conversation...")
-			return util.CmdHandler(state.CompactSessionMsg{})
-		},
-	})
-
-	// Load custom commands
-	customCommands, err := dialog.LoadCustomCommands()
-	if err != nil {
-		slog.Warn("Failed to load custom commands", "error", err)
-	} else {
-		for _, cmd := range customCommands {
-			model.RegisterCommand(cmd)
-		}
-	}
-
 	return model
 }