From a6797a750990d883f700a2c220986ec23e8191f6 Mon Sep 17 00:00:00 2001 From: Jan Hensel Date: Sat, 13 Jan 2024 19:09:24 +0100 Subject: [PATCH] refactor(tui): Implement UI-pane creation for editors removed from editor logic This seems much better to me (despite, for now, dropping the views package). There are multiple ways to decide on this sort of thing, and many ways to get lost in the abstract what-if-edgecase sauce, but I find that the editor should allow inspecting it sufficiently to build a UI from it, rather than giving the editor all the necessities to build its own UI. Especially if we ever support something other than TUI, I think it'll be much less weird this way, imagine having to implement for each UI paradigm in every control-logic component... --- internal/control/cli/controller.go | 26 ++-- internal/control/edit/editor.go | 31 ++--- .../control/edit/editors/composite_editor.go | 126 ++---------------- .../control/edit/editors/string_editor.go | 37 +---- .../control/edit/views/composite_editor.go | 12 -- internal/control/edit/views/string_editor.go | 24 ---- internal/ui/panes/composite_editor_ui_pane.go | 122 +++++++++++++---- internal/ui/panes/string_editor_ui_pane.go | 35 +++-- 8 files changed, 151 insertions(+), 262 deletions(-) delete mode 100644 internal/control/edit/views/composite_editor.go delete mode 100644 internal/control/edit/views/string_editor.go diff --git a/internal/control/cli/controller.go b/internal/control/cli/controller.go index d0e11788..e6a4e135 100644 --- a/internal/control/cli/controller.go +++ b/internal/control/cli/controller.go @@ -472,24 +472,26 @@ func NewController( controller.data.TaskEditor = nil return } - taskEditorPane, err := controller.data.TaskEditor.GetPane( - ui.NewConstrainedRenderer(renderer, func() (x, y, w, h int) { - screenWidth, screenHeight := screenSize() - taskEditorBoxWidth := int(math.Min(80, float64(screenWidth))) - taskEditorBoxHeight := int(math.Min(20, float64(screenHeight))) - return (screenWidth / 2) - (taskEditorBoxWidth / 2), (screenHeight / 2) - (taskEditorBoxHeight / 2), taskEditorBoxWidth, taskEditorBoxHeight - }), + + taskEditorRenderer := ui.NewConstrainedRenderer(renderer, func() (x, y, w, h int) { + screenWidth, screenHeight := screenSize() + taskEditorBoxWidth := int(math.Min(80, float64(screenWidth))) + taskEditorBoxHeight := int(math.Min(20, float64(screenHeight))) + return (screenWidth / 2) - (taskEditorBoxWidth / 2), (screenHeight / 2) - (taskEditorBoxHeight / 2), taskEditorBoxWidth, taskEditorBoxHeight + }) + + taskEditorPane, err := panes.NewCompositeEditorPane( + taskEditorRenderer, + cursorWrangler, func() bool { return true }, inputConfig, stylesheet, - cursorWrangler, + controller.data.TaskEditor, ) if err != nil { - log.Error().Err(err).Msgf("could not construct task editor pane") - controller.data.TaskEditor = nil - return + log.Fatal().Err(err).Msg("could not construct task editor pane (this is likely a serious programming error / omission)") } - log.Info().Str("info", taskEditorPane.(*panes.CompositeEditorPane).GetDebugInfo()).Msg("here is the debug info for the task editor pane") + controller.rootPane.PushSubpane(taskEditorPane) taskEditorDone := make(chan struct{}) controller.data.TaskEditor.AddQuitCallback(func() { diff --git a/internal/control/edit/editor.go b/internal/control/edit/editor.go index 0dffaa04..9093b178 100644 --- a/internal/control/edit/editor.go +++ b/internal/control/edit/editor.go @@ -2,12 +2,6 @@ // user). package edit -import ( - "github.com/ja-he/dayplan/internal/input" - "github.com/ja-he/dayplan/internal/styling" - "github.com/ja-he/dayplan/internal/ui" -) - // Editor is an interface for editing of objects (by the user). type Editor interface { IsActiveAndFocussed() (bool, bool) @@ -15,7 +9,6 @@ type Editor interface { GetName() string GetType() string - GetSummary() SummaryEntry // Write the state of the editor. Write() @@ -25,18 +18,18 @@ type Editor interface { // AddQuitCallback adds a callback that is called when the editor is quit. AddQuitCallback(func()) - - // GetPane returns a pane that represents this editor. - GetPane( - renderer ui.ConstrainedRenderer, - visible func() bool, - inputConfig input.InputConfig, - stylesheet styling.Stylesheet, - cursorController ui.CursorLocationRequestHandler, - ) (ui.Pane, error) } -type SummaryEntry struct { - Representation any - Represents Editor +// View is an interface for viewing of objects (by the user). +type View interface { + IsActiveAndFocussed() (bool, bool) + + GetName() string + + // GetType reutrns the type of the view. + // + // Based on this information, a cast to a relevant type can be done. + // E.g., when GetType() returns "string", the view can be cast to a + // StringEditorView. + GetType() string } diff --git a/internal/control/edit/editors/composite_editor.go b/internal/control/edit/editors/composite_editor.go index a9b6bf76..1cbe4e44 100644 --- a/internal/control/edit/editors/composite_editor.go +++ b/internal/control/edit/editors/composite_editor.go @@ -1,3 +1,4 @@ +// Package editors contains the editors for the different data types. package editors import ( @@ -12,9 +13,6 @@ import ( "github.com/ja-he/dayplan/internal/input" "github.com/ja-he/dayplan/internal/input/processors" "github.com/ja-he/dayplan/internal/model" - "github.com/ja-he/dayplan/internal/styling" - "github.com/ja-he/dayplan/internal/ui" - "github.com/ja-he/dayplan/internal/ui/panes" ) // Composite implements Editor @@ -207,58 +205,8 @@ func (e *Composite) Quit() { } } -// GetPane constructs a pane for this composite editor (including all subeditors). -func (e *Composite) GetPane( - renderer ui.ConstrainedRenderer, - visible func() bool, - inputConfig input.InputConfig, - stylesheet styling.Stylesheet, - cursorController ui.CursorLocationRequestHandler, -) (ui.Pane, error) { - subpanes := []ui.Pane{} - - // TODO: this needs to compute an enriched version of the editor tree - editorSummary := e.GetSummary() - minX, minY, maxWidth, maxHeight := renderer.Dimensions() - uiBoxModel, err := translateToUIBoxModel(editorSummary, minX, minY, maxWidth, maxHeight) - if err != nil { - return nil, fmt.Errorf("error translating editor summary to UI box model (%s)", err.Error()) - } - log.Debug().Msgf("have UI box model: %s", uiBoxModel.String()) - - for _, child := range uiBoxModel.Children { - childX, childY, childW, childH := child.X, child.Y, child.W, child.H - subRenderer := ui.NewConstrainedRenderer(renderer, func() (int, int, int, int) { return childX, childY, childW, childH }) - subeditorPane, err := child.Represents.GetPane( - subRenderer, - visible, - inputConfig, - stylesheet, - cursorController, - ) - if err != nil { - return nil, fmt.Errorf("error constructing subpane of '%s' for subeditor '%s' (%s)", e.name, child.Represents.GetName(), err.Error()) - } - subpanes = append(subpanes, subeditorPane) - } - - inputProcessor, err := e.createInputProcessor(inputConfig) - if err != nil { - return nil, fmt.Errorf("could not construct input processor (%s)", err.Error()) - } - return panes.NewCompositeEditorPane( - renderer, - visible, - inputProcessor, - stylesheet, - subpanes, - func() int { return e.activeFieldIndex }, - func() bool { return e.inField }, - e, - ), nil -} - -func (e *Composite) createInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) { +// CreateInputProcessor creates an input processor for the editor. +func (e *Composite) CreateInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) { actionspecToFunc := map[input.Actionspec]func(){ "next-field": e.SwitchToNextField, "prev-field": e.SwitchToPrevField, @@ -282,68 +230,12 @@ func (e *Composite) createInputProcessor(cfg input.InputConfig) (input.ModalInpu return processors.NewModalInputProcessor(inputTree), nil } -func (e *Composite) IsActiveAndFocussed() (bool, bool) { return e.activeAndFocussedFunc() } - -func (e *Composite) GetSummary() edit.SummaryEntry { - - result := edit.SummaryEntry{ - Representation: []edit.SummaryEntry{}, - Represents: e, - } - for _, subeditor := range e.fields { - log.Debug().Msgf("constructing subpane of '%s' for subeditor '%s'", e.name, subeditor.GetName()) - result.Representation = append(result.Representation.([]edit.SummaryEntry), subeditor.GetSummary()) - } - - return result -} - -func translateToUIBoxModel(summary edit.SummaryEntry, minX, minY, maxWidth, maxHeight int) (ui.BoxRepresentation[edit.Editor], error) { - - switch repr := summary.Representation.(type) { +func (e *Composite) GetActiveFieldIndex() int { return e.activeFieldIndex } +func (e *Composite) IsInField() bool { return e.inField } - // a slice indicates a composite - case []edit.SummaryEntry: - var children []ui.BoxRepresentation[edit.Editor] - computedHeight := 1 - rollingY := minY + 1 - for _, child := range repr { - childBoxRepresentation, err := translateToUIBoxModel(child, minX+1, rollingY, maxWidth-2, maxHeight-2) - if err != nil { - return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("error translating child '%s' (%s)", child.Represents.GetName(), err.Error()) - } - rollingY += childBoxRepresentation.H + 1 - children = append(children, childBoxRepresentation) - computedHeight += childBoxRepresentation.H + 1 - } - return ui.BoxRepresentation[edit.Editor]{ - X: minX, - Y: minY, - W: maxWidth, - H: computedHeight, - Represents: summary.Represents, - Children: children, - }, nil - - // a string indicates a leaf, i.e., a concrete editor rather than a composite - case string: - switch repr { - case "string": - return ui.BoxRepresentation[edit.Editor]{ - X: minX, - Y: minY, - W: maxWidth, - H: 1, - Represents: summary.Represents, - Children: nil, - }, nil - default: - return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("unknown editor identification value '%s'", repr) - } - - default: - return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("for editor '%s' have unknown type '%t'", summary.Represents.GetName(), summary.Representation) - - } +func (e *Composite) IsActiveAndFocussed() (bool, bool) { return e.activeAndFocussedFunc() } +// GetFields returns the subeditors of this composite editor. +func (e *Composite) GetFields() []edit.Editor { + return e.fields } diff --git a/internal/control/edit/editors/string_editor.go b/internal/control/edit/editors/string_editor.go index ec7e0ffb..acb4bcaa 100644 --- a/internal/control/edit/editors/string_editor.go +++ b/internal/control/edit/editors/string_editor.go @@ -5,12 +5,8 @@ import ( "strconv" "github.com/ja-he/dayplan/internal/control/action" - "github.com/ja-he/dayplan/internal/control/edit" "github.com/ja-he/dayplan/internal/input" "github.com/ja-he/dayplan/internal/input/processors" - "github.com/ja-he/dayplan/internal/styling" - "github.com/ja-he/dayplan/internal/ui" - "github.com/ja-he/dayplan/internal/ui/panes" "github.com/rs/zerolog/log" ) @@ -245,30 +241,8 @@ func (e *StringEditor) AddQuitCallback(f func()) { } } -// GetPane returns a UI pane representing the editor. -func (e *StringEditor) GetPane( - renderer ui.ConstrainedRenderer, - visible func() bool, - inputConfig input.InputConfig, - stylesheet styling.Stylesheet, - cursorController ui.CursorLocationRequestHandler, -) (ui.Pane, error) { - inputProcessor, err := e.createInputProcessor(inputConfig) - if err != nil { - return nil, err - } - p := panes.NewStringEditorPane( - renderer, - visible, - inputProcessor, - e, - stylesheet, - cursorController, - ) - return p, nil -} - -func (e *StringEditor) createInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) { +// CreateInputProcessor creates an input processor for the editor. +func (e *StringEditor) CreateInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) { var enterInsertMode func() var exitInsertMode func() @@ -325,10 +299,3 @@ func (e *StringEditor) createInputProcessor(cfg input.InputConfig) (input.ModalI return p, nil } - -func (e *StringEditor) GetSummary() edit.SummaryEntry { - return edit.SummaryEntry{ - Representation: "string", - Represents: e, - } -} diff --git a/internal/control/edit/views/composite_editor.go b/internal/control/edit/views/composite_editor.go deleted file mode 100644 index 91ed1359..00000000 --- a/internal/control/edit/views/composite_editor.go +++ /dev/null @@ -1,12 +0,0 @@ -package views - -// StringEditorView allows inspection of a string editor. -type CompositeEditorView interface { - - // IsActive signals whether THIS is active. (SHOULD BE MOVED TO A MORE GENERIC INTERFACE) - IsActiveAndFocussed() (bool, bool) - - GetName() string - - // TODO: more -} diff --git a/internal/control/edit/views/string_editor.go b/internal/control/edit/views/string_editor.go deleted file mode 100644 index d09de597..00000000 --- a/internal/control/edit/views/string_editor.go +++ /dev/null @@ -1,24 +0,0 @@ -package views - -import "github.com/ja-he/dayplan/internal/input" - -// StringEditorView allows inspection of a string editor. -type StringEditorView interface { - - // IsActive signals whether THIS is active. (SHOULD BE MOVED TO A MORE GENERIC INTERFACE) - IsActiveAndFocussed() (bool, bool) - - // GetMode returns the current mode of the editor. - GetMode() input.TextEditMode - - // GetCursorPos returns the current cursor position in the string, 0 being - // the first character. - GetCursorPos() int - - // GetContent returns the current (edited) contents. - GetContent() string - - GetName() string - - // TODO: more -} diff --git a/internal/ui/panes/composite_editor_ui_pane.go b/internal/ui/panes/composite_editor_ui_pane.go index 6c172953..bc49726f 100644 --- a/internal/ui/panes/composite_editor_ui_pane.go +++ b/internal/ui/panes/composite_editor_ui_pane.go @@ -7,7 +7,8 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/ja-he/dayplan/internal/control/edit/views" + "github.com/ja-he/dayplan/internal/control/edit" + "github.com/ja-he/dayplan/internal/control/edit/editors" "github.com/ja-he/dayplan/internal/input" "github.com/ja-he/dayplan/internal/styling" "github.com/ja-he/dayplan/internal/ui" @@ -20,7 +21,7 @@ type CompositeEditorPane struct { getFocussedIndex func() int isInField func() bool - view views.CompositeEditorView + e *editors.Composite subpanes []ui.Pane bgoffs int @@ -35,14 +36,14 @@ func (p *CompositeEditorPane) Draw() { // draw background style := p.Stylesheet.Editor.DarkenedBG(p.bgoffs) - active, focussed := p.view.IsActiveAndFocussed() + active, focussed := p.e.IsActiveAndFocussed() if active { style = style.DarkenedBG(20) } else if focussed { style = style.DarkenedBG(40) } p.Renderer.DrawBox(x, y, w, h, style) - p.Renderer.DrawText(x, y, w, 1, p.Stylesheet.Editor.DarkenedFG(20), p.view.GetName()) + p.Renderer.DrawText(x, y, w, 1, p.Stylesheet.Editor.DarkenedFG(20), p.e.GetName()) // draw all subpanes for _, subpane := range p.subpanes { @@ -100,14 +101,46 @@ func (p *CompositeEditorPane) GetPositionInfo(_, _ int) ui.PositionInfo { return // NewCompositeEditorPane creates a new CompositeEditorPane. func NewCompositeEditorPane( renderer ui.ConstrainedRenderer, + cursorController ui.CursorLocationRequestHandler, visible func() bool, - inputProcessor input.ModalInputProcessor, + inputConfig input.InputConfig, stylesheet styling.Stylesheet, - subEditors []ui.Pane, - getFocussedIndex func() int, - isInField func() bool, - view views.CompositeEditorView, -) *CompositeEditorPane { + e *editors.Composite, +) (*CompositeEditorPane, error) { + + subpanes := []ui.Pane{} + + minX, minY, maxWidth, maxHeight := renderer.Dimensions() + uiBoxModel, err := translateEditorsCompositeToTUI(e, minX, minY, maxWidth, maxHeight) + if err != nil { + return nil, fmt.Errorf("error translating editor summary to UI box model (%s)", err.Error()) + } + log.Debug().Msgf("have UI box model: %s", uiBoxModel.String()) + + for _, child := range uiBoxModel.Children { + childX, childY, childW, childH := child.X, child.Y, child.W, child.H + subRenderer := ui.NewConstrainedRenderer(renderer, func() (int, int, int, int) { return childX, childY, childW, childH }) + var subeditorPane ui.Pane + var err error + switch child := child.Represents.(type) { + case *editors.StringEditor: + subeditorPane, err = NewStringEditorPane(subRenderer, cursorController, visible, stylesheet, inputConfig, child) + case *editors.Composite: + subeditorPane, err = NewCompositeEditorPane(subRenderer, cursorController, visible, inputConfig, stylesheet, child) + default: + err = fmt.Errorf("unhandled subeditor type '%T' (forgot to handle case)", child) + } + if err != nil { + return nil, fmt.Errorf("error constructing subpane of '%s' for subeditor '%s' (%s)", e.GetName(), child.Represents.GetName(), err.Error()) + } + subpanes = append(subpanes, subeditorPane) + } + + inputProcessor, err := e.CreateInputProcessor(inputConfig) + if err != nil { + return nil, fmt.Errorf("could not construct input processor (%s)", err.Error()) + } + return &CompositeEditorPane{ LeafPane: ui.LeafPane{ BasePane: ui.BasePane{ @@ -119,13 +152,13 @@ func NewCompositeEditorPane( Dims: renderer.Dimensions, Stylesheet: stylesheet, }, - subpanes: subEditors, - getFocussedIndex: getFocussedIndex, - isInField: isInField, + subpanes: subpanes, + getFocussedIndex: e.GetActiveFieldIndex, + isInField: e.IsInField, log: log.With().Str("source", "composite-pane").Logger(), bgoffs: 10 + rand.Intn(20), - view: view, - } + e: e, + }, nil } // GetHelp returns the input help map for this composite pane. @@ -152,20 +185,51 @@ func (p *CompositeEditorPane) GetHelp() input.Help { return result } -func (p *CompositeEditorPane) GetDebugInfo() string { - x, y, w, h := p.Dimensions() - info := fmt.Sprintf("[ +%d+%d:%dx%d ", x, y, w, h) - for _, subpane := range p.subpanes { - switch sp := subpane.(type) { - case *CompositeEditorPane: - info += sp.GetDebugInfo() - case *StringEditorPane: - x, y, w, h := sp.Dimensions() - info += fmt.Sprintf("( %d+%d:%dx%d )", x, y, w, h) - default: - info += fmt.Sprintf("", subpane) +func translateEditorsEditorToTUI(e edit.Editor, minX, minY, maxWidth, maxHeight int) (ui.BoxRepresentation[edit.Editor], error) { + + switch e := e.(type) { + + case *editors.Composite: + return translateEditorsCompositeToTUI(e, minX, minY, maxWidth, maxHeight) + + case *editors.StringEditor: + return ui.BoxRepresentation[edit.Editor]{ + X: minX, + Y: minY, + W: maxWidth, + H: 1, + Represents: e, + Children: nil, + }, nil + + default: + return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("unhandled editor type '%T' (forgot to handle case)", e) + + } + +} + +func translateEditorsCompositeToTUI(e *editors.Composite, minX, minY, maxWidth, maxHeight int) (ui.BoxRepresentation[edit.Editor], error) { + + var children []ui.BoxRepresentation[edit.Editor] + computedHeight := 1 + rollingY := minY + 1 + for _, child := range e.GetFields() { + childBoxRepresentation, err := translateEditorsEditorToTUI(child, minX+1, rollingY, maxWidth-2, maxHeight-2) + if err != nil { + return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("error translating child '%s' (%s)", child.GetName(), err.Error()) } + rollingY += childBoxRepresentation.H + 1 + children = append(children, childBoxRepresentation) + computedHeight += childBoxRepresentation.H + 1 } - info += "]" - return info + return ui.BoxRepresentation[edit.Editor]{ + X: minX, + Y: minY, + W: maxWidth, + H: computedHeight, + Represents: e, + Children: children, + }, nil + } diff --git a/internal/ui/panes/string_editor_ui_pane.go b/internal/ui/panes/string_editor_ui_pane.go index 11eaa7c5..7c9f5dde 100644 --- a/internal/ui/panes/string_editor_ui_pane.go +++ b/internal/ui/panes/string_editor_ui_pane.go @@ -1,10 +1,12 @@ package panes import ( + "fmt" + "github.com/google/uuid" "github.com/rs/zerolog/log" - "github.com/ja-he/dayplan/internal/control/edit/views" + "github.com/ja-he/dayplan/internal/control/edit/editors" "github.com/ja-he/dayplan/internal/input" "github.com/ja-he/dayplan/internal/styling" "github.com/ja-he/dayplan/internal/ui" @@ -14,7 +16,7 @@ import ( type StringEditorPane struct { ui.LeafPane - view views.StringEditorView + e *editors.StringEditor cursorController ui.CursorLocationRequestHandler @@ -27,7 +29,7 @@ func (p *StringEditorPane) Draw() { x, y, w, h := p.Dims() baseBGStyle := p.Stylesheet.Editor - active, focussed := p.view.IsActiveAndFocussed() + active, focussed := p.e.IsActiveAndFocussed() if active { baseBGStyle = baseBGStyle.DarkenedBG(10) } else if focussed { @@ -39,10 +41,10 @@ func (p *StringEditorPane) Draw() { padding := 1 p.Renderer.DrawBox(x, y, w, h, baseBGStyle) - p.Renderer.DrawText(x+padding, y, nameWidth, h, baseBGStyle.Italicized(), p.view.GetName()) + p.Renderer.DrawText(x+padding, y, nameWidth, h, baseBGStyle.Italicized(), p.e.GetName()) if focussed { - switch p.view.GetMode() { + switch p.e.GetMode() { case input.TextEditModeInsert: p.Renderer.DrawText(x+padding+nameWidth+padding, y, modeWidth, h, baseBGStyle.DarkenedFG(30).Invert(), "(ins)") case input.TextEditModeNormal: @@ -53,10 +55,10 @@ func (p *StringEditorPane) Draw() { } contentXOffset := padding + nameWidth + padding + modeWidth + padding - p.Renderer.DrawText(x+contentXOffset, y, w-contentXOffset+padding, h, baseBGStyle.DarkenedBG(20), p.view.GetContent()) + p.Renderer.DrawText(x+contentXOffset, y, w-contentXOffset+padding, h, baseBGStyle.DarkenedBG(20), p.e.GetContent()) if focussed { - cursorX, cursorY := x+contentXOffset+(p.view.GetCursorPos()), y + cursorX, cursorY := x+contentXOffset+(p.e.GetCursorPos()), y p.cursorController.Put(ui.CursorLocation{X: cursorX, Y: cursorY}, p.idStr) } else { p.cursorController.Delete(p.idStr) @@ -76,7 +78,7 @@ func (p *StringEditorPane) GetPositionInfo(_, _ int) ui.PositionInfo { return ni // ProcessInput attempts to process the provided input. func (p *StringEditorPane) ProcessInput(k input.Key) bool { - active, _ := p.view.IsActiveAndFocussed() + active, _ := p.e.IsActiveAndFocussed() if !active { log.Warn().Msgf("string editor pane asked to process input despite view reporting not active; likely logic error") } @@ -86,12 +88,17 @@ func (p *StringEditorPane) ProcessInput(k input.Key) bool { // NewStringEditorPane creates a new StringEditorPane. func NewStringEditorPane( renderer ui.ConstrainedRenderer, + cursorController ui.CursorLocationRequestHandler, visible func() bool, - inputProcessor input.ModalInputProcessor, - view views.StringEditorView, stylesheet styling.Stylesheet, - cursorController ui.CursorLocationRequestHandler, -) *StringEditorPane { + inputConfig input.InputConfig, + e *editors.StringEditor, +) (*StringEditorPane, error) { + inputProcessor, err := e.CreateInputProcessor(inputConfig) + if err != nil { + return nil, fmt.Errorf("could not construct normal mode input tree (%s)", err.Error()) + } + return &StringEditorPane{ LeafPane: ui.LeafPane{ BasePane: ui.BasePane{ @@ -103,8 +110,8 @@ func NewStringEditorPane( Dims: renderer.Dimensions, Stylesheet: stylesheet, }, - view: view, + e: e, cursorController: cursorController, idStr: "string-editor-pane-" + uuid.Must(uuid.NewRandom()).String(), - } + }, nil }