Full Code of skanehira/github-tui for AI

main f56081431e3c cached
39 files
104.3 KB
43.2k tokens
157 symbols
1 requests
Download .txt
Repository: skanehira/github-tui
Branch: main
Commit: f56081431e3c
Files: 39
Total size: 104.3 KB

Directory structure:
gitextract_8momnkvm/

├── LICENSE
├── README.md
├── cmd/
│   └── ght/
│       └── main.go
├── config/
│   └── config.go
├── domain/
│   ├── assignees.go
│   ├── comment.go
│   ├── error.go
│   ├── issue.go
│   ├── item.go
│   ├── label.go
│   ├── milestone.go
│   └── project.go
├── github/
│   ├── client.go
│   ├── mutation_comment.go
│   ├── mutation_issue.go
│   ├── query.go
│   ├── query_assignees.go
│   ├── query_comment.go
│   ├── query_issue.go
│   ├── query_label.go
│   ├── query_milestone.go
│   ├── query_project.go
│   └── query_repository.go
├── go.mod
├── go.sum
├── ui/
│   ├── assignees.go
│   ├── comments.go
│   ├── filter.go
│   ├── issues.go
│   ├── labels.go
│   ├── milestones.go
│   ├── projects.go
│   ├── search.go
│   ├── select.go
│   ├── ui.go
│   └── view.go
└── utils/
    ├── open.go
    ├── strings.go
    └── utils.go

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2020 skanehira

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# github-tui
This is a TUI Client for GitHub.
**Still Under Development**

If you are using Vim, you can use [gh.vim](https://github.com/skanehira/gh.vim) instead.

![](https://i.gyazo.com/d7c8ca82e0aeb947f82c10b08d3eba35.png)

## Features
### Implemented
- Issue
  - list
  - create
  - close
  - open
  - open browser
  - preview
  - edit
- Issue comment
  - list
  - preview
  - delete
  - edit
  - add
  - quote reply

### Still Under Development
- Issue
  - add assignees, labels, projects, milestone
  - remove assignees, labels, projects, milestone
- PR
  - list
  - edit comment
  - add comment
  - delete comment
  - diff
  - create
  - close
  - change base
  - merge
- Github Actions
  - re-run
  - list
  - log
- File tree
  - preview
  - open browser
  - preview
- Project
  - columns
  - open(if type is issue, pr)
  - add
  - remove
  - move
  - open browser
- config
  - set default editor
  - set user keybindings

## Installation

```sh
$ git clone https://github.com/skanehira/github-tui
$ go install ./cmd/ght
```

## Settings
At first, please set personal access [token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) and email in config.yaml.

```yaml
github:
  token: xxxxxxxxxxxxxxxxx
```

The config.yaml path must be in the bellow place.

| OS         | place                                               |
|------------|-----------------------------------------------------|
| Window     | `%AppData%¥ght¥config.yaml`                         |
| Mac        | `$HOME/Library/Application Support/ght/config.yaml` |
| Linux/Unix | `$HOME/.config/ght/config.yaml`                     |

## Usage

```sh
# current repository
$ ght

# specified repository
$ ght owner/repo
```

### Keybindings

| UI       | Keybinding           | Description                      |
|----------|----------------------|----------------------------------|
| Common   | `j`/`down arrow`     | Move down by one row.            |
| Common   | `k`/`up arrow`       | Move up by one row.              |
| Common   | `g`/`home`           | Move to the top.                 |
| Common   | `G`/`end`            | Move to the bottom.              |
| Common   | `Ctrl-F`/`page down` | Move down by one page.           |
| Common   | `Ctrl-B`/`page up`   | Move up by one page.             |
| Common   | `Ctrl-N`             | Move next UI.                    |
| Common   | `Ctrl-P`             | Move previous UI.                |
| Common   | `Ctrl-C`             | Finish app.                      |
| Common   | `Ctrl-G`             | Focus to Issues                  |
| Common   | `Ctrl-T`             | Focus to Filters                 |
| Filters  | `Enter`              | Search with enter query.         |
| Issues   | `h`/`left arrow`     | Move left by one column.         |
| Issues   | `l`/`right arrow`    | Move right by one column.        |
| Issues   | `Ctrl-J`             | Check issue and move down.       |
| Issues   | `Ctrl-K`             | Check issue and move up.         |
| Issues   | `e`                  | Edit and update issue body.      |
| Issues   | `o`                  | Open checked issue.              |
| Issues   | `c`                  | Close checked issue.             |
| Issues   | `Ctrl-O`             | Open checked issue on browser.   |
| Issues   | `/`                  | filter with enter words          |
| Issues   | `n`                  | Create new issue.                |
| Issues   | `f`                  | Fetch more issue.                |
| Comments | `h`/`left arrow`     | Move left by one column.         |
| Comments | `l`/`right arrow`    | Move right by one column.        |
| Comments | `Ctrl-J`             | Check comment and move down.     |
| Comments | `Ctrl-K`             | Check comment and move up.       |
| Comments | `Ctrl-O`             | Open checked comment on browser. |
| Comments | `n`                  | Add new issue comment.           |
| Comments | `e`                  | Edit and update comment body.    |
| Comments | `r`                  | Quote reply comment.             |
| Comments | `/`                  | filter with enter words          |
| Preview  | `/`                  | search with enter words          |
| Preview  | `n`                  | move next word                   |
| Preview  | `N`                  | move previous word               |
| Preview  | `o`                  | change to full screen            |

### Note
When you creating issue, you can specify multiple labels, projects and assignees with `,`.
For instance, when you specify 2 labels then must input `label1,label2`.

![](https://i.gyazo.com/fb665369057c5f096517a24e606e7884.png)

When you edit issue body with `Edit Body` button then `$EDITOR` be used.
If `$EDITOR` is empty or not set, `vim` wll be used.

## Author
skanehira


================================================
FILE: cmd/ght/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"os/exec"
	"strings"

	"github.com/skanehira/ght/config"
	"github.com/skanehira/ght/github"
	"github.com/skanehira/ght/ui"
)

type Repo struct {
	Owner string
	Name  string
}

func main() {
	config.Init()
	getRepoInfo()
	github.NewClient(config.GitHub.Token)
	if err := ui.New().Start(); err != nil {
		log.Fatal(err)
	}
}

func getRepoInfo() {
	flag.Parse()
	if len(flag.Args()) > 0 {
		args := strings.Split(flag.Arg(0), "/")
		if len(args) < 2 {
			log.Fatal("invalid args")
		}
		config.GitHub.Owner = args[0]
		config.GitHub.Repo = args[1]
	} else {
		repo, err := getOwnerRepo()
		if err != nil {
			log.Fatalf("invalid repo: %s", err)
		}
		config.GitHub.Owner = repo.Owner
		config.GitHub.Repo = repo.Name
	}
}

func getOwnerRepo() (*Repo, error) {
	if _, err := exec.LookPath("git"); err != nil {
		return nil, err
	}
	cmd := exec.Command("git", "remote", "get-url", "origin")
	out, err := cmd.CombinedOutput()

	result := strings.TrimRight(string(out), "\r\n")
	if err != nil {
		return nil, err
	}

	return parseRemote(result)
}

func parseRemote(remote string) (*Repo, error) {
	if strings.HasSuffix(remote, ".git") {
		remote = strings.TrimRight(remote, ".git")
	}
	var ownerRepo []string
	if strings.HasPrefix(remote, "ssh") {
		p := strings.Split(remote, "/")
		if len(p) < 1 {
			return nil, fmt.Errorf("cannot get owner/repo from remote: %s", remote)
		}
		ownerRepo = p[len(p)-2:]
	} else if strings.HasPrefix(remote, "git") {
		p := strings.Split(remote, ":")
		if len(p) < 1 {
			return nil, fmt.Errorf("cannot get owner/repo from remote: %s", remote)
		}
		ownerRepo = strings.Split(p[1], "/")
	} else if strings.HasPrefix(remote, "http") || strings.HasPrefix(remote, "https") {
		p := strings.Split(remote, "/")
		if len(p) < 1 {
			return nil, fmt.Errorf("cannot get owner/repo from remote: %s", remote)
		}
		ownerRepo = p[len(p)-2:]
	}

	repo := Repo{
		Owner: ownerRepo[0],
		Name:  ownerRepo[1],
	}

	return &repo, nil
}


================================================
FILE: config/config.go
================================================
package config

import (
	"io"
	"log"
	"os"
	"path/filepath"

	"github.com/goccy/go-yaml"
)

type github struct {
	Owner string
	Repo  string
	Token string `yaml:"token"`
}

type app struct {
	File string `yaml:"file"`
}

const readThisMessage = "read this https://github.com/skanehira/github-tui?tab=readme-ov-file#settings to know more"

var (
	GitHub github
	App    app
)

func Init() {
	configDir, err := os.UserConfigDir()
	if err != nil {
		log.Fatal(err)
	}

	logFile := filepath.Join(configDir, "ght", "debug.log")
	output, err := os.Create(logFile)
	if err != nil {
		log.Fatal(err)
	}

	log.SetOutput(io.MultiWriter(output, os.Stderr))

	configFile := filepath.Join(configDir, "ght", "config.yaml")

	b, err := os.ReadFile(configFile)
	if err != nil {
		if !os.IsNotExist(err) {
			log.Fatal(err)
		}

		log.Fatalf("Could not find configuration file, %s", readThisMessage)
	}

	var conf struct {
		GitHub github `yaml:"github"`
	}

	if err := yaml.Unmarshal(b, &conf); err != nil {
		log.Fatalf("cannot deserialize config file: %s", err)
	}

	if conf.GitHub.Token == "" {
		log.Fatalf("github token is empty, %s", readThisMessage)
	}

	App.File = configFile
	GitHub = conf.GitHub
}


================================================
FILE: domain/assignees.go
================================================
package domain

import "github.com/gdamore/tcell/v2"

type AssignableUser struct {
	Login string
}

func (a *AssignableUser) Key() string {
	return a.Login
}

func (a *AssignableUser) Fields() []Field {
	return []Field{
		{Text: a.Login, Color: tcell.ColorFuchsia},
	}
}


================================================
FILE: domain/comment.go
================================================
package domain

import "github.com/gdamore/tcell/v2"

type Comment struct {
	ID        string
	Author    string
	UpdatedAt string
	URL       string
	Body      string
}

func (c *Comment) Key() string {
	return c.ID
}

func (c *Comment) Fields() []Field {
	f := []Field{
		{Text: c.Author, Color: tcell.ColorYellow},
		{Text: c.UpdatedAt, Color: tcell.ColorWhite},
	}

	return f
}


================================================
FILE: domain/error.go
================================================
package domain

import "errors"

var (
	ErrCommentBodyIsEmpty = errors.New("comment body is empty")
	ErrNotFoundComment    = errors.New("not found comment")
	ErrNotFoundIssue      = errors.New("not found issue")
)


================================================
FILE: domain/issue.go
================================================
package domain

import (
	"fmt"

	"github.com/gdamore/tcell/v2"
)

type Issue struct {
	ID        string
	Repo      string
	RepoOwner string
	Number    string
	State     string
	Title     string
	Body      string
	Author    string
	URL       string
	Labels    []Item
	Assignees []Item
	Comments  []Item
	MileStone []Item
	Projects  []Item
}

func (i *Issue) Key() string {
	return i.ID
}

func (i *Issue) Fields() []Field {
	stateColor := tcell.ColorGreen
	if i.State == "CLOSED" {
		stateColor = tcell.ColorRed
	}

	f := []Field{
		{Text: fmt.Sprintf("%s/%s", i.RepoOwner, i.Repo), Color: tcell.ColorLightSalmon},
		{Text: i.Number, Color: tcell.ColorBlue},
		{Text: i.State, Color: stateColor},
		{Text: i.Author, Color: tcell.ColorYellow},
		{Text: i.Title, Color: tcell.ColorWhite},
	}

	return f
}


================================================
FILE: domain/item.go
================================================
package domain

import "github.com/gdamore/tcell/v2"

type Item interface {
	Key() string
	Fields() []Field
}

type Field struct {
	Text  string
	Color tcell.Color
}


================================================
FILE: domain/label.go
================================================
package domain

import "github.com/gdamore/tcell/v2"

type Label struct {
	Name        string
	Description string
}

func (l *Label) Key() string {
	return l.Name
}

func (l *Label) Fields() []Field {
	return []Field{
		{Text: l.Name, Color: tcell.ColorLightYellow},
	}
}


================================================
FILE: domain/milestone.go
================================================
package domain

import "github.com/gdamore/tcell/v2"

type Milestone struct {
	ID          string
	Title       string
	State       string
	Description string
	URL         string
}

func (m *Milestone) Key() string {
	return m.Title
}

func (m *Milestone) Fields() []Field {
	return []Field{
		{Text: m.Title, Color: tcell.ColorGreen},
	}
}


================================================
FILE: domain/project.go
================================================
package domain

import "github.com/gdamore/tcell/v2"

type Project struct {
	Name string
	URL  string
}

func (p *Project) Key() string {
	return p.Name
}

func (p *Project) Fields() []Field {
	return []Field{
		{Text: p.Name, Color: tcell.ColorLightSalmon},
	}
}


================================================
FILE: github/client.go
================================================
package github

import (
	"context"

	"github.com/shurcooL/githubv4"
	"golang.org/x/oauth2"
)

var client *githubv4.Client

func NewClient(token string) {
	src := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)
	httpClient := oauth2.NewClient(context.Background(), src)
	client = githubv4.NewClient(httpClient)
}

func CreateIssue(input githubv4.CreateIssueInput) error {
	var m MutateCreateIssue
	return client.Mutate(context.Background(), &m, input, nil)
}

func GetRepos(variables map[string]interface{}) (*Repositories, error) {
	var q struct {
		RepositoryOwner struct {
			Repositories `graphql:"repositories(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
		} `graphql:"repositoryOwner(login: $login)"`
	}

	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return &q.RepositoryOwner.Repositories, nil
}

func GetRepo(variables map[string]interface{}) (*Repository, error) {
	var q struct {
		Repository `graphql:"repository(owner: $owner, name: $name)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return &q.Repository, nil
}

func GetIssues(variables map[string]interface{}) (*Issues, error) {
	var q struct {
		Search Issues `graphql:"search(query: $query, type: ISSUE, first: $first, after: $cursor)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}

	issues := &Issues{
		Nodes:    q.Search.Nodes,
		PageInfo: q.Search.PageInfo,
	}
	return issues, nil
}

func GetIssue(variables map[string]interface{}) (*Issue, error) {
	var q struct {
		Repository struct {
			Issue *Issue `graphql:"issue(number: $number)"`
		} `graphql:"repository(owner: $owner, name: $name)"`
	}

	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return q.Repository.Issue, nil
}

func GetIssueTemplates(variables map[string]interface{}) ([]IssueTemplate, error) {
	var q struct {
		Repository struct {
			IssueTemplates []IssueTemplate
		} `graphql:"repository(name: $name, owner: $owner)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return q.Repository.IssueTemplates, nil
}

func ReopenIssue(id string) error {
	input := githubv4.ReopenIssueInput{
		IssueID: githubv4.String(id),
	}

	var m MutateOpenIsseue

	return client.Mutate(context.Background(), &m, input, nil)
}

func CloseIssue(id string) error {
	input := githubv4.CloseIssueInput{
		IssueID: githubv4.String(id),
	}

	var m MutateCoseIssue
	return client.Mutate(context.Background(), &m, input, nil)
}

func GetRepoLabels(variables map[string]interface{}) (*Labels, error) {
	var q struct {
		Repository struct {
			Labels `graphql:"labels(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
		} `graphql:"repository(name: $name, owner: $owner)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return &q.Repository.Labels, nil
}

func GetRepoMillestones(variables map[string]interface{}) (*Milestones, error) {
	var q struct {
		Repository struct {
			Milestones `graphql:"milestones(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
		} `graphql:"repository(name: $name, owner: $owner)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return &q.Repository.Milestones, nil
}

func GetRepoProjects(variables map[string]interface{}) (*Projects, error) {
	var q struct {
		Repository struct {
			Projects `graphql:"projects(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
		} `graphql:"repository(name: $name, owner: $owner)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return &q.Repository.Projects, nil
}

func GetRepoAssignableUsers(variables map[string]interface{}) (*AssignableUsers, error) {
	var q struct {
		Repository struct {
			AssignableUsers `graphql:"assignableUsers(first: $first, after: $cursor)"`
		} `graphql:"repository(name: $name, owner: $owner)"`
	}
	if err := client.Query(context.Background(), &q, variables); err != nil {
		return nil, err
	}
	return &q.Repository.AssignableUsers, nil
}

func DeleteIssueComment(id string) error {
	var m MutateDeleteComment
	input := githubv4.DeleteIssueCommentInput{
		ID: githubv4.ID(id),
	}
	return client.Mutate(context.Background(), &m, input, nil)
}

func UpdateIssue(input githubv4.UpdateIssueInput) error {
	var m MutateUpdateIssue
	return client.Mutate(context.Background(), &m, input, nil)
}

func UpdateIssueComment(input githubv4.UpdateIssueCommentInput) error {
	var m MutateUpdateIssueComment
	return client.Mutate(context.Background(), &m, input, nil)
}

func AddIssueComment(input githubv4.AddCommentInput) error {
	var m MutateAddIssueComment
	return client.Mutate(context.Background(), &m, input, nil)
}


================================================
FILE: github/mutation_comment.go
================================================
package github

import "github.com/shurcooL/githubv4"

type MutateDeleteComment struct {
	DeleteIssueComment struct {
		ClientMutationId githubv4.String
	} `graphql:"deleteIssueComment(input: $input)"`
}

type MutateUpdateIssueComment struct {
	UpdateIssueComment struct {
		ClientMutationId githubv4.String
	} `graphql:"updateIssueComment(input: $input)"`
}


================================================
FILE: github/mutation_issue.go
================================================
package github

import "github.com/shurcooL/githubv4"

type MutateOpenIsseue struct {
	ReopenIssue struct {
		Issue struct {
			ID githubv4.String
		}
	} `graphql:"reopenIssue(input: $input)"`
}

type MutateCoseIssue struct {
	CloseIssue struct {
		Issue struct {
			ID githubv4.String
		}
	} `graphql:"closeIssue(input: $input)"`
}

type MutateCreateIssue struct {
	CreateIssue struct {
		Issue struct {
			ID githubv4.String
		}
	} `graphql:"createIssue(input: $input)"`
}

type MutateUpdateIssue struct {
	UpdateIssue struct {
		Issue struct {
			ID githubv4.ID
		}
	} `graphql:"updateIssue(input: $input)"`
}

type MutateAddIssueComment struct {
	AddIssueComment struct {
		ClientMutationID githubv4.String
	} `graphql:"addComment(input: $input)"`
}


================================================
FILE: github/query.go
================================================
package github

import "github.com/shurcooL/githubv4"

type PageInfo struct {
	EndCursor   githubv4.String
	HasNextPage githubv4.Boolean
}


================================================
FILE: github/query_assignees.go
================================================
package github

import (
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
)

type AssignableUser struct {
	Login githubv4.String
}

func (a *AssignableUser) ToDomain() *domain.AssignableUser {
	assignableUser := &domain.AssignableUser{
		Login: string(a.Login),
	}
	return assignableUser
}

type AssignableUsers struct {
	Nodes []struct {
		ID    githubv4.ID
		Login githubv4.String
	}
	PageInfo PageInfo
}


================================================
FILE: github/query_comment.go
================================================
package github

import (
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
)

type Comment struct {
	ID     githubv4.String
	Author struct {
		Login githubv4.String
	}
	UpdatedAt githubv4.DateTime
	Body      githubv4.String
	URL       githubv4.URI
}

func (c *Comment) ToDomain() *domain.Comment {
	comment := &domain.Comment{
		ID:        string(c.ID),
		Author:    string(c.Author.Login),
		UpdatedAt: c.UpdatedAt.Local().Format("2006/01/02 15:04:05"),
		URL:       c.URL.String(),
		Body:      string(c.Body),
	}
	return comment
}


================================================
FILE: github/query_issue.go
================================================
package github

import (
	"reflect"
	"strconv"

	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
)

type Issue struct {
	ID         githubv4.String
	Repository struct {
		ID    githubv4.String
		Owner struct {
			Login githubv4.String
		}
		Name githubv4.String
	}
	Number githubv4.Int
	Body   githubv4.String
	State  githubv4.String
	Author struct {
		Login githubv4.String
	}
	Title     githubv4.String
	URL       githubv4.URI
	Labels    Labels `graphql:"labels(first: 10)"`
	Assignees struct {
		Nodes []AssignableUser
	} `graphql:"assignees(first: 10)"`
	ProjectCards struct {
		Nodes []struct {
			Project Project
		}
	} `graphql:"projectCards(first: 10)"`
	Milestone Milestone
	Comments  struct {
		Nodes []Comment
	} `graphql:"comments(first: 100)"`
}

func (i *Issue) ToDomain() *domain.Issue {
	issue := &domain.Issue{
		ID:        string(i.ID),
		Repo:      string(i.Repository.Name),
		RepoOwner: string(i.Repository.Owner.Login),
		Number:    strconv.Itoa(int(i.Number)),
		State:     string(i.State),
		Author:    string(i.Author.Login),
		URL:       i.URL.String(),
		Title:     string(i.Title),
		Body:      string(i.Body),
	}

	labels := make([]domain.Item, len(i.Labels.Nodes))
	for i, label := range i.Labels.Nodes {
		labels[i] = label.ToDomain()
	}
	issue.Labels = labels

	assignees := make([]domain.Item, len(i.Assignees.Nodes))
	for i, a := range i.Assignees.Nodes {
		assignees[i] = a.ToDomain()
	}
	issue.Assignees = assignees

	comments := make([]domain.Item, len(i.Comments.Nodes))
	for i, comment := range i.Comments.Nodes {
		comments[i] = comment.ToDomain()
	}
	issue.Comments = comments

	if !reflect.ValueOf(i.Milestone).IsZero() {
		issue.MileStone = append(issue.MileStone, i.Milestone.ToDomain())
	}

	projects := make([]domain.Item, len(i.ProjectCards.Nodes))
	for i, card := range i.ProjectCards.Nodes {
		projects[i] = card.Project.ToDomain()
	}
	issue.Projects = projects
	return issue
}

type Issues struct {
	Nodes []struct {
		Issue Issue `graphql:"... on Issue"`
	}
	PageInfo PageInfo
}

type IssueTemplate struct {
	About githubv4.String
	Body  githubv4.String
	Name  githubv4.String
	Title githubv4.String
}


================================================
FILE: github/query_label.go
================================================
package github

import (
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
)

type Label struct {
	ID          githubv4.ID
	Name        githubv4.String
	Description githubv4.String
	Color       githubv4.String
}

func (l *Label) ToDomain() *domain.Label {
	label := &domain.Label{
		Name:        string(l.Name),
		Description: string(l.Description),
	}
	return label
}

type Labels struct {
	Nodes    []Label
	PageInfo PageInfo
}


================================================
FILE: github/query_milestone.go
================================================
package github

import (
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
)

type Milestone struct {
	ID          githubv4.ID
	Title       githubv4.String
	State       githubv4.String
	Description githubv4.String
	URL         githubv4.URI
}

func (m *Milestone) ToDomain() *domain.Milestone {
	milestone := &domain.Milestone{
		ID:          m.ID.(string),
		Title:       string(m.Title),
		State:       string(m.State),
		Description: string(m.Description),
		URL:         m.URL.String(),
	}
	return milestone
}

type Milestones struct {
	Nodes    []Milestone
	PageInfo PageInfo
}


================================================
FILE: github/query_project.go
================================================
package github

import (
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
)

type Project struct {
	ID   githubv4.ID
	Name githubv4.String
	URL  githubv4.URI
}

func (p *Project) ToDomain() *domain.Project {
	project := &domain.Project{
		Name: string(p.Name),
		URL:  p.URL.String(),
	}
	return project
}

type Projects struct {
	Nodes    []Project
	PageInfo PageInfo
}


================================================
FILE: github/query_repository.go
================================================
package github

import "github.com/shurcooL/githubv4"

type Repository struct {
	ID               githubv4.ID
	Name             githubv4.String
	NameWithOwner    githubv4.String
	CreatedAt        githubv4.DateTime
	DefaultBranchRef struct {
		Name githubv4.String
	}
	Description githubv4.String
	LicenseInfo struct {
		Name githubv4.String
	}
	StargazerCount githubv4.Int
	URL            githubv4.URI
	SSHURL         githubv4.String
}

type Repositories struct {
	Nodes    []Repository
	PageInfo PageInfo
}


================================================
FILE: go.mod
================================================
module github.com/skanehira/ght

go 1.15

require (
	github.com/atotto/clipboard v0.1.2
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/gdamore/tcell/v2 v2.2.0
	github.com/goccy/go-yaml v1.8.3
	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
	github.com/mattn/go-colorable v0.1.6 // indirect
	github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598
	github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b
	github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect
	golang.org/x/net v0.0.0-20201026091529-146b70c837a4 // indirect
	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
	golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
	golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48 // indirect
)


================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/gdamore/tcell/v2 v2.1.0 h1:UnSmozHgBkQi2PGsFr+rpdXuAPRRucMegpQp3Z3kDro=
github.com/gdamore/tcell/v2 v2.1.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4=
github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/goccy/go-yaml v1.8.3 h1:VGzw2KWSUyQX0yXai02S0nttBc+Oa4Kvh6RCFoxt8SE=
github.com/goccy/go-yaml v1.8.3/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/tview v0.0.0-20210217110421-8a8f78a6dd01 h1:rtCzDXdaqhiRakJsz0bUj+3sOUjw82bJDcJrAzQ0u+M=
github.com/rivo/tview v0.0.0-20210217110421-8a8f78a6dd01/go.mod h1:n2q/ydglZJ1kqxiNrnYO+FaX1H14vA0wKyIo953QakU=
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598 h1:AbRrGXhagPRDItERv7nauBUUPi7Ma3IGIj9FqkQKW6k=
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598/go.mod h1:VzCN9WX13RF88iH2CaGkmdHOlsy1ZZQcTmNwROqC+LI=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b h1:0/ecDXh/HTHRtSDSFnD2/Ta1yQ5J76ZspVY4u0/jGFk=
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48 h1:70qalHWW1n9yoI8B8zEQxFJO/D6NUWIX8SNmJO+rvNw=
golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.30.0 h1:Wk0Z37oBmKj9/n+tPyBHZmeL19LaCoK3Qq48VwYENss=
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=


================================================
FILE: ui/assignees.go
================================================
package ui

import (
	"github.com/gdamore/tcell/v2"
)

var AssigneesUI *SelectUI

func NewAssignableUI() {
	//getList := func(cursor *string) ([]List, github.PageInfo) {
	//	v := map[string]interface{}{
	//		"owner":  githubv4.String(config.GitHub.Owner),
	//		"name":   githubv4.String(config.GitHub.Repo),
	//		"first":  githubv4.Int(100),
	//		"cursor": (*githubv4.String)(cursor),
	//	}
	//	resp, err := github.GetRepoAssignableUsers(v)
	//	if err != nil {
	//		return nil, github.PageInfo{}
	//	}

	//	assignees := make([]List, len(resp.Nodes))
	//	for i, p := range resp.Nodes {
	//		assignees[i] = &AssignableUser{
	//			Login: string(p.Login),
	//		}
	//	}
	//	return assignees, resp.PageInfo
	//}

	setOpt := func(ui *SelectUI) {
		ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
			return event
		}
	}

	ui := NewSelectListUI(UIKindAssignee, tcell.ColorFuchsia, setOpt)
	AssigneesUI = ui
}


================================================
FILE: ui/comments.go
================================================
package ui

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/gdamore/tcell/v2"
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/domain"
	"github.com/skanehira/ght/github"
	"github.com/skanehira/ght/utils"
	"golang.org/x/sync/errgroup"
)

var CommentUI *SelectUI

func NewCommentUI() {
	setOpt := func(ui *SelectUI) {
		ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
			switch event.Rune() {
			case 'd':
				deleteComment()
			case 'n':
				item := IssueUI.GetSelect()
				if item == nil {
					return event
				}
				if err := createComment(item, ""); err != nil {
					UI.Message(err.Error(), func() {
						UI.app.SetFocus(CommentUI)
					})
				}
			case 'e':
				if err := editComment(); err != nil {
					UI.Message(err.Error(), func() {
						UI.app.SetFocus(CommentUI)
					})
				}
			case 'r':
				if err := quoteReply(); err != nil {
					UI.Message(err.Error(), func() {
						UI.app.SetFocus(CommentUI)
					})
				}
			}

			switch event.Key() {
			case tcell.KeyCtrlO:
				for _, comment := range getSelectedComments() {
					if err := utils.Open(comment.URL); err != nil {
						log.Println(err)
					}
				}
				CommentUI.ClearSelected()
				CommentUI.UpdateView()
			}

			return event
		}

		ui.header = []string{
			"",
			"Author",
			"UpdatedAt",
		}
		ui.hasHeader = len(ui.header) > 0
	}

	ui := NewSelectListUI(UIKindComment, tcell.ColorYellow, setOpt)

	ui.SetSelectionChangedFunc(func(row, col int) {
		if row > 0 {
			CommentViewUI.updateView(ui.items[row-1].(*domain.Comment).Body)
		}
	})

	CommentUI = ui
}

func quoteReply() error {
	item := CommentUI.GetSelect()
	if item == nil {
		return domain.ErrNotFoundComment
	}

	comment := item.(*domain.Comment)
	lines := strings.Split(comment.Body, "\n")
	for i := range lines {
		lines[i] = fmt.Sprintf("> %s", lines[i])
	}

	body := strings.Join(lines, "\n")

	item = IssueUI.GetSelect()
	if item == nil {
		return domain.ErrNotFoundIssue
	}
	if err := createComment(item, body); err != nil {
		return err
	}

	return nil
}

func createComment(item domain.Item, body string) error {
	if err := editCommentBody(&body); err != nil {
		return err
	}

	input := githubv4.AddCommentInput{
		SubjectID: githubv4.ID(item.Key()),
		Body:      githubv4.String(body),
	}

	if err := github.AddIssueComment(input); err != nil {
		return err
	}

	if err := updateCommentUI(); err != nil {
		return err
	}

	return nil
}

func deleteComment() {
	UI.Confirm("Do you want to delete comments?", "Yes", func() error {
		comments := getSelectedComments()
		if len(comments) == 0 {
			return nil
		}

		var eg errgroup.Group
		for _, comment := range comments {
			id := comment.ID
			eg.Go(func() error {
				return github.DeleteIssueComment(id)
			})
		}

		// When all the processing is completed this error be returned
		// because if some of delete action be success, need to update view
		deleteErr := eg.Wait()
		if deleteErr != nil {
			log.Println(deleteErr)
		}

		if err := updateCommentUI(); err != nil {
			return err
		}

		return deleteErr
	}, func() {
		UI.app.SetFocus(CommentUI)
	})
}

func editComment() error {
	item := CommentUI.GetSelect()
	if item == nil {
		return domain.ErrNotFoundComment
	}

	comment := item.(*domain.Comment)
	oldBody := comment.Body

	if err := editCommentBody(&comment.Body); err != nil {
		return err
	}

	// if comment body is not changed, do nothing
	if oldBody == comment.Body {
		return nil
	}

	input := githubv4.UpdateIssueCommentInput{
		ID:   githubv4.ID(comment.ID),
		Body: githubv4.String(comment.Body),
	}

	if err := github.UpdateIssueComment(input); err != nil {
		return err
	}

	if err := updateCommentUI(); err != nil {
		return err
	}
	return nil
}

func editCommentBody(body *string) (err error) {
	UI.app.Suspend(func() {
		err = utils.Edit(body)
	})
	if err != nil {
		return
	}

	if *body == "" {
		return domain.ErrCommentBodyIsEmpty
	}
	return
}

func getSelectedComments() []*domain.Comment {
	var comments []*domain.Comment
	if len(CommentUI.selected) == 0 {
		data := CommentUI.GetSelect()
		comments = append(comments, data.(*domain.Comment))
	} else {
		for _, item := range CommentUI.selected {
			comments = append(comments, item.(*domain.Comment))
		}
	}
	return comments
}

func updateCommentUI() error {
	item := IssueUI.GetSelect()
	if item == nil {
		return domain.ErrNotFoundIssue
	}
	oldIssue := item.(*domain.Issue)

	number, err := strconv.Atoi(oldIssue.Number)
	if err != nil {
		return err
	}
	m := map[string]interface{}{
		"owner":  githubv4.String(oldIssue.RepoOwner),
		"name":   githubv4.String(oldIssue.Repo),
		"number": githubv4.Int(number),
	}

	issue, err := github.GetIssue(m)
	if err != nil {
		return err
	}

	newIssue := issue.ToDomain()
	IssueUI.UpdateItem(newIssue)

	if len(newIssue.Comments) > 0 {
		CommentUI.SetList(newIssue.Comments)
		CommentViewUI.updateView(newIssue.Comments[0].(*domain.Comment).Body)
	} else {
		CommentUI.ClearView()
		CommentViewUI.Clear()
	}
	return nil
}


================================================
FILE: ui/filter.go
================================================
package ui

import (
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

var IssueFilterUI *FilterUI

type (
	SetFilterOpt func(ui *FilterUI)
	FilterUI     struct {
		*tview.InputField
	}
)

func NewFilterUI() {
	ui := &FilterUI{
		InputField: tview.NewInputField().SetLabel("Filters").SetLabelWidth(8),
	}
	ui.SetBorderPadding(0, 0, 1, 0)

	ui.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyEnter:
			go IssueUI.GetList()
		}
		return event
	})
	IssueFilterUI = ui
}

func (ui *FilterUI) SetQuery(query string) {
	ui.SetText(query)
}

func (ui *FilterUI) GetQuery() string {
	return ui.GetText()
}

func (ui *FilterUI) focus() {
}

func (ui *FilterUI) blur() {
}


================================================
FILE: ui/issues.go
================================================
package ui

import (
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/atotto/clipboard"
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
	"github.com/shurcooL/githubv4"
	"github.com/skanehira/ght/config"
	"github.com/skanehira/ght/domain"
	"github.com/skanehira/ght/github"
	"github.com/skanehira/ght/utils"
)

var IssueUI *SelectUI

func NewIssueUI() {
	opt := func(ui *SelectUI) {
		// initial query
		queries := []string{
			fmt.Sprintf("repo:%s/%s", config.GitHub.Owner, config.GitHub.Repo),
			"state:open",
		}

		IssueFilterUI.SetQuery(strings.Join(queries, " "))

		ui.getList = func(cursor *string) ([]domain.Item, *github.PageInfo) {
			var queries []string
			query := IssueFilterUI.GetQuery()

			if !strings.Contains(query, "is:issue") {
				queries = append(queries, "is:issue")
			}

			for _, q := range strings.Split(query, " ") {
				// execlude Pull request
				if strings.Contains(q, "type:pr") || strings.Contains(q, "is:pr") {
					continue
				}

				queries = append(queries, q)
			}
			query = strings.Join(queries, " ")
			IssueFilterUI.SetQuery(query)

			v := map[string]interface{}{
				"query":  githubv4.String(query),
				"first":  githubv4.Int(30),
				"cursor": (*githubv4.String)(cursor),
			}
			resp, err := github.GetIssues(v)
			if err != nil {
				log.Println(err)
				return nil, nil
			}

			issues := make([]domain.Item, len(resp.Nodes))
			for i, node := range resp.Nodes {
				issues[i] = node.Issue.ToDomain()
			}
			return issues, &resp.PageInfo
		}

		ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
			switch event.Rune() {
			case 'y':
				yankIssueURLs()
			case 'o':
				go openIssues()
			case 'c':
				go closeIssues()
			case 'n':
				createIssueForm()
			case 'e':
				editIssue()
			}
			switch event.Key() {
			case tcell.KeyCtrlO:
				openBrowser()
			}

			return event
		}

		ui.header = []string{
			"",
			"Repo",
			"Number",
			"State",
			"Author",
			"Title",
		}

		ui.hasHeader = len(ui.header) > 0
	}

	ui := NewSelectListUI(UIKindIssue, tcell.ColorBlue, opt)

	ui.SetSelectionChangedFunc(func(row, col int) {
		updateUIRelatedIssue(ui, row)
	})

	IssueUI = ui
}

func getSelectedIssues() []*domain.Issue {
	var issues []*domain.Issue
	if len(IssueUI.selected) == 0 {
		data := IssueUI.GetSelect()
		if data != nil {
			issues = append(issues, data.(*domain.Issue))
		}
	} else {
		for _, item := range IssueUI.selected {
			issues = append(issues, item.(*domain.Issue))
		}
	}
	return issues
}

func yankIssueURLs() {
	var urls []string
	for _, issue := range getSelectedIssues() {
		urls = append(urls, issue.URL)
	}

	url := strings.Join(urls, "\n")
	if err := clipboard.WriteAll(url); err != nil {
		log.Println(err)
	}
	IssueUI.ClearSelected()
	IssueUI.UpdateView()
}

func openIssues() {
	var wg sync.WaitGroup
	for _, issue := range getSelectedIssues() {
		wg.Add(1)
		go func(issue *domain.Issue) {
			defer wg.Done()
			if err := github.ReopenIssue(issue.ID); err != nil {
				log.Println(err)
				return
			}
			issue.State = "OPEN"
		}(issue)
	}
	wg.Wait()
	IssueUI.ClearSelected()
	IssueUI.UpdateView()
}

func closeIssues() {
	var wg sync.WaitGroup
	for _, issue := range getSelectedIssues() {
		wg.Add(1)
		go func(issue *domain.Issue) {
			defer wg.Done()
			if err := github.CloseIssue(issue.ID); err != nil {
				log.Println(err)
				return
			}
			issue.State = "CLOSED"
		}(issue)
	}
	wg.Wait()
	IssueUI.ClearSelected()
	IssueUI.UpdateView()
}

func openBrowser() {
	for _, issue := range getSelectedIssues() {
		if err := utils.Open(issue.URL); err != nil {
			log.Println(err)
		}
	}
	IssueUI.ClearSelected()
	IssueUI.UpdateView()

}

func createIssueForm() {
	// repo
	var repo string
	input := IssueFilterUI.GetQuery()
	for _, word := range strings.Split(input, " ") {
		if strings.Contains(word, "repo:") {
			repo = strings.TrimPrefix(word, "repo:")
			break
		}
	}

	if repo == "" {
		return
	}

	form := tview.NewForm()
	form.SetBorder(true)
	form.SetTitle("New issue")
	form.SetTitleAlign(tview.AlignLeft)
	inputWidth := 70

	repoInput := tview.NewInputField().SetLabel("Repository").
		SetText(repo).SetLabelWidth(inputWidth).
		SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
			return false
		})
	repoInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		if event.Key() == tcell.KeyCtrlN {
			form.SetFocus(2)
		}
		return event
	})

	form.AddFormItem(repoInput)

	s := strings.Split(repoInput.GetText(), "/")
	owner := s[0]
	name := s[1]

	var repoID githubv4.ID
	resp, err := github.GetRepo(map[string]interface{}{
		"owner": githubv4.String(owner),
		"name":  githubv4.String(name),
	})
	if err != nil {
		UI.Message(err.Error(), func() {
			UI.app.SetFocus(IssueUI)
		})
		return
	}
	repoID = resp.ID

	form.SetFocus(1)

	// autocomplete for assignees, labels, projects, milestones
	autocompleteFunc := func(text string, items []string) []string {
		if text == "" {
			return nil
		}

		words := strings.Split(text, ",")
		word := words[len(words)-1]

		var results []string
		for _, l := range items {
			var isDuplicate bool
			for _, w := range words[:len(words)-1] {
				if w == l {
					isDuplicate = true
					break
				}
			}

			if isDuplicate {
				continue
			}

			if strings.Contains(strings.ToLower(l), strings.ToLower(word)) {
				words = append(words[:len(words)-1], l)
				results = append(results, strings.Join(words, ","))
			}
		}
		return results
	}

	// graphql query variables
	v := map[string]interface{}{
		"owner":  githubv4.String(owner),
		"name":   githubv4.String(name),
		"first":  githubv4.Int(100),
		"cursor": (*githubv4.String)(nil),
	}

	// title
	titleInput := tview.NewInputField().SetLabel("Title").SetLabelWidth(inputWidth)
	form.AddFormItem(titleInput)

	// assignees
	assigneesInput := tview.NewInputField().SetLabel("Assignees").SetLabelWidth(inputWidth)
	form.AddFormItem(assigneesInput)
	userMap := map[string]githubv4.ID{}
	go func() {
		resp, err := github.GetRepoAssignableUsers(v)
		if err != nil {
			log.Println(err)
			return
		}

		if len(resp.Nodes) == 0 {
			UI.app.QueueUpdateDraw(func() {
				form.RemoveFormItem(2)
			})
			return
		}

		var users []string
		for _, u := range resp.Nodes {
			name := string(u.Login)
			userMap[name] = u.ID
			users = append(users, name)
		}
		assigneesInput.SetAutocompleteFunc(func(text string) []string {
			return autocompleteFunc(text, users)
		})
	}()

	// labels
	labelInput := tview.NewInputField().SetLabel("Labels").SetLabelWidth(inputWidth)
	labelMap := map[string]githubv4.ID{}
	form.AddFormItem(labelInput)
	go func() {
		resp, err := github.GetRepoLabels(v)
		if err != nil {
			log.Println(err)
			return
		}

		if len(resp.Nodes) == 0 {
			UI.app.QueueUpdateDraw(func() {
				form.RemoveFormItem(3)
			})
			return
		}

		var labels []string
		for _, l := range resp.Nodes {
			name := string(l.Name)
			labelMap[name] = l.ID
			labels = append(labels, name)
		}
		labelInput.SetAutocompleteFunc(func(text string) []string {
			return autocompleteFunc(text, labels)
		})
	}()

	// projects
	projectInput := tview.NewInputField().SetLabel("Projects").SetLabelWidth(inputWidth)
	projectMap := map[string]githubv4.ID{}
	form.AddFormItem(projectInput)
	go func() {
		resp, err := github.GetRepoProjects(v)
		if err != nil {
			log.Println(err)
			return
		}

		if len(resp.Nodes) == 0 {
			UI.app.QueueUpdateDraw(func() {
				form.RemoveFormItem(4)
			})
			return
		}

		var projects []string
		for _, project := range resp.Nodes {
			name := string(project.Name)
			projectMap[name] = project.ID
			projects = append(projects, name)
		}

		projectInput.SetAutocompleteFunc(func(text string) []string {
			return autocompleteFunc(text, projects)
		})
	}()

	// milestones
	milestoneDropDown := tview.NewDropDown().SetLabel("MileStone").SetLabelWidth(inputWidth)
	var milestoneID *githubv4.ID
	form.AddFormItem(milestoneDropDown)
	go func() {
		resp, err := github.GetRepoMillestones(v)
		if err != nil {
			log.Println(err)
			return
		}

		if len(resp.Nodes) == 0 {
			UI.app.QueueUpdateDraw(func() {
				form.RemoveFormItem(5)
			})
			return
		}

		milestones := map[string]*githubv4.ID{}
		var titles []string
		for _, milestone := range resp.Nodes {
			title := string(milestone.Title)
			milestones[title] = &milestone.ID
			titles = append(titles, title)
		}
		milestoneDropDown.SetOptions(titles, func(text string, index int) {
			milestoneID = milestones[text]
		})
	}()

	var issueBody string
	templateDropDown := tview.NewDropDown().SetLabel("Template").SetLabelWidth(inputWidth)
	go func() {
		v := map[string]interface{}{
			"owner": githubv4.String(owner),
			"name":  githubv4.String(name),
		}
		resp, err := github.GetIssueTemplates(v)
		if err != nil {
			log.Println(err)
			return
		}
		if len(resp) == 0 {
			return
		}

		issueTemplates := map[string]string{}
		var names []string
		for _, te := range resp {
			issueTemplates[string(te.Name)] = string(te.Body)
			names = append(names, string(te.Name))
		}

		templateDropDown.SetOptions(names, func(text string, index int) {
			issueBody = issueTemplates[text]
		})
		UI.app.QueueUpdateDraw(func() {
			form.AddFormItem(templateDropDown)
		})
	}()

	form.AddButton("Edit Body", func() {
		UI.app.Suspend(func() {
			if err := utils.Edit(&issueBody); err != nil {
				log.Println(err)
				return
			}
		})
	})
	form.AddButton("Create", func() {
		input := githubv4.CreateIssueInput{
			Title:        githubv4.String(titleInput.GetText()),
			RepositoryID: repoID,
		}
		if milestoneID != nil {
			input.MilestoneID = milestoneID
		}

		// get assignee users
		var userIDs []githubv4.ID
		if text := assigneesInput.GetText(); text != "" {
			for _, name := range strings.Split(text, ",") {
				if name == "" {
					continue
				}
				userIDs = append(userIDs, userMap[name])
			}
			input.AssigneeIDs = &userIDs
		}

		// get labels
		var labelIDs []githubv4.ID
		if text := labelInput.GetText(); text != "" {
			for _, name := range strings.Split(text, ",") {
				if name == "" {
					continue
				}
				labelIDs = append(labelIDs, labelMap[name])
			}
			input.LabelIDs = &labelIDs
		}

		// get projects
		var projectIDs []githubv4.ID
		if text := projectInput.GetText(); text != "" {
			for _, name := range strings.Split(text, ",") {
				if name == "" {
					continue
				}
				projectIDs = append(projectIDs, projectMap[name])
			}
			input.ProjectIDs = &projectIDs
		}

		body := githubv4.String(issueBody)
		input.Body = &body

		if err := github.CreateIssue(input); err != nil {
			UI.Message(err.Error(), func() {
				UI.pages.SwitchToPage("form").ShowPage("main")
			})
		} else {
			UI.pages.RemovePage("form").ShowPage("main")
			UI.app.SetFocus(IssueUI)
			go func() {
				time.Sleep(1 * time.Second)
				IssueUI.GetList()
			}()
		}
	})
	form.AddButton("Cancel", func() {
		UI.pages.RemovePage("form").ShowPage("main")
		UI.app.SetFocus(IssueUI)
	})

	form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyCtrlN:
			k := tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
			UI.app.QueueEvent(k)
		case tcell.KeyCtrlP:
			k := tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
			UI.app.QueueEvent(k)
		}
		return event
	})

	UI.pages.AddAndSwitchToPage("form", UI.Modal(form, 100, 19), true).ShowPage("main")
}

func editIssue() {
	item := IssueUI.GetSelect()
	if item == nil {
		return
	}
	issue := item.(*domain.Issue)

	focus := func() {
		UI.app.SetFocus(IssueUI)
	}

	var err error
	old := issue.Body
	UI.app.Suspend(func() {
		err = utils.Edit(&issue.Body)
	})
	if err != nil {
		UI.Message(err.Error(), focus)
		return
	}

	// if issue body not edited do nothing
	if old == issue.Body {
		return
	}

	input := githubv4.UpdateIssueInput{
		ID:   githubv4.ID(issue.ID),
		Body: githubv4.NewString(githubv4.String(issue.Body)),
	}
	if err := github.UpdateIssue(input); err != nil {
		UI.Message(err.Error(), focus)
		return
	}

	IssueViewUI.updateView(issue.Body)
}

func updateUIRelatedIssue(ui *SelectUI, row int) {
	if row > 0 && row <= len(ui.items) {
		issue := ui.items[row-1].(*domain.Issue)
		IssueViewUI.updateView(issue.Body)

		if len(issue.Comments) > 0 {
			CommentUI.SetList(issue.Comments)
			CommentViewUI.updateView(issue.Comments[0].(*domain.Comment).Body)
		} else {
			CommentUI.ClearView()
			CommentViewUI.Clear()
		}

		if len(issue.Assignees) > 0 {
			AssigneesUI.SetList(issue.Assignees)
		} else {
			AssigneesUI.ClearView()
		}

		if len(issue.Labels) > 0 {
			LabelUI.SetList(issue.Labels)
		} else {
			LabelUI.ClearView()
		}

		if len(issue.MileStone) > 0 {
			MilestoneUI.SetList(issue.MileStone)
		} else {
			MilestoneUI.ClearView()
		}

		if len(issue.Projects) > 0 {
			ProjectUI.SetList(issue.Projects)
		} else {
			ProjectUI.ClearView()
		}
	}
}


================================================
FILE: ui/labels.go
================================================
package ui

import (
	"github.com/gdamore/tcell/v2"
)

var LabelUI *SelectUI

func NewLabelsUI() {
	//getList := func(cursor *string) ([]List, github.PageInfo) {
	//	v := map[string]interface{}{
	//		"owner":  githubv4.String(config.GitHub.Owner),
	//		"name":   githubv4.String(config.GitHub.Repo),
	//		"first":  githubv4.Int(100),
	//		"cursor": (*githubv4.String)(cursor),
	//	}
	//	resp, err := github.GetRepoLabels(v)
	//	if err != nil {
	//		log.Println(err)
	//		return nil, github.PageInfo{}
	//	}

	//	labels := make([]List, len(resp.Nodes))
	//	for i, l := range resp.Nodes {
	//		name := string(l.Name)
	//		description := string(l.Description)
	//		labels[i] = &Label{
	//			Name:        name,
	//			Description: description,
	//		}
	//	}
	//	return labels, resp.PageInfo
	//}

	setOpt := func(ui *SelectUI) {
		ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
			return event
		}
	}

	ui := NewSelectListUI(UIKindLabel, tcell.ColorLightYellow, setOpt)
	LabelUI = ui
}


================================================
FILE: ui/milestones.go
================================================
package ui

import (
	"log"

	"github.com/gdamore/tcell/v2"
	"github.com/skanehira/ght/domain"
	"github.com/skanehira/ght/utils"
)

var MilestoneUI *SelectUI

func NewMilestoneUI() {
	//getList := func(cursor *string) ([]List, github.PageInfo) {
	//	v := map[string]interface{}{
	//		"owner":  githubv4.String(config.GitHub.Owner),
	//		"name":   githubv4.String(config.GitHub.Repo),
	//		"first":  githubv4.Int(100),
	//		"cursor": (*githubv4.String)(cursor),
	//	}
	//	resp, err := github.GetRepoMillestones(v)
	//	if err != nil {
	//		return nil, github.PageInfo{}
	//	}

	//	milestones := make([]List, len(resp.Nodes))
	//	for i, m := range resp.Nodes {
	//		milestones[i] = &Milestone{
	//			Title: string(m.Title),
	//		}
	//	}

	//	return milestones, resp.PageInfo
	//}

	setOpt := func(ui *SelectUI) {

		ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
			switch event.Key() {
			case tcell.KeyCtrlO:
				var urls []string
				if len(MilestoneUI.selected) == 0 {
					data := MilestoneUI.GetSelect()
					if data != nil {
						urls = append(urls, data.(*domain.Milestone).URL)
					}
				} else {
					for _, s := range MilestoneUI.selected {
						urls = append(urls, s.(*domain.Milestone).URL)
					}
				}

				for _, url := range urls {
					if err := utils.Open(url); err != nil {
						log.Println(err)
					}
				}
			}
			return event
		}
	}

	ui := NewSelectListUI(UIKindMilestones, tcell.ColorGreen, setOpt)
	MilestoneUI = ui
}


================================================
FILE: ui/projects.go
================================================
package ui

import (
	"log"

	"github.com/gdamore/tcell/v2"
	"github.com/skanehira/ght/domain"
	"github.com/skanehira/ght/utils"
)

var ProjectUI *SelectUI

func NewProjectUI() {
	//getList := func(cursor *string) ([]List, github.PageInfo) {
	//	v := map[string]interface{}{
	//		"owner":  githubv4.String(config.GitHub.Owner),
	//		"name":   githubv4.String(config.GitHub.Repo),
	//		"first":  githubv4.Int(100),
	//		"cursor": (*githubv4.String)(cursor),
	//	}
	//	resp, err := github.GetRepoProjects(v)
	//	if err != nil {
	//		return nil, github.PageInfo{}
	//	}

	//	projects := make([]List, len(resp.Nodes))
	//	for i, m := range resp.Nodes {
	//		projects[i] = &Project{
	//			Name: string(m.Name),
	//		}
	//	}

	//	return projects, resp.PageInfo
	//}

	setOpt := func(ui *SelectUI) {
		ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
			switch event.Key() {
			case tcell.KeyCtrlO:
				var urls []string
				if len(ProjectUI.selected) == 0 {
					data := ProjectUI.GetSelect()
					if data != nil {
						urls = append(urls, data.(*domain.Project).URL)
					}
				} else {
					for _, s := range ProjectUI.selected {
						urls = append(urls, s.(*domain.Project).URL)
					}
				}

				for _, url := range urls {
					if err := utils.Open(url); err != nil {
						log.Println(err)
					}
				}
			}
			return event
		}
	}

	ui := NewSelectListUI(UIKindProject, tcell.ColorLightSalmon, setOpt)
	ProjectUI = ui
}


================================================
FILE: ui/search.go
================================================
package ui

import (
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

var SearchUI *searchUI

type SearchFunc func(text string)

type searchUI struct {
	*tview.InputField
	SearchFunc SearchFunc
	FocusFunc  func()
}

func NewSearchUI() {
	ui := &searchUI{
		InputField: tview.NewInputField(),
		SearchFunc: func(text string) {},
		FocusFunc:  func() {},
	}

	ui.SetDoneFunc(func(key tcell.Key) {
		ui.FocusFunc()
	})

	SearchUI = ui
}

func (s *searchUI) SetSerachFunc(f SearchFunc) {
	s.SetChangedFunc(f)
}

func (s *searchUI) SetFocusFunc(f func()) {
	s.FocusFunc = f
}

func (s *searchUI) focus() {

}

func (s *searchUI) blur() {

}


================================================
FILE: ui/select.go
================================================
package ui

import (
	"strings"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
	"github.com/skanehira/ght/domain"
	"github.com/skanehira/ght/github"
)

const (
	unselected = "\u25ef"
	selected   = "\u25c9"
)

type UIKind string

const (
	UIKindIssue       UIKind = "issues"
	UIKindAssignee           = "assignees"
	UIKindComment            = "comments"
	UIKindLabel              = "labels"
	UIKindMilestones         = "milestones"
	UIKindProject            = "projects"
	UIKindIssueView          = "issue preview"
	UIKindCommentView        = "comment preview"
	UIKindCommonView         = "preview"
)

type (
	SetSelectUIOpt func(ui *SelectUI)
	GetListFunc    func(cursor *string) ([]domain.Item, *github.PageInfo)
	CaptureFunc    func(event *tcell.EventKey) *tcell.EventKey
)

type SelectUI struct {
	uiKind      UIKind
	cursor      *string
	hasNext     bool
	getList     GetListFunc
	capture     CaptureFunc
	header      []string
	hasHeader   bool
	originItems []domain.Item
	items       []domain.Item
	selected    map[string]domain.Item
	boxColor    tcell.Color
	searchWord  string
	*tview.Table
}

func NewSelectListUI(uiKind UIKind, boxColor tcell.Color, setOpt SetSelectUIOpt) *SelectUI {
	ui := &SelectUI{
		uiKind:   uiKind,
		hasNext:  true,
		selected: make(map[string]domain.Item),
		boxColor: boxColor,
		Table:    tview.NewTable().SetSelectable(false, false),
	}

	ui.SetBorder(true).SetTitle(string(uiKind)).SetTitleAlign(tview.AlignLeft)
	ui.SetBorderColor(boxColor)

	setOpt(ui)

	go ui.Init()
	return ui
}

func (ui *SelectUI) GetList() {
	if ui.getList != nil {
		list, pageInfo := ui.getList(nil)
		if pageInfo != nil {
			ui.hasNext = bool(pageInfo.HasNextPage)
			cursor := string(pageInfo.EndCursor)
			ui.originItems = list
			ui.cursor = &cursor
			ui.Select(0, 0)
			ui.UpdateView()
		}
	}
}

func (ui *SelectUI) SetList(list []domain.Item) {
	ui.originItems = list
	ui.selected = make(map[string]domain.Item)
	ui.Select(0, 0)
	ui.UpdateView()
}

func (ui *SelectUI) FetchList() {
	if ui.hasNext && ui.getList != nil {
		list, pageInfo := ui.getList(ui.cursor)
		ui.hasNext = bool(pageInfo.HasNextPage)
		cursor := string(pageInfo.EndCursor)
		ui.originItems = append(ui.originItems, list...)
		ui.cursor = &cursor
		ui.UpdateView()
	}
}

func (ui *SelectUI) UpdateView() {
	UI.updater <- func() {
		ui.Clear()
		for i, h := range ui.header {
			ui.SetCell(0, i, &tview.TableCell{
				Text:            h,
				NotSelectable:   true,
				Align:           tview.AlignLeft,
				Color:           tcell.ColorWhite,
				BackgroundColor: tcell.ColorDefault,
				Attributes:      tcell.AttrBold | tcell.AttrUnderline,
			})
		}

		if len(ui.originItems) < 1 {
			return
		}

		h := 0
		if ui.hasHeader {
			h++
			ui.SetFixed(1, 0)
		}

		selectColor := ui.originItems[0].Fields()[0].Color

		ui.items = []domain.Item{}
		if ui.searchWord != "" {
			for _, data := range ui.originItems {
				for _, f := range data.Fields() {
					if strings.Contains(strings.ToLower(f.Text), strings.ToLower(ui.searchWord)) {
						ui.items = append(ui.items, data)
						break
					}
				}
			}
		} else {
			ui.items = ui.originItems
		}

		for i, data := range ui.items {
			if _, ok := ui.selected[data.Key()]; ok {
				ui.SetCell(i+h, 0, tview.NewTableCell(selected).SetTextColor(selectColor))
			} else {
				ui.SetCell(i+h, 0, tview.NewTableCell(unselected).SetTextColor(selectColor))
			}
			for j, f := range data.Fields() {
				ui.SetCell(i+h, j+1, tview.NewTableCell(f.Text).SetTextColor(f.Color))
			}
		}
		ui.ScrollToBeginning()

		// when update filter, then update ui related issue primitives
		if ui.uiKind == UIKindIssue {
			row, _ := ui.GetSelection()
			if row == 0 {
				row = 1
			}
			updateUIRelatedIssue(ui, row)
		}
	}
}

func (ui *SelectUI) Init() {
	ui.GetList()

	searchFunc := func(text string) {
		ui.searchWord = text
		ui.UpdateView()
	}

	ui.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyCtrlJ:
			row, col := ui.GetSelection()
			max := len(ui.items)
			if ui.hasHeader {
				max++
			}
			if row < max {
				ui.toggleSelected(row)
			}

			if row+1 < max {
				ui.Select(row+1, col)
			}
		case tcell.KeyCtrlK:
			row, col := ui.GetSelection()
			min := 0
			if ui.hasHeader {
				min++
			}
			if row > min {
				ui.toggleSelected(row - 1)
			}
			if row > min {
				ui.Select(row-1, col)
			}
		}

		switch event.Rune() {
		case 'f':
			go ui.FetchList()
		case '/':
			SearchUI.SetSerachFunc(searchFunc)
			SearchUI.SetFocusFunc(func() {
				UI.app.SetFocus(ui)
			})
			UI.app.SetFocus(SearchUI)
		}

		return ui.capture(event)
	})
}

func (ui *SelectUI) toggleSelected(row int) {
	var data domain.Item
	if ui.hasHeader {
		data = ui.items[row-1]
	} else {
		data = ui.items[row]
	}
	selectColor := ui.items[0].Fields()[0].Color
	if _, ok := ui.selected[data.Key()]; ok {
		delete(ui.selected, data.Key())
		ui.SetCell(row, 0, tview.NewTableCell(unselected).SetTextColor(selectColor))
	} else {
		ui.selected[data.Key()] = data
		ui.SetCell(row, 0, tview.NewTableCell(selected).SetTextColor(selectColor))
	}
}

func (ui *SelectUI) UpdateItem(item domain.Item) {
	for i, t := range ui.originItems {
		if t.Key() == item.Key() {
			ui.originItems[i] = item
		}
	}
}

func (ui *SelectUI) GetSelect() domain.Item {
	row, _ := ui.GetSelection()
	if ui.hasHeader {
		row = row - 1
	}
	if len(ui.items) > row {
		id := ui.items[row].Key()
		for _, item := range ui.originItems {
			if item.Key() == id {
				return item
			}
		}
	}
	return nil
}

func (ui *SelectUI) focus() {
	ui.SetSelectable(true, false)
}

func (ui *SelectUI) blur() {
	ui.SetSelectable(false, false)
}

func (ui *SelectUI) ClearView() {
	ui.Clear()
	ui.ClearSelected()
}

func (ui *SelectUI) ClearSelected() {
	ui.selected = make(map[string]domain.Item)
}


================================================
FILE: ui/ui.go
================================================
package ui

import (
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
)

var (
	UI *ui
)

type Primitive interface {
	focus()
	blur()
	tview.Primitive
}

type ui struct {
	app          *tview.Application
	pages        *tview.Pages
	current      int
	primitives   []Primitive
	primitiveLen int
	updater      chan func()
}

func New() *ui {
	ui := &ui{
		app: tview.NewApplication(),
	}

	ui.updater = make(chan func(), 100)

	UI = ui

	return ui
}

func (ui *ui) canFocus() bool {
	fs := ui.app.GetFocus()
	if fs == nil {
		return false
	}
	switch fs.(type) {
	case *FilterUI, *SelectUI, *ViewUI:
		return true
	}
	return false
}

func (ui *ui) toNextUI() {
	if !ui.canFocus() {
		return
	}
	ui.primitives[ui.current].blur()
	if ui.primitiveLen-1 > ui.current {
		ui.current++
	} else {
		ui.current = 0
	}
	p := ui.primitives[ui.current]
	p.focus()
	ui.app.SetFocus(p)
}

func (ui *ui) toPrevUI() {
	if !ui.canFocus() {
		return
	}
	ui.primitives[ui.current].blur()
	if ui.current == 0 {
		ui.current = ui.primitiveLen - 1
	} else {
		ui.current--
	}
	p := ui.primitives[ui.current]
	p.focus()
	ui.app.SetFocus(p)
}

func (ui *ui) Modal(p tview.Primitive, width, height int) tview.Primitive {
	return tview.NewGrid().
		SetColumns(0, width, 0).
		SetRows(0, height, 0).
		AddItem(p, 1, 1, 1, 1, 0, 0, true)
}

func (ui *ui) Message(msg string, focusFunc func()) {
	modal := tview.NewModal().
		SetText(msg).
		AddButtons([]string{"OK"}).
		SetDoneFunc(func(_ int, _ string) {
			ui.pages.RemovePage("message").ShowPage("main")
			focusFunc()
		})
	ui.pages.AddAndSwitchToPage("message", ui.Modal(modal, 80, 29), true).ShowPage("main")
}

func (ui *ui) FullScreenPreview(contents string, focus func()) {
	CommonViewUI.SetText(contents).ScrollToBeginning()
	CommonViewUI.setFocus = focus
	grid := tview.NewGrid().SetRows(0, 1).
		AddItem(CommonViewUI, 0, 0, 1, 1, 0, 0, true)
	ui.pages.AddAndSwitchToPage("fullScreenPreview", grid, true).ShowPage("main")
}

func (ui *ui) Confirm(msg, doLabel string, doFunc func() error, focusFunc func()) {
	modal := tview.NewModal().
		SetText(msg).
		AddButtons([]string{doLabel, "Cancel"}).
		SetDoneFunc(func(_ int, buttonLabel string) {
			ui.pages.RemovePage("modal").ShowPage("main")
			focusFunc()
			if buttonLabel == doLabel {
				if err := doFunc(); err != nil {
					ui.Message(err.Error(), func() {
						focusFunc()
					})
				}
			}
		})
	ui.pages.AddAndSwitchToPage("modal", ui.Modal(modal, 80, 29), true).ShowPage("main")
}

func (ui *ui) Start() error {
	NewFilterUI()
	NewViewUI(UIKindIssueView)
	NewViewUI(UIKindCommentView)
	NewViewUI(UIKindCommonView)
	NewIssueUI()
	NewLabelsUI()
	NewMilestoneUI()
	NewProjectUI()
	NewAssignableUI()
	NewCommentUI()
	NewSearchUI()

	ui.primitives = []Primitive{IssueFilterUI, AssigneesUI, LabelUI, MilestoneUI,
		ProjectUI, IssueUI, IssueViewUI, CommentUI, CommentViewUI}
	ui.primitiveLen = len(ui.primitives)

	// for readability
	row, col, rowSpan, colSpan := 0, 0, 0, 0

	grid := tview.NewGrid().SetRows(1, 0, 0, 0, 0, 0, 0, 0, 0, 1).
		AddItem(IssueFilterUI, row, col, rowSpan+1, colSpan+3, 0, 0, true).
		AddItem(IssueUI, row+1, col+1, rowSpan+4, colSpan+3, 0, 0, true).
		AddItem(AssigneesUI, row+1, col, rowSpan+1, colSpan+1, 0, 0, true).
		AddItem(LabelUI, row+2, col, rowSpan+1, colSpan+1, 0, 0, true).
		AddItem(MilestoneUI, row+3, col, rowSpan+1, colSpan+1, 0, 0, true).
		AddItem(ProjectUI, row+4, col, rowSpan+1, colSpan+1, 0, 0, true).
		AddItem(CommentUI, row+5, col, rowSpan+4, colSpan+4, 0, 0, true).
		AddItem(IssueViewUI, row+1, col+4, rowSpan+4, colSpan+3, 0, 0, true).
		AddItem(CommentViewUI, row+5, col+4, rowSpan+4, colSpan+3, 0, 0, true).
		AddItem(SearchUI, row+9, col, rowSpan+1, colSpan+7, 0, 0, true)

	ui.pages = tview.NewPages().
		AddAndSwitchToPage("main", grid, true)

	ui.app.SetRoot(ui.pages, true)

	ui.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyCtrlN:
			UI.toNextUI()
		case tcell.KeyCtrlP:
			UI.toPrevUI()
		case tcell.KeyCtrlG:
			ui.primitives[ui.current].blur()
			ui.current = 5
			p := ui.primitives[ui.current]
			p.focus()
			ui.app.SetFocus(IssueUI)
		case tcell.KeyCtrlT:
			ui.primitives[ui.current].blur()
			ui.current = 0
			p := ui.primitives[ui.current]
			p.focus()
			ui.app.SetFocus(IssueFilterUI)
		}
		return event
	})

	ui.current = 5
	ui.app.SetFocus(IssueUI)
	IssueUI.focus()

	go func() {
		for f := range UI.updater {
			go ui.app.QueueUpdateDraw(f)
		}
	}()

	if err := ui.app.Run(); err != nil {
		ui.app.Stop()
		return err
	}

	return nil
}


================================================
FILE: ui/view.go
================================================
package ui

import (
	"strconv"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
	"github.com/skanehira/ght/utils"
)

var (
	IssueViewUI   *ViewUI
	CommentViewUI *ViewUI
	CommonViewUI  *ViewUI
)

type ViewUI struct {
	*tview.TextView
	regionIndex  int
	regionLength int
	regionIDs    []string
	uiKind       UIKind
	setFocus     func()
}

func NewViewUI(uiKind UIKind) {
	ui := &ViewUI{
		TextView: tview.NewTextView(),
		uiKind:   uiKind,
	}

	ui.SetBorder(true).SetTitle(string(uiKind)).SetTitleAlign(tview.AlignLeft)
	ui.SetDynamicColors(true).SetWordWrap(false).SetRegions(true)
	ui.SetBorderPadding(1, 1, 1, 1)

	var setFocus func()

	switch uiKind {
	case UIKindIssueView:
		IssueViewUI = ui
		setFocus = func() {
			UI.app.SetFocus(IssueViewUI)
		}
	case UIKindCommentView:
		CommentViewUI = ui
		setFocus = func() {
			UI.app.SetFocus(CommentViewUI)
		}
	case UIKindCommonView:
		CommonViewUI = ui
	}

	searchFunc := func(input string) {
		text := ui.GetText(true)
		if input != "" {
			ui.regionIDs, text = utils.Replace(text, input, `[#ff0000]["%d"]`+input+`[""][white]`, -1)
			ui.regionLength = len(ui.regionIDs)
			if ui.regionLength > 0 {
				ui.regionIndex = 0
				ui.Highlight(ui.regionIDs[0]).ScrollToHighlight()
			}
		}

		go ui.updateView(text)
	}

	ui.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Rune() {
		case '/':
			SearchUI.SetText("")
			SearchUI.SetSerachFunc(searchFunc)
			SearchUI.SetFocusFunc(func() {
				UI.app.SetFocus(ui)
			})
			UI.app.SetFocus(SearchUI)
		case 'n':
			if ui.regionLength > 0 {
				ui.regionIndex = (ui.regionIndex + 1) % ui.regionLength
				ui.Highlight(strconv.Itoa(ui.regionIndex)).ScrollToHighlight()
			}
		case 'N':
			if ui.regionLength > 0 {
				ui.regionIndex = (ui.regionIndex - 1 + ui.regionLength) % ui.regionLength
				ui.Highlight(strconv.Itoa(ui.regionIndex)).ScrollToHighlight()
			}
		case 'o':
			if ui.uiKind == UIKindCommonView {
				UI.pages.SwitchToPage("main")
				ui.setFocus()
				return event
			}
			UI.FullScreenPreview(ui.GetText(true), setFocus)
		}

		//switch event.Key() {
		//}
		return event
	})

}

func (ui *ViewUI) updateView(text string) {
	UI.updater <- func() {
		ui.SetText(text).ScrollToBeginning()
		//out, err := glamour.Render(text, "dark")
		//if err != nil {
		//	out = err.Error()
		//}
		//ui.SetText(tview.TranslateANSI(out)).ScrollToBeginning()
	}
}

func (v *ViewUI) focus() {}

func (v *ViewUI) blur() {}


================================================
FILE: utils/open.go
================================================
package utils

import (
	"errors"
	"os/exec"
	"runtime"
	"strings"
)

func Open(url string) error {
	args := []string{}
	switch runtime.GOOS {
	case "windows":
		r := strings.NewReplacer("&", "^&")
		args = []string{"cmd", "start", "/", r.Replace(url)}
	case "linux":
		args = []string{"xdg-open", url}
	case "darwin":
		args = []string{"open", url}
	}

	out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
	if err != nil {
		return errors.New(string(out))
	}
	return nil
}


================================================
FILE: utils/strings.go
================================================
package utils

import (
	"fmt"
	"strconv"
	"strings"
	"unicode/utf8"
)

// Replace is customized for this project
// https://github.com/golang/go/blob/a8942d2cffd80c68febe1c908a0eb464d2f5bb40/src/strings/strings.go#L924
func Replace(s, old, new string, n int) ([]string, string) {
	if old == new || n == 0 {
		return nil, s // avoid allocation
	}

	// Compute number of replacements.
	if m := strings.Count(s, old); m == 0 {
		return nil, s // avoid allocation
	} else if n < 0 || m < n {
		n = m
	}

	// Apply replacements to buffer.
	t := make([]byte, len(s)+n*(len(new)-len(old)))
	w := 0
	start := 0
	var regionIDs []string
	for i := 0; i < n; i++ {
		j := start
		if len(old) == 0 {
			if i > 0 {
				_, wid := utf8.DecodeRuneInString(s[start:])
				j += wid
			}
		} else {
			j += strings.Index(s[start:], old)
		}
		w += copy(t[w:], s[start:j])
		w += copy(t[w:], fmt.Sprintf(new, i))
		regionIDs = append(regionIDs, strconv.Itoa(i))
		start = j + len(old)
	}
	w += copy(t[w:], s[start:])
	return regionIDs, string(t[0:w])
}


================================================
FILE: utils/utils.go
================================================
package utils

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
)

func Edit(contents *string) error {
	f, err := ioutil.TempFile("", "*.md")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	if *contents != "" {
		if _, err := io.Copy(f, strings.NewReader(*contents)); err != nil {
			log.Println(err)
		}
	}
	f.Close()

	editor := os.Getenv("EDITOR")
	if editor == "" {
		editor = "vim"
	}
	cmd := exec.Command(editor, f.Name())
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return err
	}

	b, err := ioutil.ReadFile(f.Name())
	if err != nil {
		return err
	}

	newContents := string(b)
	*contents = newContents
	return nil
}
Download .txt
gitextract_8momnkvm/

├── LICENSE
├── README.md
├── cmd/
│   └── ght/
│       └── main.go
├── config/
│   └── config.go
├── domain/
│   ├── assignees.go
│   ├── comment.go
│   ├── error.go
│   ├── issue.go
│   ├── item.go
│   ├── label.go
│   ├── milestone.go
│   └── project.go
├── github/
│   ├── client.go
│   ├── mutation_comment.go
│   ├── mutation_issue.go
│   ├── query.go
│   ├── query_assignees.go
│   ├── query_comment.go
│   ├── query_issue.go
│   ├── query_label.go
│   ├── query_milestone.go
│   ├── query_project.go
│   └── query_repository.go
├── go.mod
├── go.sum
├── ui/
│   ├── assignees.go
│   ├── comments.go
│   ├── filter.go
│   ├── issues.go
│   ├── labels.go
│   ├── milestones.go
│   ├── projects.go
│   ├── search.go
│   ├── select.go
│   ├── ui.go
│   └── view.go
└── utils/
    ├── open.go
    ├── strings.go
    └── utils.go
Download .txt
SYMBOL INDEX (157 symbols across 34 files)

FILE: cmd/ght/main.go
  type Repo (line 15) | type Repo struct
  function main (line 20) | func main() {
  function getRepoInfo (line 29) | func getRepoInfo() {
  function getOwnerRepo (line 48) | func getOwnerRepo() (*Repo, error) {
  function parseRemote (line 63) | func parseRemote(remote string) (*Repo, error) {

FILE: config/config.go
  type github (line 12) | type github struct
  type app (line 18) | type app struct
  constant readThisMessage (line 22) | readThisMessage = "read this https://github.com/skanehira/github-tui?tab...
  function Init (line 29) | func Init() {

FILE: domain/assignees.go
  type AssignableUser (line 5) | type AssignableUser struct
    method Key (line 9) | func (a *AssignableUser) Key() string {
    method Fields (line 13) | func (a *AssignableUser) Fields() []Field {

FILE: domain/comment.go
  type Comment (line 5) | type Comment struct
    method Key (line 13) | func (c *Comment) Key() string {
    method Fields (line 17) | func (c *Comment) Fields() []Field {

FILE: domain/issue.go
  type Issue (line 9) | type Issue struct
    method Key (line 26) | func (i *Issue) Key() string {
    method Fields (line 30) | func (i *Issue) Fields() []Field {

FILE: domain/item.go
  type Item (line 5) | type Item interface
  type Field (line 10) | type Field struct

FILE: domain/label.go
  type Label (line 5) | type Label struct
    method Key (line 10) | func (l *Label) Key() string {
    method Fields (line 14) | func (l *Label) Fields() []Field {

FILE: domain/milestone.go
  type Milestone (line 5) | type Milestone struct
    method Key (line 13) | func (m *Milestone) Key() string {
    method Fields (line 17) | func (m *Milestone) Fields() []Field {

FILE: domain/project.go
  type Project (line 5) | type Project struct
    method Key (line 10) | func (p *Project) Key() string {
    method Fields (line 14) | func (p *Project) Fields() []Field {

FILE: github/client.go
  function NewClient (line 12) | func NewClient(token string) {
  function CreateIssue (line 20) | func CreateIssue(input githubv4.CreateIssueInput) error {
  function GetRepos (line 25) | func GetRepos(variables map[string]interface{}) (*Repositories, error) {
  function GetRepo (line 38) | func GetRepo(variables map[string]interface{}) (*Repository, error) {
  function GetIssues (line 48) | func GetIssues(variables map[string]interface{}) (*Issues, error) {
  function GetIssue (line 63) | func GetIssue(variables map[string]interface{}) (*Issue, error) {
  function GetIssueTemplates (line 76) | func GetIssueTemplates(variables map[string]interface{}) ([]IssueTemplat...
  function ReopenIssue (line 88) | func ReopenIssue(id string) error {
  function CloseIssue (line 98) | func CloseIssue(id string) error {
  function GetRepoLabels (line 107) | func GetRepoLabels(variables map[string]interface{}) (*Labels, error) {
  function GetRepoMillestones (line 119) | func GetRepoMillestones(variables map[string]interface{}) (*Milestones, ...
  function GetRepoProjects (line 131) | func GetRepoProjects(variables map[string]interface{}) (*Projects, error) {
  function GetRepoAssignableUsers (line 143) | func GetRepoAssignableUsers(variables map[string]interface{}) (*Assignab...
  function DeleteIssueComment (line 155) | func DeleteIssueComment(id string) error {
  function UpdateIssue (line 163) | func UpdateIssue(input githubv4.UpdateIssueInput) error {
  function UpdateIssueComment (line 168) | func UpdateIssueComment(input githubv4.UpdateIssueCommentInput) error {
  function AddIssueComment (line 173) | func AddIssueComment(input githubv4.AddCommentInput) error {

FILE: github/mutation_comment.go
  type MutateDeleteComment (line 5) | type MutateDeleteComment struct
  type MutateUpdateIssueComment (line 11) | type MutateUpdateIssueComment struct

FILE: github/mutation_issue.go
  type MutateOpenIsseue (line 5) | type MutateOpenIsseue struct
  type MutateCoseIssue (line 13) | type MutateCoseIssue struct
  type MutateCreateIssue (line 21) | type MutateCreateIssue struct
  type MutateUpdateIssue (line 29) | type MutateUpdateIssue struct
  type MutateAddIssueComment (line 37) | type MutateAddIssueComment struct

FILE: github/query.go
  type PageInfo (line 5) | type PageInfo struct

FILE: github/query_assignees.go
  type AssignableUser (line 8) | type AssignableUser struct
    method ToDomain (line 12) | func (a *AssignableUser) ToDomain() *domain.AssignableUser {
  type AssignableUsers (line 19) | type AssignableUsers struct

FILE: github/query_comment.go
  type Comment (line 8) | type Comment struct
    method ToDomain (line 18) | func (c *Comment) ToDomain() *domain.Comment {

FILE: github/query_issue.go
  type Issue (line 11) | type Issue struct
    method ToDomain (line 43) | func (i *Issue) ToDomain() *domain.Issue {
  type Issues (line 86) | type Issues struct
  type IssueTemplate (line 93) | type IssueTemplate struct

FILE: github/query_label.go
  type Label (line 8) | type Label struct
    method ToDomain (line 15) | func (l *Label) ToDomain() *domain.Label {
  type Labels (line 23) | type Labels struct

FILE: github/query_milestone.go
  type Milestone (line 8) | type Milestone struct
    method ToDomain (line 16) | func (m *Milestone) ToDomain() *domain.Milestone {
  type Milestones (line 27) | type Milestones struct

FILE: github/query_project.go
  type Project (line 8) | type Project struct
    method ToDomain (line 14) | func (p *Project) ToDomain() *domain.Project {
  type Projects (line 22) | type Projects struct

FILE: github/query_repository.go
  type Repository (line 5) | type Repository struct
  type Repositories (line 22) | type Repositories struct

FILE: ui/assignees.go
  function NewAssignableUI (line 9) | func NewAssignableUI() {

FILE: ui/comments.go
  function NewCommentUI (line 19) | func NewCommentUI() {
  function quoteReply (line 82) | func quoteReply() error {
  function createComment (line 107) | func createComment(item domain.Item, body string) error {
  function deleteComment (line 128) | func deleteComment() {
  function editComment (line 160) | func editComment() error {
  function editCommentBody (line 193) | func editCommentBody(body *string) (err error) {
  function getSelectedComments (line 207) | func getSelectedComments() []*domain.Comment {
  function updateCommentUI (line 220) | func updateCommentUI() error {

FILE: ui/filter.go
  type SetFilterOpt (line 11) | type SetFilterOpt
  type FilterUI (line 12) | type FilterUI struct
    method SetQuery (line 33) | func (ui *FilterUI) SetQuery(query string) {
    method GetQuery (line 37) | func (ui *FilterUI) GetQuery() string {
    method focus (line 41) | func (ui *FilterUI) focus() {
    method blur (line 44) | func (ui *FilterUI) blur() {
  function NewFilterUI (line 17) | func NewFilterUI() {

FILE: ui/issues.go
  function NewIssueUI (line 22) | func NewIssueUI() {
  function getSelectedIssues (line 111) | func getSelectedIssues() []*domain.Issue {
  function yankIssueURLs (line 126) | func yankIssueURLs() {
  function openIssues (line 140) | func openIssues() {
  function closeIssues (line 158) | func closeIssues() {
  function openBrowser (line 176) | func openBrowser() {
  function createIssueForm (line 187) | func createIssueForm() {
  function editIssue (line 522) | func editIssue() {
  function updateUIRelatedIssue (line 560) | func updateUIRelatedIssue(ui *SelectUI, row int) {

FILE: ui/labels.go
  function NewLabelsUI (line 9) | func NewLabelsUI() {

FILE: ui/milestones.go
  function NewMilestoneUI (line 13) | func NewMilestoneUI() {

FILE: ui/projects.go
  function NewProjectUI (line 13) | func NewProjectUI() {

FILE: ui/search.go
  type SearchFunc (line 10) | type SearchFunc
  type searchUI (line 12) | type searchUI struct
    method SetSerachFunc (line 32) | func (s *searchUI) SetSerachFunc(f SearchFunc) {
    method SetFocusFunc (line 36) | func (s *searchUI) SetFocusFunc(f func()) {
    method focus (line 40) | func (s *searchUI) focus() {
    method blur (line 44) | func (s *searchUI) blur() {
  function NewSearchUI (line 18) | func NewSearchUI() {

FILE: ui/select.go
  constant unselected (line 13) | unselected = "\u25ef"
  constant selected (line 14) | selected   = "\u25c9"
  type UIKind (line 17) | type UIKind
  constant UIKindIssue (line 20) | UIKindIssue       UIKind = "issues"
  constant UIKindAssignee (line 21) | UIKindAssignee           = "assignees"
  constant UIKindComment (line 22) | UIKindComment            = "comments"
  constant UIKindLabel (line 23) | UIKindLabel              = "labels"
  constant UIKindMilestones (line 24) | UIKindMilestones         = "milestones"
  constant UIKindProject (line 25) | UIKindProject            = "projects"
  constant UIKindIssueView (line 26) | UIKindIssueView          = "issue preview"
  constant UIKindCommentView (line 27) | UIKindCommentView        = "comment preview"
  constant UIKindCommonView (line 28) | UIKindCommonView         = "preview"
  type SetSelectUIOpt (line 32) | type SetSelectUIOpt
  type GetListFunc (line 33) | type GetListFunc
  type CaptureFunc (line 34) | type CaptureFunc
  type SelectUI (line 37) | type SelectUI struct
    method GetList (line 71) | func (ui *SelectUI) GetList() {
    method SetList (line 85) | func (ui *SelectUI) SetList(list []domain.Item) {
    method FetchList (line 92) | func (ui *SelectUI) FetchList() {
    method UpdateView (line 103) | func (ui *SelectUI) UpdateView() {
    method Init (line 166) | func (ui *SelectUI) Init() {
    method toggleSelected (line 218) | func (ui *SelectUI) toggleSelected(row int) {
    method UpdateItem (line 235) | func (ui *SelectUI) UpdateItem(item domain.Item) {
    method GetSelect (line 243) | func (ui *SelectUI) GetSelect() domain.Item {
    method focus (line 259) | func (ui *SelectUI) focus() {
    method blur (line 263) | func (ui *SelectUI) blur() {
    method ClearView (line 267) | func (ui *SelectUI) ClearView() {
    method ClearSelected (line 272) | func (ui *SelectUI) ClearSelected() {
  function NewSelectListUI (line 53) | func NewSelectListUI(uiKind UIKind, boxColor tcell.Color, setOpt SetSele...

FILE: ui/ui.go
  type Primitive (line 12) | type Primitive interface
  type ui (line 18) | type ui struct
    method canFocus (line 39) | func (ui *ui) canFocus() bool {
    method toNextUI (line 51) | func (ui *ui) toNextUI() {
    method toPrevUI (line 66) | func (ui *ui) toPrevUI() {
    method Modal (line 81) | func (ui *ui) Modal(p tview.Primitive, width, height int) tview.Primit...
    method Message (line 88) | func (ui *ui) Message(msg string, focusFunc func()) {
    method FullScreenPreview (line 99) | func (ui *ui) FullScreenPreview(contents string, focus func()) {
    method Confirm (line 107) | func (ui *ui) Confirm(msg, doLabel string, doFunc func() error, focusF...
    method Start (line 125) | func (ui *ui) Start() error {
  function New (line 27) | func New() *ui {

FILE: ui/view.go
  type ViewUI (line 17) | type ViewUI struct
    method updateView (line 102) | func (ui *ViewUI) updateView(text string) {
    method focus (line 113) | func (v *ViewUI) focus() {}
    method blur (line 115) | func (v *ViewUI) blur() {}
  function NewViewUI (line 26) | func NewViewUI(uiKind UIKind) {

FILE: utils/open.go
  function Open (line 10) | func Open(url string) error {

FILE: utils/strings.go
  function Replace (line 12) | func Replace(s, old, new string, n int) ([]string, string) {

FILE: utils/utils.go
  function Edit (line 12) | func Edit(contents *string) error {
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (116K chars).
[
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2020 skanehira\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "README.md",
    "chars": 4846,
    "preview": "# github-tui\nThis is a TUI Client for GitHub.\n**Still Under Development**\n\nIf you are using Vim, you can use [gh.vim](ht"
  },
  {
    "path": "cmd/ght/main.go",
    "chars": 1994,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/skanehira/ght/config\"\n\t\"github.com/skan"
  },
  {
    "path": "config/config.go",
    "chars": 1192,
    "preview": "package config\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/goccy/go-yaml\"\n)\n\ntype github struct {\n\tOwner"
  },
  {
    "path": "domain/assignees.go",
    "chars": 271,
    "preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype AssignableUser struct {\n\tLogin string\n}\n\nfunc (a *AssignableU"
  },
  {
    "path": "domain/comment.go",
    "chars": 380,
    "preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Comment struct {\n\tID        string\n\tAuthor    string\n\tUpdated"
  },
  {
    "path": "domain/error.go",
    "chars": 214,
    "preview": "package domain\n\nimport \"errors\"\n\nvar (\n\tErrCommentBodyIsEmpty = errors.New(\"comment body is empty\")\n\tErrNotFoundComment "
  },
  {
    "path": "domain/issue.go",
    "chars": 803,
    "preview": "package domain\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\ntype Issue struct {\n\tID        string\n\tRepo      stri"
  },
  {
    "path": "domain/item.go",
    "chars": 166,
    "preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Item interface {\n\tKey() string\n\tFields() []Field\n}\n\ntype Fiel"
  },
  {
    "path": "domain/label.go",
    "chars": 272,
    "preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Label struct {\n\tName        string\n\tDescription string\n}\n\nfun"
  },
  {
    "path": "domain/milestone.go",
    "chars": 340,
    "preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Milestone struct {\n\tID          string\n\tTitle       string\n\tS"
  },
  {
    "path": "domain/project.go",
    "chars": 264,
    "preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Project struct {\n\tName string\n\tURL  string\n}\n\nfunc (p *Projec"
  },
  {
    "path": "github/client.go",
    "chars": 4988,
    "preview": "package github\n\nimport (\n\t\"context\"\n\n\t\"github.com/shurcooL/githubv4\"\n\t\"golang.org/x/oauth2\"\n)\n\nvar client *githubv4.Clie"
  },
  {
    "path": "github/mutation_comment.go",
    "chars": 359,
    "preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype MutateDeleteComment struct {\n\tDeleteIssueComment struct {\n\t\t"
  },
  {
    "path": "github/mutation_issue.go",
    "chars": 754,
    "preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype MutateOpenIsseue struct {\n\tReopenIssue struct {\n\t\tIssue stru"
  },
  {
    "path": "github/query.go",
    "chars": 139,
    "preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype PageInfo struct {\n\tEndCursor   githubv4.String\n\tHasNextPage "
  },
  {
    "path": "github/query_assignees.go",
    "chars": 425,
    "preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype AssignableUser struc"
  },
  {
    "path": "github/query_comment.go",
    "chars": 551,
    "preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Comment struct {\n\tID"
  },
  {
    "path": "github/query_issue.go",
    "chars": 2171,
    "preview": "package github\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\nty"
  },
  {
    "path": "github/query_label.go",
    "chars": 447,
    "preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Label struct {\n\tID  "
  },
  {
    "path": "github/query_milestone.go",
    "chars": 599,
    "preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Milestone struct {\n\t"
  },
  {
    "path": "github/query_project.go",
    "chars": 389,
    "preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Project struct {\n\tID"
  },
  {
    "path": "github/query_repository.go",
    "chars": 508,
    "preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype Repository struct {\n\tID               githubv4.ID\n\tName     "
  },
  {
    "path": "go.mod",
    "chars": 750,
    "preview": "module github.com/skanehira/ght\n\ngo 1.15\n\nrequire (\n\tgithub.com/atotto/clipboard v0.1.2\n\tgithub.com/davecgh/go-spew v1.1"
  },
  {
    "path": "go.sum",
    "chars": 43886,
    "preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
  },
  {
    "path": "ui/assignees.go",
    "chars": 911,
    "preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\nvar AssigneesUI *SelectUI\n\nfunc NewAssignableUI() {\n\t//getList :="
  },
  {
    "path": "ui/comments.go",
    "chars": 4984,
    "preview": "package ui\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/shurcooL/githubv4\""
  },
  {
    "path": "ui/filter.go",
    "chars": 725,
    "preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nvar IssueFilterUI *FilterUI\n\ntype (\n\tSet"
  },
  {
    "path": "ui/issues.go",
    "chars": 12799,
    "preview": "package ui\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/atotto/clipboard\"\n\t\"github.com/gdamore/tcell"
  },
  {
    "path": "ui/labels.go",
    "chars": 992,
    "preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\nvar LabelUI *SelectUI\n\nfunc NewLabelsUI() {\n\t//getList := func(cu"
  },
  {
    "path": "ui/milestones.go",
    "chars": 1458,
    "preview": "package ui\n\nimport (\n\t\"log\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/skanehira/ght/domain\"\n\t\"github.com/skanehira/gh"
  },
  {
    "path": "ui/projects.go",
    "chars": 1431,
    "preview": "package ui\n\nimport (\n\t\"log\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/skanehira/ght/domain\"\n\t\"github.com/skanehira/gh"
  },
  {
    "path": "ui/search.go",
    "chars": 646,
    "preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nvar SearchUI *searchUI\n\ntype SearchFunc "
  },
  {
    "path": "ui/select.go",
    "chars": 5826,
    "preview": "package ui\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n\t\"github.com/skanehira/ght/doma"
  },
  {
    "path": "ui/ui.go",
    "chars": 4570,
    "preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nvar (\n\tUI *ui\n)\n\ntype Primitive interfac"
  },
  {
    "path": "ui/view.go",
    "chars": 2454,
    "preview": "package ui\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n\t\"github.com/skanehira/ght/util"
  },
  {
    "path": "utils/open.go",
    "chars": 486,
    "preview": "package utils\n\nimport (\n\t\"errors\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nfunc Open(url string) error {\n\targs := []string{}\n"
  },
  {
    "path": "utils/strings.go",
    "chars": 1033,
    "preview": "package utils\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\n// Replace is customized for this project\n// htt"
  },
  {
    "path": "utils/utils.go",
    "chars": 715,
    "preview": "package utils\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc Edit(contents *string) error {\n\tf,"
  }
]

About this extraction

This page contains the full source code of the skanehira/github-tui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (104.3 KB), approximately 43.2k tokens, and a symbol index with 157 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!