Files
fush/internal/history/history.go
2026-05-22 00:26:27 +03:00

176 lines
4.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// history.go - управление историей команд fush shell
// Хранит до 1000 последних команд в файле с атомарными операциями
// Обеспечивает навигацию по истории (вверх/вниз)
// Предотвращает дублирование последовательных одинаковых команд
package history
import (
"bufio"
"os"
"strings"
"sync/atomic"
"unsafe"
)
// History управляет историей команд
type History struct {
commands []string
position int
maxSize int
file string
loaded atomic.Bool
commandsPtr unsafe.Pointer // Атомарный указатель на слайс команд
}
// New создает новый объект истории
func New(historyFile string, maxSize int) *History {
h := &History{
commands: make([]string, 0, maxSize),
position: -1,
maxSize: maxSize,
file: historyFile,
}
// Инициализируем атомарный указатель
atomic.StorePointer(&h.commandsPtr, unsafe.Pointer(&h.commands))
// Загружаем историю из файла
h.load()
return h
}
// load загружает историю из файла
func (h *History) load() {
if h.loaded.Load() {
return
}
file, err := os.Open(h.file)
if err != nil {
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
commands := make([]string, 0, h.maxSize)
for scanner.Scan() {
cmd := strings.TrimSpace(scanner.Text())
if cmd != "" {
commands = append(commands, cmd)
}
}
// Ограничиваем размер
if len(commands) > h.maxSize {
commands = commands[len(commands)-h.maxSize:]
}
// Атомарно обновляем команды
h.commands = commands
atomic.StorePointer(&h.commandsPtr, unsafe.Pointer(&h.commands))
h.loaded.Store(true)
}
// Add добавляет команду в историю
func (h *History) Add(command string) {
if command == "" {
return
}
// Получаем текущие команды
oldCommands := h.getCommands()
// Не добавляем дубликаты с предыдущей командой
if len(oldCommands) > 0 && oldCommands[len(oldCommands)-1] == command {
return
}
// Создаем новый слайс
newCommands := make([]string, 0, h.maxSize)
newCommands = append(newCommands, oldCommands...)
newCommands = append(newCommands, command)
// Ограничиваем размер
if len(newCommands) > h.maxSize {
newCommands = newCommands[len(newCommands)-h.maxSize:]
}
// Атомарно обновляем
h.commands = newCommands
atomic.StorePointer(&h.commandsPtr, unsafe.Pointer(&h.commands))
// Сохраняем в файл
h.save()
}
// save сохраняет историю в файл
func (h *History) save() {
file, err := os.OpenFile(h.file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, cmd := range h.getCommands() {
writer.WriteString(cmd + "\n")
}
writer.Flush()
}
// getCommands атомарно возвращает текущий список команд
func (h *History) getCommands() []string {
ptr := atomic.LoadPointer(&h.commandsPtr)
return *(*[]string)(ptr)
}
// Previous возвращает предыдущую команду
func (h *History) Previous() string {
commands := h.getCommands()
if len(commands) == 0 {
return ""
}
if h.position == -1 {
h.position = len(commands) - 1
} else if h.position > 0 {
h.position--
}
if h.position >= 0 && h.position < len(commands) {
return commands[h.position]
}
return ""
}
// Next возвращает следующую команду
func (h *History) Next() string {
commands := h.getCommands()
if len(commands) == 0 {
return ""
}
if h.position < len(commands)-1 {
h.position++
return commands[h.position]
} else if h.position == len(commands)-1 {
h.position = -1
}
return ""
}
// ResetPosition сбрасывает позицию в истории
func (h *History) ResetPosition() {
h.position = -1
}
// GetSize возвращает размер истории
func (h *History) GetSize() int {
return len(h.getCommands())
}