first commit

This commit is contained in:
2026-05-22 00:26:27 +03:00
commit c9d6d38491
17 changed files with 2311 additions and 0 deletions

175
internal/history/history.go Normal file
View 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())
}