Skip to content

Commit f9c8df8

Browse files
committed
Added evalkeys command
1 parent 227d870 commit f9c8df8

8 files changed

+189
-41
lines changed

cmd/grv/config.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,16 @@ type Configuration struct {
322322
channels Channels
323323
variables GRVVariableGetter
324324
customCommands map[string]string
325+
inputConsumer InputConsumer
325326
}
326327

327328
// NewConfiguration creates a Configuration instance with default values
328-
func NewConfiguration(keyBindings KeyBindings, channels Channels, variables GRVVariableGetter) *Configuration {
329+
func NewConfiguration(keyBindings KeyBindings, channels Channels, variables GRVVariableGetter, inputConsumer InputConsumer) *Configuration {
329330
config := &Configuration{
330331
keyBindings: keyBindings,
331332
channels: channels,
332333
variables: variables,
334+
inputConsumer: inputConsumer,
333335
customCommands: map[string]string{},
334336
themes: map[string]MutableTheme{
335337
cfClassicThemeName: NewClassicTheme(),
@@ -534,6 +536,8 @@ func (config *Configuration) processCommand(command ConfigCommand, inputSource s
534536
err = config.processUndefCommand(command, inputSource)
535537
case *CustomCommand:
536538
err = config.processCustomCommand(command)
539+
case *EvalKeysCommand:
540+
err = config.processEvalKeysCommand(command)
537541
default:
538542
log.Errorf("Unknown command type %T", command)
539543
}
@@ -948,6 +952,12 @@ func (config *Configuration) processConfigCommandBody(commandBody string, args [
948952
return processedCommandBody.String()
949953
}
950954

955+
func (config *Configuration) processEvalKeysCommand(evalKeysCommand *EvalKeysCommand) (err error) {
956+
log.Debugf("Processing keys: %v", evalKeysCommand.keys)
957+
config.inputConsumer.ProcessInput(evalKeysCommand.keys)
958+
return
959+
}
960+
951961
func (config *Configuration) runCommand(command string, outputType ShellCommandOutputType) {
952962
NewShellCommandProcessor(config.channels, config.variables, command, outputType).Execute()
953963
}

cmd/grv/config_command_help.go

+23
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,26 @@ func GenerateUndefCommandHelpSections(config Config) (helpSections []*HelpSectio
453453
},
454454
}
455455
}
456+
457+
// GenerateEvalKeysCommandHelpSections generates help documentation for the addtab command
458+
func GenerateEvalKeysCommandHelpSections(config Config) (helpSections []*HelpSection) {
459+
description := []HelpSectionText{
460+
{text: "evalkeys", themeComponentID: CmpHelpViewSectionSubTitle},
461+
{},
462+
{text: "The evalkeys command executes the provided key string sequence."},
463+
{text: "The format of the command is:"},
464+
{},
465+
{text: "evalkeys keys", themeComponentID: CmpHelpViewSectionCodeBlock},
466+
{},
467+
{text: "For example, running the following will switch to the next tab:"},
468+
{},
469+
{text: "evalkeys <grv-next-tab>", themeComponentID: CmpHelpViewSectionCodeBlock},
470+
{},
471+
}
472+
473+
return []*HelpSection{
474+
{
475+
description: description,
476+
},
477+
}
478+
}

cmd/grv/config_parse.go

+74-25
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"io"
88
"regexp"
9+
"strings"
10+
"unicode"
911

1012
slice "github.com/bradfitz/slice"
1113
)
@@ -27,6 +29,7 @@ const (
2729
helpCommand = "help"
2830
defCommand = "def"
2931
undefCommand = "undef"
32+
evalkeysCommand = "evalkeys"
3033
)
3134

3235
const (
@@ -35,6 +38,9 @@ const (
3538
)
3639

3740
var isIdentifier = regexp.MustCompile(`[[:alnum:]]+`).MatchString
41+
var commentTokens = map[ConfigTokenType]bool{
42+
CtkComment: true,
43+
}
3844
var whiteSpaceTokens = map[ConfigTokenType]bool{
3945
CtkWhiteSpace: true,
4046
CtkComment: true,
@@ -166,6 +172,13 @@ type CustomCommand struct {
166172

167173
func (customCommand *CustomCommand) configCommand() {}
168174

175+
// EvalKeysCommand represents a key evaluation command
176+
type EvalKeysCommand struct {
177+
keys string
178+
}
179+
180+
func (evalKeysCommand *EvalKeysCommand) configCommand() {}
181+
169182
type commandHelpGenerator func(config Config) []*HelpSection
170183
type commandCustomParser func(parser *ConfigParser) (tokens []*ConfigToken, err error)
171184

@@ -184,7 +197,7 @@ func DefineCustomCommand(commandName string) (err error) {
184197
}
185198

186199
commandDescriptors[commandName] = &commandDescriptor{
187-
customParser: parseVarArgsCommand,
200+
customParser: parseVarArgsCommand(),
188201
constructor: customCommandConstructor,
189202
userDefined: true,
190203
}
@@ -238,32 +251,32 @@ var commandDescriptors = map[string]*commandDescriptor{
238251
commandHelpGenerator: GenerateRmTabCommandHelpSections,
239252
},
240253
addviewCommand: {
241-
customParser: parseVarArgsCommand,
254+
customParser: parseVarArgsCommand(),
242255
constructor: addViewCommandConstructor,
243256
commandHelpGenerator: GenerateAddViewCommandHelpSections,
244257
},
245258
vsplitCommand: {
246-
customParser: parseVarArgsCommand,
259+
customParser: parseVarArgsCommand(),
247260
constructor: splitViewCommandConstructor,
248261
commandHelpGenerator: GenerateVSplitCommandHelpSections,
249262
},
250263
hsplitCommand: {
251-
customParser: parseVarArgsCommand,
264+
customParser: parseVarArgsCommand(),
252265
constructor: splitViewCommandConstructor,
253266
commandHelpGenerator: GenerateHSplitCommandHelpSections,
254267
},
255268
splitCommand: {
256-
customParser: parseVarArgsCommand,
269+
customParser: parseVarArgsCommand(),
257270
constructor: splitViewCommandConstructor,
258271
commandHelpGenerator: GenerateSplitCommandHelpSections,
259272
},
260273
gitCommand: {
261-
customParser: parseVarArgsCommand,
274+
customParser: parseVarArgsCommand(),
262275
constructor: gitCommandConstructor,
263276
commandHelpGenerator: GenerateGitCommandHelpSections,
264277
},
265278
gitInteractiveCommand: {
266-
customParser: parseVarArgsCommand,
279+
customParser: parseVarArgsCommand(),
267280
constructor: gitCommandConstructor,
268281
commandHelpGenerator: GenerateGitiCommandHelpSections,
269282
},
@@ -281,6 +294,11 @@ var commandDescriptors = map[string]*commandDescriptor{
281294
constructor: undefCommandConstructor,
282295
commandHelpGenerator: GenerateUndefCommandHelpSections,
283296
},
297+
evalkeysCommand: {
298+
customParser: parseVarArgsParserGenerator(false),
299+
constructor: evalKeysCommandConstructor,
300+
commandHelpGenerator: GenerateEvalKeysCommandHelpSections,
301+
},
284302
}
285303

286304
// GenerateConfigCommandHelpSections generates help documentation for all configuration commands
@@ -481,28 +499,41 @@ func (parser *ConfigParser) parseCommand(commandToken *ConfigToken) (command Con
481499
return
482500
}
483501

484-
func parseVarArgsCommand(parser *ConfigParser) (tokens []*ConfigToken, err error) {
485-
OuterLoop:
486-
for {
487-
var token *ConfigToken
488-
token, err = parser.scan()
502+
func parseVarArgsCommand() commandCustomParser {
503+
return parseVarArgsParserGenerator(true)
504+
}
489505

490-
switch {
491-
case err != nil:
492-
return
493-
case token.err != nil:
494-
err = parser.generateParseError(token, "Syntax Error")
495-
return
496-
case token.tokenType == CtkEOF:
497-
break OuterLoop
498-
case token.tokenType == CtkTerminator:
499-
break OuterLoop
506+
func parseVarArgsParserGenerator(ignoreWhitespace bool) commandCustomParser {
507+
var ignoreTokens map[ConfigTokenType]bool
508+
if ignoreWhitespace {
509+
ignoreTokens = whiteSpaceTokens
510+
} else {
511+
ignoreTokens = commentTokens
512+
}
513+
514+
return func(parser *ConfigParser) (tokens []*ConfigToken, err error) {
515+
OuterLoop:
516+
for {
517+
var token *ConfigToken
518+
token, err = parser.scanAndIgnore(ignoreTokens)
519+
520+
switch {
521+
case err != nil:
522+
return
523+
case token.err != nil:
524+
err = parser.generateParseError(token, "Syntax Error")
525+
return
526+
case token.tokenType == CtkEOF:
527+
break OuterLoop
528+
case token.tokenType == CtkTerminator:
529+
break OuterLoop
530+
}
531+
532+
tokens = append(tokens, token)
500533
}
501534

502-
tokens = append(tokens, token)
535+
return
503536
}
504-
505-
return
506537
}
507538

508539
func parseDefCommand(parser *ConfigParser) (tokens []*ConfigToken, err error) {
@@ -720,3 +751,21 @@ func customCommandConstructor(parser *ConfigParser, commandToken *ConfigToken, t
720751
args: args,
721752
}, nil
722753
}
754+
755+
func evalKeysCommandConstructor(parser *ConfigParser, commandToken *ConfigToken, tokens []*ConfigToken) (configCommand ConfigCommand, err error) {
756+
if len(tokens) == 0 {
757+
return nil, parser.generateParseError(commandToken, "No keys specified for %v command", evalkeysCommand)
758+
}
759+
760+
var buffer bytes.Buffer
761+
762+
for _, token := range tokens {
763+
buffer.WriteString(token.value)
764+
}
765+
766+
keys := strings.TrimLeftFunc(buffer.String(), unicode.IsSpace)
767+
768+
return &EvalKeysCommand{
769+
keys: keys,
770+
}, nil
771+
}

cmd/grv/config_parse_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,23 @@ func (customCommandValues *CustomCommandValues) Equal(command ConfigCommand) boo
292292
reflect.DeepEqual(customCommandValues.args, other.args)
293293
}
294294

295+
type EvalKeysCommandValues struct {
296+
keys string
297+
}
298+
299+
func (evalKeysCommandValues *EvalKeysCommandValues) Equal(command ConfigCommand) bool {
300+
if command == nil {
301+
return false
302+
}
303+
304+
other, ok := command.(*EvalKeysCommand)
305+
if !ok {
306+
return false
307+
}
308+
309+
return evalKeysCommandValues.keys == other.keys
310+
}
311+
295312
func TestParseSingleCommand(t *testing.T) {
296313
var singleCommandTests = []struct {
297314
input string
@@ -439,6 +456,12 @@ func TestParseSingleCommand(t *testing.T) {
439456
functionBody: " addtab \"}\" ",
440457
},
441458
},
459+
{
460+
input: "evalkeys <grv-next-tab><grv-search-prompt>Untracked files<Enter>",
461+
expectedCommand: &EvalKeysCommandValues{
462+
keys: "<grv-next-tab><grv-search-prompt>Untracked files<Enter>",
463+
},
464+
},
442465
}
443466

444467
for _, singleCommandTest := range singleCommandTests {

cmd/grv/config_test.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@ package main
22

33
import (
44
"testing"
5+
6+
"github.com/stretchr/testify/mock"
57
)
68

9+
type MockInputConsumer struct {
10+
mock.Mock
11+
}
12+
13+
func (inputConsumer *MockInputConsumer) ProcessInput(input string) {
14+
inputConsumer.Called(input)
15+
}
16+
717
func TestConfigVariablesHaveRequiredFieldsSet(t *testing.T) {
8-
config := NewConfiguration(&MockKeyBindings{}, &MockChannels{}, &MockGRVVariableSetter{})
18+
config := NewConfiguration(&MockKeyBindings{}, &MockChannels{}, &MockGRVVariableSetter{}, &MockInputConsumer{})
919

1020
for configVariableName, configVariable := range config.configVariables {
1121
if configVariable.defaultValue == nil {
@@ -40,7 +50,7 @@ func TestViewNamesContainsEntriesForAllViews(t *testing.T) {
4050
}
4151

4252
func TestCommandBodyArgumentsAreExpanded(t *testing.T) {
43-
config := NewConfiguration(&MockKeyBindings{}, &MockChannels{}, &MockGRVVariableSetter{})
53+
config := NewConfiguration(&MockKeyBindings{}, &MockChannels{}, &MockGRVVariableSetter{}, &MockInputConsumer{})
4454

4555
args := []string{"do", "re", "mi"}
4656

cmd/grv/documentation_generator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const (
1414

1515
// GenerateDocumentation generates grv markdown documentation
1616
func GenerateDocumentation() (err error) {
17-
helpSections, _, err := GenerateHelpView(NewConfiguration(NewKeyBindingManager(), nil, nil))
17+
helpSections, _, err := GenerateHelpView(NewConfiguration(NewKeyBindingManager(), nil, nil, nil))
1818
if err != nil {
1919
return
2020
}

cmd/grv/grv.go

+28-12
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ type gRVChannels struct {
3636
errorCh chan error
3737
}
3838

39-
func (grvChannels gRVChannels) Channels() Channels {
39+
func (grvChannels gRVChannels) Channels() *channels {
4040
return &channels{
41-
displayCh: grvChannels.displayCh,
42-
exitCh: grvChannels.exitCh,
43-
errorCh: grvChannels.errorCh,
44-
actionCh: grvChannels.actionCh,
45-
eventCh: grvChannels.eventCh,
41+
displayCh: grvChannels.displayCh,
42+
exitCh: grvChannels.exitCh,
43+
errorCh: grvChannels.errorCh,
44+
actionCh: grvChannels.actionCh,
45+
eventCh: grvChannels.eventCh,
46+
inputKeyCh: grvChannels.inputKeyCh,
4647
}
4748
}
4849

@@ -58,11 +59,17 @@ type Channels interface {
5859
}
5960

6061
type channels struct {
61-
displayCh chan<- bool
62-
exitCh <-chan bool
63-
errorCh chan<- error
64-
actionCh chan<- Action
65-
eventCh chan<- Event
62+
displayCh chan<- bool
63+
exitCh <-chan bool
64+
errorCh chan<- error
65+
actionCh chan<- Action
66+
eventCh chan<- Event
67+
inputKeyCh chan<- string
68+
}
69+
70+
// InputConsumer can consumer and process key string input
71+
type InputConsumer interface {
72+
ProcessInput(input string)
6673
}
6774

6875
// EventType identifies a type of event
@@ -162,6 +169,15 @@ func (channels *channels) ReportStatus(format string, args ...interface{}) {
162169
}
163170
}
164171

172+
// ProcessInput sends the provided input to be processed
173+
func (channels *channels) ProcessInput(input string) {
174+
select {
175+
case channels.inputKeyCh <- input:
176+
default:
177+
log.Errorf("Unable to add input \"%v\" to input channel", input)
178+
}
179+
}
180+
165181
// NewGRV creates a new instace of GRV
166182
func NewGRV(readOnly bool) *GRV {
167183
grvChannels := gRVChannels{
@@ -176,7 +192,7 @@ func NewGRV(readOnly bool) *GRV {
176192
channels := grvChannels.Channels()
177193
keyBindings := NewKeyBindingManager()
178194
variables := NewGRVVariables()
179-
config := NewConfiguration(keyBindings, channels, variables)
195+
config := NewConfiguration(keyBindings, channels, variables, channels)
180196

181197
repoDataLoader := NewRepoDataLoader(channels, config)
182198
repoData := NewRepositoryData(repoDataLoader, channels, variables)

0 commit comments

Comments
 (0)