176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
// 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())
|
||
}
|