Compare commits

...
19 Commits
Author SHA1 Message Date
Dax Raad bdbf31f0b9 force CGO
release / goreleaser (push) Has been cancelled
2025-04-24 12:34:55 -04:00
Dax Raad 4e6560efb9 turn on cgo
release / goreleaser (push) Has been cancelled
2025-04-24 12:30:33 -04:00
Dax Raad f2f6efdd35 fix version ldflags
release / goreleaser (push) Has been cancelled
2025-04-24 12:26:42 -04:00
Kujtim Hoxha b106787a50 change package name 2025-04-24 18:26:16 +02:00
Kujtim Hoxha e1b2ce483f change additions/removals 2025-04-24 16:40:36 +02:00
Kujtim Hoxha c42d94c465 small fixes 2025-04-24 16:40:36 +02:00
Kujtim Hoxha f879a94c95 update mainter email 2025-04-24 16:40:36 +02:00
Kujtim Hoxha 6d05d5a7c3 small changes 2025-04-24 16:40:36 +02:00
Kujtim Hoxha 2c5003e3fc remove edit/normal mode 2025-04-24 16:40:36 +02:00
Dax Raad 1b22acbc58 sync 2025-04-22 21:29:32 -04:00
Dax Raad 7648a2d790 remove cache
release / goreleaser (push) Has been cancelled
2025-04-22 21:23:20 -04:00
Dax Raad 789ff3c59b sync
release / goreleaser (push) Has been cancelled
2025-04-22 21:21:53 -04:00
Dax Raad 259858df5f attempt cache builds 2025-04-22 21:20:35 -04:00
Dax 289f6af3de CI (#43)
release / goreleaser (push) Has been cancelled
* sync

* ci test

* ci test

* tag

* sync

* sync

* ci

* sync

* enable cgo

* sync

* add back brew

* switch to main
2025-04-22 21:16:14 -04:00
Kujtim Hoxha cded7ee9fd Merge pull request #38 from rgodha24/fix-provider-defaults
fix: set provider defaults correctly in AWS projects
2025-04-22 12:15:27 +02:00
Rohan Godha c02721ce6b fix: set provider defaults correctly in AWS projects 2025-04-22 00:53:05 -04:00
Kujtim Hoxha 5d7a9a98b3 Merge pull request #34 from shreyassanthu77/main 2025-04-22 00:06:03 +02:00
Shreyas Mididoddi ce8bf37f15 look for $HOME/.config for config in addition to $XDG_CONFIG_HOME/ 2025-04-22 00:49:23 +05:30
Kujtim Hoxha f33dff8772 Merge pull request #27 from kujtimiihoxha/opencode
OpenCode - Initial Implementation
2025-04-21 19:59:35 +02:00
73 changed files with 796 additions and 416 deletions
+37
View File
@@ -0,0 +1,37 @@
name: build
on:
workflow_dispatch:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.23.2"
cache: true
cache-dependency-path: go.sum
- run: go mod download
- uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: build --snapshot --clean
+40
View File
@@ -0,0 +1,40 @@
name: release
on:
workflow_dispatch:
push:
tags:
- "*"
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
packages: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v5
with:
go-version: ">=1.23.2"
cache: true
cache-dependency-path: go.sum
- run: go mod download
- uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
+78
View File
@@ -0,0 +1,78 @@
version: 2
project_name: opencode
before:
hooks:
builds:
- env:
- CGO_ENABLED=1
goos:
- linux
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w -X github.com/opencode-ai/opencode/internal/version.Version={{.Version}}
main: ./main.go
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.
name_template: >-
opencode-
{{- if eq .Os "darwin" }}mac-
{{- else if eq .Os "windows" }}windows-
{{- else if eq .Os "linux" }}linux-{{end}}
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "#86" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "0.0.0-{{ .Timestamp }}"
aurs:
- name: opencode
homepage: "https://github.com/opencode-ai/opencode"
description: "terminal based agent that can build anything"
maintainers:
- "kujtimiihoxha <kujtimii.h@gmail.com>"
license: "MIT"
private_key: "{{ .Env.AUR_KEY }}"
git_url: "ssh://aur@aur.archlinux.org/opencode-bin.git"
provides:
- opencode
conflicts:
- opencode
package: |-
install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"
brews:
- repository:
owner: opencode-ai
name: homebrew-tap
nfpms:
- maintainer: kujtimiihoxha
description: terminal based agent that can build anything
formats:
- deb
- rpm
file_name_template: >-
{{ .ProjectName }}-
{{- if eq .Os "darwin" }}mac
{{- else }}{{ .Os }}{{ end }}-{{ .Arch }}
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^doc:"
- "^test:"
- "^ci:"
- "^ignore:"
- "^example:"
- "^wip:"
+3 -3
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. > **⚠️ 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.
@@ -24,7 +24,7 @@ OpenCode is a Go-based CLI application that brings AI assistance to your termina
```bash ```bash
# Coming soon # Coming soon
go install github.com/kujtimiihoxha/opencode@latest go install github.com/opencode-ai/opencode@latest
``` ```
## Configuration ## Configuration
@@ -341,7 +341,7 @@ While the LSP client implementation supports the full LSP protocol (including co
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/kujtimiihoxha/opencode.git git clone https://github.com/opencode-ai/opencode.git
cd opencode cd opencode
# Build # Build
+13 -7
View File
@@ -8,13 +8,14 @@ import (
"time" "time"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/llm/agent" "github.com/opencode-ai/opencode/internal/llm/agent"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui" "github.com/opencode-ai/opencode/internal/tui"
"github.com/opencode-ai/opencode/internal/version"
zone "github.com/lrstanley/bubblezone" zone "github.com/lrstanley/bubblezone"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -31,6 +32,10 @@ to assist developers in writing, debugging, and understanding code directly from
cmd.Help() cmd.Help()
return nil return nil
} }
if cmd.Flag("version").Changed {
fmt.Println(version.Version)
return nil
}
// Load the config // Load the config
debug, _ := cmd.Flags().GetBool("debug") debug, _ := cmd.Flags().GetBool("debug")
@@ -247,6 +252,7 @@ func Execute() {
func init() { func init() {
rootCmd.Flags().BoolP("help", "h", false, "Help") rootCmd.Flags().BoolP("help", "h", false, "Help")
rootCmd.Flags().BoolP("version", "v", false, "Version")
rootCmd.Flags().BoolP("debug", "d", false, "Debug") rootCmd.Flags().BoolP("debug", "d", false, "Debug")
rootCmd.Flags().StringP("cwd", "c", "", "Current working directory") rootCmd.Flags().StringP("cwd", "c", "", "Current working directory")
} }
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
// JSONSchemaType represents a JSON Schema type // JSONSchemaType represents a JSON Schema type
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/kujtimiihoxha/opencode module github.com/opencode-ai/opencode
go 1.24.0 go 1.24.0
Executable
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env bash
set -euo pipefail
APP=opencode
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
ORANGE='\033[38;2;255;140;0m'
NC='\033[0m' # No Color
requested_version=${VERSION:-}
os=$(uname -s | tr '[:upper:]' '[:lower:]')
if [[ "$os" == "darwin" ]]; then
os="mac"
fi
arch=$(uname -m)
if [[ "$arch" == "aarch64" ]]; then
arch="arm64"
fi
filename="$APP-$os-$arch.tar.gz"
case "$filename" in
*"-linux-"*)
[[ "$arch" == "x86_64" || "$arch" == "arm64" || "$arch" == "i386" ]] || exit 1
;;
*"-mac-"*)
[[ "$arch" == "x86_64" || "$arch" == "arm64" ]] || exit 1
;;
*)
echo "${RED}Unsupported OS/Arch: $os/$arch${NC}"
exit 1
;;
esac
INSTALL_DIR=$HOME/.opencode/bin
mkdir -p "$INSTALL_DIR"
if [ -z "$requested_version" ]; then
url="https://github.com/opencode-ai/opencode/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/opencode-ai/opencode/releases/latest | awk -F'"' '/"tag_name": "/ {gsub(/^v/, "", $4); print $4}')
if [[ $? -ne 0 ]]; then
echo "${RED}Failed to fetch version information${NC}"
exit 1
fi
else
url="https://github.com/opencode-ai/opencode/releases/download/v${requested_version}/$filename"
specific_version=$requested_version
fi
print_message() {
local level=$1
local message=$2
local color=""
case $level in
info) color="${GREEN}" ;;
warning) color="${YELLOW}" ;;
error) color="${RED}" ;;
esac
echo -e "${color}${message}${NC}"
}
check_version() {
if command -v opencode >/dev/null 2>&1; then
opencode_path=$(which opencode)
## TODO: check if version is installed
# installed_version=$(opencode version)
installed_version="0.0.1"
installed_version=$(echo $installed_version | awk '{print $2}')
if [[ "$installed_version" != "$specific_version" ]]; then
print_message info "Installed version: ${YELLOW}$installed_version."
else
print_message info "Version ${YELLOW}$specific_version${GREEN} already installed"
exit 0
fi
fi
}
download_and_install() {
print_message info "Downloading ${ORANGE}opencode ${GREEN}version: ${YELLOW}$specific_version ${GREEN}..."
mkdir -p opencodetmp && cd opencodetmp
curl -# -L $url | tar xz
mv opencode $INSTALL_DIR
cd .. && rm -rf opencodetmp
}
check_version
download_and_install
add_to_path() {
local config_file=$1
local command=$2
if [[ -w $config_file ]]; then
echo -e "\n# opencode" >> "$config_file"
echo "$command" >> "$config_file"
print_message info "Successfully added ${ORANGE}opencode ${GREEN}to \$PATH in $config_file"
else
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " $command"
fi
}
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
current_shell=$(basename "$SHELL")
case $current_shell in
fish)
config_files="$HOME/.config/fish/config.fish"
;;
zsh)
config_files="$HOME/.zshrc $HOME/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
;;
bash)
config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
;;
ash)
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
;;
sh)
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
;;
*)
# Default case if none of the above matches
config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
;;
esac
config_file=""
for file in $config_files; do
if [[ -f $file ]]; then
config_file=$file
break
fi
done
if [[ -z $config_file ]]; then
print_message error "No config file found for $current_shell. Checked files: ${config_files[@]}"
exit 1
fi
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
case $current_shell in
fish)
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
;;
zsh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
bash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
ash)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
sh)
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
;;
*)
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " export PATH=$INSTALL_DIR:\$PATH"
;;
esac
fi
if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "$INSTALL_DIR" >> $GITHUB_PATH
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
fi
+9 -9
View File
@@ -7,15 +7,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/llm/agent" "github.com/opencode-ai/opencode/internal/llm/agent"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
type App struct { type App struct {
+4 -4
View File
@@ -4,10 +4,10 @@ import (
"context" "context"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/watcher" "github.com/opencode-ai/opencode/internal/lsp/watcher"
) )
func (app *App) initLSPClients(ctx context.Context) { func (app *App) initLSPClients(ctx context.Context) {
+31 -24
View File
@@ -7,8 +7,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@@ -177,6 +177,7 @@ func configureViper() {
viper.SetConfigType("json") viper.SetConfigType("json")
viper.AddConfigPath("$HOME") viper.AddConfigPath("$HOME")
viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName)) viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName))
viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", appName))
viper.SetEnvPrefix(strings.ToUpper(appName)) viper.SetEnvPrefix(strings.ToUpper(appName))
viper.AutomaticEnv() viper.AutomaticEnv()
} }
@@ -199,22 +200,16 @@ func setDefaults(debug bool) {
// 1. Anthropic // 1. Anthropic
// 2. OpenAI // 2. OpenAI
// 3. Google Gemini // 3. Google Gemini
// 4. AWS Bedrock // 4. Groq
// 5. AWS Bedrock
func setProviderDefaults() { func setProviderDefaults() {
// Groq configuration // Anthropic configuration
if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" { if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" {
viper.SetDefault("providers.groq.apiKey", apiKey) viper.SetDefault("providers.anthropic.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.QWENQwq) viper.SetDefault("agents.coder.model", models.Claude37Sonnet)
viper.SetDefault("agents.task.model", models.QWENQwq) viper.SetDefault("agents.task.model", models.Claude37Sonnet)
viper.SetDefault("agents.title.model", models.QWENQwq) viper.SetDefault("agents.title.model", models.Claude37Sonnet)
} return
// Google Gemini configuration
if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
viper.SetDefault("providers.gemini.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.Gemini25)
viper.SetDefault("agents.task.model", models.Gemini25Flash)
viper.SetDefault("agents.title.model", models.Gemini25Flash)
} }
// OpenAI configuration // OpenAI configuration
@@ -223,21 +218,33 @@ func setProviderDefaults() {
viper.SetDefault("agents.coder.model", models.GPT41) viper.SetDefault("agents.coder.model", models.GPT41)
viper.SetDefault("agents.task.model", models.GPT41Mini) viper.SetDefault("agents.task.model", models.GPT41Mini)
viper.SetDefault("agents.title.model", models.GPT41Mini) viper.SetDefault("agents.title.model", models.GPT41Mini)
return
} }
// Anthropic configuration // Google Gemini configuration
if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" { if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
viper.SetDefault("providers.anthropic.apiKey", apiKey) viper.SetDefault("providers.gemini.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.Claude37Sonnet) viper.SetDefault("agents.coder.model", models.Gemini25)
viper.SetDefault("agents.task.model", models.Claude37Sonnet) viper.SetDefault("agents.task.model", models.Gemini25Flash)
viper.SetDefault("agents.title.model", models.Claude37Sonnet) viper.SetDefault("agents.title.model", models.Gemini25Flash)
return
} }
// Groq configuration
if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" {
viper.SetDefault("providers.groq.apiKey", apiKey)
viper.SetDefault("agents.coder.model", models.QWENQwq)
viper.SetDefault("agents.task.model", models.QWENQwq)
viper.SetDefault("agents.title.model", models.QWENQwq)
return
}
// AWS Bedrock configuration
if hasAWSCredentials() { if hasAWSCredentials() {
viper.SetDefault("agents.coder.model", models.BedrockClaude37Sonnet) viper.SetDefault("agents.coder.model", models.BedrockClaude37Sonnet)
viper.SetDefault("agents.task.model", models.BedrockClaude37Sonnet) viper.SetDefault("agents.task.model", models.BedrockClaude37Sonnet)
viper.SetDefault("agents.title.model", models.BedrockClaude37Sonnet) viper.SetDefault("agents.title.model", models.BedrockClaude37Sonnet)
return
} }
} }
+2 -2
View File
@@ -12,8 +12,8 @@ import (
"github.com/golang-migrate/migrate/v4/database/sqlite3" "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
) )
func Connect() (*sql.DB, error) { func Connect() (*sql.DB, error) {
+2 -2
View File
@@ -19,8 +19,8 @@ import (
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/sergi/go-diff/diffmatchpatch" "github.com/sergi/go-diff/diffmatchpatch"
) )
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
const ( const (
+5 -5
View File
@@ -5,11 +5,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
type agentTool struct { type agentTool struct {
+9 -9
View File
@@ -7,15 +7,15 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/prompt" "github.com/opencode-ai/opencode/internal/llm/prompt"
"github.com/kujtimiihoxha/opencode/internal/llm/provider" "github.com/opencode-ai/opencode/internal/llm/provider"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
// Common errors // Common errors
+5 -5
View File
@@ -5,11 +5,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/version" "github.com/opencode-ai/opencode/internal/version"
"github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
+6 -6
View File
@@ -3,12 +3,12 @@ package agent
import ( import (
"context" "context"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
) )
func CoderAgentTools( func CoderAgentTools(
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
) )
func CoderPrompt(provider models.ModelProvider) string { func CoderPrompt(provider models.ModelProvider) string {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
// contextFiles is a list of potential context files to check for // contextFiles is a list of potential context files to check for
+1 -1
View File
@@ -3,7 +3,7 @@ package prompt
import ( import (
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
func TaskPrompt(_ models.ModelProvider) string { func TaskPrompt(_ models.ModelProvider) string {
+1 -1
View File
@@ -1,6 +1,6 @@
package prompt package prompt
import "github.com/kujtimiihoxha/opencode/internal/llm/models" import "github.com/opencode-ai/opencode/internal/llm/models"
func TitlePrompt(_ models.ModelProvider) string { func TitlePrompt(_ models.ModelProvider) string {
return `you will generate a short title based on the first message a user begins a conversation with return `you will generate a short title based on the first message a user begins a conversation with
+4 -4
View File
@@ -12,10 +12,10 @@ import (
"github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock" "github.com/anthropics/anthropic-sdk-go/bedrock"
"github.com/anthropics/anthropic-sdk-go/option" "github.com/anthropics/anthropic-sdk-go/option"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
) )
type anthropicOptions struct { type anthropicOptions struct {
+2 -2
View File
@@ -7,8 +7,8 @@ import (
"os" "os"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
) )
type bedrockOptions struct { type bedrockOptions struct {
+4 -4
View File
@@ -11,10 +11,10 @@ import (
"github.com/google/generative-ai-go/genai" "github.com/google/generative-ai-go/genai"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
"google.golang.org/api/option" "google.golang.org/api/option"
) )
+4 -4
View File
@@ -8,10 +8,10 @@ import (
"io" "io"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/openai/openai-go" "github.com/openai/openai-go"
"github.com/openai/openai-go/option" "github.com/openai/openai-go/option"
"github.com/openai/openai-go/shared" "github.com/openai/openai-go/shared"
+3 -3
View File
@@ -4,9 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
) )
type EventType string type EventType string
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/tools/shell" "github.com/opencode-ai/opencode/internal/llm/tools/shell"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type BashParams struct { type BashParams struct {
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
type DiagnosticsParams struct { type DiagnosticsParams struct {
+6 -6
View File
@@ -9,12 +9,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type EditParams struct { type EditParams struct {
+2 -2
View File
@@ -11,8 +11,8 @@ import (
md "github.com/JohannesKaufmann/html-to-markdown" md "github.com/JohannesKaufmann/html-to-markdown"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type FetchParams struct { type FetchParams struct {
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"time" "time"
"github.com/bmatcuk/doublestar/v4" "github.com/bmatcuk/doublestar/v4"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
) )
const ( const (
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
) )
type GrepParams struct { type GrepParams struct {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
) )
type LSParams struct { type LSParams struct {
+6 -6
View File
@@ -8,12 +8,12 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type PatchParams struct { type PatchParams struct {
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
) )
type ViewParams struct { type ViewParams struct {
+6 -6
View File
@@ -9,12 +9,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
) )
type WriteParams struct { type WriteParams struct {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/go-logfmt/logfmt" "github.com/go-logfmt/logfmt"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
const ( const (
+3 -3
View File
@@ -14,9 +14,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
type Client struct { type Client struct {
+4 -4
View File
@@ -3,10 +3,10 @@ package lsp
import ( import (
"encoding/json" "encoding/json"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
"github.com/kujtimiihoxha/opencode/internal/lsp/util" "github.com/opencode-ai/opencode/internal/lsp/util"
) )
// Requests // Requests
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
func DetectLanguageID(uri string) protocol.LanguageKind { func DetectLanguageID(uri string) protocol.LanguageKind {
+1 -1
View File
@@ -4,7 +4,7 @@ package lsp
import ( import (
"context" "context"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
// Implementation sends a textDocument/implementation request to the LSP server. // Implementation sends a textDocument/implementation request to the LSP server.
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"io" "io"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
) )
// Write writes an LSP message to the given writer // Write writes an LSP message to the given writer
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error { func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error {
+40 -36
View File
@@ -11,10 +11,10 @@ import (
"github.com/bmatcuk/doublestar/v4" "github.com/bmatcuk/doublestar/v4"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
) )
// WorkspaceWatcher manages LSP file watching // WorkspaceWatcher manages LSP file watching
@@ -96,19 +96,19 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// Determine server type for specialized handling // Determine server type for specialized handling
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
logging.Debug("Server type detected", "serverName", serverName) logging.Debug("Server type detected", "serverName", serverName)
// Check if this server has sent file watchers // Check if this server has sent file watchers
hasFileWatchers := len(watchers) > 0 hasFileWatchers := len(watchers) > 0
// For servers that need file preloading, we'll use a smart approach // For servers that need file preloading, we'll use a smart approach
if shouldPreloadFiles(serverName) || !hasFileWatchers { if shouldPreloadFiles(serverName) || !hasFileWatchers {
go func() { go func() {
startTime := time.Now() startTime := time.Now()
filesOpened := 0 filesOpened := 0
// Determine max files to open based on server type // Determine max files to open based on server type
maxFilesToOpen := 50 // Default conservative limit maxFilesToOpen := 50 // Default conservative limit
switch serverName { switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls": case "typescript", "typescript-language-server", "tsserver", "vtsls":
// TypeScript servers benefit from seeing more files // 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 // Java servers need to see many files for project model
maxFilesToOpen = 200 maxFilesToOpen = 200
} }
// First, open high-priority files // First, open high-priority files
highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName) highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName)
filesOpened += highPriorityFilesOpened filesOpened += highPriorityFilesOpened
if cnf.DebugLSP { if cnf.DebugLSP {
logging.Debug("Opened high-priority files", logging.Debug("Opened high-priority files",
"count", highPriorityFilesOpened, "count", highPriorityFilesOpened,
"serverName", serverName) "serverName", serverName)
} }
// If we've already opened enough high-priority files, we might not need more // If we've already opened enough high-priority files, we might not need more
if filesOpened >= maxFilesToOpen { if filesOpened >= maxFilesToOpen {
if cnf.DebugLSP { if cnf.DebugLSP {
@@ -137,9 +137,9 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
} }
return return
} }
// For the remaining slots, walk the directory and open matching files // 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 { err := filepath.WalkDir(w.workspacePath, func(path string, d os.DirEntry, err error) error {
if err != nil { if err != nil {
return err 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 { func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int {
cnf := config.Get() cnf := config.Get()
filesOpened := 0 filesOpened := 0
// Define patterns for high-priority files based on server type // Define patterns for high-priority files based on server type
var patterns []string var patterns []string
switch serverName { switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls": case "typescript", "typescript-language-server", "tsserver", "vtsls":
patterns = []string{ patterns = []string{
@@ -256,7 +256,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
"**/.editorconfig", "**/.editorconfig",
} }
} }
// For each pattern, find and open matching files // For each pattern, find and open matching files
for _, pattern := range patterns { for _, pattern := range patterns {
// Use doublestar.Glob to find files matching the pattern (supports ** 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 continue
} }
for _, match := range matches { for _, match := range matches {
// Convert relative path to absolute // Convert relative path to absolute
fullPath := filepath.Join(w.workspacePath, match) fullPath := filepath.Join(w.workspacePath, match)
// Skip directories and excluded files // Skip directories and excluded files
info, err := os.Stat(fullPath) info, err := os.Stat(fullPath)
if err != nil || info.IsDir() || shouldExcludeFile(fullPath) { if err != nil || info.IsDir() || shouldExcludeFile(fullPath) {
continue continue
} }
// Open the file // Open the file
if err := w.client.OpenFile(ctx, fullPath); err != nil { if err := w.client.OpenFile(ctx, fullPath); err != nil {
if cnf.DebugLSP { if cnf.DebugLSP {
@@ -289,17 +289,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
logging.Debug("Opened high-priority file", "path", fullPath) logging.Debug("Opened high-priority file", "path", fullPath)
} }
} }
// Add a small delay to prevent overwhelming the server // Add a small delay to prevent overwhelming the server
time.Sleep(20 * time.Millisecond) time.Sleep(20 * time.Millisecond)
// Limit the number of files opened per pattern // Limit the number of files opened per pattern
if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") { if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") {
break break
} }
} }
} }
return filesOpened return filesOpened
} }
@@ -310,16 +310,16 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
// Store the watcher in the context for later use // Store the watcher in the context for later use
ctx = context.WithValue(ctx, "workspaceWatcher", w) ctx = context.WithValue(ctx, "workspaceWatcher", w)
// If the server name isn't already in the context, try to detect it // If the server name isn't already in the context, try to detect it
if _, ok := ctx.Value("serverName").(string); !ok { if _, ok := ctx.Value("serverName").(string); !ok {
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
ctx = context.WithValue(ctx, "serverName", serverName) ctx = context.WithValue(ctx, "serverName", serverName)
} }
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName) logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
// Register handler for file watcher registrations from the server // Register handler for file watcher registrations from the server
lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) { lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
w.AddRegistrations(ctx, id, watchers) w.AddRegistrations(ctx, id, watchers)
@@ -414,7 +414,11 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
case event.Op&fsnotify.Create != 0: case event.Op&fsnotify.Create != 0:
// Already handled earlier in the event loop // Already handled earlier in the event loop
// Just send the notification if needed // 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 { if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
w.debounceHandleFileEvent(ctx, uri, protocol.FileChangeType(protocol.Created)) 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 != "" { if serverName, ok := ctx.Value("serverName").(string); ok && serverName != "" {
return strings.ToLower(serverName) return strings.ToLower(serverName)
} }
// Otherwise, try to extract server name from the client command path // 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 { if w, ok := ctx.Value("workspaceWatcher").(*WorkspaceWatcher); ok && w != nil && w.client != nil && w.client.Cmd != nil {
path := strings.ToLower(w.client.Cmd.Path) 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 { if watched, _ := w.isPathWatched(path); watched {
// Get server name for specialized handling // Get server name for specialized handling
serverName := getServerNameFromContext(ctx) serverName := getServerNameFromContext(ctx)
// Check if the file is a high-priority file that should be opened immediately // Check if the file is a high-priority file that should be opened immediately
// This helps with project initialization for certain language servers // This helps with project initialization for certain language servers
if isHighPriorityFile(path, serverName) { 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 // For non-high-priority files, we'll use different strategies based on server type
if shouldPreloadFiles(serverName) { if shouldPreloadFiles(serverName) {
// For servers that benefit from preloading, open files but with limits // For servers that benefit from preloading, open files but with limits
// Check file size - for preloading we're more conservative // Check file size - for preloading we're more conservative
if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
if cnf.DebugLSP { if cnf.DebugLSP {
@@ -889,13 +893,13 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
} }
return return
} }
// Check file extension for common source files // Check file extension for common source files
ext := strings.ToLower(filepath.Ext(path)) ext := strings.ToLower(filepath.Ext(path))
// Only preload source files for the specific language // Only preload source files for the specific language
shouldOpen := false shouldOpen := false
switch serverName { switch serverName {
case "typescript", "typescript-language-server", "tsserver", "vtsls": case "typescript", "typescript-language-server", "tsserver", "vtsls":
shouldOpen = ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx" 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 // For unknown servers, be conservative
shouldOpen = false shouldOpen = false
} }
if shouldOpen { if shouldOpen {
// Don't need to check if it's already open - the client.OpenFile handles that // 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 { 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" fileName == "main.js"
case "gopls": case "gopls":
// For Go, we want to open go.mod files immediately // For Go, we want to open go.mod files immediately
return fileName == "go.mod" || return fileName == "go.mod" ||
fileName == "go.sum" || fileName == "go.sum" ||
// Also open main.go files // Also open main.go files
fileName == "main.go" fileName == "main.go"
case "rust-analyzer": case "rust-analyzer":
// For Rust, we want to open Cargo.toml files immediately // For Rust, we want to open Cargo.toml files immediately
return fileName == "Cargo.toml" || return fileName == "Cargo.toml" ||
fileName == "Cargo.lock" || fileName == "Cargo.lock" ||
// Also open lib.rs and main.rs // Also open lib.rs and main.rs
fileName == "lib.rs" || fileName == "lib.rs" ||
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"slices" "slices"
"time" "time"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
) )
type MessageRole string type MessageRole string
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
type CreateMessageParams struct { type CreateMessageParams struct {
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
var ErrorPermissionDenied = errors.New("permission denied") var ErrorPermissionDenied = errors.New("permission denied")
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"database/sql" "database/sql"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/kujtimiihoxha/opencode/internal/db" "github.com/opencode-ai/opencode/internal/db"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
) )
type Session struct { type Session struct {
+5 -5
View File
@@ -6,10 +6,10 @@ import (
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/version" "github.com/opencode-ai/opencode/internal/version"
) )
type SendMsg struct { type SendMsg struct {
@@ -92,7 +92,7 @@ func logo(width int) string {
} }
func repo(width int) string { func repo(width int) string {
repo := "https://github.com/kujtimiihoxha/opencode" repo := "https://github.com/opencode-ai/opencode"
return styles.BaseStyle. return styles.BaseStyle.
Foreground(styles.ForgroundDim). Foreground(styles.ForgroundDim).
Width(width). Width(width).
+29 -59
View File
@@ -8,11 +8,11 @@ import (
"github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textarea"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type editorCmp struct { type editorCmp struct {
@@ -21,12 +21,9 @@ type editorCmp struct {
textarea textarea.Model textarea textarea.Model
} }
type FocusEditorMsg bool type EditorKeyMaps struct {
type focusedEditorKeyMaps struct {
Send key.Binding Send key.Binding
OpenEditor key.Binding OpenEditor key.Binding
Blur key.Binding
} }
type bluredEditorKeyMaps struct { type bluredEditorKeyMaps struct {
@@ -35,29 +32,10 @@ type bluredEditorKeyMaps struct {
OpenEditor key.Binding OpenEditor key.Binding
} }
var focusedKeyMaps = focusedEditorKeyMaps{ var editorMaps = EditorKeyMaps{
Send: key.NewBinding( Send: key.NewBinding(
key.WithKeys("ctrl+s"), key.WithKeys("enter", "ctrl+s"),
key.WithHelp("ctrl+s", "send message"), key.WithHelp("enter", "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"),
), ),
OpenEditor: key.NewBinding( OpenEditor: key.NewBinding(
key.WithKeys("ctrl+e"), key.WithKeys("ctrl+e"),
@@ -88,6 +66,9 @@ func openEditor() tea.Cmd {
if err != nil { if err != nil {
return util.ReportError(err) return util.ReportError(err)
} }
if len(content) == 0 {
return util.ReportWarn("Message is empty")
}
os.Remove(tmpfile.Name()) os.Remove(tmpfile.Name())
return SendMsg{ return SendMsg{
Text: string(content), Text: string(content),
@@ -106,7 +87,6 @@ func (m *editorCmp) send() tea.Cmd {
value := m.textarea.Value() value := m.textarea.Value()
m.textarea.Reset() m.textarea.Reset()
m.textarea.Blur()
if value == "" { if value == "" {
return nil return nil
} }
@@ -125,32 +105,28 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.session = msg m.session = msg
} }
return m, nil return m, nil
case FocusEditorMsg:
if msg {
m.textarea.Focus()
return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
}
case tea.KeyMsg: 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) { if m.app.CoderAgent.IsSessionBusy(m.session.ID) {
return m, util.ReportWarn("Agent is working, please wait...") return m, util.ReportWarn("Agent is working, please wait...")
} }
return m, openEditor() return m, openEditor()
} }
// if the key does not match any binding, return // Handle Enter key
if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Send) { if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) {
return m, m.send() value := m.textarea.Value()
} if len(value) > 0 && value[len(value)-1] == '\\' {
if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Send) { // If the last character is a backslash, remove it and add a newline
return m, m.send() m.textarea.SetValue(value[:len(value)-1] + "\n")
} return m, nil
if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Blur) { } else {
m.textarea.Blur() // Otherwise, send the message
return m, util.CmdHandler(EditorFocusMsg(false)) return m, m.send()
} }
if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Focus) {
m.textarea.Focus()
return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
} }
} }
m.textarea, cmd = m.textarea.Update(msg) m.textarea, cmd = m.textarea.Update(msg)
@@ -175,13 +151,7 @@ func (m *editorCmp) GetSize() (int, int) {
func (m *editorCmp) BindingKeys() []key.Binding { func (m *editorCmp) BindingKeys() []key.Binding {
bindings := []key.Binding{} bindings := []key.Binding{}
if m.textarea.Focused() { bindings = append(bindings, layout.KeyMapToSlice(editorMaps)...)
bindings = append(bindings, layout.KeyMapToSlice(focusedKeyMaps)...)
} else {
bindings = append(bindings, layout.KeyMapToSlice(bluredKeyMaps)...)
}
bindings = append(bindings, layout.KeyMapToSlice(m.textarea.KeyMap)...)
return bindings return bindings
} }
+60 -27
View File
@@ -10,13 +10,12 @@ import (
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/util"
"github.com/kujtimiihoxha/opencode/internal/tui/util"
) )
type cacheItem struct { type cacheItem struct {
@@ -26,7 +25,6 @@ type cacheItem struct {
type messagesCmp struct { type messagesCmp struct {
app *app.App app *app.App
width, height int width, height int
writingMode bool
viewport viewport.Model viewport viewport.Model
session session.Session session session.Session
messages []message.Message messages []message.Message
@@ -38,6 +36,32 @@ type messagesCmp struct {
} }
type renderFinishedMsg 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 { func (m *messagesCmp) Init() tea.Cmd {
return tea.Batch(m.viewport.Init(), m.spinner.Tick) 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) { func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case EditorFocusMsg:
m.writingMode = bool(msg)
case SessionSelectedMsg: case SessionSelectedMsg:
if msg.ID != m.session.ID { if msg.ID != m.session.ID {
cmd := m.SetSession(msg) cmd := m.SetSession(msg)
@@ -60,13 +83,17 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.rendering = false m.rendering = false
return m, nil 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: case renderFinishedMsg:
m.rendering = false m.rendering = false
m.viewport.GotoBottom() m.viewport.GotoBottom()
case tea.KeyMsg:
if m.writingMode {
return m, nil
}
case pubsub.Event[message.Message]: case pubsub.Event[message.Message]:
needsRerender := false needsRerender := false
if msg.Type == pubsub.CreatedEvent { 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) spinner, cmd := m.spinner.Update(msg)
m.spinner = spinner m.spinner = spinner
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
@@ -326,22 +349,24 @@ func (m *messagesCmp) working() string {
func (m *messagesCmp) help() string { func (m *messagesCmp) help() string {
text := "" text := ""
if m.writingMode { if m.app.CoderAgent.IsBusy() {
text += lipgloss.JoinHorizontal( text += lipgloss.JoinHorizontal(
lipgloss.Left, lipgloss.Left,
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "), styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("esc"), 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 { } else {
text += lipgloss.JoinHorizontal( text += lipgloss.JoinHorizontal(
lipgloss.Left, lipgloss.Left,
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "), styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("i"), styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("enter"),
styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to start writing"), 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. return styles.BaseStyle.
Width(m.width). Width(m.width).
Render(text) Render(text)
@@ -398,18 +423,26 @@ func (m *messagesCmp) SetSession(session session.Session) tea.Cmd {
} }
func (m *messagesCmp) BindingKeys() []key.Binding { func (m *messagesCmp) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(m.viewport.KeyMap) return []key.Binding{
return bindings m.viewport.KeyMap.PageDown,
m.viewport.KeyMap.PageUp,
m.viewport.KeyMap.HalfPageUp,
m.viewport.KeyMap.HalfPageDown,
}
} }
func NewMessagesCmp(app *app.App) tea.Model { func NewMessagesCmp(app *app.App) tea.Model {
s := spinner.New() s := spinner.New()
s.Spinner = spinner.Pulse 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{ return &messagesCmp{
app: app, app: app,
writingMode: true,
cachedContent: make(map[string]cacheItem), cachedContent: make(map[string]cacheItem),
viewport: viewport.New(0, 0), viewport: vp,
spinner: s, spinner: s,
} }
} }
+9 -9
View File
@@ -12,13 +12,13 @@ import (
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/llm/agent" "github.com/opencode-ai/opencode/internal/llm/agent"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/message" "github.com/opencode-ai/opencode/internal/message"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type uiMessageType int type uiMessageType int
@@ -28,7 +28,7 @@ const (
assistantMessageType assistantMessageType
toolMessageType toolMessageType
maxResultHeight = 15 maxResultHeight = 10
) )
var diffStyle = diff.NewStyleConfig(diff.WithShowHeader(false), diff.WithShowHunkHeader(false)) var diffStyle = diff.NewStyleConfig(diff.WithShowHeader(false), diff.WithShowHunkHeader(false))
@@ -148,7 +148,7 @@ func renderAssistantMessage(
content = "*Finished without output*" content = "*Finished without output*"
} }
content = renderMessage(content, false, msg.ID == focusedUIMessageId, width, info...) content = renderMessage(content, false, true, width, info...)
messages = append(messages, uiMessage{ messages = append(messages, uiMessage{
ID: msg.ID, ID: msg.ID,
messageType: assistantMessageType, messageType: assistantMessageType,
+15 -10
View File
@@ -8,12 +8,12 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/history" "github.com/opencode-ai/opencode/internal/history"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type sidebarCmp struct { type sidebarCmp struct {
@@ -116,13 +116,18 @@ func (m *sidebarCmp) sessionSection() string {
func (m *sidebarCmp) modifiedFile(filePath string, additions, removals int) string { func (m *sidebarCmp) modifiedFile(filePath string, additions, removals int) string {
stats := "" stats := ""
if additions > 0 && removals > 0 { if additions > 0 && removals > 0 {
stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf(" %d additions and %d removals", additions, removals)) additions := styles.BaseStyle.Foreground(styles.Green).PaddingLeft(1).Render(fmt.Sprintf("+%d", additions))
removals := styles.BaseStyle.Foreground(styles.Red).PaddingLeft(1).Render(fmt.Sprintf("-%d", removals))
content := lipgloss.JoinHorizontal(lipgloss.Left, additions, removals)
stats = styles.BaseStyle.Width(lipgloss.Width(content)).Render(content)
} else if additions > 0 { } else if additions > 0 {
stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf(" %d additions", additions)) additions := fmt.Sprintf(" %s", styles.BaseStyle.PaddingLeft(1).Foreground(styles.Green).Render(fmt.Sprintf("+%d", additions)))
stats = styles.BaseStyle.Width(lipgloss.Width(additions)).Render(additions)
} else if removals > 0 { } else if removals > 0 {
stats = styles.BaseStyle.Foreground(styles.ForgroundDim).Render(fmt.Sprintf(" %d removals", removals)) removals := fmt.Sprintf(" %s", styles.BaseStyle.PaddingLeft(1).Foreground(styles.Red).Render(fmt.Sprintf("-%d", removals)))
stats = styles.BaseStyle.Width(lipgloss.Width(removals)).Render(removals)
} }
filePathStr := styles.BaseStyle.Foreground(styles.Forground).Render(filePath) filePathStr := styles.BaseStyle.Render(filePath)
return styles.BaseStyle. return styles.BaseStyle.
Width(m.width). Width(m.width).
+9 -9
View File
@@ -7,15 +7,15 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/llm/models" "github.com/opencode-ai/opencode/internal/llm/models"
"github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/opencode-ai/opencode/internal/lsp"
"github.com/kujtimiihoxha/opencode/internal/lsp/protocol" "github.com/opencode-ai/opencode/internal/lsp/protocol"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/components/chat" "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type StatusCmp interface { type StatusCmp interface {
+3 -5
View File
@@ -4,9 +4,9 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
// Command represents a command that can be executed // Command represents a command that can be executed
@@ -190,7 +190,6 @@ func (c *commandDialogCmp) View() string {
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, commandItems...)), styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, commandItems...)),
styles.BaseStyle.Width(maxWidth).Render(""), 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). return styles.BaseStyle.Padding(1, 2).
@@ -244,4 +243,3 @@ func NewCommandDialogCmp() CommandDialog {
selectedCommandID: "", selectedCommandID: "",
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type helpCmp struct { type helpCmp struct {
@@ -62,7 +62,7 @@ func (h *helpCmp) render() string {
var ( var (
pairs []string pairs []string
width int width int
rows = 14 - 2 rows = 10 - 2
) )
for i := 0; i < len(bindings); i += rows { for i := 0; i < len(bindings); i += rows {
var ( var (
+5 -11
View File
@@ -5,8 +5,8 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
// InitDialogCmp is a component that asks the user if they want to initialize the project. // InitDialogCmp is a component that asks the user if they want to initialize the project.
@@ -46,8 +46,8 @@ func (k initDialogKeyMap) ShortHelp() []key.Binding {
key.WithHelp("enter", "confirm"), key.WithHelp("enter", "confirm"),
), ),
key.NewBinding( key.NewBinding(
key.WithKeys("esc"), key.WithKeys("esc", "q"),
key.WithHelp("esc", "cancel"), key.WithHelp("esc/q", "cancel"),
), ),
key.NewBinding( key.NewBinding(
key.WithKeys("y", "n"), key.WithKeys("y", "n"),
@@ -114,6 +114,7 @@ func (m InitDialogCmp) View() string {
Padding(1, 1). Padding(1, 1).
Render("Would you like to initialize this project?") Render("Would you like to initialize this project?")
maxWidth = min(maxWidth, m.width-10)
yesStyle := styles.BaseStyle yesStyle := styles.BaseStyle
noStyle := styles.BaseStyle noStyle := styles.BaseStyle
@@ -144,12 +145,6 @@ func (m InitDialogCmp) View() string {
Padding(1, 0). Padding(1, 0).
Render(buttons) 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( content := lipgloss.JoinVertical(
lipgloss.Left, lipgloss.Left,
title, title,
@@ -158,7 +153,6 @@ func (m InitDialogCmp) View() string {
question, question,
buttons, buttons,
styles.BaseStyle.Width(maxWidth).Render(""), styles.BaseStyle.Width(maxWidth).Render(""),
help,
) )
return styles.BaseStyle.Padding(1, 2). return styles.BaseStyle.Padding(1, 2).
+10 -14
View File
@@ -9,12 +9,12 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/diff" "github.com/opencode-ai/opencode/internal/diff"
"github.com/kujtimiihoxha/opencode/internal/llm/tools" "github.com/opencode-ai/opencode/internal/llm/tools"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type PermissionAction string type PermissionAction string
@@ -67,8 +67,8 @@ var permissionsKeys = permissionsMapping{
key.WithHelp("a", "allow"), key.WithHelp("a", "allow"),
), ),
AllowSession: key.NewBinding( AllowSession: key.NewBinding(
key.WithKeys("A"), key.WithKeys("s"),
key.WithHelp("A", "allow for session"), key.WithHelp("s", "allow for session"),
), ),
Deny: key.NewBinding( Deny: key.NewBinding(
key.WithKeys("d"), key.WithKeys("d"),
@@ -171,7 +171,7 @@ func (p *permissionDialogCmp) renderButtons() string {
} }
allowButton := allowStyle.Padding(0, 1).Render("Allow (a)") 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)") denyButton := denyStyle.Padding(0, 1).Render("Deny (d)")
content := lipgloss.JoinHorizontal( content := lipgloss.JoinHorizontal(
@@ -375,9 +375,6 @@ func (p *permissionDialogCmp) render() string {
contentFinal = p.renderDefaultContent() 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( content := lipgloss.JoinVertical(
lipgloss.Top, lipgloss.Top,
title, title,
@@ -385,8 +382,7 @@ func (p *permissionDialogCmp) render() string {
headerContent, headerContent,
contentFinal, contentFinal,
buttons, buttons,
styles.BaseStyle.Render(strings.Repeat(" ", p.width - 4)), styles.BaseStyle.Render(strings.Repeat(" ", p.width-4)),
helpText,
) )
return styles.BaseStyle. return styles.BaseStyle.
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
const question = "Are you sure you want to quit?" const question = "Are you sure you want to quit?"
+6 -6
View File
@@ -4,10 +4,10 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
// SessionSelectedMsg is sent when a session is selected // SessionSelectedMsg is sent when a session is selected
@@ -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 // Limit height to avoid taking up too much screen space
maxVisibleSessions := min(10, len(s.sessions)) 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(""),
styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)), styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)),
styles.BaseStyle.Width(maxWidth).Render(""), 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). return styles.BaseStyle.Padding(1, 2).
@@ -223,4 +224,3 @@ func NewSessionDialogCmp() SessionDialog {
selectedSessionID: "", selectedSessionID: "",
} }
} }
+3 -3
View File
@@ -9,9 +9,9 @@ import (
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type DetailComponent interface { type DetailComponent interface {
+5 -5
View File
@@ -7,11 +7,11 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type TableComponent interface { type TableComponent interface {
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type Container interface { type Container interface {
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings" "strings"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/muesli/ansi" "github.com/muesli/ansi"
"github.com/muesli/reflow/truncate" "github.com/muesli/reflow/truncate"
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
type SplitPaneLayout interface { type SplitPaneLayout interface {
+16 -24
View File
@@ -5,22 +5,21 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/session" "github.com/opencode-ai/opencode/internal/session"
"github.com/kujtimiihoxha/opencode/internal/tui/components/chat" "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
var ChatPage PageID = "chat" var ChatPage PageID = "chat"
type chatPage struct { type chatPage struct {
app *app.App app *app.App
editor layout.Container editor layout.Container
messages layout.Container messages layout.Container
layout layout.SplitPaneLayout layout layout.SplitPaneLayout
session session.Session session session.Session
editingMode bool
} }
type ChatKeyMap struct { type ChatKeyMap struct {
@@ -34,8 +33,8 @@ var keyMap = ChatKeyMap{
key.WithHelp("ctrl+n", "new session"), key.WithHelp("ctrl+n", "new session"),
), ),
Cancel: key.NewBinding( Cancel: key.NewBinding(
key.WithKeys("ctrl+x"), key.WithKeys("esc"),
key.WithHelp("ctrl+x", "cancel"), key.WithHelp("esc", "cancel"),
), ),
} }
@@ -65,8 +64,6 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
} }
p.session = msg p.session = msg
case chat.EditorFocusMsg:
p.editingMode = bool(msg)
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, keyMap.NewSession): case key.Matches(msg, keyMap.NewSession):
@@ -136,11 +133,7 @@ func (p *chatPage) View() string {
func (p *chatPage) BindingKeys() []key.Binding { func (p *chatPage) BindingKeys() []key.Binding {
bindings := layout.KeyMapToSlice(keyMap) bindings := layout.KeyMapToSlice(keyMap)
if p.editingMode { bindings = append(bindings, p.messages.BindingKeys()...)
bindings = append(bindings, p.editor.BindingKeys()...)
} else {
bindings = append(bindings, p.messages.BindingKeys()...)
}
return bindings return bindings
} }
@@ -155,10 +148,9 @@ func NewChatPage(app *app.App) tea.Model {
layout.WithBorder(true, false, false, false), layout.WithBorder(true, false, false, false),
) )
return &chatPage{ return &chatPage{
app: app, app: app,
editor: editorContainer, editor: editorContainer,
messages: messagesContainer, messages: messagesContainer,
editingMode: true,
layout: layout.NewSplitPane( layout: layout.NewSplitPane(
layout.WithLeftPanel(messagesContainer), layout.WithLeftPanel(messagesContainer),
layout.WithBottomPanel(editorContainer), layout.WithBottomPanel(editorContainer),
+3 -3
View File
@@ -4,9 +4,9 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/tui/components/logs" "github.com/opencode-ai/opencode/internal/tui/components/logs"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/styles" "github.com/opencode-ai/opencode/internal/tui/styles"
) )
var LogsPage PageID = "logs" var LogsPage PageID = "logs"
+16 -22
View File
@@ -6,17 +6,17 @@ import (
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/kujtimiihoxha/opencode/internal/app" "github.com/opencode-ai/opencode/internal/app"
"github.com/kujtimiihoxha/opencode/internal/config" "github.com/opencode-ai/opencode/internal/config"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
"github.com/kujtimiihoxha/opencode/internal/permission" "github.com/opencode-ai/opencode/internal/permission"
"github.com/kujtimiihoxha/opencode/internal/pubsub" "github.com/opencode-ai/opencode/internal/pubsub"
"github.com/kujtimiihoxha/opencode/internal/tui/components/chat" "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/kujtimiihoxha/opencode/internal/tui/components/core" "github.com/opencode-ai/opencode/internal/tui/components/core"
"github.com/kujtimiihoxha/opencode/internal/tui/components/dialog" "github.com/opencode-ai/opencode/internal/tui/components/dialog"
"github.com/kujtimiihoxha/opencode/internal/tui/layout" "github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/kujtimiihoxha/opencode/internal/tui/page" "github.com/opencode-ai/opencode/internal/tui/page"
"github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/opencode-ai/opencode/internal/tui/util"
) )
type keyMap struct { type keyMap struct {
@@ -30,7 +30,7 @@ type keyMap struct {
var keys = keyMap{ var keys = keyMap{
Logs: key.NewBinding( Logs: key.NewBinding(
key.WithKeys("ctrl+l"), key.WithKeys("ctrl+l"),
key.WithHelp("ctrl+L", "logs"), key.WithHelp("ctrl+l", "logs"),
), ),
Quit: key.NewBinding( Quit: key.NewBinding(
@@ -49,7 +49,7 @@ var keys = keyMap{
Commands: key.NewBinding( Commands: key.NewBinding(
key.WithKeys("ctrl+k"), key.WithKeys("ctrl+k"),
key.WithHelp("ctrl+K", "commands"), key.WithHelp("ctrl+k", "commands"),
), ),
} }
@@ -95,8 +95,6 @@ type appModel struct {
showInitDialog bool showInitDialog bool
initDialog dialog.InitDialogCmp initDialog dialog.InitDialogCmp
editingMode bool
} }
func (a appModel) Init() tea.Cmd { 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) a.initDialog.SetSize(msg.Width, msg.Height)
return a, tea.Batch(cmds...) return a, tea.Batch(cmds...)
case chat.EditorFocusMsg:
a.editingMode = bool(msg)
// Status // Status
case util.InfoMsg: case util.InfoMsg:
s, cmd := a.status.Update(msg) 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) a.app.Permissions.GrantPersistant(msg.Permission)
case dialog.PermissionDeny: case dialog.PermissionDeny:
a.app.Permissions.Deny(msg.Permission) a.app.Permissions.Deny(msg.Permission)
cmd = util.CmdHandler(chat.FocusEditorMsg(true))
} }
a.showPermissions = false a.showPermissions = false
return a, cmd return a, cmd
@@ -360,7 +355,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.showHelp = !a.showHelp a.showHelp = !a.showHelp
return a, nil return a, nil
case key.Matches(msg, helpEsc): case key.Matches(msg, helpEsc):
if !a.editingMode { if a.app.CoderAgent.IsBusy() {
if a.showQuit { if a.showQuit {
return a, nil 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") a.status.SetHelpMsg("ctrl+? help")
} else { } else {
a.status.SetHelpMsg("? help") a.status.SetHelpMsg("? help")
@@ -494,7 +489,7 @@ func (a appModel) View() string {
if a.currentPage == page.LogsPage { if a.currentPage == page.LogsPage {
bindings = append(bindings, logsKeyReturnKey) bindings = append(bindings, logsKeyReturnKey)
} }
if !a.editingMode { if !a.app.CoderAgent.IsBusy() {
bindings = append(bindings, helpEsc) bindings = append(bindings, helpEsc)
} }
a.help.SetBindings(bindings) a.help.SetBindings(bindings)
@@ -585,7 +580,6 @@ func New(app *app.App) tea.Model {
permissions: dialog.NewPermissionDialogCmp(), permissions: dialog.NewPermissionDialogCmp(),
initDialog: dialog.NewInitDialogCmp(), initDialog: dialog.NewInitDialogCmp(),
app: app, app: app,
editingMode: true,
commands: []dialog.Command{}, commands: []dialog.Command{},
pages: map[page.PageID]tea.Model{ pages: map[page.PageID]tea.Model{
page.ChatPage: page.NewChatPage(app), page.ChatPage: page.NewChatPage(app),
+1 -1
View File
@@ -5,7 +5,7 @@ import "runtime/debug"
// Build-time parameters set via -ldflags // Build-time parameters set via -ldflags
var Version = "unknown" var Version = "unknown"
// A user may install pug using `go install github.com/kujtimiihoxha/opencode@latest`. // A user may install pug using `go install github.com/opencode-ai/opencode@latest`.
// without -ldflags, in which case the version above is unset. As a workaround // without -ldflags, in which case the version above is unset. As a workaround
// we use the embedded build version that *is* set when using `go install` (and // we use the embedded build version that *is* set when using `go install` (and
// is only set for `go install` and not for `go build`). // is only set for `go install` and not for `go build`).
+2 -2
View File
@@ -1,8 +1,8 @@
package main package main
import ( import (
"github.com/kujtimiihoxha/opencode/cmd" "github.com/opencode-ai/opencode/cmd"
"github.com/kujtimiihoxha/opencode/internal/logging" "github.com/opencode-ai/opencode/internal/logging"
) )
func main() { func main() {
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Parse command line arguments
minor=false
while [ "$#" -gt 0 ]; do
case "$1" in
--minor) minor=true; shift 1;;
*) echo "Unknown parameter: $1"; exit 1;;
esac
done
git fetch --force --tags
# Get the latest Git tag
latest_tag=$(git tag --sort=committerdate | grep -E '[0-9]' | tail -1)
# If there is no tag, exit the script
if [ -z "$latest_tag" ]; then
echo "No tags found"
exit 1
fi
echo "Latest tag: $latest_tag"
# Split the tag into major, minor, and patch numbers
IFS='.' read -ra VERSION <<< "$latest_tag"
if [ "$minor" = true ]; then
# Increment the minor version and reset patch to 0
minor_number=${VERSION[1]}
let "minor_number++"
new_version="${VERSION[0]}.$minor_number.0"
else
# Increment the patch version
patch_number=${VERSION[2]}
let "patch_number++"
new_version="${VERSION[0]}.${VERSION[1]}.$patch_number"
fi
echo "New version: $new_version"
git tag $new_version
git push --tags
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
set -e
goreleaser build --clean --snapshot --skip validate