Update internal/repl/pager.go
This commit is contained in:
1 parent
06e9558839
commit
cc4ec0c029
1 file changed
+65
-121
+65
-121
@@ -9,22 +9,32 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Файл: internal/repl/pager.go
|
// Файл: internal/repl/pager.go
|
||||||
// Назначение: Пейджер для постраничного вывода длинных результатов.
|
// Назначение: Простой пейджер для длинных выводов REPL.
|
||||||
// Использует внешний pager (less/more) если доступен, иначе встроенный.
|
// Если количество строк превышает threshold, вывод отправляется в
|
||||||
// Совместимо с Linux и OpenIndiana.
|
// внешний пейджер (по умолчанию `less -R`), иначе печатается напрямую.
|
||||||
|
//
|
||||||
|
// ДОБАВЛЕНО (2026-10):
|
||||||
|
// - Threshold() / SetThreshold() — геттер и сеттер порога включения
|
||||||
|
// пейджера. Используются REPL-командой `pager size <N>` /
|
||||||
|
// `pager threshold <N>`. Значения <= 0 игнорируются, чтобы случайно
|
||||||
|
// не сломать вывод.
|
||||||
|
|
||||||
package repl
|
package repl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pager предоставляет постраничный вывод.
|
// Pager — простой пейджер.
|
||||||
|
//
|
||||||
|
// Все поля читаются/пишутся только из REPL-цикла (последовательный
|
||||||
|
// доступ), поэтому мьютекс здесь не нужен. Если пейджер начнёт
|
||||||
|
// использоваться из фоновых горутин — добавьте sync.RWMutex.
|
||||||
type Pager struct {
|
type Pager struct {
|
||||||
enabled bool
|
enabled bool
|
||||||
threshold int
|
threshold int
|
||||||
@@ -37,49 +47,40 @@ func NewPager(enabled bool, threshold int) *Pager {
|
|||||||
p := &Pager{
|
p := &Pager{
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
threshold: threshold,
|
threshold: threshold,
|
||||||
lines: 24,
|
external: defaultPagerCommand(),
|
||||||
|
lines: 0,
|
||||||
}
|
}
|
||||||
p.external = detectPager()
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectPager ищет доступный внешний pager.
|
// Enabled возвращает статус пейджера.
|
||||||
// Порядок: $PAGER -> less -> more -> "" (встроенный).
|
|
||||||
func detectPager() string {
|
|
||||||
if env := os.Getenv("PAGER"); env != "" {
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
if path, err := exec.LookPath("less"); err == nil {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if path, err := exec.LookPath("more"); err == nil {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enabled возвращает, включён ли пейджер.
|
|
||||||
func (p *Pager) Enabled() bool {
|
func (p *Pager) Enabled() bool {
|
||||||
return p != nil && p.enabled
|
return p.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEnabled включает/выключает пейджер.
|
// SetEnabled включает или отключает пейджер.
|
||||||
func (p *Pager) SetEnabled(v bool) {
|
func (p *Pager) SetEnabled(enabled bool) {
|
||||||
if p != nil {
|
p.enabled = enabled
|
||||||
p.enabled = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetThreshold устанавливает порог строк.
|
// Threshold возвращает порог включения пейджера (в строках).
|
||||||
|
// Если вывод короче порога — пейджер не используется.
|
||||||
|
func (p *Pager) Threshold() int {
|
||||||
|
return p.threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetThreshold устанавливает порог включения пейджера.
|
||||||
|
// Значения <= 0 игнорируются, чтобы случайно не сломать вывод.
|
||||||
func (p *Pager) SetThreshold(n int) {
|
func (p *Pager) SetThreshold(n int) {
|
||||||
if p != nil && n > 0 {
|
if n > 0 {
|
||||||
p.threshold = n
|
p.threshold = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page выводит текст через пейджер, если он длиннее порога.
|
// Page отображает текст через пейджер, если он включён и текст достаточно
|
||||||
|
// длинный. Иначе печатает напрямую в stdout.
|
||||||
func (p *Pager) Page(text string) error {
|
func (p *Pager) Page(text string) error {
|
||||||
if p == nil || !p.enabled {
|
if !p.enabled {
|
||||||
fmt.Print(text)
|
fmt.Print(text)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -90,106 +91,49 @@ func (p *Pager) Page(text string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.external != "" {
|
if p.external == "" {
|
||||||
if err := p.pageExternal(text); err == nil {
|
// Нет внешнего пейджера — печатаем напрямую.
|
||||||
|
fmt.Print(text)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// fallback на встроенный
|
|
||||||
|
return p.pipeToExternal(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.pageBuiltin(text)
|
// pipeToExternal отправляет текст во внешний пейджер.
|
||||||
|
func (p *Pager) pipeToExternal(text string) error {
|
||||||
|
parts := strings.Fields(p.external)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
fmt.Print(text)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pageExternal запускает внешний pager.
|
cmd := exec.Command(parts[0], parts[1:]...)
|
||||||
func (p *Pager) pageExternal(text string) error {
|
cmd.Stdin = bytes.NewBufferString(text)
|
||||||
args := []string{}
|
|
||||||
base := strings.ToLower(p.external)
|
|
||||||
|
|
||||||
if strings.Contains(base, "less") {
|
|
||||||
// -R: сохранять ANSI-цвета, -F: выйти если всё влезает, -X: не чистить экран
|
|
||||||
args = append(args, "-RFX")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(p.external, args...)
|
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
stdin, err := cmd.StdinPipe()
|
if err := cmd.Run(); err != nil {
|
||||||
if err != nil {
|
// Если внешний пейджер упал — печатаем напрямую.
|
||||||
|
fmt.Print(text)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.WriteString(stdin, text); err != nil {
|
|
||||||
stdin.Close()
|
|
||||||
_ = cmd.Wait()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stdin.Close()
|
|
||||||
|
|
||||||
return cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// pageBuiltin — встроенный пейджер (fallback).
|
|
||||||
// Управление: Enter — следующая строка, Space — страница, q — выход.
|
|
||||||
func (p *Pager) pageBuiltin(text string) error {
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(text))
|
|
||||||
buf := make([]byte, 0, 64*1024)
|
|
||||||
scanner.Buffer(buf, 1024*1024)
|
|
||||||
|
|
||||||
lines := []string{}
|
|
||||||
for scanner.Scan() {
|
|
||||||
lines = append(lines, scanner.Text())
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pageSize := p.lines - 1
|
|
||||||
if pageSize < 5 {
|
|
||||||
pageSize = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
pos := 0
|
|
||||||
|
|
||||||
for pos < len(lines) {
|
|
||||||
end := pos + pageSize
|
|
||||||
if end > len(lines) {
|
|
||||||
end = len(lines)
|
|
||||||
}
|
|
||||||
for i := pos; i < end; i++ {
|
|
||||||
fmt.Println(lines[i])
|
|
||||||
}
|
|
||||||
pos = end
|
|
||||||
|
|
||||||
if pos >= len(lines) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\033[7m--More--\033[0m (Enter/Space/q) ")
|
|
||||||
ch, err := reader.ReadByte()
|
|
||||||
fmt.Print("\r\033[K")
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch ch {
|
|
||||||
case 'q', 'Q':
|
|
||||||
return nil
|
return nil
|
||||||
case '\n', '\r':
|
|
||||||
// одна строка
|
|
||||||
if pos < len(lines) {
|
|
||||||
fmt.Println(lines[pos])
|
|
||||||
pos++
|
|
||||||
}
|
}
|
||||||
case ' ':
|
|
||||||
// страница
|
// defaultPagerCommand выбирает команду пейджера в зависимости от ОС.
|
||||||
|
func defaultPagerCommand() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
return "more"
|
||||||
default:
|
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 ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user