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