Skip to content

Commit

Permalink
feat: add alt + shift modifiers (#316)
Browse files Browse the repository at this point in the history
* feat: add alt + shift modifiers

* style: fix lint issues

* Update parser.go

Co-authored-by: Maas Lalani <maas@lalani.dev>

* Theme test: fix ineffectual assignment

* test: add more test cases for suggestions

* feat: setting to control cursor blinking (#324)

* feat: setting to control cursor blinking

* feat: create bool type

* feat: parser updated

* readme wording

Co-authored-by: Maas Lalani <maas@lalani.dev>

* Update README.md

Co-authored-by: Maas Lalani <maas@lalani.dev>

* Update parser.go

* Update token.go

* Update parser.go

* Apply suggestions from code review

---------

Co-authored-by: Maas Lalani <maas@lalani.dev>

* docs: Set CursorBlink examples

* docs: fix CursorBlink example sizing

* feat(deps): bump github.com/go-rod/rod from 0.112.8 to 0.113.3 (#321)

Bumps [github.com/go-rod/rod](https://github.com/go-rod/rod) from 0.112.8 to 0.113.3.
- [Release notes](https://github.com/go-rod/rod/releases)
- [Commits](go-rod/rod@v0.112.8...v0.113.3)

---
updated-dependencies:
- dependency-name: github.com/go-rod/rod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(deps): bump github.com/mattn/go-isatty from 0.0.18 to 0.0.19 (#310)

Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.18 to 0.0.19.
- [Commits](mattn/go-isatty@v0.0.18...v0.0.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* add note on TypingSpeed for new users in `demo.tape` (#327)

Added note to the comment that is generated when a user calls `vhs new` to call out that `TypingSpeed` can be customized globally, and defaults to 50ms

* docs: modify Ctrl keyword syntax

* docs: update readme

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Maas Lalani <maas@lalani.dev>
Co-authored-by: Andreas Deininger <andreas@deininger.net>
Co-authored-by: bashbunni <bunni@bashbunni.dev>
Co-authored-by: Pavel Storozhenko <storozhenkopf@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Johnston <m@ttjohnston.com>
  • Loading branch information
7 people committed Jun 14, 2023
1 parent 58d4bf9 commit 4dd9a81
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 19 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ There are a few basic types of VHS commands:
* [`Type "<characters>"`](#type): emulate typing
* [`Left`](#arrow-keys) [`Right`](#arrow-keys) [`Up`](#arrow-keys) [`Down`](#arrow-keys): arrow keys
* [`Backspace`](#backspace) [`Enter`](#enter) [`Tab`](#tab) [`Space`](#space): special keys
* [`Ctrl+<char>`](#ctrl): press control + key
* [`Ctrl[+Alt][+Shift]+<char>`](#ctrl): press control + key and/or modifier
* [`Sleep <time>`](#sleep): wait for a certain amount of time
* [`Hide`](#hide): hide commands from output
* [`Show`](#show): stop hiding commands from output
Expand Down
38 changes: 31 additions & 7 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,40 @@ func ExecuteKey(k input.Key) CommandFunc {
}
}

// ExecuteCtrl is a CommandFunc that presses the argument key with the ctrl key
// held down on the running instance of vhs.
// ExecuteCtrl is a CommandFunc that presses the argument keys and/or modifiers
// with the ctrl key held down on the running instance of vhs.
func ExecuteCtrl(c Command, v *VHS) {
_ = v.Page.Keyboard.Press(input.ControlLeft)
for _, r := range c.Args {
if k, ok := keymap[r]; ok {
_ = v.Page.Keyboard.Type(k)
// Create key combination by holding ControlLeft
action := v.Page.KeyActions().Press(input.ControlLeft)
keys := strings.Split(c.Args, " ")

for i, key := range keys {
var inputKey *input.Key

switch key {
case "Shift":
inputKey = &input.ShiftLeft
case "Alt":
inputKey = &input.AltLeft
default:
r := rune(key[0])
if k, ok := keymap[r]; ok {
inputKey = &k
}
}

// Press or hold key in case it's valid
if inputKey != nil {
if i != len(keys)-1 {
action.Press(*inputKey)
} else {
// Other keys will remain pressed until the combination reaches the end
action.Type(*inputKey)
}
}
}
_ = v.Page.Keyboard.Release(input.ControlLeft)

action.MustDo()
}

// ExecuteAlt is a CommandFunc that presses the argument key with the alt key
Expand Down
2 changes: 1 addition & 1 deletion man.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The following is a list of all possible commands in VHS:
* %Set% <setting> <value>
* %Sleep% <time>
* %Type% "<string>"
* %Ctrl%+<key>
* %Ctrl% [+Alt][+Shift]+<char>
* %Backspace% [repeat]
* %Down% [repeat]
* %Enter% [repeat]
Expand Down
43 changes: 35 additions & 8 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,48 @@ func (p *Parser) parseTime() string {
}

// parseCtrl parses a control command.
// A control command takes a character to type while the modifier is held down.
// A control command takes one or multiples characters and/or modifiers to type while ctrl is held down.
//
// Ctrl+<character>
// Ctrl[+Alt][+Shift]+<char>
// E.g:
// Ctrl+Shift+O
// Ctrl+Alt+Shift+P
func (p *Parser) parseCtrl() Command {
if p.peek.Type == PLUS {
var args []string

for p.peek.Type == PLUS {
p.nextToken()
if p.peek.Type == STRING {
c := p.peek.Literal
peek := p.peek

// Get key from keywords and check if it's a valid modifier
if k, ok := keywords[peek.Literal]; ok {
p.nextToken()
if IsModifier(k) {
args = append(args, peek.Literal)
} else {
p.errors = append(p.errors, NewError(p.cur, "not a valid modifier"))
}
}

// Add key argument
if peek.Type == STRING && len(peek.Literal) == 1 {
p.nextToken()
return Command{Type: CTRL, Args: c}
args = append(args, peek.Literal)
}

// Key arguments with len > 1 are not valid
if peek.Type == STRING && len(peek.Literal) > 1 {
p.nextToken()
p.errors = append(p.errors, NewError(p.cur, "Invalid control argument: "+p.cur.Literal))
}
}

if len(args) == 0 {
p.errors = append(p.errors, NewError(p.cur, "Expected control character with args, got "+p.cur.Literal))
}

p.errors = append(p.errors, NewError(p.cur, "Expected control character, got "+p.cur.Literal))
return Command{Type: CTRL}
ctrlArgs := strings.Join(args, " ")
return Command{Type: CTRL, Args: ctrlArgs}
}

// parseAlt parses an alt command.
Expand Down
48 changes: 48 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"os"
"strings"
"testing"
)

Expand Down Expand Up @@ -194,3 +195,50 @@ func TestParseTapeFile(t *testing.T) {
}
}
}

func TestParseCtrl(t *testing.T) {
t.Run("should parse with multiple modifiers", func(t *testing.T) {
tape := "Ctrl+Shift+Alt+C"
l := NewLexer(tape)
p := NewParser(l)

cmd := p.parseCtrl()

expectedArgs := []string{"Shift", "Alt", "C"}
args := strings.Split(cmd.Args, " ")

if len(expectedArgs) != len(args) {
t.Fatalf("Unable to parse args, expected args %d, got %d", len(expectedArgs), len(args))
}

for i, arg := range args {
if expectedArgs[i] != arg {
t.Errorf("Arg %d is wrong, expected %s, got %s", i, expectedArgs[i], arg)
}
}
})

t.Run("should parse with errors when using unknown modifier", func(t *testing.T) {
tape := "Ctrl+AltRight"
l := NewLexer(tape)
p := NewParser(l)

_ = p.parseCtrl()

if len(p.errors) == 0 {
t.Errorf("Expected to parse with errors but was success")
}
})

t.Run("should parse with errors when using keyword as modifier", func(t *testing.T) {
tape := "Ctrl+Backspace"
l := NewLexer(tape)
p := NewParser(l)

_ = p.parseCtrl()

if len(p.errors) == 0 {
t.Errorf("Expected to parse with errors but was success")
}
})
}
13 changes: 11 additions & 2 deletions token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import "strings"
import (
"strings"
)

// TokenType represents a token's type.
type TokenType string
Expand Down Expand Up @@ -46,6 +48,7 @@ const (
SLEEP = "SLEEP"
SPACE = "SPACE"
TAB = "TAB"
SHIFT = "SHIFT"

COMMENT = "COMMENT"
NUMBER = "NUMBER"
Expand Down Expand Up @@ -101,6 +104,7 @@ var keywords = map[string]TokenType{
"Backspace": BACKSPACE,
"Ctrl": CTRL,
"Alt": ALT,
"Shift": SHIFT,
"Down": DOWN,
"Left": LEFT,
"Right": RIGHT,
Expand Down Expand Up @@ -151,7 +155,7 @@ func IsSetting(t TokenType) bool {
}
}

// IsCommand returns whether the string is a command
// IsCommand returns whether the string is a command.
func IsCommand(t TokenType) bool {
switch t {
case TYPE, SLEEP,
Expand All @@ -164,6 +168,11 @@ func IsCommand(t TokenType) bool {
}
}

// IsModifier returns whether the token is a modifier.
func IsModifier(t TokenType) bool {
return t == ALT || t == SHIFT
}

// String converts a token to it's human readable string format.
func (t TokenType) String() string {
if IsCommand(t) || IsSetting(t) {
Expand Down

0 comments on commit 4dd9a81

Please sign in to comment.