diff --git a/pkg/cmd/actions/actions.go b/pkg/cmd/actions/actions.go index 0da1e73f9..1ce0dc233 100644 --- a/pkg/cmd/actions/actions.go +++ b/pkg/cmd/actions/actions.go @@ -56,8 +56,5 @@ func actionsExplainer(cs *iostreams.ColorScheme) string { gh workflow run: Trigger a workflow_dispatch run for a workflow file To see more help, run 'gh help workflow ' - - For more in depth help including examples, see online documentation at: - `, header, runHeader, workflowHeader) } diff --git a/pkg/cmd/browse/browse.go b/pkg/cmd/browse/browse.go index 38b866432..fdb500cbf 100644 --- a/pkg/cmd/browse/browse.go +++ b/pkg/cmd/browse/browse.go @@ -169,12 +169,28 @@ func parseFileArg(fileArg string) (string, error) { if len(arr) > 2 { return "", fmt.Errorf("invalid use of colon\nUse 'gh browse --help' for more information about browse\n") } + if len(arr) > 1 { - if !isNumber(arr[1]) { - return "", fmt.Errorf("invalid line number after colon\nUse 'gh browse --help' for more information about browse\n") + out := arr[0] + "#L" + lineRange := strings.Split(arr[1], "-") + + if len(lineRange) > 0 { + if !isNumber(lineRange[0]) { + return "", fmt.Errorf("invalid line number after colon\nUse 'gh browse --help' for more information about browse\n") + } + out += lineRange[0] } - return arr[0] + "#L" + arr[1], nil + + if len(lineRange) > 1 { + if !isNumber(lineRange[1]) { + return "", fmt.Errorf("invalid line range after colon\nUse 'gh browse --help' for more information about browse\n") + } + out += "-L" + lineRange[1] + } + + return out, nil } + return arr[0], nil } diff --git a/pkg/cmd/browse/browse_test.go b/pkg/cmd/browse/browse_test.go index 3aacc50b8..bf3349c3c 100644 --- a/pkg/cmd/browse/browse_test.go +++ b/pkg/cmd/browse/browse_test.go @@ -215,6 +215,15 @@ func Test_runBrowse(t *testing.T) { defaultBranch: "trunk", expectedURL: "https://github.com/ravocean/angur/tree/trunk/path/to/file.txt#L32", }, + { + name: "file with line range", + opts: BrowseOptions{ + SelectorArg: "path/to/file.txt:32-40", + }, + baseRepo: ghrepo.New("ravocean", "angur"), + defaultBranch: "trunk", + expectedURL: "https://github.com/ravocean/angur/tree/trunk/path/to/file.txt#L32-L40", + }, { name: "file with invalid line number", opts: BrowseOptions{ @@ -223,6 +232,14 @@ func Test_runBrowse(t *testing.T) { baseRepo: ghrepo.New("ttran112", "ttrain211"), wantsErr: true, }, + { + name: "file with invalid line range", + opts: BrowseOptions{ + SelectorArg: "path/to/file.txt:32-abc", + }, + baseRepo: ghrepo.New("ttran112", "ttrain211"), + wantsErr: true, + }, { name: "branch with issue number", opts: BrowseOptions{ diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go index 1a7975625..b488704e5 100644 --- a/pkg/cmd/extension/manager.go +++ b/pkg/cmd/extension/manager.go @@ -268,7 +268,7 @@ func (m *Manager) Create(name string) error { } fileTmpl := heredoc.Docf(` - #!/bin/bash + #!/usr/bin/env bash set -e echo "Hello %[1]s!" diff --git a/pkg/cmd/issue/create/create.go b/pkg/cmd/issue/create/create.go index 69d61b849..455a1f2d6 100644 --- a/pkg/cmd/issue/create/create.go +++ b/pkg/cmd/issue/create/create.go @@ -62,7 +62,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co $ gh issue create --label "bug,help wanted" $ gh issue create --label bug --label "help wanted" $ gh issue create --assignee monalisa,hubot - $ gh issue create --assignee @me + $ gh issue create --assignee "@me" $ gh issue create --project "Roadmap" `), Args: cmdutil.NoArgsQuoteReminder, diff --git a/pkg/cmd/issue/edit/edit.go b/pkg/cmd/issue/edit/edit.go index 01a9ddf86..a88623d46 100644 --- a/pkg/cmd/issue/edit/edit.go +++ b/pkg/cmd/issue/edit/edit.go @@ -50,7 +50,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman Example: heredoc.Doc(` $ gh issue edit 23 --title "I found a bug" --body "Nothing works" $ gh issue edit 23 --add-label "bug,help wanted" --remove-label "core" - $ gh issue edit 23 --add-assignee @me --remove-assignee monalisa,hubot + $ gh issue edit 23 --add-assignee "@me" --remove-assignee monalisa,hubot $ gh issue edit 23 --add-project "Roadmap" --remove-project v1,v2 $ gh issue edit 23 --milestone "Version 1" $ gh issue edit 23 --body-file body.txt diff --git a/pkg/cmd/issue/list/list.go b/pkg/cmd/issue/list/list.go index 2f58eafec..48d76d23e 100644 --- a/pkg/cmd/issue/list/list.go +++ b/pkg/cmd/issue/list/list.go @@ -57,7 +57,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman Example: heredoc.Doc(` $ gh issue list -l "bug" -l "help wanted" $ gh issue list -A monalisa - $ gh issue list -a @me + $ gh issue list -a "@me" $ gh issue list --web $ gh issue list --milestone "The big 1.0" $ gh issue list --search "error no:assignee sort:created-asc" diff --git a/pkg/cmd/pr/edit/edit.go b/pkg/cmd/pr/edit/edit.go index b905edc00..ecbcceb42 100644 --- a/pkg/cmd/pr/edit/edit.go +++ b/pkg/cmd/pr/edit/edit.go @@ -55,7 +55,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman $ gh pr edit 23 --title "I found a bug" --body "Nothing works" $ gh pr edit 23 --add-label "bug,help wanted" --remove-label "core" $ gh pr edit 23 --add-reviewer monalisa,hubot --remove-reviewer myorg/team-name - $ gh pr edit 23 --add-assignee @me --remove-assignee monalisa,hubot + $ gh pr edit 23 --add-assignee "@me" --remove-assignee monalisa,hubot $ gh pr edit 23 --add-project "Roadmap" --remove-project v1,v2 $ gh pr edit 23 --milestone "Version 1" `), diff --git a/pkg/cmd/pr/list/list.go b/pkg/cmd/pr/list/list.go index 07a3583d5..15c943541 100644 --- a/pkg/cmd/pr/list/list.go +++ b/pkg/cmd/pr/list/list.go @@ -51,10 +51,10 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman Short: "List and filter pull requests in this repository", Example: heredoc.Doc(` List PRs authored by you - $ gh pr list --author @me + $ gh pr list --author "@me" List PRs assigned to you - $ gh pr list --assignee @me + $ gh pr list --assignee "@me" List PRs by label, combining multiple labels with AND $ gh pr list --label bug --label "priority 1" diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 5313fba73..c649b5394 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -8,11 +8,9 @@ import ( "net/http" "os" "os/exec" - "os/signal" "runtime" "strconv" "strings" - "syscall" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/ghinstance" @@ -21,6 +19,7 @@ import ( "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" + "golang.org/x/term" ) type Geometry struct { @@ -51,10 +50,11 @@ type Cell struct { } const ( - DirUp = iota + DirUp Direction = iota DirDown DirLeft DirRight + Quit ) type Direction = int @@ -182,18 +182,6 @@ func gardenRun(opts *GardenOptions) error { maxCommits := (geo.Width * geo.Height) / 2 - sttyFileArg := "-F" - if runtime.GOOS == "darwin" { - sttyFileArg = "-f" - } - - oldTTYCommand := exec.Command("stty", sttyFileArg, "/dev/tty", "-g") - oldTTYSettings, err := oldTTYCommand.CombinedOutput() - if err != nil { - fmt.Fprintln(out, "getting TTY settings failed:", string(oldTTYSettings)) - return err - } - opts.IO.StartProgressIndicator() fmt.Fprintln(out, "gathering commits; this could take a minute...") commits, err := getCommits(httpClient, toView, maxCommits) @@ -215,57 +203,42 @@ func gardenRun(opts *GardenOptions) error { clear(opts.IO) drawGarden(opts.IO, garden, player) - // thanks stackoverflow https://stackoverflow.com/a/17278776 - _ = exec.Command("stty", sttyFileArg, "/dev/tty", "cbreak", "min", "1").Run() - _ = exec.Command("stty", sttyFileArg, "/dev/tty", "-echo").Run() - - walkAway := func() { - clear(opts.IO) - fmt.Fprint(out, "\033[?25h") - _ = exec.Command("stty", sttyFileArg, "/dev/tty", strings.TrimSpace(string(oldTTYSettings))).Run() - fmt.Fprintln(out) - fmt.Fprintln(out, cs.Bold("You turn and walk away from the wildflower garden...")) + // TODO: use opts.IO instead of os.Stdout + oldTermState, err := term.MakeRaw(int(os.Stdout.Fd())) + if err != nil { + return fmt.Errorf("term.MakeRaw: %w", err) } - c := make(chan os.Signal) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) + dirc := make(chan Direction) go func() { - <-c - walkAway() - os.Exit(0) + b := make([]byte, 3) + for { + _, _ = opts.IO.In.Read(b) + switch { + case isLeft(b): + dirc <- DirLeft + case isRight(b): + dirc <- DirRight + case isUp(b): + dirc <- DirUp + case isDown(b): + dirc <- DirDown + case isQuit(b): + dirc <- Quit + } + } }() - var b []byte = make([]byte, 3) +mainLoop: for { - _, _ = opts.IO.In.Read(b) - oldX := player.X oldY := player.Y - moved := false - quitting := false - continuing := false - switch { - case isLeft(b): - moved = player.move(DirLeft) - case isRight(b): - moved = player.move(DirRight) - case isUp(b): - moved = player.move(DirUp) - case isDown(b): - moved = player.move(DirDown) - case isQuit(b): - quitting = true - default: - continuing = true - } - - if quitting { - break - } - - if !moved || continuing { - continue + d := <-dirc + if d == Quit { + break mainLoop + } else if !player.move(d) { + continue mainLoop } underPlayer := garden[player.Y][player.X] @@ -315,7 +288,12 @@ func gardenRun(opts *GardenOptions) error { fmt.Fprint(out, cs.Bold(sl)) } - walkAway() + clear(opts.IO) + fmt.Fprint(out, "\033[?25h") + // TODO: use opts.IO instead of os.Stdout + _ = term.Restore(int(os.Stdout.Fd()), oldTermState) + fmt.Fprintln(out, cs.Bold("You turn and walk away from the wildflower garden...")) + return nil } @@ -343,8 +321,10 @@ func isUp(b []byte) bool { return bytes.EqualFold(b, up) || r == 'w' || r == 'k' } +var ctrlC = []byte{0x3, 0x5b, 0x43} + func isQuit(b []byte) bool { - return rune(b[0]) == 'q' + return rune(b[0]) == 'q' || bytes.Equal(b, ctrlC) } func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell {