Compare commits

...

4 Commits

Author SHA1 Message Date
Kujtim Hoxha
cc514da850 small fixes 2025-04-24 13:01:32 +02:00
Kujtim Hoxha
263bea8051 update mainter email 2025-04-23 12:26:50 +02:00
Kujtim Hoxha
332243d4c8 small changes 2025-04-23 12:15:23 +02:00
Kujtim Hoxha
4d9082c1d4 remove edit/normal mode 2025-04-23 12:09:47 +02:00
14 changed files with 152 additions and 165 deletions

View File

@@ -32,9 +32,9 @@ snapshot:
aurs:
- name: opencode
homepage: "https://github.com/opencode-ai/opencode"
description: "Deploy anything"
description: "terminal based agent that can build anything"
maintainers:
- "opencode <noreply@opencode.ai>"
- "kujtimiihoxha <kujtimii.h@gmail.com>"
license: "MIT"
private_key: "{{ .Env.AUR_KEY }}"
git_url: "ssh://aur@aur.archlinux.org/opencode-bin.git"
@@ -49,7 +49,7 @@ brews:
owner: opencode-ai
name: homebrew-tap
nfpms:
- maintainer: opencode
- maintainer: kujtimiihoxha
description: terminal based agent that can build anything
formats:
- deb

View File

@@ -1,4 +1,4 @@
# OpenCode
# OpenCode
> **⚠️ Early Development Notice:** This project is in early development and is not yet ready for production use. Features may change, break, or be incomplete. Use at your own risk.

View File

@@ -15,6 +15,7 @@ import (
"github.com/kujtimiihoxha/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui"
"github.com/kujtimiihoxha/opencode/internal/version"
zone "github.com/lrstanley/bubblezone"
"github.com/spf13/cobra"
)
@@ -31,6 +32,10 @@ to assist developers in writing, debugging, and understanding code directly from
cmd.Help()
return nil
}
if cmd.Flag("version").Changed {
fmt.Println(version.Version)
return nil
}
// Load the config
debug, _ := cmd.Flags().GetBool("debug")
@@ -247,6 +252,7 @@ func Execute() {
func init() {
rootCmd.Flags().BoolP("help", "h", false, "Help")
rootCmd.Flags().BoolP("version", "v", false, "Version")
rootCmd.Flags().BoolP("debug", "d", false, "Debug")
rootCmd.Flags().StringP("cwd", "c", "", "Current working directory")
}

View File

@@ -96,19 +96,19 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// Determine server type for specialized handling
serverName := getServerNameFromContext(ctx)
logging.Debug("Server type detected", "serverName", serverName)
// Check if this server has sent file watchers
hasFileWatchers := len(watchers) > 0
// For servers that need file preloading, we'll use a smart approach
if shouldPreloadFiles(serverName) || !hasFileWatchers {
go func() {
startTime := time.Now()
filesOpened := 0
// Determine max files to open based on server type
maxFilesToOpen := 50 // Default conservative limit
switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls":
// TypeScript servers benefit from seeing more files
@@ -117,17 +117,17 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// Java servers need to see many files for project model
maxFilesToOpen = 200
}
// First, open high-priority files
highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName)
filesOpened += highPriorityFilesOpened
if cnf.DebugLSP {
logging.Debug("Opened high-priority files",
logging.Debug("Opened high-priority files",
"count", highPriorityFilesOpened,
"serverName", serverName)
}
// If we've already opened enough high-priority files, we might not need more
if filesOpened >= maxFilesToOpen {
if cnf.DebugLSP {
@@ -137,9 +137,9 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
}
return
}
// For the remaining slots, walk the directory and open matching files
err := filepath.WalkDir(w.workspacePath, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
@@ -199,10 +199,10 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int {
cnf := config.Get()
filesOpened := 0
// Define patterns for high-priority files based on server type
var patterns []string
switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls":
patterns = []string{
@@ -256,7 +256,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
"**/.editorconfig",
}
}
// For each pattern, find and open matching files
for _, pattern := range patterns {
// Use doublestar.Glob to find files matching the pattern (supports ** patterns)
@@ -267,17 +267,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
}
continue
}
for _, match := range matches {
// Convert relative path to absolute
fullPath := filepath.Join(w.workspacePath, match)
// Skip directories and excluded files
info, err := os.Stat(fullPath)
if err != nil || info.IsDir() || shouldExcludeFile(fullPath) {
continue
}
// Open the file
if err := w.client.OpenFile(ctx, fullPath); err != nil {
if cnf.DebugLSP {
@@ -289,17 +289,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
logging.Debug("Opened high-priority file", "path", fullPath)
}
}
// Add a small delay to prevent overwhelming the server
time.Sleep(20 * time.Millisecond)
// Limit the number of files opened per pattern
if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") {
break
}
}
}
return filesOpened
}
@@ -310,16 +310,16 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
// Store the watcher in the context for later use
ctx = context.WithValue(ctx, "workspaceWatcher", w)
// If the server name isn't already in the context, try to detect it
if _, ok := ctx.Value("serverName").(string); !ok {
serverName := getServerNameFromContext(ctx)
ctx = context.WithValue(ctx, "serverName", serverName)
}
serverName := getServerNameFromContext(ctx)
logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
// Register handler for file watcher registrations from the server
lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
w.AddRegistrations(ctx, id, watchers)
@@ -414,7 +414,11 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
case event.Op&fsnotify.Create != 0:
// Already handled earlier in the event loop
// Just send the notification if needed
info, _ := os.Stat(event.Name)
info, err := os.Stat(event.Name)
if err != nil {
logging.Error("Error getting file info", "path", event.Name, "error", err)
return
}
if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
w.debounceHandleFileEvent(ctx, uri, protocol.FileChangeType(protocol.Created))
}
@@ -682,7 +686,7 @@ func getServerNameFromContext(ctx context.Context) string {
if serverName, ok := ctx.Value("serverName").(string); ok && serverName != "" {
return strings.ToLower(serverName)
}
// Otherwise, try to extract server name from the client command path
if w, ok := ctx.Value("workspaceWatcher").(*WorkspaceWatcher); ok && w != nil && w.client != nil && w.client.Cmd != nil {
path := strings.ToLower(w.client.Cmd.Path)
@@ -865,7 +869,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
if watched, _ := w.isPathWatched(path); watched {
// Get server name for specialized handling
serverName := getServerNameFromContext(ctx)
// Check if the file is a high-priority file that should be opened immediately
// This helps with project initialization for certain language servers
if isHighPriorityFile(path, serverName) {
@@ -881,7 +885,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
// For non-high-priority files, we'll use different strategies based on server type
if shouldPreloadFiles(serverName) {
// For servers that benefit from preloading, open files but with limits
// Check file size - for preloading we're more conservative
if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
if cnf.DebugLSP {
@@ -889,13 +893,13 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
}
return
}
// Check file extension for common source files
ext := strings.ToLower(filepath.Ext(path))
// Only preload source files for the specific language
shouldOpen := false
switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls":
shouldOpen = ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx"
@@ -913,7 +917,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
// For unknown servers, be conservative
shouldOpen = false
}
if shouldOpen {
// Don't need to check if it's already open - the client.OpenFile handles that
if err := w.client.OpenFile(ctx, path); err != nil && cnf.DebugLSP {
@@ -943,13 +947,13 @@ func isHighPriorityFile(path string, serverName string) bool {
fileName == "main.js"
case "gopls":
// For Go, we want to open go.mod files immediately
return fileName == "go.mod" ||
return fileName == "go.mod" ||
fileName == "go.sum" ||
// Also open main.go files
fileName == "main.go"
case "rust-analyzer":
// For Rust, we want to open Cargo.toml files immediately
return fileName == "Cargo.toml" ||
return fileName == "Cargo.toml" ||
fileName == "Cargo.lock" ||
// Also open lib.rs and main.rs
fileName == "lib.rs" ||

View File

@@ -21,12 +21,9 @@ type editorCmp struct {
textarea textarea.Model
}
type FocusEditorMsg bool
type focusedEditorKeyMaps struct {
type EditorKeyMaps struct {
Send key.Binding
OpenEditor key.Binding
Blur key.Binding
}
type bluredEditorKeyMaps struct {
@@ -35,29 +32,10 @@ type bluredEditorKeyMaps struct {
OpenEditor key.Binding
}
var focusedKeyMaps = focusedEditorKeyMaps{
var editorMaps = EditorKeyMaps{
Send: key.NewBinding(
key.WithKeys("ctrl+s"),
key.WithHelp("ctrl+s", "send message"),
),
Blur: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "focus messages"),
),
OpenEditor: key.NewBinding(
key.WithKeys("ctrl+e"),
key.WithHelp("ctrl+e", "open 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"),
key.WithKeys("enter", "ctrl+s"),
key.WithHelp("enter", "send message"),
),
OpenEditor: key.NewBinding(
key.WithKeys("ctrl+e"),
@@ -88,6 +66,9 @@ func openEditor() tea.Cmd {
if err != nil {
return util.ReportError(err)
}
if len(content) == 0 {
return util.ReportWarn("Message is empty")
}
os.Remove(tmpfile.Name())
return SendMsg{
Text: string(content),
@@ -106,7 +87,6 @@ func (m *editorCmp) send() tea.Cmd {
value := m.textarea.Value()
m.textarea.Reset()
m.textarea.Blur()
if value == "" {
return nil
}
@@ -125,32 +105,28 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.session = msg
}
return m, nil
case FocusEditorMsg:
if msg {
m.textarea.Focus()
return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
}
case tea.KeyMsg:
if key.Matches(msg, focusedKeyMaps.OpenEditor) {
if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) ||
key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) {
return m, nil
}
if key.Matches(msg, editorMaps.OpenEditor) {
if m.app.CoderAgent.IsSessionBusy(m.session.ID) {
return m, util.ReportWarn("Agent is working, please wait...")
}
return m, openEditor()
}
// if the key does not match any binding, return
if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Send) {
return m, m.send()
}
if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Send) {
return m, m.send()
}
if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Blur) {
m.textarea.Blur()
return m, util.CmdHandler(EditorFocusMsg(false))
}
if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Focus) {
m.textarea.Focus()
return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
// Handle Enter key
if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) {
value := m.textarea.Value()
if len(value) > 0 && value[len(value)-1] == '\\' {
// If the last character is a backslash, remove it and add a newline
m.textarea.SetValue(value[:len(value)-1] + "\n")
return m, nil
} else {
// Otherwise, send the message
return m, m.send()
}
}
}
m.textarea, cmd = m.textarea.Update(msg)
@@ -175,13 +151,7 @@ func (m *editorCmp) GetSize() (int, int) {
func (m *editorCmp) BindingKeys() []key.Binding {
bindings := []key.Binding{}
if m.textarea.Focused() {
bindings = append(bindings, layout.KeyMapToSlice(focusedKeyMaps)...)
} else {
bindings = append(bindings, layout.KeyMapToSlice(bluredKeyMaps)...)
}
bindings = append(bindings, layout.KeyMapToSlice(m.textarea.KeyMap)...)
bindings = append(bindings, layout.KeyMapToSlice(editorMaps)...)
return bindings
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/kujtimiihoxha/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util"
)
@@ -26,7 +25,6 @@ type cacheItem struct {
type messagesCmp struct {
app *app.App
width, height int
writingMode bool
viewport viewport.Model
session session.Session
messages []message.Message
@@ -38,6 +36,32 @@ type messagesCmp struct {
}
type renderFinishedMsg struct{}
type MessageKeys struct {
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
}
var messageKeys = MessageKeys{
PageDown: key.NewBinding(
key.WithKeys("pgdown"),
key.WithHelp("f/pgdn", "page down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup"),
key.WithHelp("b/pgup", "page up"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("ctrl+u"),
key.WithHelp("ctrl+u", "½ page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("ctrl+d", "ctrl+d"),
key.WithHelp("ctrl+d", "½ page down"),
),
}
func (m *messagesCmp) Init() tea.Cmd {
return tea.Batch(m.viewport.Init(), m.spinner.Tick)
}
@@ -45,8 +69,7 @@ func (m *messagesCmp) Init() tea.Cmd {
func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case EditorFocusMsg:
m.writingMode = bool(msg)
case SessionSelectedMsg:
if msg.ID != m.session.ID {
cmd := m.SetSession(msg)
@@ -60,13 +83,17 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.rendering = false
return m, nil
case tea.KeyMsg:
if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) ||
key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) {
u, cmd := m.viewport.Update(msg)
m.viewport = u
cmds = append(cmds, cmd)
}
case renderFinishedMsg:
m.rendering = false
m.viewport.GotoBottom()
case tea.KeyMsg:
if m.writingMode {
return m, nil
}
case pubsub.Event[message.Message]:
needsRerender := false
if msg.Type == pubsub.CreatedEvent {
@@ -122,10 +149,6 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
u, cmd := m.viewport.Update(msg)
m.viewport = u
cmds = append(cmds, cmd)
spinner, cmd := m.spinner.Update(msg)
m.spinner = spinner
cmds = append(cmds, cmd)
@@ -326,22 +349,24 @@ func (m *messagesCmp) working() string {
func (m *messagesCmp) help() string {
text := ""
if m.writingMode {
if m.app.CoderAgent.IsBusy() {
text += lipgloss.JoinHorizontal(
lipgloss.Left,
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("esc"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to exit writing mode"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to exit cancel"),
)
} else {
text += lipgloss.JoinHorizontal(
lipgloss.Left,
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("i"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to start writing"),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("enter"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to send the message,"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" write"),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render(" \\"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" and enter to add a new line"),
)
}
return styles.BaseStyle.
Width(m.width).
Render(text)
@@ -398,18 +423,26 @@ func (m *messagesCmp) SetSession(session session.Session) tea.Cmd {
}
func (m *messagesCmp) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(m.viewport.KeyMap)
return bindings
return []key.Binding{
m.viewport.KeyMap.PageDown,
m.viewport.KeyMap.PageUp,
m.viewport.KeyMap.HalfPageUp,
m.viewport.KeyMap.HalfPageDown,
}
}
func NewMessagesCmp(app *app.App) tea.Model {
s := spinner.New()
s.Spinner = spinner.Pulse
vp := viewport.New(0, 0)
vp.KeyMap.PageUp = messageKeys.PageUp
vp.KeyMap.PageDown = messageKeys.PageDown
vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
return &messagesCmp{
app: app,
writingMode: true,
cachedContent: make(map[string]cacheItem),
viewport: viewport.New(0, 0),
viewport: vp,
spinner: s,
}
}

View File

@@ -28,7 +28,7 @@ const (
assistantMessageType
toolMessageType
maxResultHeight = 15
maxResultHeight = 10
)
var diffStyle = diff.NewStyleConfig(diff.WithShowHeader(false), diff.WithShowHunkHeader(false))
@@ -148,7 +148,7 @@ func renderAssistantMessage(
content = "*Finished without output*"
}
content = renderMessage(content, false, msg.ID == focusedUIMessageId, width, info...)
content = renderMessage(content, false, true, width, info...)
messages = append(messages, uiMessage{
ID: msg.ID,
messageType: assistantMessageType,

View File

@@ -190,7 +190,6 @@ func (c *commandDialogCmp) View() string {
styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, commandItems...)),
styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
)
return styles.BaseStyle.Padding(1, 2).
@@ -244,4 +243,3 @@ func NewCommandDialogCmp() CommandDialog {
selectedCommandID: "",
}
}

View File

@@ -62,7 +62,7 @@ func (h *helpCmp) render() string {
var (
pairs []string
width int
rows = 14 - 2
rows = 10 - 2
)
for i := 0; i < len(bindings); i += rows {
var (

View File

@@ -46,8 +46,8 @@ func (k initDialogKeyMap) ShortHelp() []key.Binding {
key.WithHelp("enter", "confirm"),
),
key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "cancel"),
key.WithKeys("esc", "q"),
key.WithHelp("esc/q", "cancel"),
),
key.NewBinding(
key.WithKeys("y", "n"),
@@ -114,6 +114,7 @@ func (m InitDialogCmp) View() string {
Padding(1, 1).
Render("Would you like to initialize this project?")
maxWidth = min(maxWidth, m.width-10)
yesStyle := styles.BaseStyle
noStyle := styles.BaseStyle
@@ -144,12 +145,6 @@ func (m InitDialogCmp) View() string {
Padding(1, 0).
Render(buttons)
help := styles.BaseStyle.
Width(maxWidth).
Padding(0, 1).
Foreground(styles.ForgroundDim).
Render("tab/←/→: toggle y/n: yes/no enter: confirm esc: cancel")
content := lipgloss.JoinVertical(
lipgloss.Left,
title,
@@ -158,7 +153,6 @@ func (m InitDialogCmp) View() string {
question,
buttons,
styles.BaseStyle.Width(maxWidth).Render(""),
help,
)
return styles.BaseStyle.Padding(1, 2).

View File

@@ -67,8 +67,8 @@ var permissionsKeys = permissionsMapping{
key.WithHelp("a", "allow"),
),
AllowSession: key.NewBinding(
key.WithKeys("A"),
key.WithHelp("A", "allow for session"),
key.WithKeys("s"),
key.WithHelp("s", "allow for session"),
),
Deny: key.NewBinding(
key.WithKeys("d"),
@@ -171,7 +171,7 @@ func (p *permissionDialogCmp) renderButtons() string {
}
allowButton := allowStyle.Padding(0, 1).Render("Allow (a)")
allowSessionButton := allowSessionStyle.Padding(0, 1).Render("Allow for session (A)")
allowSessionButton := allowSessionStyle.Padding(0, 1).Render("Allow for session (s)")
denyButton := denyStyle.Padding(0, 1).Render("Deny (d)")
content := lipgloss.JoinHorizontal(
@@ -375,9 +375,6 @@ func (p *permissionDialogCmp) render() string {
contentFinal = p.renderDefaultContent()
}
// Add help text
helpText := styles.BaseStyle.Width(p.width - 4).Padding(0, 1).Foreground(styles.ForgroundDim).Render("←/→/tab: switch options a: allow A: allow for session d: deny enter/space: confirm")
content := lipgloss.JoinVertical(
lipgloss.Top,
title,
@@ -385,8 +382,7 @@ func (p *permissionDialogCmp) render() string {
headerContent,
contentFinal,
buttons,
styles.BaseStyle.Render(strings.Repeat(" ", p.width - 4)),
helpText,
styles.BaseStyle.Render(strings.Repeat(" ", p.width-4)),
)
return styles.BaseStyle.

View File

@@ -122,6 +122,8 @@ func (s *sessionDialogCmp) View() string {
}
}
maxWidth = max(30, min(maxWidth, s.width-15)) // Limit width to avoid overflow
// Limit height to avoid taking up too much screen space
maxVisibleSessions := min(10, len(s.sessions))
@@ -169,7 +171,6 @@ func (s *sessionDialogCmp) View() string {
styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)),
styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"),
)
return styles.BaseStyle.Padding(1, 2).
@@ -223,4 +224,3 @@ func NewSessionDialogCmp() SessionDialog {
selectedSessionID: "",
}
}

View File

@@ -15,12 +15,11 @@ import (
var ChatPage PageID = "chat"
type chatPage struct {
app *app.App
editor layout.Container
messages layout.Container
layout layout.SplitPaneLayout
session session.Session
editingMode bool
app *app.App
editor layout.Container
messages layout.Container
layout layout.SplitPaneLayout
session session.Session
}
type ChatKeyMap struct {
@@ -34,8 +33,8 @@ var keyMap = ChatKeyMap{
key.WithHelp("ctrl+n", "new session"),
),
Cancel: key.NewBinding(
key.WithKeys("ctrl+x"),
key.WithHelp("ctrl+x", "cancel"),
key.WithKeys("esc"),
key.WithHelp("esc", "cancel"),
),
}
@@ -65,8 +64,6 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
p.session = msg
case chat.EditorFocusMsg:
p.editingMode = bool(msg)
case tea.KeyMsg:
switch {
case key.Matches(msg, keyMap.NewSession):
@@ -136,11 +133,7 @@ func (p *chatPage) View() string {
func (p *chatPage) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(keyMap)
if p.editingMode {
bindings = append(bindings, p.editor.BindingKeys()...)
} else {
bindings = append(bindings, p.messages.BindingKeys()...)
}
bindings = append(bindings, p.messages.BindingKeys()...)
return bindings
}
@@ -155,10 +148,9 @@ func NewChatPage(app *app.App) tea.Model {
layout.WithBorder(true, false, false, false),
)
return &chatPage{
app: app,
editor: editorContainer,
messages: messagesContainer,
editingMode: true,
app: app,
editor: editorContainer,
messages: messagesContainer,
layout: layout.NewSplitPane(
layout.WithLeftPanel(messagesContainer),
layout.WithBottomPanel(editorContainer),

View File

@@ -30,7 +30,7 @@ type keyMap struct {
var keys = keyMap{
Logs: key.NewBinding(
key.WithKeys("ctrl+l"),
key.WithHelp("ctrl+L", "logs"),
key.WithHelp("ctrl+l", "logs"),
),
Quit: key.NewBinding(
@@ -49,7 +49,7 @@ var keys = keyMap{
Commands: key.NewBinding(
key.WithKeys("ctrl+k"),
key.WithHelp("ctrl+K", "commands"),
key.WithHelp("ctrl+k", "commands"),
),
}
@@ -95,8 +95,6 @@ type appModel struct {
showInitDialog bool
initDialog dialog.InitDialogCmp
editingMode bool
}
func (a appModel) Init() tea.Cmd {
@@ -164,8 +162,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.initDialog.SetSize(msg.Width, msg.Height)
return a, tea.Batch(cmds...)
case chat.EditorFocusMsg:
a.editingMode = bool(msg)
// Status
case util.InfoMsg:
s, cmd := a.status.Update(msg)
@@ -228,7 +224,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.app.Permissions.GrantPersistant(msg.Permission)
case dialog.PermissionDeny:
a.app.Permissions.Deny(msg.Permission)
cmd = util.CmdHandler(chat.FocusEditorMsg(true))
}
a.showPermissions = false
return a, cmd
@@ -360,7 +355,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.showHelp = !a.showHelp
return a, nil
case key.Matches(msg, helpEsc):
if !a.editingMode {
if a.app.CoderAgent.IsBusy() {
if a.showQuit {
return a, nil
}
@@ -477,7 +472,7 @@ func (a appModel) View() string {
)
}
if a.editingMode {
if !a.app.CoderAgent.IsBusy() {
a.status.SetHelpMsg("ctrl+? help")
} else {
a.status.SetHelpMsg("? help")
@@ -494,7 +489,7 @@ func (a appModel) View() string {
if a.currentPage == page.LogsPage {
bindings = append(bindings, logsKeyReturnKey)
}
if !a.editingMode {
if !a.app.CoderAgent.IsBusy() {
bindings = append(bindings, helpEsc)
}
a.help.SetBindings(bindings)
@@ -585,7 +580,6 @@ func New(app *app.App) tea.Model {
permissions: dialog.NewPermissionDialogCmp(),
initDialog: dialog.NewInitDialogCmp(),
app: app,
editingMode: true,
commands: []dialog.Command{},
pages: map[page.PageID]tea.Model{
page.ChatPage: page.NewChatPage(app),