mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 10:41:58 +02:00
Add command_palette command to display bound keys
Add a Go-based command_palette kitten that provides a searchable, interactive overlay for browsing and triggering keyboard shortcuts and actions. - New Go kitten at kittens/command_palette/ with FZF fuzzy search, grouped/flat views, and remote control action triggering - Python collect_keys_data() does data collection, passed via stdin - Navigation: arrows, ctrl+j/k (vim), ctrl+n/p (emacs), page up/down - Enter triggers the selected action via RC command - Help text displayed in footer for selected binding - Added Go tests (main_test.go) and Python tests (command_palette.py)
This commit is contained in:
614
kittens/command_palette/main.go
Normal file
614
kittens/command_palette/main.go
Normal file
@@ -0,0 +1,614 @@
|
||||
// License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
package command_palette
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/cli"
|
||||
"github.com/kovidgoyal/kitty/tools/fzf"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/loop"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
"github.com/kovidgoyal/kitty/tools/wcswidth"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
// JSON data structures matching Python collect_keys_data output
|
||||
type Binding struct {
|
||||
Key string `json:"key"`
|
||||
Action string `json:"action"`
|
||||
ActionDisplay string `json:"action_display"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
Category string
|
||||
Mode string
|
||||
IsMouse bool
|
||||
}
|
||||
|
||||
type InputData struct {
|
||||
Modes map[string]map[string][]Binding `json:"modes"`
|
||||
Mouse []Binding `json:"mouse"`
|
||||
ModeOrder []string `json:"mode_order"`
|
||||
CategoryOrder map[string][]string `json:"category_order"`
|
||||
}
|
||||
|
||||
// DisplayItem wraps a binding with its search text for FZF scoring
|
||||
type DisplayItem struct {
|
||||
binding Binding
|
||||
searchText string // key + action_display + category for FZF
|
||||
}
|
||||
|
||||
type displayLine struct {
|
||||
text string
|
||||
isHeader bool
|
||||
isModeHdr bool
|
||||
itemIdx int // index into filtered_idx, -1 for headers
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
lp *loop.Loop
|
||||
screen_size loop.ScreenSize
|
||||
all_items []DisplayItem
|
||||
search_texts []string // parallel to all_items, for FZF scoring
|
||||
matcher *fzf.FuzzyMatcher
|
||||
filtered_idx []int // indices into all_items for current results
|
||||
query string
|
||||
selected_idx int
|
||||
scroll_offset int
|
||||
input_data InputData
|
||||
}
|
||||
|
||||
func (h *Handler) initialize() (string, error) {
|
||||
sz, err := h.lp.ScreenSize()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h.screen_size = sz
|
||||
h.lp.SetCursorVisible(true)
|
||||
h.lp.SetCursorShape(loop.BAR_CURSOR, true)
|
||||
h.lp.AllowLineWrapping(false)
|
||||
h.lp.SetWindowTitle("Command Palette")
|
||||
|
||||
if err := h.loadData(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h.matcher = fzf.NewFuzzyMatcher(fzf.DEFAULT_SCHEME)
|
||||
h.updateFilter()
|
||||
h.draw_screen()
|
||||
h.lp.SendOverlayReady()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (h *Handler) loadData() error {
|
||||
data, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read stdin: %w", err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("no input data received on stdin; this kitten must be launched from kitty")
|
||||
}
|
||||
if err := json.Unmarshal(data, &h.input_data); err != nil {
|
||||
return fmt.Errorf("failed to parse input data: %w", err)
|
||||
}
|
||||
|
||||
h.flattenBindings()
|
||||
return nil
|
||||
}
|
||||
|
||||
// flattenBindings converts the hierarchical mode/category/binding data into
|
||||
// a flat list suitable for display and FZF scoring. Uses the explicit ordering
|
||||
// arrays from Python since Go maps do not preserve insertion order.
|
||||
func (h *Handler) flattenBindings() {
|
||||
// Use explicit mode ordering from Python, falling back to sorted keys
|
||||
modeNames := h.input_data.ModeOrder
|
||||
if len(modeNames) == 0 {
|
||||
modeNames = make([]string, 0, len(h.input_data.Modes))
|
||||
for name := range h.input_data.Modes {
|
||||
modeNames = append(modeNames, name)
|
||||
}
|
||||
sort.Slice(modeNames, func(i, j int) bool {
|
||||
if modeNames[i] == "" {
|
||||
return true
|
||||
}
|
||||
if modeNames[j] == "" {
|
||||
return false
|
||||
}
|
||||
return modeNames[i] < modeNames[j]
|
||||
})
|
||||
}
|
||||
|
||||
for _, modeName := range modeNames {
|
||||
categories, ok := h.input_data.Modes[modeName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use explicit category ordering from Python, falling back to sorted keys
|
||||
catNames := h.input_data.CategoryOrder[modeName]
|
||||
if len(catNames) == 0 {
|
||||
catNames = make([]string, 0, len(categories))
|
||||
for name := range categories {
|
||||
catNames = append(catNames, name)
|
||||
}
|
||||
sort.Strings(catNames)
|
||||
}
|
||||
|
||||
for _, catName := range catNames {
|
||||
bindings, ok := categories[catName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, b := range bindings {
|
||||
b.Category = catName
|
||||
b.Mode = modeName
|
||||
b.IsMouse = false
|
||||
searchText := b.Key + " " + b.ActionDisplay + " " + catName
|
||||
if modeName != "" {
|
||||
searchText += " " + modeName
|
||||
}
|
||||
h.all_items = append(h.all_items, DisplayItem{
|
||||
binding: b,
|
||||
searchText: searchText,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse bindings
|
||||
for _, b := range h.input_data.Mouse {
|
||||
b.Category = "Mouse actions"
|
||||
b.Mode = ""
|
||||
b.IsMouse = true
|
||||
searchText := b.Key + " " + b.ActionDisplay + " Mouse"
|
||||
h.all_items = append(h.all_items, DisplayItem{
|
||||
binding: b,
|
||||
searchText: searchText,
|
||||
})
|
||||
}
|
||||
|
||||
// Build parallel search texts array for FZF
|
||||
h.search_texts = make([]string, len(h.all_items))
|
||||
for i, item := range h.all_items {
|
||||
h.search_texts[i] = item.searchText
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) updateFilter() {
|
||||
if h.query == "" {
|
||||
// Show all items in original order
|
||||
h.filtered_idx = make([]int, len(h.all_items))
|
||||
for i := range h.all_items {
|
||||
h.filtered_idx[i] = i
|
||||
}
|
||||
} else {
|
||||
results, err := h.matcher.Score(h.search_texts, h.query)
|
||||
if err != nil {
|
||||
h.filtered_idx = nil
|
||||
return
|
||||
}
|
||||
type scored struct {
|
||||
idx int
|
||||
score uint
|
||||
}
|
||||
var matches []scored
|
||||
for i, r := range results {
|
||||
if r.Score > 0 {
|
||||
matches = append(matches, scored{idx: i, score: r.Score})
|
||||
}
|
||||
}
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
return matches[i].score > matches[j].score
|
||||
})
|
||||
h.filtered_idx = make([]int, len(matches))
|
||||
for i, m := range matches {
|
||||
h.filtered_idx[i] = m.idx
|
||||
}
|
||||
}
|
||||
h.selected_idx = 0
|
||||
h.scroll_offset = 0
|
||||
}
|
||||
|
||||
func (h *Handler) selectedBinding() *Binding {
|
||||
if h.selected_idx < 0 || h.selected_idx >= len(h.filtered_idx) {
|
||||
return nil
|
||||
}
|
||||
idx := h.filtered_idx[h.selected_idx]
|
||||
if idx < 0 || idx >= len(h.all_items) {
|
||||
return nil
|
||||
}
|
||||
return &h.all_items[idx].binding
|
||||
}
|
||||
|
||||
func (h *Handler) draw_screen() {
|
||||
h.lp.StartAtomicUpdate()
|
||||
defer h.lp.EndAtomicUpdate()
|
||||
h.lp.ClearScreen()
|
||||
|
||||
width := int(h.screen_size.WidthCells)
|
||||
height := int(h.screen_size.HeightCells)
|
||||
if width < 10 || height < 5 {
|
||||
return
|
||||
}
|
||||
|
||||
// Layout: line 1 = search bar, lines 2..height-2 = results,
|
||||
// line height-1 = help text, line height = key hints
|
||||
searchBarY := 1
|
||||
resultsStartY := 2
|
||||
helpY := height - 1
|
||||
hintsY := height
|
||||
resultsHeight := helpY - resultsStartY
|
||||
if resultsHeight < 1 {
|
||||
resultsHeight = 1
|
||||
}
|
||||
|
||||
// Draw search bar
|
||||
h.lp.MoveCursorTo(1, searchBarY)
|
||||
h.lp.QueueWriteString(h.lp.SprintStyled("fg=bright-yellow", "> "))
|
||||
h.lp.QueueWriteString(h.query)
|
||||
|
||||
// Draw results
|
||||
if h.query == "" {
|
||||
h.drawGroupedResults(resultsStartY, resultsHeight, width)
|
||||
} else {
|
||||
h.drawFlatResults(resultsStartY, resultsHeight, width)
|
||||
}
|
||||
|
||||
// Draw help text for selected binding
|
||||
h.lp.MoveCursorTo(1, helpY)
|
||||
if b := h.selectedBinding(); b != nil && b.Help != "" {
|
||||
helpStr := b.Help
|
||||
maxLen := width - 2
|
||||
if maxLen < 3 {
|
||||
maxLen = 3
|
||||
}
|
||||
if wcswidth.Stringwidth(helpStr) > maxLen {
|
||||
// Truncate by runes to avoid breaking multi-byte characters
|
||||
runes := []rune(helpStr)
|
||||
for len(runes) > 0 && wcswidth.Stringwidth(string(runes))+3 > maxLen {
|
||||
runes = runes[:len(runes)-1]
|
||||
}
|
||||
helpStr = string(runes) + "..."
|
||||
}
|
||||
h.lp.QueueWriteString(h.lp.SprintStyled("dim italic", " "+helpStr))
|
||||
}
|
||||
|
||||
// Draw key hints footer
|
||||
h.lp.MoveCursorTo(1, hintsY)
|
||||
footer := h.lp.SprintStyled("fg=bright-yellow", "[Enter]") + " Run " +
|
||||
h.lp.SprintStyled("fg=bright-yellow", "[Esc]") + " Quit " +
|
||||
h.lp.SprintStyled("fg=bright-yellow", "\u2191\u2193") + " Navigate"
|
||||
matchInfo := ""
|
||||
if h.query != "" {
|
||||
matchInfo = fmt.Sprintf(" %d/%d", len(h.filtered_idx), len(h.all_items))
|
||||
}
|
||||
h.lp.QueueWriteString(" " + footer + h.lp.SprintStyled("dim", matchInfo))
|
||||
|
||||
// Position cursor at end of search text for typing
|
||||
h.lp.MoveCursorTo(3+wcswidth.Stringwidth(h.query), searchBarY)
|
||||
}
|
||||
|
||||
func (h *Handler) drawGroupedResults(startY, maxRows, width int) {
|
||||
var lines []displayLine
|
||||
lastMode := ""
|
||||
lastCategory := ""
|
||||
|
||||
for fi, idx := range h.filtered_idx {
|
||||
item := &h.all_items[idx]
|
||||
b := &item.binding
|
||||
|
||||
// Mode header when mode changes
|
||||
if b.Mode != lastMode {
|
||||
lastMode = b.Mode
|
||||
lastCategory = ""
|
||||
if b.Mode != "" {
|
||||
if len(lines) > 0 {
|
||||
lines = append(lines, displayLine{itemIdx: -1, isHeader: true})
|
||||
}
|
||||
lines = append(lines, displayLine{
|
||||
text: fmt.Sprintf(" Mode: %s", b.Mode),
|
||||
isModeHdr: true, isHeader: true, itemIdx: -1,
|
||||
})
|
||||
lines = append(lines, displayLine{itemIdx: -1, isHeader: true})
|
||||
}
|
||||
}
|
||||
|
||||
// Category header when category changes
|
||||
if b.Category != lastCategory {
|
||||
lastCategory = b.Category
|
||||
if len(lines) > 0 && !lines[len(lines)-1].isHeader {
|
||||
lines = append(lines, displayLine{itemIdx: -1, isHeader: true})
|
||||
}
|
||||
catWidth := wcswidth.Stringwidth(b.Category)
|
||||
sepLen := max(0, width-catWidth-6)
|
||||
sep := strings.Repeat("\u2500", sepLen)
|
||||
lines = append(lines, displayLine{
|
||||
text: fmt.Sprintf(" \u2500\u2500 %s %s", b.Category, sep),
|
||||
isHeader: true, itemIdx: -1,
|
||||
})
|
||||
}
|
||||
|
||||
// Binding line
|
||||
keyWidth := wcswidth.Stringwidth(b.Key)
|
||||
keyPad := 30
|
||||
if keyWidth > keyPad-4 {
|
||||
keyPad = keyWidth + 6
|
||||
}
|
||||
lines = append(lines, displayLine{
|
||||
text: fmt.Sprintf(" %-*s %s", keyPad, b.Key, b.ActionDisplay),
|
||||
itemIdx: fi,
|
||||
})
|
||||
}
|
||||
|
||||
h.drawLines(lines, startY, maxRows, width)
|
||||
}
|
||||
|
||||
func (h *Handler) drawFlatResults(startY, maxRows, width int) {
|
||||
var lines []displayLine
|
||||
for fi, idx := range h.filtered_idx {
|
||||
item := &h.all_items[idx]
|
||||
b := &item.binding
|
||||
keyWidth := wcswidth.Stringwidth(b.Key)
|
||||
keyPad := 30
|
||||
if keyWidth > keyPad-4 {
|
||||
keyPad = keyWidth + 6
|
||||
}
|
||||
catSuffix := ""
|
||||
if b.Mode != "" {
|
||||
catSuffix = fmt.Sprintf(" [%s/%s]", b.Mode, b.Category)
|
||||
} else {
|
||||
catSuffix = fmt.Sprintf(" [%s]", b.Category)
|
||||
}
|
||||
lines = append(lines, displayLine{
|
||||
text: fmt.Sprintf(" %-*s %-30s%s", keyPad, b.Key, b.ActionDisplay, catSuffix),
|
||||
itemIdx: fi,
|
||||
})
|
||||
}
|
||||
|
||||
h.drawLines(lines, startY, maxRows, width)
|
||||
}
|
||||
|
||||
func (h *Handler) drawLines(lines []displayLine, startY, maxRows, width int) {
|
||||
if maxRows <= 0 || len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust scroll to keep selected item visible
|
||||
selectedLineIdx := -1
|
||||
for i, dl := range lines {
|
||||
if dl.itemIdx == h.selected_idx {
|
||||
selectedLineIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if selectedLineIdx >= 0 {
|
||||
if selectedLineIdx < h.scroll_offset {
|
||||
h.scroll_offset = selectedLineIdx
|
||||
}
|
||||
if selectedLineIdx >= h.scroll_offset+maxRows {
|
||||
h.scroll_offset = selectedLineIdx - maxRows + 1
|
||||
}
|
||||
}
|
||||
h.scroll_offset = max(0, h.scroll_offset)
|
||||
h.scroll_offset = min(h.scroll_offset, max(0, len(lines)-maxRows))
|
||||
|
||||
end := min(h.scroll_offset+maxRows, len(lines))
|
||||
for row, li := range lines[h.scroll_offset:end] {
|
||||
h.lp.MoveCursorTo(1, startY+row)
|
||||
text := li.text
|
||||
// Truncate at rune boundary to avoid breaking multi-byte characters
|
||||
if wcswidth.Stringwidth(text) > width {
|
||||
runes := []rune(text)
|
||||
for len(runes) > 0 && wcswidth.Stringwidth(string(runes)) > width {
|
||||
runes = runes[:len(runes)-1]
|
||||
}
|
||||
text = string(runes)
|
||||
}
|
||||
|
||||
if li.isModeHdr {
|
||||
h.lp.QueueWriteString(h.lp.SprintStyled("bold fg=magenta", text))
|
||||
} else if li.isHeader {
|
||||
h.lp.QueueWriteString(h.lp.SprintStyled("fg=bright-blue", text))
|
||||
} else if li.itemIdx == h.selected_idx {
|
||||
// Selected item: highlight with reverse video
|
||||
padded := text
|
||||
textWidth := wcswidth.Stringwidth(text)
|
||||
if textWidth < width {
|
||||
padded += strings.Repeat(" ", width-textWidth)
|
||||
}
|
||||
h.lp.QueueWriteString(h.lp.SprintStyled("fg=black bg=white", padded))
|
||||
} else {
|
||||
h.drawBindingLine(text, li.itemIdx, width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) drawBindingLine(text string, filteredIdx, width int) {
|
||||
if filteredIdx < 0 || filteredIdx >= len(h.filtered_idx) {
|
||||
h.lp.QueueWriteString(text)
|
||||
return
|
||||
}
|
||||
idx := h.filtered_idx[filteredIdx]
|
||||
if idx < 0 || idx >= len(h.all_items) {
|
||||
h.lp.QueueWriteString(text)
|
||||
return
|
||||
}
|
||||
b := &h.all_items[idx].binding
|
||||
|
||||
// Style the key portion green, leave action unstyled
|
||||
keyWidth := wcswidth.Stringwidth(b.Key)
|
||||
keyPad := 30
|
||||
if keyWidth > keyPad-4 {
|
||||
keyPad = keyWidth + 6
|
||||
}
|
||||
keyPrefix := fmt.Sprintf(" %-*s", keyPad, b.Key)
|
||||
rest := ""
|
||||
if len(text) > len(keyPrefix) {
|
||||
rest = text[len(keyPrefix):]
|
||||
}
|
||||
h.lp.QueueWriteString(h.lp.SprintStyled("fg=green", keyPrefix))
|
||||
h.lp.QueueWriteString(rest)
|
||||
}
|
||||
|
||||
func (h *Handler) onKeyEvent(ev *loop.KeyEvent) error {
|
||||
if ev.MatchesPressOrRepeat("escape") {
|
||||
ev.Handled = true
|
||||
if h.query != "" {
|
||||
h.query = ""
|
||||
h.updateFilter()
|
||||
h.draw_screen()
|
||||
} else {
|
||||
h.lp.Quit(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("enter") {
|
||||
ev.Handled = true
|
||||
h.triggerSelected()
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("up") || ev.MatchesPressOrRepeat("ctrl+k") || ev.MatchesPressOrRepeat("ctrl+p") {
|
||||
ev.Handled = true
|
||||
h.moveSelection(-1)
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("down") || ev.MatchesPressOrRepeat("ctrl+j") || ev.MatchesPressOrRepeat("ctrl+n") {
|
||||
ev.Handled = true
|
||||
h.moveSelection(1)
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("page_up") {
|
||||
ev.Handled = true
|
||||
delta := max(1, int(h.screen_size.HeightCells)-4)
|
||||
h.moveSelection(-delta)
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("page_down") {
|
||||
ev.Handled = true
|
||||
delta := max(1, int(h.screen_size.HeightCells)-4)
|
||||
h.moveSelection(delta)
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("home") || ev.MatchesPressOrRepeat("ctrl+home") {
|
||||
ev.Handled = true
|
||||
h.selected_idx = 0
|
||||
h.draw_screen()
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("end") || ev.MatchesPressOrRepeat("ctrl+end") {
|
||||
ev.Handled = true
|
||||
if len(h.filtered_idx) > 0 {
|
||||
h.selected_idx = len(h.filtered_idx) - 1
|
||||
}
|
||||
h.draw_screen()
|
||||
return nil
|
||||
}
|
||||
if ev.MatchesPressOrRepeat("backspace") {
|
||||
ev.Handled = true
|
||||
if h.query != "" {
|
||||
g := wcswidth.SplitIntoGraphemes(h.query)
|
||||
h.query = strings.Join(g[:len(g)-1], "")
|
||||
h.updateFilter()
|
||||
h.draw_screen()
|
||||
} else {
|
||||
h.lp.Beep()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) onText(text string, from_key_event bool, in_bracketed_paste bool) error {
|
||||
h.query += text
|
||||
h.updateFilter()
|
||||
h.draw_screen()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) onResize(old, new_size loop.ScreenSize) error {
|
||||
h.screen_size = new_size
|
||||
h.draw_screen()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) moveSelection(delta int) {
|
||||
if len(h.filtered_idx) == 0 {
|
||||
return
|
||||
}
|
||||
h.selected_idx += delta
|
||||
h.selected_idx = max(0, h.selected_idx)
|
||||
h.selected_idx = min(h.selected_idx, len(h.filtered_idx)-1)
|
||||
h.draw_screen()
|
||||
}
|
||||
|
||||
func (h *Handler) triggerSelected() {
|
||||
b := h.selectedBinding()
|
||||
if b == nil || b.IsMouse {
|
||||
h.lp.Beep()
|
||||
return
|
||||
}
|
||||
|
||||
// Send RC action command via DCS escape code.
|
||||
// Do not set "self" or "match_window" — the action runs globally via
|
||||
// the boss, same as if the user had pressed the keyboard shortcut.
|
||||
payload := map[string]any{
|
||||
"action": b.ActionDisplay,
|
||||
}
|
||||
|
||||
rc := utils.RemoteControlCmd{
|
||||
Cmd: "action",
|
||||
Version: [3]int{0, 26, 0},
|
||||
NoResponse: true,
|
||||
}
|
||||
rc.Payload = payload
|
||||
|
||||
data, err := json.Marshal(rc)
|
||||
if err != nil {
|
||||
h.lp.Beep()
|
||||
return
|
||||
}
|
||||
h.lp.QueueWriteString("\x1bP@kitty-cmd")
|
||||
h.lp.QueueWriteString(string(data))
|
||||
h.lp.QueueWriteString("\x1b\\")
|
||||
h.lp.Quit(0)
|
||||
}
|
||||
|
||||
func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
lp, err := loop.New()
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
handler := &Handler{lp: lp}
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
return handler.initialize()
|
||||
}
|
||||
lp.OnFinalize = func() string { return "" }
|
||||
lp.OnKeyEvent = handler.onKeyEvent
|
||||
lp.OnText = handler.onText
|
||||
lp.OnResize = handler.onResize
|
||||
|
||||
err = lp.Run()
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
ds := lp.DeathSignalName()
|
||||
if ds != "" {
|
||||
fmt.Println("Killed by signal:", ds)
|
||||
lp.KillIfSignalled()
|
||||
return
|
||||
}
|
||||
rc = lp.ExitCode()
|
||||
return
|
||||
}
|
||||
|
||||
func EntryPoint(parent *cli.Command) {
|
||||
create_cmd(parent, main)
|
||||
}
|
||||
Reference in New Issue
Block a user