grimoir/help.go
2026-05-07 14:57:14 +03:00

257 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
)
// === Help экран ===
var helpSections = []helpSection{
{
title: "РЕЖИМЫ",
items: []helpItem{
{"i / a / A", "INSERT: вставить / после курсора / в конец строки"},
{"I / o / O", "INSERT: в начало / новая строка ниже / выше"},
{"Esc", "вернуться в NORMAL"},
{":", "режим команд"},
{"/", "режим поиска"},
},
},
{
title: "НАВИГАЦИЯ (NORMAL)",
items: []helpItem{
{"h j k l", "← ↓ ↑ →"},
{"w / b / e", "слово вперёд / назад / конец слова"},
{"0 / ^ / $", "начало / первый символ / конец строки"},
{"gg / G", "начало / конец файла"},
{"5G / 42G", "перейти на строку 5 / 42"},
{"Ctrl+D / Ctrl+U", "страница вниз / вверх"},
},
},
{
title: "НАВИГАЦИЯ (INSERT)",
items: []helpItem{
{"↑ ↓ ← →", "перемещение курсора"},
{"Home / End", "начало / конец строки"},
{"Delete", "удалить символ под курсором"},
},
},
{
title: "ПОИСК И ЗАМЕНА",
items: []helpItem{
{"/pattern", "поиск по файлу"},
{"n / N", "следующее / предыдущее совпадение"},
{"f{c} / F{c}", "символ c вправо / влево в строке"},
{"t{c} / T{c}", "до символа c вправо / влево"},
{"; / ,", "повторить f/t / в обратном направлении"},
{"%", "парная скобка () [] {}"},
{":s/old/new/", "заменить первое вхождение"},
{":s/old/new/g", "заменить все вхождения в файле"},
},
},
{
title: "РЕДАКТИРОВАНИЕ",
items: []helpItem{
{"x", "удалить символ под курсором"},
{"dd / 3dd", "удалить строку / 3 строки"},
{"yy", "скопировать строку"},
{"p / P", "вставить ниже / выше"},
{"u", "отменить (undo)"},
{"Ctrl+R", "повторить (redo)"},
},
},
{
title: "ФАЙЛ",
items: []helpItem{
{":w", "сохранить"},
{":w path", "сохранить копию в path"},
{":saveas path", "сохранить как (переключиться на path)"},
{":q / :q!", "выйти / выйти без сохранения"},
{":wq / :x", "сохранить и выйти"},
{":e path", "открыть файл"},
{":e", "перезагрузить текущий файл"},
{":new", "новый пустой буфер"},
{":new path", "создать / открыть файл"},
{":N", "перейти на строку N"},
},
},
{
title: "ПАНЕЛИ",
items: []helpItem{
{"Ctrl+B", "показать / скрыть файловое дерево"},
{"Tab", "переключить фокус между панелями"},
{"< / >", "уменьшить / увеличить ширину панели"},
{"Пробел", "показать / скрыть preview (только .md)"},
{":treewidth N", "установить ширину дерева"},
{":previewwidth N", "установить ширину preview"},
},
},
{
title: "ФАЙЛОВОЕ ДЕРЕВО (при фокусе)",
items: []helpItem{
{"j / k", "вниз / вверх"},
{"l / Enter", "открыть файл / развернуть папку"},
{"h", "свернуть папку / к родителю"},
{"gg / G", "начало / конец списка"},
{"r", "обновить дерево"},
{"Esc", "вернуть фокус редактору"},
},
},
{
title: "ЭКСПОРТ И ЗАГРУЗКА",
items: []helpItem{
{":export pdf", "экспорт в PDF (рядом с файлом)"},
{":export pdf path", "экспорт в указанный путь"},
{":fetch URL", "скачать статью и открыть"},
{"grimoir URL", "запустить с URL (скачать сразу)"},
},
},
{
title: "НАСТРОЙКИ",
items: []helpItem{
{":set", "показать все настройки"},
{":set number / nonumber", "номера строк вкл / выкл"},
{":set tab_width=4", "ширина таба"},
{":set autosave=60", "автосейв каждые N секунд (0=выкл)"},
{":config show", "откуда загружен конфиг"},
{":config paths", "пути поиска конфига"},
{":config write", "записать дефолтный конфиг"},
{":config reload", "перезагрузить конфиг"},
},
},
{
title: "HELP",
items: []helpItem{
{":help / :h / ?", "этот экран"},
{"q / Esc", "закрыть help"},
},
},
}
type helpSection struct {
title string
items []helpItem
}
type helpItem struct {
key string
desc string
}
// renderHelp рендерит help экран
func renderHelp(m *EditorModel) string {
var out strings.Builder
// Стили
headerStyle := lipgloss.NewStyle().
Background(lipgloss.Color("4")).
Foreground(lipgloss.Color("15")).
Bold(true).
Width(m.width).
Padding(0, 2)
sectionStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("11")).
Bold(true)
keyStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("81")).
Width(22)
descStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("252"))
dimStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("8"))
footerStyle := lipgloss.NewStyle().
Background(lipgloss.Color("236")).
Foreground(lipgloss.Color("8")).
Width(m.width).
Padding(0, 2)
// Заголовок
out.WriteString(headerStyle.Render(" grimoir — справка по командам"))
out.WriteString("\n")
// Считаем сколько строк доступно
available := m.height - 3 // заголовок + footer + отступ
lineCount := 0
// Две колонки
colWidth := m.width / 2
leftCol := &strings.Builder{}
rightCol := &strings.Builder{}
mid := len(helpSections) / 2
if len(helpSections)%2 != 0 {
mid++
}
renderColumn := func(sections []helpSection, col *strings.Builder) {
for _, sec := range sections {
col.WriteString(sectionStyle.Render(" "+sec.title) + "\n")
for _, item := range sec.items {
key := keyStyle.Render(" " + item.key)
desc := descStyle.Render(item.desc)
col.WriteString(key + dimStyle.Render(" ") + desc + "\n")
}
col.WriteString("\n")
}
}
renderColumn(helpSections[:mid], leftCol)
renderColumn(helpSections[mid:], rightCol)
// Объединяем колонки построчно
leftLines := strings.Split(leftCol.String(), "\n")
rightLines := strings.Split(rightCol.String(), "\n")
maxLines := len(leftLines)
if len(rightLines) > maxLines {
maxLines = len(rightLines)
}
for i := 0; i < maxLines && lineCount < available; i++ {
left := ""
right := ""
if i < len(leftLines) {
left = leftLines[i]
}
if i < len(rightLines) {
right = rightLines[i]
}
// Обрезаем левую колонку до colWidth видимых символов
leftVis := visibleLen(left)
if leftVis < colWidth {
left += strings.Repeat(" ", colWidth-leftVis)
} else if leftVis > colWidth {
left = truncateANSI(left, colWidth)
}
out.WriteString(left)
out.WriteString(right)
out.WriteString("\n")
lineCount++
}
// Заполняем оставшееся место
for lineCount < available {
out.WriteString("\n")
lineCount++
}
// Footer
out.WriteString(footerStyle.Render(fmt.Sprintf(
" q/Esc — закрыть │ %s │ %d строк в файле",
grimVersion, len(m.lines),
)))
return out.String()
}
// visibleLen определена в render.go