// 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()) }