1package ui23import (4 tea "charm.land/bubbletea/v2"56 "src.rest/cat/012/internal/sheet"7 "src.rest/cat/012/internal/ui/cmdline"8)910// The kitty keyboard protocol. Bubble Tea asks every terminal for its11// first level, which tells apart the keys legacy encodings merge:12// Shift+Enter from Enter (it accepts an entry and moves up, as in13// Sheets), Ctrl+I from Tab (italic), Ctrl+M from Enter, and Alt+letter14// from Esc followed by the letter. 012 also asks for key events15// (View.KeyboardEnhancements.ReportEventTypes), which say when a key is16// let go; holding a key then previews something until it is released:17// Space held on a selected chart shows it across the grid, and in the18// theme picker hides the list to show the sheet in the highlighted19// theme. A terminal without the protocol sends neither, and those keys20// do what they do there: Shift+Enter is Enter, Ctrl+I is Tab, and Space21// is typed.2223// session is what lasts as long as the program, across files opened.24type session struct {25 history cmdline.History // lines run on the : line26 // releases is whether the terminal reports key releases, so a key27 // can be held.28 releases bool29}3031// keyboard notes what the terminal said of the keyboard protocol.32func (m *Model) keyboard(msg tea.KeyboardEnhancementsMsg) {33 m.session.releases = msg.SupportsEventTypes()34}3536// releaser is an overlay that acts when a key it holds is let go.37type releaser interface {38 Release(k tea.KeyReleaseMsg) tea.Cmd39}4041// held reports whether an open overlay is waiting for a release.42func (m *Model) held() bool {43 _, ok := m.overlay.(releaser)44 return ok45}4647// keyReleased passes a key's release to the overlay holding it.48func (m *Model) keyReleased(k tea.KeyReleaseMsg) tea.Cmd {49 if r, ok := m.overlay.(releaser); ok {50 return r.Release(k)51 }52 return nil53}5455// holdKey starts holding Space on a selected chart, on a terminal that56// reports releases, and reports whether it took k. Repeats while it's57// held do nothing; any other key lets go first.58func (s *chartSel) holdKey(m chartHost, c sheet.Chart, k tea.KeyPressMsg) bool {59 if k.String() != "space" || !m.holdsKeys() {60 s.unzoom()61 return false62 }63 if !s.zoom {64 z := m.zoomed(c)65 s.preview, s.zoom = &z, true66 }67 return true68}6970// Release ends a hold of Space.71func (s *chartSel) Release(k tea.KeyReleaseMsg) tea.Cmd {72 if k.String() == "space" {73 s.unzoom()74 }75 return nil76}7778func (s *chartSel) unzoom() {79 if s.zoom {80 s.preview, s.zoom = nil, false81 }82}8384// holdsKeys reports whether the terminal reports keys let go, so a key85// can be held.86func (m *Model) holdsKeys() bool { return m.session.releases }8788// zoomed is chart c spread over the scrolling part of the grid.89func (m *Model) zoomed(c sheet.Chart) sheet.Chart {90 c.At = sheet.Addr{Col: m.left, Row: m.top}91 x, y := m.chartScreen(c)92 c.W = min(max(m.width-x, sheet.MinChartW), sheet.MaxChartW)93 c.H = min(max(gridTop+m.visibleRows()-y, sheet.MinChartH), sheet.MaxChartH)94 return c95}9697// chartOrder is the order charts are drawn in, bottom first: as they98// are, but with a chart held open on top.99func (m *Model) chartOrder(n int) []int {100 order := make([]int, 0, n)101 z := -1102 if s, ok := m.overlay.(*chartSel); ok && s.zoom {103 z = s.i104 }105 for i := range n {106 if i != z {107 order = append(order, i)108 }109 }110 if z >= 0 && z < n {111 order = append(order, z)112 }113 return order114}