xbase 2 týždňov pred
rodič
commit
429e5df15f
1 zmenil súbory, kde vykonal 95 pridanie a 0 odobranie
  1. 95 0
      cli.go

+ 95 - 0
cli.go

@@ -6,6 +6,8 @@ import (
 	"fmt"
 	"net"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"regexp"
 	"runtime"
 	"sort"
@@ -108,14 +110,66 @@ func formatBytes(bytes int64) string {
 	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
 }
 
+// scanCRLF is a split function for a Scanner that returns each line of
+// text, stripped of any trailing end-of-line marker. The returned line may
+// be empty. The end-of-line marker is one optional carriage return followed
+// by one mandatory newline, OR just a carriage return.
+func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) {
+	if atEOF && len(data) == 0 {
+		return 0, nil, nil
+	}
+	if i := bytes.IndexByte(data, '\n'); i >= 0 {
+		// We have a full newline-terminated line.
+		return i + 1, dropCR(data[0:i]), nil
+	}
+	if i := bytes.IndexByte(data, '\r'); i >= 0 {
+		// We have a CR-terminated line.
+		return i + 1, data[0:i], nil
+	}
+	// If we're at EOF, we have a final, non-terminated line. Return it.
+	if atEOF {
+		return len(data), dropCR(data), nil
+	}
+	// Request more data.
+	return 0, nil, nil
+}
+
+// dropCR drops a terminal \r from the data.
+func dropCR(data []byte) []byte {
+	if len(data) > 0 && data[len(data)-1] == '\r' {
+		return data[0 : len(data)-1]
+	}
+	return data
+}
+
 // Start starts the command line interface
 func (c *CLI) Start() {
+	// Attempt to fix terminal settings if they are messed up (e.g. missing ICRNL)
+	// This helps when Enter sends \r but the terminal driver expects \n
+	cmd := exec.Command("stty", "sane")
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	_ = cmd.Run()
+
 	// Boxed startup message
 	var sb strings.Builder
 	sb.WriteString(fmt.Sprintf("Node %s%s%s CLI Started\n", ColorGreen, c.server.Raft.nodeID, ColorReset))
 	sb.WriteString("Type 'help' for commands.")
 	printBoxed(sb.String())
 
+	// Initialize history handling
+	historyPath := filepath.Join(os.TempDir(), fmt.Sprintf(".raft_history_%s", c.server.Raft.nodeID))
+	historyFile, err := os.OpenFile(historyPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		// Just warn, don't fail
+		fmt.Printf("%sWarning: Could not open history file: %v%s\n", ColorYellow, err, ColorReset)
+	}
+	defer func() {
+		if historyFile != nil {
+			historyFile.Close()
+		}
+	}()
+
 	// State Monitor Loop
 	go func() {
 		var lastState string
@@ -146,6 +200,7 @@ func (c *CLI) Start() {
 	}()
 
 	scanner := bufio.NewScanner(os.Stdin)
+	scanner.Split(scanCRLF)
 	fmt.Print("> ")
 	for scanner.Scan() {
 		text := strings.TrimSpace(scanner.Text())
@@ -153,6 +208,14 @@ func (c *CLI) Start() {
 			fmt.Print("> ")
 			continue
 		}
+
+		// Save command to history
+		if historyFile != nil {
+			if _, err := historyFile.WriteString(text + "\n"); err != nil {
+				// Ignore write error
+			}
+		}
+
 		parts := strings.Fields(text)
 		cmdName := strings.ToLower(parts[0])
 
@@ -171,6 +234,8 @@ func (c *CLI) Start() {
 					infoCmd.Handler(parts, c.server)
 				}
 				c.mu.RUnlock()
+			case "history":
+				c.printHistory(historyPath)
 			default:
 				printBoxed("Unknown command. Type 'help'.")
 			}
@@ -179,6 +244,32 @@ func (c *CLI) Start() {
 	}
 }
 
+// printHistory prints the command history
+func (c *CLI) printHistory(path string) {
+	content, err := os.ReadFile(path)
+	if err != nil {
+		printBoxed("No history found.")
+		return
+	}
+	
+	lines := strings.Split(string(content), "\n")
+	// Show last 20 lines
+	start := 0
+	if len(lines) > 20 {
+		start = len(lines) - 20
+	}
+	
+	var sb strings.Builder
+	sb.WriteString(fmt.Sprintf("%sCommand History (Last 20):%s\n", ColorCyan, ColorReset))
+	for i := start; i < len(lines); i++ {
+		line := strings.TrimSpace(lines[i])
+		if line != "" {
+			sb.WriteString(fmt.Sprintf("%d: %s\n", i+1, line))
+		}
+	}
+	printBoxed(sb.String())
+}
+
 // RegisterCommand registers a new CLI command
 func (c *CLI) RegisterCommand(name, description string, handler func(parts []string, server *KVServer)) {
 	c.mu.Lock()
@@ -407,6 +498,10 @@ func (c *CLI) registerDefaultCommands() {
 		os.Exit(0)
 	})
 
+	c.RegisterCommand("clear", "Clear screen", func(parts []string, server *KVServer) {
+		fmt.Print("\033[H\033[2J")
+	})
+
 	c.RegisterCommand("help", "Show commands", func(parts []string, server *KVServer) {
 		var sb strings.Builder
 		sb.WriteString(fmt.Sprintf("%sCommands:%s\n", ColorGreen, ColorReset))