/* * Copyright 2026 Safronov Grigorii * * Licensed under the CDDL, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * https://opensource.org/licenses/CDDL-1.0 */ // Файл: internal/repl/pager.go // Назначение: Простой пейджер для длинных выводов REPL. // Если количество строк превышает threshold, вывод отправляется в // внешний пейджер (по умолчанию `less -R`), иначе печатается напрямую. // // ДОБАВЛЕНО (2026-10): // - Threshold() / SetThreshold() — геттер и сеттер порога включения // пейджера. Используются REPL-командой `pager size ` / // `pager threshold `. Значения <= 0 игнорируются, чтобы случайно // не сломать вывод. package repl import ( "bytes" "fmt" "os" "os/exec" "runtime" "strings" ) // Pager — простой пейджер. // // Все поля читаются/пишутся только из REPL-цикла (последовательный // доступ), поэтому мьютекс здесь не нужен. Если пейджер начнёт // использоваться из фоновых горутин — добавьте sync.RWMutex. type Pager struct { enabled bool threshold int external string lines int } // NewPager создаёт пейджер. func NewPager(enabled bool, threshold int) *Pager { p := &Pager{ enabled: enabled, threshold: threshold, external: defaultPagerCommand(), lines: 0, } return p } // Enabled возвращает статус пейджера. func (p *Pager) Enabled() bool { return p.enabled } // SetEnabled включает или отключает пейджер. func (p *Pager) SetEnabled(enabled bool) { p.enabled = enabled } // Threshold возвращает порог включения пейджера (в строках). // Если вывод короче порога — пейджер не используется. func (p *Pager) Threshold() int { return p.threshold } // SetThreshold устанавливает порог включения пейджера. // Значения <= 0 игнорируются, чтобы случайно не сломать вывод. func (p *Pager) SetThreshold(n int) { if n > 0 { p.threshold = n } } // Page отображает текст через пейджер, если он включён и текст достаточно // длинный. Иначе печатает напрямую в stdout. func (p *Pager) Page(text string) error { if !p.enabled { fmt.Print(text) return nil } lineCount := strings.Count(text, "\n") if lineCount < p.threshold { fmt.Print(text) return nil } if p.external == "" { // Нет внешнего пейджера — печатаем напрямую. fmt.Print(text) return nil } return p.pipeToExternal(text) } // pipeToExternal отправляет текст во внешний пейджер. func (p *Pager) pipeToExternal(text string) error { parts := strings.Fields(p.external) if len(parts) == 0 { fmt.Print(text) return nil } cmd := exec.Command(parts[0], parts[1:]...) cmd.Stdin = bytes.NewBufferString(text) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { // Если внешний пейджер упал — печатаем напрямую. fmt.Print(text) return err } return nil } // defaultPagerCommand выбирает команду пейджера в зависимости от ОС. func defaultPagerCommand() string { switch runtime.GOOS { case "windows": return "more" default: // less -R: -R сохраняет ANSI-цвета, используется на Linux/BSD/illumos. if _, err := exec.LookPath("less"); err == nil { return "less -R" } if _, err := exec.LookPath("more"); err == nil { return "more" } return "" } }