first commit
This commit is contained in:
175
internal/history/history.go
Normal file
175
internal/history/history.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// 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())
|
||||
}
|
||||
Reference in New Issue
Block a user