package pages import ( "fmt" "log/slog" "strings" "tasksquire/common" "time" "tasksquire/taskwarrior" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" ) type mode int const ( modeNormal mode = iota modeInsert ) type TaskEditorPage struct { common *common.Common task taskwarrior.Task colWidth int colHeight int mode mode columnCursor int area int areaPicker *areaPicker areas []area } func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage { p := TaskEditorPage{ common: com, task: task, } if p.task.Project == "" { p.task.Project = "(none)" } tagOptions := p.common.TW.GetTags() p.areas = []area{ NewTaskEdit(p.common, &p.task), NewTagEdit(p.common, &p.task.Tags, tagOptions), NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until), NewDetailsEdit(p.common, &p.task), } // p.areaList = NewAreaList(common, areaItems) // p.selectedArea = areaTask // p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...) p.areaPicker = NewAreaPicker(com, []string{"Task", "Tags", "Dates"}) p.columnCursor = 1 if p.task.Uuid == "" { p.mode = modeInsert } else { p.mode = modeNormal } p.SetSize(com.Width(), com.Height()) return &p } func (p *TaskEditorPage) SetSize(width, height int) { p.common.SetSize(width, height) if width >= 70 { p.colWidth = 70 - p.common.Styles.ColumnFocused.GetVerticalFrameSize() } else { p.colWidth = width - p.common.Styles.ColumnFocused.GetVerticalFrameSize() } if height >= 40 { p.colHeight = 40 - p.common.Styles.ColumnFocused.GetVerticalFrameSize() } else { p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize() } } func (p *TaskEditorPage) Init() tea.Cmd { return nil } func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: p.SetSize(msg.Width, msg.Height) case changeAreaMsg: p.area = int(msg) case changeModeMsg: p.mode = mode(msg) case prevColumnMsg: p.columnCursor-- if p.columnCursor < 0 { p.columnCursor = len(p.areas) - 1 } case nextColumnMsg: p.columnCursor++ if p.columnCursor > len(p.areas)-1 { p.columnCursor = 0 } case prevAreaMsg: p.area-- if p.area < 0 { p.area = len(p.areas) - 1 } p.areas[p.area].SetCursor(-1) case nextAreaMsg: p.area++ if p.area > len(p.areas)-1 { p.area = 0 } p.areas[p.area].SetCursor(0) } switch p.mode { case modeNormal: switch msg := msg.(type) { case tea.KeyMsg: switch { case key.Matches(msg, p.common.Keymap.Back): model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit } return model, BackCmd case key.Matches(msg, p.common.Keymap.Insert): return p, changeMode(modeInsert) case key.Matches(msg, p.common.Keymap.Ok): model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit } return model, p.updateTasksCmd case key.Matches(msg, p.common.Keymap.Left): return p, prevColumn() case key.Matches(msg, p.common.Keymap.Right): return p, nextColumn() case key.Matches(msg, p.common.Keymap.Up): if p.columnCursor == 0 { picker, cmd := p.areaPicker.Update(msg) p.areaPicker = picker.(*areaPicker) return p, cmd } else { model, cmd := p.areas[p.area].Update(prevFieldMsg{}) p.areas[p.area] = model.(area) return p, cmd } case key.Matches(msg, p.common.Keymap.Down): if p.columnCursor == 0 { picker, cmd := p.areaPicker.Update(msg) p.areaPicker = picker.(*areaPicker) return p, cmd } else { model, cmd := p.areas[p.area].Update(nextFieldMsg{}) p.areas[p.area] = model.(area) return p, cmd } } } // var cmd tea.Cmd // if p.columnCursor == 0 { // p., cmd = p.areaList.Update(msg) // p.selectedArea = p.areaList.(areaList).Area() // cmds = append(cmds, cmd) // } else { // p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg) // cmds = append(cmds, cmd) // } case modeInsert: switch msg := msg.(type) { case tea.KeyMsg: switch { case key.Matches(msg, p.common.Keymap.Back): return p, changeMode(modeNormal) case key.Matches(msg, p.common.Keymap.Prev): if p.columnCursor == 0 { picker, cmd := p.areaPicker.Update(msg) p.areaPicker = picker.(*areaPicker) return p, cmd } else { model, cmd := p.areas[p.area].Update(prevFieldMsg{}) p.areas[p.area] = model.(area) return p, cmd } case key.Matches(msg, p.common.Keymap.Next): if p.columnCursor == 0 { picker, cmd := p.areaPicker.Update(msg) p.areaPicker = picker.(*areaPicker) return p, cmd } else { model, cmd := p.areas[p.area].Update(nextFieldMsg{}) p.areas[p.area] = model.(area) return p, cmd } case key.Matches(msg, p.common.Keymap.Ok): model, cmd := p.areas[p.area].Update(msg) p.areas[p.area] = model.(area) return p, tea.Batch(cmd, nextField()) } } if p.columnCursor == 0 { picker, cmd := p.areaPicker.Update(msg) p.areaPicker = picker.(*areaPicker) return p, cmd } else { model, cmd := p.areas[p.area].Update(msg) p.areas[p.area] = model.(area) return p, cmd } } return p, nil } func (p *TaskEditorPage) View() string { var focusedStyle, blurredStyle lipgloss.Style if p.mode == modeInsert { focusedStyle = p.common.Styles.ColumnInsert.Width(p.colWidth).Height(p.colHeight) } else { focusedStyle = p.common.Styles.ColumnFocused.Width(p.colWidth).Height(p.colHeight) } blurredStyle = p.common.Styles.ColumnBlurred.Width(p.colWidth).Height(p.colHeight) // var picker, area string var area string if p.columnCursor == 0 { // picker = focusedStyle.Render(p.areaPicker.View()) area = blurredStyle.Render(p.areas[p.area].View()) } else { // picker = blurredStyle.Render(p.areaPicker.View()) area = focusedStyle.Render(p.areas[p.area].View()) } if p.task.Uuid != "" { area = lipgloss.JoinHorizontal( lipgloss.Top, area, p.common.Styles.ColumnFocused.Render(p.common.TW.GetInformation(&p.task)), ) } // return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, lipgloss.JoinHorizontal( // lipgloss.Center, // picker, // area, // )) return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, area) } type area interface { tea.Model SetCursor(c int) } type areaPicker struct { common *common.Common list list.Model } type item string func (i item) Title() string { return string(i) } func (i item) Description() string { return "test" } func (i item) FilterValue() string { return "" } func NewAreaPicker(common *common.Common, items []string) *areaPicker { listItems := make([]list.Item, len(items)) for i, itm := range items { listItems[i] = item(itm) } list := list.New(listItems, list.DefaultDelegate{}, 20, 50) list.SetFilteringEnabled(false) list.SetShowStatusBar(false) list.SetShowHelp(false) list.SetShowPagination(false) list.SetShowTitle(false) return &areaPicker{ common: common, list: list, } } func (a *areaPicker) Area() int { // switch a.list.SelectedItem() { // case item("Task"): // return areaTask // case item("Tags"): // return areaTags // case item("Dates"): // return areaTime // default: // return areaTask // } return 0 } func (a *areaPicker) Init() tea.Cmd { return nil } func (a *areaPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd cursor := a.list.Cursor() // switch msg.(type) { // case nextFieldMsg: // a.list, cmd = a.list.Update(a.list.KeyMap.CursorDown) // case prevFieldMsg: // a.list, cmd = a.list.Update(a.list.KeyMap.CursorUp) // } a.list, cmd = a.list.Update(msg) cmds = append(cmds, cmd) if cursor != a.list.Cursor() { cmds = append(cmds, changeArea(a.Area())) } return a, tea.Batch(cmds...) } func (a *areaPicker) View() string { return a.list.View() } type taskEdit struct { common *common.Common fields []huh.Field cursor int newProjectName *string newAnnotation *string udaValues map[string]*string } func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit { newProject := "" projectOptions := append([]string{"(none)"}, com.TW.GetProjects()...) if task.Project == "" { task.Project = "(none)" } defaultKeymap := huh.NewDefaultKeyMap() fields := []huh.Field{ huh.NewInput(). Title("Task"). Value(&task.Description). Validate(func(desc string) error { if desc == "" { return fmt.Errorf("task description is required") } return nil }). Inline(true). Prompt(": "). WithTheme(com.Styles.Form), huh.NewSelect[string](). Options(huh.NewOptions(projectOptions...)...). Title("Project"). Value(&task.Project). WithKeyMap(defaultKeymap). WithTheme(com.Styles.Form), huh.NewInput(). Title("New Project"). Value(&newProject). Inline(true). Prompt(": "). WithTheme(com.Styles.Form), } udaValues := make(map[string]*string) for _, uda := range com.Udas { switch uda.Type { case taskwarrior.UdaTypeNumeric: val := "" udaValues[uda.Name] = &val fields = append(fields, huh.NewInput(). Title(uda.Label). Value(udaValues[uda.Name]). Validate(taskwarrior.ValidateNumeric). Inline(true). Prompt(": "). WithTheme(com.Styles.Form)) case taskwarrior.UdaTypeString: if len(uda.Values) > 0 { var val string values := make([]string, len(uda.Values)) for i, v := range uda.Values { values[i] = v if v == "" { values[i] = "(none)" } if v == uda.Default { val = values[i] } } if val == "" { val = values[0] } if v, ok := task.Udas[uda.Name]; ok { //TODO: handle uda types correctly val = v.(string) } udaValues[uda.Name] = &val fields = append(fields, huh.NewSelect[string](). Options(huh.NewOptions(values...)...). Title(uda.Label). Value(udaValues[uda.Name]). WithKeyMap(defaultKeymap). WithTheme(com.Styles.Form)) } else { val := "" udaValues[uda.Name] = &val fields = append(fields, huh.NewInput(). Title(uda.Label). Value(udaValues[uda.Name]). Inline(true). Prompt(": "). WithTheme(com.Styles.Form)) } case taskwarrior.UdaTypeDate: val := "" udaValues[uda.Name] = &val fields = append(fields, huh.NewInput(). Title(uda.Label). Value(udaValues[uda.Name]). Validate(taskwarrior.ValidateDate). Inline(true). Prompt(": "). WithTheme(com.Styles.Form)) case taskwarrior.UdaTypeDuration: val := "" udaValues[uda.Name] = &val fields = append(fields, huh.NewInput(). Title(uda.Label). Value(udaValues[uda.Name]). Validate(taskwarrior.ValidateDuration). Inline(true). Prompt(": "). WithTheme(com.Styles.Form)) } } newAnnotation := "" fields = append(fields, huh.NewInput(). Title("New Annotation"). Value(&newAnnotation). Inline(true). Prompt(": "). WithTheme(com.Styles.Form)) t := taskEdit{ common: com, fields: fields, udaValues: udaValues, newProjectName: &newProject, newAnnotation: &newAnnotation, } t.fields[0].Focus() return &t } func (t *taskEdit) SetCursor(c int) { t.fields[t.cursor].Blur() if c < 0 { t.cursor = len(t.fields) - 1 } else { t.cursor = c } t.fields[t.cursor].Focus() } func (t *taskEdit) Init() tea.Cmd { return nil } func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: if t.cursor == len(t.fields)-1 { t.fields[t.cursor].Blur() return t, nextArea() } t.fields[t.cursor].Blur() t.cursor++ t.fields[t.cursor].Focus() case prevFieldMsg: if t.cursor == 0 { t.fields[t.cursor].Blur() return t, prevArea() } t.fields[t.cursor].Blur() t.cursor-- t.fields[t.cursor].Focus() default: field, cmd := t.fields[t.cursor].Update(msg) t.fields[t.cursor] = field.(huh.Field) return t, cmd } return t, nil } func (t *taskEdit) View() string { views := make([]string, len(t.fields)) for i, field := range t.fields { views[i] = field.View() if i < len(t.fields)-1 { views[i] += "\n" } } return lipgloss.JoinVertical( lipgloss.Left, views..., ) } type tagEdit struct { common *common.Common fields []huh.Field cursor int newTagsValue *string } func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit { newTags := "" defaultKeymap := huh.NewDefaultKeyMap() t := tagEdit{ common: common, fields: []huh.Field{ huh.NewMultiSelect[string](). Options(huh.NewOptions(options...)...). // Key("tags"). Title("Tags"). Value(selected). Filterable(true). WithKeyMap(defaultKeymap). WithTheme(common.Styles.Form), huh.NewInput(). Title("New Tags"). Value(&newTags). Inline(true). Prompt(": "). WithTheme(common.Styles.Form), }, newTagsValue: &newTags, } return &t } func (t *tagEdit) SetCursor(c int) { t.fields[t.cursor].Blur() if c < 0 { t.cursor = len(t.fields) - 1 } else { t.cursor = c } t.fields[t.cursor].Focus() } func (t *tagEdit) Init() tea.Cmd { return nil } func (t *tagEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: if t.cursor == len(t.fields)-1 { t.fields[t.cursor].Blur() return t, nextArea() } t.fields[t.cursor].Blur() t.cursor++ t.fields[t.cursor].Focus() case prevFieldMsg: if t.cursor == 0 { t.fields[t.cursor].Blur() return t, prevArea() } t.fields[t.cursor].Blur() t.cursor-- t.fields[t.cursor].Focus() default: field, cmd := t.fields[t.cursor].Update(msg) t.fields[t.cursor] = field.(huh.Field) return t, cmd } return t, nil } func (t tagEdit) View() string { views := make([]string, len(t.fields)) for i, field := range t.fields { views[i] = field.View() } return lipgloss.JoinVertical( lipgloss.Left, views..., ) } type timeEdit struct { common *common.Common fields []huh.Field cursor int } func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *string, until *string) *timeEdit { // defaultKeymap := huh.NewDefaultKeyMap() t := timeEdit{ common: common, fields: []huh.Field{ huh.NewInput(). Title("Due"). Value(due). Validate(taskwarrior.ValidateDate). Inline(true). Prompt(": "). WithTheme(common.Styles.Form), huh.NewInput(). Title("Scheduled"). Value(scheduled). Validate(taskwarrior.ValidateDate). Inline(true). Prompt(": "). WithTheme(common.Styles.Form), huh.NewInput(). Title("Wait"). Value(wait). Validate(taskwarrior.ValidateDate). Inline(true). Prompt(": "). WithTheme(common.Styles.Form), huh.NewInput(). Title("Until"). Value(until). Validate(taskwarrior.ValidateDate). Inline(true). Prompt(": "). WithTheme(common.Styles.Form), }, } return &t } func (t *timeEdit) SetCursor(c int) { t.fields[t.cursor].Blur() if c < 0 { t.cursor = len(t.fields) - 1 } else { t.cursor = c } t.fields[t.cursor].Focus() } func (t *timeEdit) Init() tea.Cmd { return nil } func (t *timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: if t.cursor == len(t.fields)-1 { t.fields[t.cursor].Blur() return t, nextArea() } t.fields[t.cursor].Blur() t.cursor++ t.fields[t.cursor].Focus() case prevFieldMsg: if t.cursor == 0 { t.fields[t.cursor].Blur() return t, prevArea() } t.fields[t.cursor].Blur() t.cursor-- t.fields[t.cursor].Focus() default: field, cmd := t.fields[t.cursor].Update(msg) t.fields[t.cursor] = field.(huh.Field) return t, cmd } return t, nil } func (t *timeEdit) View() string { views := make([]string, len(t.fields)) for i, field := range t.fields { views[i] = field.View() } return lipgloss.JoinVertical( lipgloss.Left, views..., ) } type detailsEdit struct { com *common.Common renderer *glamour.TermRenderer vp viewport.Model } func NewDetailsEdit(com *common.Common, task *taskwarrior.Task) *detailsEdit { renderer, err := glamour.NewTermRenderer( // glamour.WithStandardStyle("light"), glamour.WithAutoStyle(), glamour.WithWordWrap(40), ) if err != nil { slog.Error(err.Error()) return nil } vp := viewport.New(40, 30) d := detailsEdit{ com: com, renderer: renderer, vp: vp, } return &d } func (d *detailsEdit) SetCursor(c int) { } func (d *detailsEdit) Init() tea.Cmd { return nil } func (d *detailsEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: return d, nextArea() case prevFieldMsg: return d, prevArea() default: var cmd tea.Cmd d.vp, cmd = d.vp.Update(msg) return d, cmd } } func (d *detailsEdit) View() string { dtls := ` # Cool Details! ## Things I need - [ ] A thing - [x] Done thing ## People - pe1 - pe2 ` details, err := d.renderer.Render(dtls) if err != nil { slog.Error(err.Error()) return "Could not parse markdown" } d.vp.SetContent(details) return d.vp.View() } // func (p *TaskEditorPage) SetSize(width, height int) { // p.common.SetSize(width, height) // } // func (p *TaskEditorPage) Init() tea.Cmd { // // return p.form.Init() // return nil // } // func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // var cmds []tea.Cmd // switch msg := msg.(type) { // case SwitchModeMsg: // switch mode(msg) { // case modeNormal: // p.mode = modeNormal // case modeInsert: // p.mode = modeInsert // } // case changeAreaMsg: // p.selectedArea = area(msg) // p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...) // case nextColumnMsg: // p.columnCursor++ // if p.columnCursor > len(p.columns)-1 { // p.columnCursor = 0 // } // case prevColumnMsg: // p.columnCursor-- // if p.columnCursor < 0 { // p.columnCursor = len(p.columns) - 1 // } // } // switch p.mode { // case modeNormal: // switch msg := msg.(type) { // case tea.KeyMsg: // switch { // case key.Matches(msg, p.common.Keymap.Back): // model, err := p.common.PopPage() // if err != nil { // slog.Error("page stack empty") // return nil, tea.Quit // } // return model, BackCmd // case key.Matches(msg, p.common.Keymap.Insert): // return p, p.switchModeCmd(modeInsert) // // case key.Matches(msg, p.common.Keymap.Ok): // // p.form.State = huh.StateCompleted // case key.Matches(msg, p.common.Keymap.Left): // return p, prevColumn() // case key.Matches(msg, p.common.Keymap.Right): // return p, nextColumn() // } // } // case modeInsert: // switch msg := msg.(type) { // case tea.KeyMsg: // switch { // case key.Matches(msg, p.common.Keymap.Back): // return p, p.switchModeCmd(modeNormal) // } // } // var cmd tea.Cmd // if p.columnCursor == 0 { // p.areaList, cmd = p.areaList.Update(msg) // p.selectedArea = p.areaList.(areaList).Area() // cmds = append(cmds, cmd) // } else { // p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg) // cmds = append(cmds, cmd) // } // } // var cmd tea.Cmd // if p.columnCursor == 0 { // p.areaList, cmd = p.areaList.Update(msg) // p.selectedArea = p.areaList.(areaList).Area() // cmds = append(cmds, cmd) // } else { // p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg) // cmds = append(cmds, cmd) // } // p.statusline, cmd = p.statusline.Update(msg) // cmds = append(cmds, cmd) // // if p.form.State == huh.StateCompleted { // // cmds = append(cmds, p.updateTasksCmd) // // model, err := p.common.PopPage() // // if err != nil { // // slog.Error("page stack empty") // // return nil, tea.Quit // // } // // return model, tea.Batch(cmds...) // // } // return p, tea.Batch(cmds...) // } // func (p *TaskEditorPage) View() string { // columns := make([]string, len(p.columns)) // for i, c := range p.columns { // columns[i] = c.View() // } // return lipgloss.JoinVertical( // lipgloss.Left, // lipgloss.JoinHorizontal( // lipgloss.Top, // // lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceBackground(p.common.Styles.Warning.GetForeground())), // // lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceChars(" . ")), // columns..., // ), // p.statusline.View(), // ) // } func (p *TaskEditorPage) updateTasksCmd() tea.Msg { if p.task.Project == "(none)" { p.task.Project = "" } for _, uda := range p.common.Udas { if val, ok := p.areas[0].(*taskEdit).udaValues[uda.Name]; ok { if *val == "(none)" { *val = "" } p.task.Udas[uda.Name] = *val } } if *(p.areas[0].(*taskEdit).newProjectName) != "" { p.task.Project = *p.areas[0].(*taskEdit).newProjectName } if *(p.areas[1].(*tagEdit).newTagsValue) != "" { newTags := strings.Split(*p.areas[1].(*tagEdit).newTagsValue, " ") if len(newTags) > 0 { p.task.Tags = append(p.task.Tags, newTags...) } } if *(p.areas[0].(*taskEdit).newAnnotation) != "" { p.task.Annotations = append(p.task.Annotations, taskwarrior.Annotation{ Entry: time.Now().Format("20060102T150405Z"), Description: *(p.areas[0].(*taskEdit).newAnnotation), }) // p.common.TW.AddTaskAnnotation(p.task.Uuid, *p.areas[0].(*taskEdit).newAnnotation) } p.common.TW.ImportTask(&p.task) return UpdatedTasksMsg{} } // type StatusLine struct { // common *common.Common // mode mode // input textinput.Model // } // func NewStatusLine(common *common.Common, mode mode) *StatusLine { // input := textinput.New() // input.Placeholder = "" // input.Prompt = "" // input.Blur() // return &StatusLine{ // input: textinput.New(), // common: common, // mode: mode, // } // } // func (s *StatusLine) Init() tea.Cmd { // s.input.Blur() // return nil // } // func (s *StatusLine) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // var cmd tea.Cmd // switch msg := msg.(type) { // case SwitchModeMsg: // s.mode = mode(msg) // switch s.mode { // case modeNormal: // s.input.Blur() // case modeInsert: // s.input.Focus() // } // case tea.KeyMsg: // switch { // case key.Matches(msg, s.common.Keymap.Back): // s.input.Blur() // case key.Matches(msg, s.common.Keymap.Input): // s.input.Focus() // } // } // s.input, cmd = s.input.Update(msg) // return s, cmd // } // func (s *StatusLine) View() string { // var mode string // switch s.mode { // case modeNormal: // mode = s.common.Styles.Base.Render("NORMAL") // case modeInsert: // mode = s.common.Styles.Active.Inline(true).Reverse(true).Render("INSERT") // } // return lipgloss.JoinHorizontal(lipgloss.Left, mode, s.input.View()) // } // // TODO: move this to taskwarrior; add missing date formats // type itemDelegate struct{} // func (d itemDelegate) Height() int { return 1 } // func (d itemDelegate) Spacing() int { return 0 } // func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } // func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { // i, ok := listItem.(item) // if !ok { // return // } // str := fmt.Sprintf("%s", i) // fn := itemStyle.Render // if index == m.Index() { // fn = func(s ...string) string { // return selectedItemStyle.Render("> " + strings.Join(s, " ")) // } // } // fmt.Fprint(w, fn(str)) // } // var ( // titleStyle = lipgloss.NewStyle().MarginLeft(2) // itemStyle = lipgloss.NewStyle().PaddingLeft(4) // selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) // paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) // helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) // quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) // ) // type item string // func (i item) FilterValue() string { return "" }