Help us shape the future of Go! Take the 2026 Go Developer Survey

model

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package model is the Bubble Tea root: it owns the diff, the selection, and every child component, and routes each key to the handler for the current mode.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CtrlKey

func CtrlKey(key rune) tea.KeyPressMsg

CtrlKey builds the message Bubble Tea sends for key held with control, such as ctrl+d.

func KeyPress

func KeyPress(key rune) tea.KeyPressMsg

KeyPress builds the message Bubble Tea sends for a printable key.

func SpecialKey

func SpecialKey(code rune) tea.KeyPressMsg

SpecialKey builds the message Bubble Tea sends for a named key such as tea.KeyEsc. Text stays empty, which is how Bubble Tea marks a key that does not produce a printable character.

func TestChanges

func TestChanges() []diff.FileChange

TestChanges returns three file changes covering the modified, added, and deleted paths.

Types

type Assertion added in v0.1.2

type Assertion struct {
	// contains filtered or unexported fields
}

Assertion reads unexported Model state, which is why it lives in the package rather than a test file. Every check reports through t.Errorf and returns the receiver, so a chain runs to the end and reports every mismatch instead of stopping at the first.

func Assert

func Assert(t *testing.T, m *Model) *Assertion

Assert opens a chain of assertions over a snapshot of m. Later mutations of m are not observed.

func (*Assertion) FileListFilterModeDisabled added in v0.1.2

func (a *Assertion) FileListFilterModeDisabled()

FileListFilterModeDisabled checks that the file list has handed key handling back to the model.

func (*Assertion) FileListFilterModeEnabled added in v0.1.2

func (a *Assertion) FileListFilterModeEnabled()

FileListFilterModeEnabled checks that the file list is capturing keys for its inline filter.

func (*Assertion) FocusedPanelIs added in v0.1.2

func (a *Assertion) FocusedPanelIs(panel FocusedPanel)

FocusedPanelIs checks which panel receives navigation keys.

func (*Assertion) HasChanges added in v0.1.2

func (a *Assertion) HasChanges(count int)

HasChanges checks how many file changes are loaded, ignoring any file-list filter.

func (*Assertion) HasDestination added in v0.1.2

func (a *Assertion) HasDestination(dest string)

HasDestination checks the revset the model will move changes to, which is empty until one is picked.

func (*Assertion) HasError added in v0.1.2

func (a *Assertion) HasError()

HasError checks that the model is holding an error for the view to render.

func (*Assertion) HasHunkNotSelected added in v0.1.2

func (a *Assertion) HasHunkNotSelected(filePath string, hunkIdx int)

HasHunkNotSelected checks that no part of the hunk is marked for apply.

func (*Assertion) HasHunkSelected added in v0.1.2

func (a *Assertion) HasHunkSelected(filePath string, hunkIdx int)

HasHunkSelected checks that the hunk is marked for apply. A partially selected hunk still counts.

func (*Assertion) HasLineCursor added in v0.1.2

func (a *Assertion) HasLineCursor(position int)

HasLineCursor checks the line cursor as an index into the rendered rows of the selected hunk.

func (*Assertion) HasNoError added in v0.1.2

func (a *Assertion) HasNoError()

HasNoError checks that no error is pending, and prints the error it found when one is.

func (*Assertion) HasSelectedFile added in v0.1.2

func (a *Assertion) HasSelectedFile(idx int)

HasSelectedFile checks the file-list cursor, which is an index into the unfiltered change list.

func (*Assertion) HasSelectedHunk added in v0.1.2

func (a *Assertion) HasSelectedHunk(idx int)

HasSelectedHunk checks the hunk cursor, which is an index within the currently selected file only.

func (*Assertion) HelpIsNotVisible added in v0.1.2

func (a *Assertion) HelpIsNotVisible()

HelpIsNotVisible checks that the help overlay is down.

func (*Assertion) HelpIsVisible added in v0.1.2

func (a *Assertion) HelpIsVisible()

HelpIsVisible checks that the help overlay is up.

func (*Assertion) IsInVisualMode added in v0.1.2

func (a *Assertion) IsInVisualMode()

IsInVisualMode checks that line-range selection is active.

func (*Assertion) IsNotInVisualMode added in v0.1.2

func (a *Assertion) IsNotInVisualMode()

IsNotInVisualMode checks that line-range selection is off, so space acts on the whole hunk.

func (*Assertion) ModeIs added in v0.1.2

func (a *Assertion) ModeIs(mode OperatingMode)

ModeIs checks the operating mode, which is fixed at construction and never changes at runtime.

func (*Assertion) NoModalsVisible added in v0.1.2

func (a *Assertion) NoModalsVisible()

NoModalsVisible checks every overlay and the file-list filter at once, reporting each one that is up.

func (*Assertion) SearchIsNotVisible added in v0.1.2

func (a *Assertion) SearchIsNotVisible()

SearchIsNotVisible checks that the search prompt is down.

func (*Assertion) SearchIsVisible added in v0.1.2

func (a *Assertion) SearchIsVisible()

SearchIsVisible checks that the search prompt is up, which is separate from the file-list filter.

func (*Assertion) SplitAssignIsVisible added in v0.2.1

func (a *Assertion) SplitAssignIsVisible()

SplitAssignIsVisible checks that the multi-way split's destination assignment modal is up.

type DestinationSpec

type DestinationSpec struct {
	ChangeID    string
	Description string
	Type        DestinationType
}

DestinationSpec is where one tag's hunks land. ChangeID is empty for DestNewCommit, where Description becomes the message of the commit that gets created.

type DestinationType

type DestinationType int

DestinationType separates a split target that already exists from one that will be created.

const (
	DestExistingRevision DestinationType = iota
	DestNewCommit
)

Destinations a tag can be sent to. DestExistingRevision is the zero value, so a DestinationSpec that was never filled in reads as targeting an existing revision.

type FileSelection

type FileSelection struct {
	Hunks map[int]*HunkSelection
}

FileSelection holds one file's selected hunks, keyed by the hunk's index in the parsed file. The indices go stale when the diff is reloaded.

type FocusedPanel

type FocusedPanel int

FocusedPanel is which of the two panes takes navigation keys.

const (
	PanelFileList FocusedPanel = iota
	PanelDiffView
)

The two panes Tab switches between. PanelFileList is the zero value, so a fresh model starts on the file list.

type HunkSelection

type HunkSelection struct {
	SelectedLines map[int]bool
	WholeHunk     bool
}

HunkSelection is one hunk's selection. WholeHunk wins over SelectedLines, and selecting the whole hunk discards the per-line set, so the two are never both meaningful.

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model is the whole application state, held by pointer: handlers mutate the receiver in place and return only a command. A tea.Cmd runs off the Update loop, so a command factory must snapshot the state it needs rather than read the model when the command runs.

func NewModel

func NewModel(
	client *jj.Client,
	source, destination string,
	mode OperatingMode,
	cfg config.Config,
) (Model, error)

NewModel builds a model reading its diff from a jj revision.

func NewModelWithSource

func NewModelWithSource(
	source diff.Source,
	client *jj.Client,
	destination string,
	mode OperatingMode,
	cfg config.Config,
) (Model, error)

NewModelWithSource builds a model over any diff source, which is how diff-editor mode supplies two directories instead of a revision. The client is still needed for the write paths and may only be nil when nothing will be applied.

func NewTestModel

func NewTestModel(t *testing.T, mode OperatingMode) *Model

NewTestModel builds a Model backed by a jj client rooted at a fresh temp dir.

func Update

func Update(t *testing.T, m *Model, msg tea.Msg) *Model

Update runs one Update round and asserts the result is still a Model, discarding the command.

func (*Model) Init

func (m *Model) Init() tea.Cmd

Init starts the first diff load. Nothing is rendered until it returns and a window size arrives.

func (*Model) Update

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update handles one message in place and returns the receiver, so the concrete type is always *Model and callers chaining updates can assert it.

func (*Model) View

func (m *Model) View() tea.View

View wraps the rendered content in the alternate screen buffer, which the program stays in for its whole run.

func (*Model) WithChanges

func (m *Model) WithChanges(changes []diff.FileChange) *Model

WithChanges loads changes and selects the first file, mirroring what a real diff load does.

func (*Model) WithDestination

func (m *Model) WithDestination(dest string) *Model

WithDestination sets the move destination without going through the picker.

type MultiSplitState

type MultiSplitState struct {
	Selections   map[SplitTag]*SelectionState
	Destinations map[SplitTag]*DestinationSpec
	CurrentTag   SplitTag
	Active       bool
}

MultiSplitState is an in-progress multi-way split: one selection and one destination per tag. A tag with a selection but no destination is incomplete and blocks the split from being applied.

func NewMultiSplitState

func NewMultiSplitState() *MultiSplitState

NewMultiSplitState returns an inactive split with 'A' as the current tag.

type OperatingMode

type OperatingMode int

OperatingMode is which of the three entry points the app was started for, which decides what the selection means and what applying it does.

const (
	ModeBrowse OperatingMode = iota
	ModeInteractive
	ModeDiffEditor
)

The three ways the app is launched. ModeBrowse is read-only, ModeInteractive moves selected hunks into another revision, and ModeDiffEditor writes the selection back into the trees jj passed.

type SelectionState

type SelectionState struct {
	Files map[string]*FileSelection
}

SelectionState is what the user has picked across every file, keyed by the diff's path. Build it with NewSelectionState, because the mutators assume the map exists.

func NewSelectionState

func NewSelectionState() *SelectionState

NewSelectionState returns an empty selection.

func (*SelectionState) HasPartialSelection

func (s *SelectionState) HasPartialSelection(filePath string, hunkIdx int) bool

HasPartialSelection reports whether a hunk has lines picked without being selected as a whole, which is what the renderer draws the partial marker for.

func (*SelectionState) IsHunkSelected

func (s *SelectionState) IsHunkSelected(filePath string, hunkIdx int) bool

IsHunkSelected reports whether the whole hunk is selected, which is false for a hunk that only has individual lines picked.

func (*SelectionState) IsLineSelected

func (s *SelectionState) IsLineSelected(filePath string, hunkIdx, lineIdx int) bool

IsLineSelected reports whether one line is selected, which is true for every line of a hunk selected as a whole.

func (*SelectionState) SelectLineRange

func (s *SelectionState) SelectLineRange(filePath string, hunkIdx, startLine, endLine int)

SelectLineRange selects an inclusive range of lines, accepting the bounds in either order. It clears whole-hunk selection, and it only adds, so lines already selected outside the range stay.

func (*SelectionState) ToggleHunk

func (s *SelectionState) ToggleHunk(filePath string, hunkIdx int)

ToggleHunk flips whole-hunk selection, creating the file and hunk entries as needed. Selecting a hunk discards any lines picked inside it, so a toggle out and back in loses the line selection.

func (*SelectionState) ToggleLine

func (s *SelectionState) ToggleLine(filePath string, hunkIdx, lineIdx int)

ToggleLine flips one line's selection. It does nothing while the hunk is selected as a whole, because that state has no per-line detail to change.

type SplitTag

type SplitTag rune

SplitTag is the single character a hunk carries while a multi-way split is being assembled. Tags are handed out from 'A' upward.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL