grimoir/help.go
2026-07-04 13:04:33 +03:00

255 lines
No EOL
7.7 KiB
Go
Executable file
Raw Permalink 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 строки"},
{"dw / de / db", "удалить до слова / конца слова / назад"},
{"yy", "скопировать строку"},
{"p / P", "вставить ниже / выше"},
{"u", "отменить (undo)"},
{"Ctrl+R", "повторить (redo)"},
},
},
{
title: "ВЫДЕЛЕНИЕ (VISUAL LINE)",
items: []helpItem{
{"V", "войти в режим выделения строк"},
{"j / k", "расширить выделение вниз / вверх"},
{"gg / G", "выделить до начала / конца файла"},
{"d / x", "удалить выделенные строки"},
{"y", "скопировать выделенные строки"},
{"Esc / V", "выйти без действия"},
},
},
{
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{
{"< / >", "уменьшить / увеличить ширину preview"},
{"Пробел", "показать / скрыть preview (только .md)"},
{":previewwidth N", "установить ширину preview"},
},
},
{
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