summaryrefslogtreecommitdiffstats
path: root/pkg/gui/services
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2022-02-24 13:29:48 +1100
committerJesse Duffield <jessedduffield@gmail.com>2022-03-17 19:13:40 +1100
commitef7c4c9ca93ec15db4886d8d6f78c85a1db7edef (patch)
tree57c2cff341b03d12b69ac57226376563e9089f75 /pkg/gui/services
parent952a4f3f2388da4ab88005b02f264aca0172afe7 (diff)
refactor custom commands
more custom command refactoring
Diffstat (limited to 'pkg/gui/services')
-rw-r--r--pkg/gui/services/custom_commands/client.go52
-rw-r--r--pkg/gui/services/custom_commands/handler_creator.go187
-rw-r--r--pkg/gui/services/custom_commands/keybinding_creator.go91
-rw-r--r--pkg/gui/services/custom_commands/menu_generator.go138
-rw-r--r--pkg/gui/services/custom_commands/menu_generator_test.go65
-rw-r--r--pkg/gui/services/custom_commands/resolver.go98
-rw-r--r--pkg/gui/services/custom_commands/session_state_loader.go56
7 files changed, 687 insertions, 0 deletions
diff --git a/pkg/gui/services/custom_commands/client.go b/pkg/gui/services/custom_commands/client.go
new file mode 100644
index 000000000..fc36405c1
--- /dev/null
+++ b/pkg/gui/services/custom_commands/client.go
@@ -0,0 +1,52 @@
+package custom_commands
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+// Client is the entry point to this package. It reutrns a list of keybindings based on the config's user-defined custom commands.
+// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md for more info.
+type Client struct {
+ customCommands []config.CustomCommand
+ handlerCreator *HandlerCreator
+ keybindingCreator *KeybindingCreator
+}
+
+func NewClient(
+ c *types.HelperCommon,
+ os *oscommands.OSCommand,
+ git *commands.GitCommand,
+ contexts *context.ContextTree,
+ helpers *helpers.Helpers,
+ getKey func(string) interface{},
+) *Client {
+ sessionStateLoader := NewSessionStateLoader(contexts, helpers)
+ handlerCreator := NewHandlerCreator(c, os, git, sessionStateLoader)
+ keybindingCreator := NewKeybindingCreator(contexts, getKey)
+ customCommands := c.UserConfig.CustomCommands
+
+ return &Client{
+ customCommands: customCommands,
+ keybindingCreator: keybindingCreator,
+ handlerCreator: handlerCreator,
+ }
+}
+
+func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) {
+ bindings := []*types.Binding{}
+ for _, customCommand := range self.customCommands {
+ handler := self.handlerCreator.call(customCommand)
+ binding, err := self.keybindingCreator.call(customCommand, handler)
+ if err != nil {
+ return nil, err
+ }
+ bindings = append(bindings, binding)
+ }
+
+ return bindings, nil
+}
diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go
new file mode 100644
index 000000000..04e6cb644
--- /dev/null
+++ b/pkg/gui/services/custom_commands/handler_creator.go
@@ -0,0 +1,187 @@
+package custom_commands
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/style"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+// takes a custom command and returns a function that will be called when the corresponding user-defined keybinding is pressed
+type HandlerCreator struct {
+ c *types.HelperCommon
+ os *oscommands.OSCommand
+ git *commands.GitCommand
+ sessionStateLoader *SessionStateLoader
+ resolver *Resolver
+ menuGenerator *MenuGenerator
+}
+
+func NewHandlerCreator(
+ c *types.HelperCommon,
+ os *oscommands.OSCommand,
+ git *commands.GitCommand,
+ sessionStateLoader *SessionStateLoader,
+) *HandlerCreator {
+ resolver := NewResolver(c.Common)
+ menuGenerator := NewMenuGenerator(c.Common)
+
+ return &HandlerCreator{
+ c: c,
+ os: os,
+ git: git,
+ sessionStateLoader: sessionStateLoader,
+ resolver: resolver,
+ menuGenerator: menuGenerator,
+ }
+}
+
+func (self *HandlerCreator) call(customCommand config.CustomCommand) func() error {
+ return func() error {
+ sessionState := self.sessionStateLoader.call()
+ promptResponses := make([]string, len(customCommand.Prompts))
+
+ f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses) }
+
+ // if we have prompts we'll recursively wrap our confirm handlers with more prompts
+ // until we reach the actual command
+ for reverseIdx := range customCommand.Prompts {
+ // reassigning so that we don't end up with an infinite recursion
+ g := f
+ idx := len(customCommand.Prompts) - 1 - reverseIdx
+
+ // going backwards so the outermost prompt is the first one
+ prompt := customCommand.Prompts[idx]
+
+ wrappedF := func(response string) error {
+ promptResponses[idx] = response
+ return g()
+ }
+
+ resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
+ resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ switch prompt.Type {
+ case "input":
+ f = func() error {
+ return self.inputPrompt(resolvedPrompt, wrappedF)
+ }
+ case "menu":
+ f = func() error {
+ return self.menuPrompt(resolvedPrompt, wrappedF)
+ }
+ case "menuFromCommand":
+ f = func() error {
+ return self.menuPromptFromCommand(resolvedPrompt, wrappedF)
+ }
+ default:
+ return self.c.ErrorMsg("custom command prompt must have a type of 'input', 'menu' or 'menuFromCommand'")
+ }
+ }
+
+ return f()
+ }
+}
+
+func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error {
+ return self.c.Prompt(types.PromptOpts{
+ Title: prompt.Title,
+ InitialContent: prompt.InitialValue,
+ HandleConfirm: func(str string) error {
+ return wrappedF(str)
+ },
+ })
+}
+
+func (self *HandlerCreator) menuPrompt(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error {
+ menuItems := make([]*types.MenuItem, len(prompt.Options))
+ for i, option := range prompt.Options {
+ option := option
+ menuItems[i] = &types.MenuItem{
+ DisplayStrings: []string{option.Name, style.FgYellow.Sprint(option.Description)},
+ OnPress: func() error {
+ return wrappedF(option.Value)
+ },
+ }
+ }
+
+ return self.c.Menu(types.CreateMenuOptions{Title: prompt.Title, Items: menuItems})
+}
+
+func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error {
+ // Run and save output
+ message, err := self.git.Custom.RunWithOutput(prompt.Command)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ // Need to make a menu out of what the cmd has displayed
+ candidates, err := self.menuGenerator.call(message, prompt.Filter, prompt.ValueFormat, prompt.LabelFormat)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ menuItems := make([]*types.MenuItem, len(candidates))
+ for i := range candidates {
+ i := i
+ menuItems[i] = &types.MenuItem{
+ DisplayStrings: []string{candidates[i].label},
+ OnPress: func() error {
+ return wrappedF(candidates[i].value)
+ },
+ }
+ }
+
+ return self.c.Menu(types.CreateMenuOptions{Title: prompt.Title, Items: menuItems})
+}
+
+type CustomCommandObjects struct {
+ *SessionState
+ PromptResponses []string
+}
+
+func (self *HandlerCreator) getResolveTemplateFn(promptResponses []string, sessionState *SessionState) func(string) (string, error) {
+ objects := CustomCommandObjects{
+ SessionState: sessionState,
+ PromptResponses: promptResponses,
+ }
+
+ return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects) }
+}
+
+func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string) error {
+ resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
+ cmdStr, err := resolveTemplate(customCommand.Command)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ cmdObj := self.os.Cmd.NewShell(cmdStr)
+
+ if customCommand.Subprocess {
+ return self.c.RunSubprocessAndRefresh(cmdObj)
+ }
+
+ loadingText := customCommand.LoadingText
+ if loadingText == "" {
+ loadingText = self.c.Tr.LcRunningCustomCommandStatus
+ }
+
+ return self.c.WithWaitingStatus(loadingText, func() error {
+ self.c.LogAction(self.c.Tr.Actions.CustomCommand)
+
+ if customCommand.Stream {
+ cmdObj.StreamOutput()
+ }
+ err := cmdObj.Run()
+ if err != nil {
+ return self.c.Error(err)
+ }
+ return self.c.Refresh(types.RefreshOptions{})
+ })
+}
diff --git a/pkg/gui/services/custom_commands/keybinding_creator.go b/pkg/gui/services/custom_commands/keybinding_creator.go
new file mode 100644
index 000000000..e3c233951
--- /dev/null
+++ b/pkg/gui/services/custom_commands/keybinding_creator.go
@@ -0,0 +1,91 @@
+package custom_commands
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+// KeybindingCreator takes a custom command along with its handler and returns a corresponding keybinding
+type KeybindingCreator struct {
+ contexts *context.ContextTree
+ getKey func(string) interface{}
+}
+
+func NewKeybindingCreator(contexts *context.ContextTree, getKey func(string) interface{}) *KeybindingCreator {
+ return &KeybindingCreator{
+ contexts: contexts,
+ getKey: getKey,
+ }
+}
+
+func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler func() error) (*types.Binding, error) {
+ if customCommand.Context == "" {
+ return nil, formatContextNotProvidedError(customCommand)
+ }
+
+ viewName, contexts, err := self.getViewNameAndContexts(customCommand)
+ if err != nil {
+ return nil, err
+ }
+
+ description := customCommand.Description
+ if description == "" {
+ description = customCommand.Command
+ }
+
+ return &types.Binding{
+ ViewName: viewName,
+ Contexts: contexts,
+ Key: self.getKey(customCommand.Key),
+ Modifier: gocui.ModNone,
+ Handler: handler,
+ Description: description,
+ }, nil
+}
+
+func (self *KeybindingCreator) getViewNameAndContexts(customCommand config.CustomCommand) (string, []string, error) {
+ if customCommand.Context == "global" {
+ return "", nil, nil
+ }
+
+ ctx, ok := self.contextForContextKey(types.ContextKey(customCommand.Context))
+ if !ok {
+ return "", nil, formatUnknownContextError(customCommand)
+ }
+
+ // here we assume that a given context will always belong to the same view.
+ // Currently this is a safe bet but it's by no means guaranteed in the long term
+ // and we might need to make some changes in the future to support it.
+ viewName := ctx.GetViewName()
+ contexts := []string{customCommand.Context}
+ return viewName, contexts, nil
+}
+
+func (self *KeybindingCreator) contextForContextKey(contextKey types.ContextKey) (types.Context, bool) {
+ for _, context := range self.contexts.Flatten() {
+ if context.GetKey() == contextKey {
+ return context, true
+ }
+ }
+
+ return nil, false
+}
+
+func formatUnknownContextError(customCommand config.CustomCommand) error {
+ // stupid golang making me build an array of strings for this.
+ allContextKeyStrings := make([]string, len(context.AllContextKeys))
+ for i := range context.AllContextKeys {
+ allContextKeyStrings[i] = string(context.AllContextKeys[i])
+ }
+
+ return fmt.Errorf("Error when setting custom command keybindings: unknown context: %s. Key: %s, Command: %s.\nPermitted contexts: %s", customCommand.Context, customCommand.Key, customCommand.Command, strings.Join(allContextKeyStrings, ", "))
+}
+
+func formatContextNotProvidedError(customCommand config.CustomCommand) error {
+ return fmt.Errorf("Error parsing custom command keybindings: context not provided (use context: 'global' for the global context). Key: %s, Command: %s", customCommand.Key, customCommand.Command)
+}
diff --git a/pkg/gui/services/custom_commands/menu_generator.go b/pkg/gui/services/custom_commands/menu_generator.go
new file mode 100644
index 000000000..5bec1db91
--- /dev/null
+++ b/pkg/gui/services/custom_commands/menu_generator.go
@@ -0,0 +1,138 @@
+package custom_commands
+
+import (
+ "bytes"
+ "errors"
+ "regexp"
+ "strconv"
+ "strings"
+ "text/template"
+
+ "github.com/jesseduffield/lazygit/pkg/common"
+ "github.com/jesseduffield/lazygit/pkg/gui/style"
+)
+
+type MenuGenerator struct {
+ c *common.Common
+}
+
+// takes the output of a command and returns a list of menu entries based on a filter
+// and value/label format templates provided by the user
+func NewMenuGenerator(c *common.Common) *MenuGenerator {
+ return &MenuGenerator{c: c}
+}
+
+type commandMenuEntry struct {
+ label string
+ value string
+}
+
+func (self *MenuGenerator) call(commandOutput, filter, valueFormat, labelFormat string) ([]*commandMenuEntry, error) {
+ regex, err := regexp.Compile(filter)
+ if err != nil {
+ return nil, errors.New("unable to parse filter regex, error: " + err.Error())
+ }
+
+ valueTemplateAux, err := template.New("format").Parse(valueFormat)
+ if err != nil {
+ return nil, errors.New("unable to parse value format, error: " + err.Error())
+ }
+ valueTemplate := NewTrimmerTemplate(valueTemplateAux)
+
+ var labelTemplate *TrimmerTemplate
+ if labelFormat != "" {
+ colorFuncMap := style.TemplateFuncMapAddColors(template.FuncMap{})
+ labelTemplateAux, err := template.New("format").Funcs(colorFuncMap).Parse(labelFormat)
+ if err != nil {
+ return nil, errors.New("unable to parse label format, error: " + err.Error())
+ }
+ labelTemplate = NewTrimmerTemplate(labelTemplateAux)
+ } else {
+ labelTemplate = valueTemplate
+ }
+
+ candidates := []*commandMenuEntry{}
+ for _, line := range strings.Split(commandOutput, "\n") {
+ if line == "" {
+ continue
+ }
+
+ candidate, err := self.generateMenuCandidate(
+ line,
+ regex,
+ valueTemplate,
+ labelTemplate,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ candidates = append(candidates, candidate)
+ }
+
+ return candidates, err
+}
+
+func (self *MenuGenerator) generateMenuCandidate(
+ line string,
+ regex *regexp.Regexp,
+ valueTemplate *TrimmerTemplate,
+ labelTemplate *TrimmerTemplate,
+) (*commandMenuEntry, error) {
+ tmplData := self.parseLine(line, regex)
+
+ entry := &commandMenuEntry{}
+
+ var err error
+ entry.value, err = valueTemplate.execute(tmplData)
+ if err != nil {
+ return nil, err
+ }
+
+ entry.label, err = labelTemplate.execute(tmplData)
+ if err != nil {
+ return nil, err
+ }
+
+ return entry, nil
+}
+
+func (self *MenuGenerator) parseLine(line string, regex *regexp.Regexp) map[string]string {
+ tmplData := map[string]string{}
+ out := regex.FindAllStringSubmatch(line, -1)
+ if len(out) > 0 {
+ for groupIdx, group := range regex.SubexpNames() {
+ // Record matched group with group ids
+ matchName := "group_" + strconv.Itoa(groupIdx)
+ tmplData[matchName] = out[0][groupIdx]
+ // Record last named group non-empty matches as group matches
+ if group != "" {
+ tmplData[group] = out[0][groupIdx]
+ }
+ }
+ }
+
+ return tmplData
+}
+
+// wrapper around a template which trims the output
+type TrimmerTemplate struct {
+ template *template.Template
+ buffer *bytes.Buffer
+}
+
+func NewTrimmerTemplate(template *template.Template) *TrimmerTemplate {
+ return &TrimmerTemplate{
+ template: template,
+ buffer: bytes.NewBuffer(nil),
+ }
+}
+
+func (self *TrimmerTemplate) execute(tmplData map[string]string) (string, error) {
+ self.buffer.Reset()
+ err := self.template.Execute(self.buffer, tmplData)
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(self.buffer.String()), nil
+}
diff --git a/pkg/gui/services/custom_commands/menu_generator_test.go b/pkg/gui/services/custom_commands/menu_generator_test.go
new file mode 100644
index 000000000..7dd3e58e8
--- /dev/null
+++ b/pkg/gui/services/custom_commands/menu_generator_test.go
@@ -0,0 +1,65 @@
+package custom_commands
+
+import (
+ "testing"
+
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMenuGenerator(t *testing.T) {
+ type scenario struct {
+ testName string
+ cmdOut string
+ filter string
+ valueFormat string
+ labelFormat string
+ test func([]*commandMenuEntry, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "Extract remote branch name",
+ "upstream/pr-1",
+ "(?P<remote>[a-z_]+)/(?P<branch>.*)",
+ "{{ .branch }}",
+ "Remote: {{ .remote }}",
+ func(actualEntry []*commandMenuEntry, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "pr-1", actualEntry[0].value)
+ assert.EqualValues(t, "Remote: upstream", actualEntry[0].label)
+ },
+ },
+ {
+ "Multiple named groups with empty labelFormat",
+ "upstream/pr-1",
+ "(?P<remote>[a-z]*)/(?P<branch>.*)",
+ "{{ .branch }}|{{ .remote }}",
+ "",
+ func(actualEntry []*commandMenuEntry, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value)
+ assert.EqualValues(t, "pr-1|upstream", actualEntry[0].label)
+ },
+ },
+ {
+ "Multiple named groups with group ids",
+ "upstream/pr-1",
+ "(?P<remote>[a-z]*)/(?P<branch>.*)",
+ "{{ .group_2 }}|{{ .group_1 }}",
+ "Remote: {{ .group_1 }}",
+ func(actualEntry []*commandMenuEntry, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "pr-1|upstream", actualEntry[0].value)
+ assert.EqualValues(t, "Remote: upstream", actualEntry[0].label)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ s := s
+ t.Run(s.testName, func(t *testing.T) {
+ s.test(NewMenuGenerator(utils.NewDummyCommon()).call(s.cmdOut, s.filter, s.valueFormat, s.labelFormat))
+ })
+ }
+}
diff --git a/pkg/gui/services/custom_commands/resolver.go b/pkg/gui/services/custom_commands/resolver.go
new file mode 100644
index 000000000..ee965e5cd
--- /dev/null
+++ b/pkg/gui/services/custom_commands/resolver.go
@@ -0,0 +1,98 @@
+package custom_commands
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/common"
+ "github.com/jesseduffield/lazygit/pkg/config"
+)
+
+// takes a prompt that is defined in terms of template strings and resolves the templates to contain actual values
+type Resolver struct {
+ c *common.Common
+}
+
+func NewResolver(c *common.Common) *Resolver {
+ return &Resolver{c: c}
+}
+
+func (self *Resolver) resolvePrompt(
+ prompt *config.CustomCommandPrompt,
+ resolveTemplate func(string) (string, error),
+) (*config.CustomCommandPrompt, error) {
+ var err error
+ result := &config.CustomCommandPrompt{
+ ValueFormat: prompt.ValueFormat,
+ LabelFormat: prompt.LabelFormat,
+ }
+
+ result.Title, err = resolveTemplate(prompt.Title)
+ if err != nil {
+ return nil, err
+ }
+
+ result.InitialValue, err = resolveTemplate(prompt.InitialValue)
+ if err != nil {
+ return nil, err
+ }
+
+ result.Command, err = resolveTemplate(prompt.Command)
+ if err != nil {
+ return nil, err
+ }
+
+ result.Filter, err = resolveTemplate(prompt.Filter)
+ if err != nil {
+ return nil, err
+ }
+
+ if prompt.Type == "menu" {
+ result.Options, err = self.resolveMenuOptions(prompt, resolveTemplate)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return result, nil
+}
+
+func (self *Resolver) resolveMenuOptions(prompt *config.CustomCommandPrompt, resolveTemplate func(string) (string, error)) ([]config.CustomCommandMenuOption, error) {
+ newOptions := make([]config.CustomCommandMenuOption, 0, len(prompt.Options))
+ for _, option := range prompt.Options {
+ option := option
+ newOption, err := self.resolveMenuOption(&option, resolveTemplate)
+ if err != nil {
+ return nil, err
+ }
+ newOptions = append(newOptions, *newOption)
+ }
+
+ return newOptions, nil
+}
+
+func (self *Resolver) resolveMenuOption(option *config.CustomCommandMenuOption, resolveTemplate func(string) (string, error)) (*config.CustomCommandMenuOption, error) {
+ nameTemplate := option.Name
+ if nameTemplate == "" {
+ // this allows you to only pass values rather than bother with names/descriptions
+ nameTemplate = option.Value
+ }
+
+ name, err := resolveTemplate(nameTemplate)
+ if err != nil {
+ return nil, err
+ }
+
+ description, err := resolveTemplate(option.Description)
+ if err != nil {
+ return nil, err
+ }
+
+ value, err := resolveTemplate(option.Value)
+ if err != nil {
+ return nil, err
+ }
+
+ return &config.CustomCommandMenuOption{
+ Name: name,
+ Description: description,
+ Value: value,
+ }, nil
+}
diff --git a/pkg/gui/services/custom_commands/session_state_loader.go b/pkg/gui/services/custom_commands/session_state_loader.go
new file mode 100644
index 000000000..42f3403ec
--- /dev/null
+++ b/pkg/gui/services/custom_commands/session_state_loader.go
@@ -0,0 +1,56 @@
+package custom_commands
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
+)
+
+// loads the session state at the time that a custom command is invoked, for use
+// in the custom command's template strings
+type SessionStateLoader struct {
+ contexts *context.ContextTree
+ helpers *helpers.Helpers
+}
+
+func NewSessionStateLoader(contexts *context.ContextTree, helpers *helpers.Helpers) *SessionStateLoader {
+ return &SessionStateLoader{
+ contexts: contexts,
+ helpers: helpers,
+ }
+}
+
+// SessionState captures the current state of the application for use in custom commands
+type SessionState struct {
+ SelectedLocalCommit *models.Commit
+ SelectedReflogCommit *models.Commit
+ SelectedSubCommit *models.Commit
+ SelectedFile *models.File
+ SelectedPath string
+ SelectedLocalBranch *models.Branch
+ SelectedRemoteBranch *models.RemoteBranch
+ SelectedRemote *models.Remote
+ SelectedTag *models.Tag
+ SelectedStashEntry *models.StashEntry
+ SelectedCommitFile *models.CommitFile
+ SelectedCommitFilePath string
+ CheckedOutBranch *models.Branch
+}
+
+func (self *SessionStateLoader) call() *SessionState {
+ return &SessionState{
+ SelectedFile: self.contexts.Files.GetSelectedFile(),
+ SelectedPath: self.contexts.Files.GetSelectedPath(),
+ SelectedLocalCommit: self.contexts.LocalCommits.GetSelected(),
+ SelectedReflogCommit: self.contexts.ReflogCommits.GetSelected(),
+ SelectedLocalBranch: self.contexts.Branches.GetSelected(),
+ SelectedRemoteBranch: self.contexts.RemoteBranches.GetSelected(),
+ SelectedRemote: self.contexts.Remotes.GetSelected(),
+ SelectedTag: self.contexts.Tags.GetSelected(),
+ SelectedStashEntry: self.contexts.Stash.GetSelected(),
+ SelectedCommitFile: self.contexts.CommitFiles.GetSelectedFile(),
+ SelectedCommitFilePath: self.contexts.CommitFiles.GetSelectedPath(),
+ SelectedSubCommit: self.contexts.SubCommits.GetSelected(),
+ CheckedOutBranch: self.helpers.Refs.GetCheckedOutRef(),
+ }
+}