235 lines
5.0 KiB
Go
235 lines
5.0 KiB
Go
package lib
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"plandex-cli/fs"
|
|
"plandex-cli/term"
|
|
"strconv"
|
|
"syscall"
|
|
)
|
|
|
|
var ReplSettingsDir string
|
|
|
|
type ReplMode string
|
|
|
|
const (
|
|
ReplModeTell ReplMode = "tell"
|
|
ReplModeChat ReplMode = "chat"
|
|
)
|
|
|
|
type ReplState struct {
|
|
Mode ReplMode
|
|
IsMulti bool
|
|
}
|
|
|
|
var CurrentReplState = ReplState{
|
|
Mode: ReplModeChat,
|
|
IsMulti: false,
|
|
}
|
|
|
|
type ReplSettings struct {
|
|
State ReplState
|
|
History []string
|
|
}
|
|
|
|
var ReplCmdAliases = map[string]string{
|
|
"chat": "ch",
|
|
"tell": "t",
|
|
"multi": "m",
|
|
"quit": "q",
|
|
"help": "h",
|
|
"run": "r",
|
|
"send": "s",
|
|
}
|
|
|
|
func init() {
|
|
ReplSettingsDir = filepath.Join(fs.HomePlandexDir, "repl_settings")
|
|
}
|
|
|
|
func EnsureReplSettingsFile() {
|
|
if err := os.MkdirAll(ReplSettingsDir, os.ModePerm); err != nil {
|
|
term.OutputErrorAndExit("Error creating repl history directory: %v", err)
|
|
}
|
|
|
|
settingsFile := filepath.Join(ReplSettingsDir, CurrentProjectId+".json")
|
|
if _, err := os.Stat(settingsFile); os.IsNotExist(err) {
|
|
file, err := os.Create(settingsFile)
|
|
if err != nil {
|
|
term.OutputErrorAndExit("Error creating history file: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write empty settings object
|
|
var settings ReplSettings
|
|
|
|
data, err := json.Marshal(settings)
|
|
if err != nil {
|
|
term.OutputErrorAndExit("Error converting settings to JSON: %v", err)
|
|
}
|
|
|
|
if _, err := file.Write(data); err != nil {
|
|
term.OutputErrorAndExit("Error writing to history file: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func writeSettings(settings *ReplSettings) {
|
|
settingsFile := filepath.Join(ReplSettingsDir, CurrentProjectId+".json")
|
|
data, err := json.Marshal(settings)
|
|
if err != nil {
|
|
term.OutputErrorAndExit("Error converting settings to JSON: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(settingsFile, data, 0644); err != nil {
|
|
term.OutputErrorAndExit("Error writing settings file: %v", err)
|
|
}
|
|
}
|
|
|
|
func getSettings() *ReplSettings {
|
|
EnsureReplSettingsFile()
|
|
|
|
settingsFile := filepath.Join(ReplSettingsDir, CurrentProjectId+".json")
|
|
|
|
// Read existing settings
|
|
data, err := os.ReadFile(settingsFile)
|
|
if err != nil {
|
|
term.OutputErrorAndExit("Error reading history file: %v", err)
|
|
}
|
|
|
|
// Parse JSON
|
|
var settings ReplSettings
|
|
if err := json.Unmarshal(data, &settings); err != nil {
|
|
term.OutputErrorAndExit("Error parsing history file: %v", err)
|
|
}
|
|
|
|
return &settings
|
|
}
|
|
|
|
func LoadState() {
|
|
settings := getSettings()
|
|
|
|
if settings.State.Mode != "" {
|
|
CurrentReplState = settings.State
|
|
} else {
|
|
// Write default state
|
|
WriteState()
|
|
}
|
|
}
|
|
|
|
func WriteState() {
|
|
settings := getSettings()
|
|
settings.State = CurrentReplState
|
|
writeSettings(settings)
|
|
}
|
|
|
|
func WriteHistory(input string) {
|
|
settings := getSettings()
|
|
// Add new input
|
|
settings.History = append(settings.History, input)
|
|
writeSettings(settings)
|
|
}
|
|
|
|
func GetHistory() []string {
|
|
settings := getSettings()
|
|
return settings.History
|
|
}
|
|
|
|
// ExecPlandexCommand spawns the same binary, wiring std streams directly so you
|
|
// don't have to capture output. Any os.Exit calls in the child won't kill your REPL.
|
|
func ExecPlandexCommand(args []string) (string, error) {
|
|
return ExecPlandexCommandWithParams(args, ExecPlandexCommandParams{})
|
|
}
|
|
|
|
type ExecPlandexCommandParams struct {
|
|
DisableSuggestions bool
|
|
SessionId string
|
|
}
|
|
|
|
func ExecPlandexCommandWithParams(args []string, params ExecPlandexCommandParams) (string, error) {
|
|
// Create temp file
|
|
tmpFile, err := os.CreateTemp("", "plandex-output-*")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
tmpPath := tmpFile.Name()
|
|
tmpFile.Close()
|
|
defer os.Remove(tmpPath)
|
|
|
|
var env []string = os.Environ()
|
|
if os.Getenv("PLANDEX_REPL") == "" {
|
|
columns := term.GetTerminalWidth()
|
|
hasDarkBackground := term.HasDarkBackground()
|
|
streamForegroundColor := term.GetStreamForegroundColor()
|
|
|
|
var glamourStyle string
|
|
if hasDarkBackground {
|
|
glamourStyle = "dark"
|
|
} else {
|
|
glamourStyle = "light"
|
|
}
|
|
|
|
// Set env vars
|
|
env = append(env,
|
|
"PLANDEX_REPL=1",
|
|
"PLANDEX_REPL_OUTPUT_FILE="+tmpPath,
|
|
"PLANDEX_COLUMNS="+strconv.Itoa(columns),
|
|
"PLANDEX_STREAM_FOREGROUND_COLOR="+streamForegroundColor.Sequence(false),
|
|
"GLAMOUR_STYLE="+glamourStyle,
|
|
"PLANDEX_SKIP_UPGRADE=1",
|
|
)
|
|
|
|
if params.SessionId != "" {
|
|
env = append(env, "PLANDEX_REPL_SESSION_ID="+params.SessionId)
|
|
}
|
|
}
|
|
|
|
if params.DisableSuggestions {
|
|
env = append(env, "PLANDEX_DISABLE_SUGGESTIONS=1")
|
|
}
|
|
|
|
// Run command
|
|
cmd := exec.Command(os.Args[0], args...)
|
|
cmd.Env = env
|
|
|
|
// Connect stdin directly
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
signal.Ignore(syscall.SIGINT)
|
|
defer signal.Reset(syscall.SIGINT)
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
sig := <-sigChan
|
|
syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal))
|
|
}()
|
|
|
|
err = cmd.Wait()
|
|
if err != nil {
|
|
if _, ok := err.(*exec.ExitError); ok {
|
|
return "", nil
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
// Read output from temp file
|
|
output, err := os.ReadFile(tmpPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|