Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96f0df9538 | ||
|
|
47358ed9be | ||
|
|
1f156c0da2 | ||
|
|
ee559035ad | ||
|
|
daffd293d1 | ||
|
|
9aa36d889b | ||
|
|
f2e1afea16 | ||
|
|
c855d2521a | ||
|
|
299b53c708 | ||
|
|
3882d55f69 | ||
|
|
f92379c8fa | ||
|
|
846c0edd7d | ||
|
|
cdd2883fe9 | ||
|
|
9a26e675ee | ||
|
|
f20b09ff16 | ||
|
|
7308b10312 | ||
|
|
f199e412ab | ||
|
|
9beec6e30f | ||
|
|
bfc3ed1c16 | ||
|
|
613c9b23b4 | ||
|
|
c8d56beaa7 | ||
|
|
3857644bb8 | ||
|
|
bb0601ae1f | ||
|
|
5099288efd | ||
|
|
4c299c5563 | ||
|
|
e5965af368 | ||
|
|
c1d48b1df5 | ||
|
|
e127b8c555 | ||
|
|
f642546ccb |
@@ -1,6 +0,0 @@
|
||||
development.log
|
||||
commands.log
|
||||
extra/lgit.rb
|
||||
notes/go.notes
|
||||
TODO.notes
|
||||
TODO.md
|
||||
@@ -1,59 +0,0 @@
|
||||
# This is an example goreleaser.yaml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- freebsd
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
brew:
|
||||
# Reporitory to push the tap to.
|
||||
github:
|
||||
owner: jesseduffield
|
||||
name: homebrew-lazygit
|
||||
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: "https://github.com/jesseduffield/lazygit/"
|
||||
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: "A simple terminal UI for git commands, written in Go"
|
||||
|
||||
# # Packages your package depends on.
|
||||
# dependencies:
|
||||
# - git
|
||||
# - zsh
|
||||
|
||||
# # Packages that conflict with your package.
|
||||
# conflicts:
|
||||
# - svn
|
||||
# - bash
|
||||
|
||||
# test comment to see if goreleaser only releases on new commits
|
||||
@@ -1,74 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the [project leader](https://github.com/jesseduffield).
|
||||
All complaints will be reviewed and investigated and will result in a response that
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
@@ -1,34 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
|
||||
♥ We love pull requests from everyone !
|
||||
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish
|
||||
to make via issue, email, or any other method with the owners of this repository
|
||||
before making a change.
|
||||
|
||||
## So all code changes happen through Pull Requests
|
||||
Pull requests are the best way to propose changes to the codebase. We actively
|
||||
welcome your pull requests:
|
||||
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've added code that need documentation, update the documentation.
|
||||
4. Be sure to test your modifications.
|
||||
5. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
6. Issue that pull request!
|
||||
|
||||
## Code of conduct
|
||||
Please note by participating in this project, you agree to abide by the [code of conduct].
|
||||
|
||||
[code of conduct]: https://github.com/jesseduffield/lazygit/blob/master/CODE-OF-CONDUCT.md
|
||||
|
||||
## Any contributions you make will be under the MIT Software License
|
||||
In short, when you submit code changes, your submissions are understood to be
|
||||
under the same [MIT License](http://choosealicense.com/licenses/mit/) that
|
||||
covers the project. Feel free to contact the maintainers if that's a concern.
|
||||
|
||||
## Report bugs using Github's [issues](https://github.com/jesseduffield/lazygit/issues)
|
||||
We use GitHub issues to track public bugs. Report a bug by [opening a new
|
||||
issue](https://github.com/jesseduffield/lazygit/issues/new); it's that easy!
|
||||
@@ -1,286 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:de4a74b504df31145ffa8ca0c4edbffa2f3eb7f466753962184611b618fa5981"
|
||||
name = "github.com/emirpasic/gods"
|
||||
packages = [
|
||||
"containers",
|
||||
"lists",
|
||||
"lists/arraylist",
|
||||
"trees",
|
||||
"trees/binaryheap",
|
||||
"utils",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "f6c17b524822278a87e3b3bd809fec33b51f5b46"
|
||||
version = "v1.9.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ade392a843b2035effb4b4a2efa2c3bab3eb29b992e98bacf9c898b0ecb54e45"
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4a8ed9b8cf22bd03bee5d74179fa06a282e4a73b6de949f7a865ff56cd2537e0"
|
||||
name = "github.com/golang-collections/collections"
|
||||
packages = ["stack"]
|
||||
pruneopts = "NUT"
|
||||
revision = "604e922904d35e97f98a774db7881f049cd8d970"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:62fe3a7ea2050ecbd753a71889026f83d73329337ada66325cbafd5dea5f713d"
|
||||
name = "github.com/jbenet/go-context"
|
||||
packages = ["io"]
|
||||
pruneopts = "NUT"
|
||||
revision = "d14ea06fba99483203c19d92cfcd13ebe73135f4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:82b3bbc50ba7b6065b0229ebf0fc990a76e47410acd510294e435aeb3523293e"
|
||||
name = "github.com/jesseduffield/gocui"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "3c923f53ac9952af649af04a067405843558d56f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8021af4dcbd531ae89433c8c3a6520e51064114aaf8eb1724c3cf911c497c9ba"
|
||||
name = "github.com/kevinburke/ssh_config"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "9fc7bb800b555d63157c65a904c86a2cc7b4e795"
|
||||
version = "0.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08c231ec84231a7e23d67e4b58f975e1423695a32467a362ee55a803f9de8061"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bc4f7eec3b7be8c6cb1f0af6c1e3333d5bb71072951aaaae2f05067b0803f287"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cb591533458f6eb6e2c1065ff3eac6b50263d7847deb23fc9f79b25bc608970e"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a4df73029d2c42fabcb6b41e327d2f87e685284ec03edf76921c267d9cfc9c23"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "58046073cbffe2f25d425fe1331102f55cf719de"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:34d9354c2c5d916c05864327553047df59fc10e86ff1f408e4136eba0a25a5ec"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf254277d898b713195cc6b4a3fac8bf738b9f1121625df27843b52b267eec6c"
|
||||
name = "github.com/pelletier/go-buffruneio"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "c37440a7cf42ac63b919c752ca73a85067e05992"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d917313f309bda80d27274d53985bc65651f81a5b66b820749ac7f8ef061fd04"
|
||||
name = "github.com/sergi/go-diff"
|
||||
packages = ["diffmatchpatch"]
|
||||
pruneopts = "NUT"
|
||||
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ccca1dcd18bc54e23b517a3c5babeff2e3924a7d8fc1932162225876cfe4bfb0"
|
||||
name = "github.com/src-d/gcfg"
|
||||
packages = [
|
||||
".",
|
||||
"scanner",
|
||||
"token",
|
||||
"types",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "f187355171c936ac84a82793659ebb4936bc1c23"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cd5ffc5bda4e0296ab3e4de90dbb415259c78e45e7fab13694b14cde8ab74541"
|
||||
name = "github.com/tcnksm/go-gitconfig"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d154598bacbf4501c095a309753c5d4af66caa81"
|
||||
version = "v0.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3148cb3478c26a92b4c1a18abb9428234b281e278af6267840721a24b6cbc6a3"
|
||||
name = "github.com/xanzy/ssh-agent"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "640f0ab560aeb89d523bb6ac322b1244d5c3796c"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c76f8b24a4d9b99b502fb7b61ad769125075cb570efff9b9b73e6c428629532d"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"cast5",
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"openpgp",
|
||||
"openpgp/armor",
|
||||
"openpgp/elgamal",
|
||||
"openpgp/errors",
|
||||
"openpgp/packet",
|
||||
"openpgp/s2k",
|
||||
"poly1305",
|
||||
"ssh",
|
||||
"ssh/agent",
|
||||
"ssh/knownhosts",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context"]
|
||||
pruneopts = "NUT"
|
||||
revision = "f9ce57c11b242f0f1599cf25c89d8cb02c45295a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:de22999d08e1604a7c3186ef64b5a18a90e399b99c4e8ff2a8d6c6abf42b7934"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "0ffbfd41fbef8ffcf9b62b0b0aa3a5873ed7a4fe"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"internal/gen",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"transform",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:47a697b155f5214ff14e68e39ce9c2e8d93e1fb035ae5ba7e247d044e0ce64e3"
|
||||
name = "gopkg.in/src-d/go-billy.v4"
|
||||
packages = [
|
||||
".",
|
||||
"helper/chroot",
|
||||
"helper/polyfill",
|
||||
"osfs",
|
||||
"util",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "83cf655d40b15b427014d7875d10850f96edba14"
|
||||
version = "v4.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fa265385a5175b4aaed0cccec60059a3f363c3f209bba1b658e9a12baddbc5e0"
|
||||
name = "gopkg.in/src-d/go-git.v4"
|
||||
packages = [
|
||||
".",
|
||||
"config",
|
||||
"internal/revision",
|
||||
"plumbing",
|
||||
"plumbing/cache",
|
||||
"plumbing/filemode",
|
||||
"plumbing/format/config",
|
||||
"plumbing/format/diff",
|
||||
"plumbing/format/gitignore",
|
||||
"plumbing/format/idxfile",
|
||||
"plumbing/format/index",
|
||||
"plumbing/format/objfile",
|
||||
"plumbing/format/packfile",
|
||||
"plumbing/format/pktline",
|
||||
"plumbing/object",
|
||||
"plumbing/protocol/packp",
|
||||
"plumbing/protocol/packp/capability",
|
||||
"plumbing/protocol/packp/sideband",
|
||||
"plumbing/revlist",
|
||||
"plumbing/storer",
|
||||
"plumbing/transport",
|
||||
"plumbing/transport/client",
|
||||
"plumbing/transport/file",
|
||||
"plumbing/transport/git",
|
||||
"plumbing/transport/http",
|
||||
"plumbing/transport/internal/common",
|
||||
"plumbing/transport/server",
|
||||
"plumbing/transport/ssh",
|
||||
"storage",
|
||||
"storage/filesystem",
|
||||
"storage/filesystem/dotgit",
|
||||
"storage/memory",
|
||||
"utils/binary",
|
||||
"utils/diff",
|
||||
"utils/ioutil",
|
||||
"utils/merkletrie",
|
||||
"utils/merkletrie/filesystem",
|
||||
"utils/merkletrie/index",
|
||||
"utils/merkletrie/internal/frame",
|
||||
"utils/merkletrie/noder",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "3bd5e82b2512d85becae9677fa06b5a973fd4cfb"
|
||||
version = "v4.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b233ad4ec87ac916e7bf5e678e98a2cb9e8b52f6de6ad3e11834fc7a71b8e3bf"
|
||||
name = "gopkg.in/warnings.v0"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "ec4a0fea49c7b46c2aeb0b51aac55779c607e52b"
|
||||
version = "v0.1.2"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/fatih/color",
|
||||
"github.com/golang-collections/collections/stack",
|
||||
"github.com/jesseduffield/gocui",
|
||||
"github.com/tcnksm/go-gitconfig",
|
||||
"gopkg.in/src-d/go-git.v4",
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/object",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
@@ -1,38 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
non-go = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatih/color"
|
||||
version = "1.7.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/golang-collections/collections"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/jesseduffield/gocui"
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Jesse Duffield
|
||||
|
||||
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.
|
||||
@@ -1,76 +0,0 @@
|
||||
# lazygit [](https://goreportcard.com/report/github.com/jesseduffield/lazygit)
|
||||
|
||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui "gocui") library.
|
||||
|
||||
Are YOU tired of typing every git command directly into the terminal, but you're too stubborn to use Sourcetree because you'll never forgive Atlassian for making Jira? This is the app for you!
|
||||
|
||||
[Tutorial](https://www.youtube.com/watch?v=VDXvbHZYeKY)
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
### Homebrew
|
||||
```sh
|
||||
brew tap jesseduffield/lazygit
|
||||
brew install lazygit
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
Packages for Ubuntu 16.04, 18.04 and 18.10 are available via Launchpad PPA.
|
||||
|
||||
They are built daily, straight from master branch.
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:lazygit-team/daily
|
||||
sudo apt-get update
|
||||
sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
### Binary Release (Windows/Linux/OSX)
|
||||
You can download a binary release [here](https://github.com/jesseduffield/lazygit/releases)
|
||||
|
||||
### Go
|
||||
```sh
|
||||
go get github.com/jesseduffield/lazygit
|
||||
```
|
||||
|
||||
Please note:
|
||||
If you get an error claiming that lazygit cannot be found or is not defined, you may need to add `~/go/bin` to your $PATH (MacOS/Linux), or `%HOME%\go\bin` (Windows). Not to be mistaked for `C:\Go\bin` (which is for Go's own binaries, not apps like Lazygit)
|
||||
|
||||
|
||||
## Usage
|
||||
Call `lazygit` in your terminal inside a git repository.
|
||||
If you want, you can also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or whichever rc file you're using).
|
||||
Basic tutorial [Here](https://www.youtube.com/watch?v=VDXvbHZYeKY)
|
||||
|
||||
[Keybindings](https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md)
|
||||
|
||||
## Cool features
|
||||
- Adding files easily
|
||||
- Resolving merge conflicts
|
||||
- Easily check out recent branches
|
||||
- Scroll through logs/diffs of branches/commits/stash
|
||||
- Quick pushing/pulling
|
||||
- Squash down and rename commits
|
||||
|
||||
### Resolving merge conflicts
|
||||

|
||||
|
||||
### Viewing commit diffs
|
||||

|
||||
|
||||
## Milestones
|
||||
- [x] Easy Installation (homebrew, release binaries)
|
||||
- [ ] Configurable Keybindings
|
||||
- [ ] Configurable Color Themes
|
||||
- [ ] Spawning Subprocesses (help needed - have a look at https://github.com/jesseduffield/lazygit/pull/18)
|
||||
- [ ] Maintainability
|
||||
- [ ] Performance
|
||||
- [ ] i18n
|
||||
|
||||
## Contributing
|
||||
We love your input! Please check out the [contributing guide](CONTRIBUTING.md).
|
||||
|
||||
## Work in progress
|
||||
This is still a work in progress so there's still bugs to iron out and as this is my first project in Go the code could no doubt use an increase in quality, but I'll be improving on it whenever I find the time. If you have any feedback feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[submit a PR](https://github.com/jesseduffield/lazygit/pulls).
|
||||
@@ -1,55 +0,0 @@
|
||||
// call from project root with
|
||||
// go run bin/push_new_patch.go
|
||||
|
||||
// goreleaser expects a $GITHUB_TOKEN env variable to be defined
|
||||
// in order to push the release got github
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
version, err := ioutil.ReadFile("VERSION")
|
||||
if err != nil {
|
||||
log.Panicln(err.Error())
|
||||
}
|
||||
stringVersion := string(version)
|
||||
fmt.Println("VERSION was " + stringVersion)
|
||||
|
||||
runCommand("git", "pull")
|
||||
|
||||
splitVersion := strings.Split(stringVersion, ".")
|
||||
patch := splitVersion[len(splitVersion)-1]
|
||||
newPatch, err := strconv.Atoi(patch)
|
||||
splitVersion[len(splitVersion)-1] = strconv.FormatInt(int64(newPatch)+1, 10)
|
||||
newVersion := strings.Join(splitVersion, ".")
|
||||
|
||||
err = ioutil.WriteFile("VERSION", []byte(newVersion), 0644)
|
||||
if err != nil {
|
||||
log.Panicln(err.Error())
|
||||
}
|
||||
|
||||
runCommand("git", "add", "VERSION")
|
||||
runCommand("git", "commit", "-m", "bump version to "+newVersion, "--", "VERSION")
|
||||
runCommand("git", "push")
|
||||
runCommand("git", "tag", newVersion)
|
||||
runCommand("git", "push", "origin", newVersion)
|
||||
runCommand("goreleaser", "--rm-dist")
|
||||
runCommand("rm", "-rf", "dist")
|
||||
}
|
||||
|
||||
func runCommand(args ...string) {
|
||||
fmt.Println(strings.Join(args, " "))
|
||||
output, err := exec.Command(args[0], args[1:]...).CombinedOutput()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
log.Print(string(output))
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func handleBranchPress(g *gocui.Gui, v *gocui.View) error {
|
||||
index := getItemPosition(v)
|
||||
if index == 0 {
|
||||
return createErrorPanel(g, "You have already checked out this branch")
|
||||
}
|
||||
branch := getSelectedBranch(v)
|
||||
if output, err := gitCheckout(branch.Name, false); err != nil {
|
||||
createErrorPanel(g, output)
|
||||
}
|
||||
return refreshSidePanels(g)
|
||||
}
|
||||
|
||||
func handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := getSelectedBranch(v)
|
||||
return createConfirmationPanel(g, v, "Force Checkout Branch", "Are you sure you want force checkout? You will lose all local changes", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if output, err := gitCheckout(branch.Name, true); err != nil {
|
||||
createErrorPanel(g, output)
|
||||
}
|
||||
return refreshSidePanels(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
|
||||
createPromptPanel(g, v, "Branch Name:", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if output, err := gitCheckout(trimmedContent(v), false); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
return refreshSidePanels(g)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := state.Branches[0]
|
||||
createPromptPanel(g, v, "New Branch Name (Branch is off of "+branch.Name+")", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if output, err := gitNewBranch(trimmedContent(v)); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
refreshSidePanels(g)
|
||||
return handleBranchSelect(g, v)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
checkedOutBranch := state.Branches[0]
|
||||
selectedBranch := getSelectedBranch(v)
|
||||
defer refreshSidePanels(g)
|
||||
if checkedOutBranch.Name == selectedBranch.Name {
|
||||
return createErrorPanel(g, "You cannot merge a branch into itself")
|
||||
}
|
||||
if output, err := gitMerge(selectedBranch.Name); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSelectedBranch(v *gocui.View) Branch {
|
||||
lineNumber := getItemPosition(v)
|
||||
return state.Branches[lineNumber]
|
||||
}
|
||||
|
||||
func renderBranchesOptions(g *gocui.Gui) error {
|
||||
return renderOptionsMap(g, map[string]string{
|
||||
"space": "checkout",
|
||||
"f": "force checkout",
|
||||
"m": "merge",
|
||||
"c": "checkout by name",
|
||||
"n": "new branch",
|
||||
"← → ↑ ↓": "navigate",
|
||||
})
|
||||
}
|
||||
|
||||
// may want to standardise how these select methods work
|
||||
func handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := renderBranchesOptions(g); err != nil {
|
||||
return err
|
||||
}
|
||||
// This really shouldn't happen: there should always be a master branch
|
||||
if len(state.Branches) == 0 {
|
||||
return renderString(g, "main", "No branches for this repo")
|
||||
}
|
||||
go func() {
|
||||
branch := getSelectedBranch(v)
|
||||
diff, err := getBranchGraph(branch.Name, branch.BaseBranch)
|
||||
if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") {
|
||||
diff = "There is no tracking for this branch"
|
||||
}
|
||||
renderString(g, "main", diff)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a state.Branches array to pick the current branch from
|
||||
func refreshBranches(g *gocui.Gui) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("branches")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
state.Branches = getGitBranches()
|
||||
v.Clear()
|
||||
for _, branch := range state.Branches {
|
||||
fmt.Fprintln(v, branch.DisplayString)
|
||||
}
|
||||
resetOrigin(v)
|
||||
return refreshStatus(g)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
After Width: | Height: | Size: 88 KiB |
@@ -1,138 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoCommits : When no commits are found for the branch
|
||||
ErrNoCommits = errors.New("No commits for this branch")
|
||||
)
|
||||
|
||||
func refreshCommits(g *gocui.Gui) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
state.Commits = getCommits()
|
||||
v, err := g.View("commits")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.Clear()
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
white := color.New(color.FgWhite)
|
||||
shaColor := white
|
||||
for _, commit := range state.Commits {
|
||||
if commit.Pushed {
|
||||
shaColor = red
|
||||
} else {
|
||||
shaColor = yellow
|
||||
}
|
||||
shaColor.Fprint(v, commit.Sha+" ")
|
||||
white.Fprintln(v, commit.Name)
|
||||
}
|
||||
refreshStatus(g)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
|
||||
return createConfirmationPanel(g, commitView, "Reset To Commit", "Are you sure you want to reset to this commit?", func(g *gocui.Gui, v *gocui.View) error {
|
||||
commit, err := getSelectedCommit(g)
|
||||
devLog(commit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if output, err := gitResetToCommit(commit.Sha); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
if err := refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := refreshFiles(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resetOrigin(commitView)
|
||||
return handleCommitSelect(g, nil)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func renderCommitsOptions(g *gocui.Gui) error {
|
||||
return renderOptionsMap(g, map[string]string{
|
||||
"s": "squash down",
|
||||
"r": "rename",
|
||||
"g": "reset to this commit",
|
||||
"← → ↑ ↓": "navigate",
|
||||
})
|
||||
}
|
||||
|
||||
func handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := renderCommitsOptions(g); err != nil {
|
||||
return err
|
||||
}
|
||||
commit, err := getSelectedCommit(g)
|
||||
if err != nil {
|
||||
if err != ErrNoCommits {
|
||||
return err
|
||||
}
|
||||
return renderString(g, "main", "No commits for this branch")
|
||||
}
|
||||
commitText := gitShow(commit.Sha)
|
||||
return renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
func handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if getItemPosition(v) != 0 {
|
||||
return createErrorPanel(g, "Can only squash topmost commit")
|
||||
}
|
||||
if len(state.Commits) == 1 {
|
||||
return createErrorPanel(g, "You have no commits to squash with")
|
||||
}
|
||||
commit, err := getSelectedCommit(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if output, err := gitSquashPreviousTwoCommits(commit.Name); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
if err := refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
refreshStatus(g)
|
||||
return handleCommitSelect(g, v)
|
||||
}
|
||||
|
||||
func handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
if getItemPosition(v) != 0 {
|
||||
return createErrorPanel(g, "Can only rename topmost commit")
|
||||
}
|
||||
createPromptPanel(g, v, "Rename Commit", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if output, err := gitRenameCommit(v.Buffer()); err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
if err := refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return handleCommitSelect(g, v)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSelectedCommit(g *gocui.Gui) (Commit, error) {
|
||||
v, err := g.View("commits")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(state.Commits) == 0 {
|
||||
return Commit{}, ErrNoCommits
|
||||
}
|
||||
lineNumber := getItemPosition(v)
|
||||
if lineNumber > len(state.Commits)-1 {
|
||||
devLog("potential error in getSelected Commit (mismatched ui and state)", state.Commits, lineNumber)
|
||||
return state.Commits[len(state.Commits)-1], nil
|
||||
}
|
||||
return state.Commits[lineNumber], nil
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// lots of this has been directly ported from one of the example files, will brush up later
|
||||
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
if function != nil {
|
||||
if err := function(g, v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return closeConfirmationPrompt(g)
|
||||
}
|
||||
}
|
||||
|
||||
func closeConfirmationPrompt(g *gocui.Gui) error {
|
||||
view, err := g.View("confirmation")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := returnFocus(g, view); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
g.DeleteKeybindings("confirmation")
|
||||
return g.DeleteView("confirmation")
|
||||
}
|
||||
|
||||
func getMessageHeight(message string, width int) int {
|
||||
lines := strings.Split(message, "\n")
|
||||
lineCount := 0
|
||||
for _, line := range lines {
|
||||
lineCount += len(line)/width + 1
|
||||
}
|
||||
return lineCount
|
||||
}
|
||||
|
||||
func getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) {
|
||||
width, height := g.Size()
|
||||
panelWidth := width / 2
|
||||
panelHeight := getMessageHeight(prompt, panelWidth)
|
||||
return width/2 - panelWidth/2,
|
||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||
width/2 + panelWidth/2,
|
||||
height/2 + panelHeight/2
|
||||
}
|
||||
|
||||
func createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
||||
// only need to fit one line
|
||||
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, "")
|
||||
if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Cursor = true
|
||||
|
||||
confirmationView.Editable = true
|
||||
confirmationView.Title = title
|
||||
confirmationView.FgColor = gocui.ColorWhite
|
||||
switchFocus(g, currentView, confirmationView)
|
||||
return setKeyBindings(g, handleConfirm, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
// delete the existing confirmation panel if it exists
|
||||
if view, _ := g.View("confirmation"); view != nil {
|
||||
if err := closeConfirmationPrompt(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, prompt)
|
||||
if confirmationView, err := g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
confirmationView.Title = title
|
||||
confirmationView.FgColor = gocui.ColorWhite
|
||||
renderString(g, "confirmation", prompt)
|
||||
switchFocus(g, currentView, confirmationView)
|
||||
return setKeyBindings(g, handleConfirm, handleClose)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleNewline(g *gocui.Gui, v *gocui.View) error {
|
||||
// resising ahead of time so that the top line doesn't get hidden to make
|
||||
// room for the cursor on the second line
|
||||
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, v.Buffer())
|
||||
if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.EditNewLine()
|
||||
return nil
|
||||
}
|
||||
|
||||
func setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
renderString(g, "options", "esc: close, enter: confirm")
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, wrappedConfirmationFunction(handleConfirm)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, handleNewline); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, wrappedConfirmationFunction(handleClose))
|
||||
}
|
||||
|
||||
func createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
|
||||
return createConfirmationPanel(g, currentView, title, prompt, nil, nil)
|
||||
}
|
||||
|
||||
func createErrorPanel(g *gocui.Gui, message string) error {
|
||||
currentView := g.CurrentView()
|
||||
colorFunction := color.New(color.FgRed).SprintFunc()
|
||||
coloredMessage := colorFunction(strings.TrimSpace(message))
|
||||
return createConfirmationPanel(g, currentView, "Error", coloredMessage, nil, nil)
|
||||
}
|
||||
|
||||
func trimTrailingNewline(str string) string {
|
||||
if strings.HasSuffix(str, "\n") {
|
||||
return str[:len(str)-1]
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func resizeConfirmationPanel(g *gocui.Gui) error {
|
||||
// If the confirmation panel is already displayed, just resize the width,
|
||||
// otherwise continue
|
||||
if v, err := g.View("confirmation"); err == nil {
|
||||
content := trimTrailingNewline(v.Buffer())
|
||||
x0, y0, x1, y1 := getConfirmationPanelDimensions(g, content)
|
||||
if _, err = g.SetView("confirmation", x0, y0, x1, y1, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 473 KiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 820 KiB |
|
After Width: | Height: | Size: 6.9 MiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 462 KiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 650 KiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 314 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 431 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 962 KiB |
|
After Width: | Height: | Size: 7.0 MiB |
|
After Width: | Height: | Size: 626 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 611 KiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -1,56 +0,0 @@
|
||||
# Keybindings:
|
||||
|
||||
## Global:
|
||||
|
||||
← → ↑ ↓/h j k l: navigate
|
||||
PgUp/PgDn: scroll diff panel (use fn+up/down on osx)
|
||||
q: quit
|
||||
p: pull
|
||||
shift+P: push
|
||||
|
||||
## Files Panel:
|
||||
|
||||
space: toggle staged
|
||||
c: commit changes
|
||||
shift+S: stash files
|
||||
t: add patched (i.e. pick chunks of a file to add)
|
||||
o: open
|
||||
e: edit
|
||||
s: open in sublime (requires 'subl' command)
|
||||
v: open in vscode (requires 'code' command)
|
||||
i: add to .gitignore
|
||||
d: delete if untracked checkout if tracked (aka go away)
|
||||
shift+R: refresh files
|
||||
|
||||
## Branches Panel:
|
||||
|
||||
space: checkout branch
|
||||
f: force checkout branch
|
||||
m: merge into currently checked out branch
|
||||
c: checkout by name
|
||||
n: new branch
|
||||
|
||||
## Commits Panel:
|
||||
|
||||
s: squash down (only available for topmost commit)
|
||||
r: rename commit
|
||||
g: reset to this commit
|
||||
|
||||
## Stash Panel:
|
||||
|
||||
space: apply
|
||||
g: pop
|
||||
d: drop
|
||||
|
||||
## Popup Panel:
|
||||
|
||||
esc: close/cancel
|
||||
enter: confirm
|
||||
|
||||
## Resolving Merge Conflicts (Diff Panel):
|
||||
|
||||
← →/h l: navigate conflicts
|
||||
↑ ↓/ k j: select hunk
|
||||
space: pick hunk
|
||||
b: pick both hunks
|
||||
z: undo (only available while still inside diff panel)
|
||||
@@ -1,365 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
// "io"
|
||||
// "io/ioutil"
|
||||
|
||||
// "strings"
|
||||
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoFiles = errors.New("No changed files")
|
||||
errNoUsername = errors.New(`No username set. Please do: git config --global user.name "Your Name"`)
|
||||
)
|
||||
|
||||
func stagedFiles(files []GitFile) []GitFile {
|
||||
result := make([]GitFile, 0)
|
||||
for _, file := range files {
|
||||
if file.HasStagedChanges {
|
||||
result = append(result, file)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func stageSelectedFile(g *gocui.Gui) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return stageFile(file.Name)
|
||||
}
|
||||
|
||||
func handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err == errNoFiles {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if file.HasMergeConflicts {
|
||||
return handleSwitchToMerge(g, v)
|
||||
}
|
||||
|
||||
if file.HasUnstagedChanges {
|
||||
stageFile(file.Name)
|
||||
} else {
|
||||
unStageFile(file.Name, file.Tracked)
|
||||
}
|
||||
|
||||
if err := refreshFiles(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return handleFileSelect(g, v)
|
||||
}
|
||||
|
||||
func handleAddPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err == errNoFiles {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !file.HasUnstagedChanges {
|
||||
return createErrorPanel(g, "File has no unstaged changes to add")
|
||||
}
|
||||
if !file.Tracked {
|
||||
return createErrorPanel(g, "Cannot git add --patch untracked files")
|
||||
}
|
||||
gitAddPatch(g, file.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func getSelectedFile(g *gocui.Gui) (GitFile, error) {
|
||||
if len(state.GitFiles) == 0 {
|
||||
return GitFile{}, errNoFiles
|
||||
}
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lineNumber := getItemPosition(filesView)
|
||||
return state.GitFiles[lineNumber], nil
|
||||
}
|
||||
|
||||
func handleFileRemove(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err == errNoFiles {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
var deleteVerb string
|
||||
if file.Tracked {
|
||||
deleteVerb = "checkout"
|
||||
} else {
|
||||
deleteVerb = "delete"
|
||||
}
|
||||
return createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", "Are you sure you want to "+deleteVerb+" "+file.Name+" (you will lose your changes)?", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := removeFile(file); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return refreshFiles(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
return createErrorPanel(g, err.Error())
|
||||
}
|
||||
if file.Tracked {
|
||||
return createErrorPanel(g, "Cannot ignore tracked files")
|
||||
}
|
||||
gitIgnore(file.Name)
|
||||
return refreshFiles(g)
|
||||
}
|
||||
|
||||
func renderfilesOptions(g *gocui.Gui, gitFile *GitFile) error {
|
||||
optionsMap := map[string]string{
|
||||
"← → ↑ ↓": "navigate",
|
||||
"S": "stash files",
|
||||
"c": "commit changes",
|
||||
"o": "open",
|
||||
"i": "ignore",
|
||||
"d": "delete",
|
||||
"space": "toggle staged",
|
||||
"R": "refresh",
|
||||
"t": "add patch",
|
||||
"e": "edit",
|
||||
}
|
||||
if state.HasMergeConflicts {
|
||||
optionsMap["a"] = "abort merge"
|
||||
optionsMap["m"] = "resolve merge conflicts"
|
||||
}
|
||||
if gitFile == nil {
|
||||
return renderOptionsMap(g, optionsMap)
|
||||
}
|
||||
if gitFile.Tracked {
|
||||
optionsMap["d"] = "checkout"
|
||||
}
|
||||
return renderOptionsMap(g, optionsMap)
|
||||
}
|
||||
|
||||
func handleFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
gitFile, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != errNoFiles {
|
||||
return err
|
||||
}
|
||||
renderString(g, "main", "No changed files")
|
||||
return renderfilesOptions(g, nil)
|
||||
}
|
||||
renderfilesOptions(g, &gitFile)
|
||||
var content string
|
||||
if gitFile.HasMergeConflicts {
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
content = getDiff(gitFile)
|
||||
return renderString(g, "main", content)
|
||||
}
|
||||
|
||||
func handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
if len(stagedFiles(state.GitFiles)) == 0 && !state.HasMergeConflicts {
|
||||
return createErrorPanel(g, "There are no staged files to commit")
|
||||
}
|
||||
createPromptPanel(g, filesView, "Commit message", func(g *gocui.Gui, v *gocui.View) error {
|
||||
message := trimmedContent(v)
|
||||
if message == "" {
|
||||
return createErrorPanel(g, "You cannot commit without a commit message")
|
||||
}
|
||||
if output, err := gitCommit(g, message); err != nil {
|
||||
if err == errNoUsername {
|
||||
return createErrorPanel(g, err.Error())
|
||||
}
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
refreshFiles(g)
|
||||
return refreshCommits(g)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func genericFileOpen(g *gocui.Gui, v *gocui.View, open func(*gocui.Gui, string) (string, error)) error {
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != errNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if _, err := open(g, file.Name); err != nil {
|
||||
return createErrorPanel(g, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleFileEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, editFile)
|
||||
}
|
||||
|
||||
func handleFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, openFile)
|
||||
}
|
||||
|
||||
func handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, sublimeOpenFile)
|
||||
}
|
||||
|
||||
func handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
return genericFileOpen(g, v, vsCodeOpenFile)
|
||||
}
|
||||
|
||||
func handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
return refreshFiles(g)
|
||||
}
|
||||
|
||||
func refreshStateGitFiles() {
|
||||
// get files to stage
|
||||
gitFiles := getGitStatusFiles()
|
||||
state.GitFiles = mergeGitStatusFiles(state.GitFiles, gitFiles)
|
||||
updateHasMergeConflictStatus()
|
||||
}
|
||||
|
||||
func updateHasMergeConflictStatus() error {
|
||||
merging, err := isInMergeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.HasMergeConflicts = merging
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderGitFile(gitFile GitFile, filesView *gocui.View) {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !gitFile.Tracked && !gitFile.HasStagedChanges {
|
||||
red.Fprintln(filesView, gitFile.DisplayString)
|
||||
return
|
||||
}
|
||||
green.Fprint(filesView, gitFile.DisplayString[0:1])
|
||||
red.Fprint(filesView, gitFile.DisplayString[1:3])
|
||||
if gitFile.HasUnstagedChanges {
|
||||
red.Fprintln(filesView, gitFile.Name)
|
||||
} else {
|
||||
green.Fprintln(filesView, gitFile.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func catSelectedFile(g *gocui.Gui) (string, error) {
|
||||
item, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != errNoFiles {
|
||||
return "", err
|
||||
}
|
||||
return "", renderString(g, "main", "No file to display")
|
||||
}
|
||||
cat, err := catFile(item.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func refreshFiles(g *gocui.Gui) error {
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refreshStateGitFiles()
|
||||
filesView.Clear()
|
||||
for _, gitFile := range state.GitFiles {
|
||||
renderGitFile(gitFile, filesView)
|
||||
}
|
||||
correctCursor(filesView)
|
||||
if filesView == g.CurrentView() {
|
||||
handleFileSelect(g, filesView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
devLog("pulling...")
|
||||
createMessagePanel(g, v, "", "Pulling...")
|
||||
go func() {
|
||||
if output, err := gitPull(); err != nil {
|
||||
createErrorPanel(g, output)
|
||||
} else {
|
||||
closeConfirmationPrompt(g)
|
||||
refreshCommits(g)
|
||||
refreshStatus(g)
|
||||
devLog("pulled.")
|
||||
}
|
||||
refreshFiles(g)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
devLog("pushing...")
|
||||
createMessagePanel(g, v, "", "Pushing...")
|
||||
go func() {
|
||||
if output, err := gitPush(); err != nil {
|
||||
createErrorPanel(g, output)
|
||||
} else {
|
||||
closeConfirmationPrompt(g)
|
||||
refreshCommits(g)
|
||||
refreshStatus(g)
|
||||
devLog("pushed.")
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
mergeView, err := g.View("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != errNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !file.HasMergeConflicts {
|
||||
return createErrorPanel(g, "This file has no merge conflicts")
|
||||
}
|
||||
switchFocus(g, v, mergeView)
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
output, err := gitAbortMerge()
|
||||
if err != nil {
|
||||
return createErrorPanel(g, output)
|
||||
}
|
||||
createMessagePanel(g, v, "", "Merge aborted")
|
||||
refreshStatus(g)
|
||||
return refreshFiles(g)
|
||||
}
|
||||
|
||||
func handleResetHard(g *gocui.Gui, v *gocui.View) error {
|
||||
return createConfirmationPanel(g, v, "Clear file panel", "Are you sure you want `reset --hard HEAD`? You may lose changes", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gitResetHard(); err != nil {
|
||||
createErrorPanel(g, err.Error())
|
||||
}
|
||||
return refreshFiles(g)
|
||||
}, nil)
|
||||
}
|
||||
@@ -1,658 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
// "log"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
gitconfig "github.com/tcnksm/go-gitconfig"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoCheckedOutBranch : When we have no checked out branch
|
||||
ErrNoCheckedOutBranch = errors.New("No currently checked out branch")
|
||||
|
||||
// ErrNoOpenCommand : When we don't know which command to use to open a file
|
||||
ErrNoOpenCommand = errors.New("Unsure what command to use to open this file")
|
||||
)
|
||||
|
||||
// GitFile : A staged/unstaged file
|
||||
// TODO: decide whether to give all of these the Git prefix
|
||||
type GitFile struct {
|
||||
Name string
|
||||
HasStagedChanges bool
|
||||
HasUnstagedChanges bool
|
||||
Tracked bool
|
||||
Deleted bool
|
||||
HasMergeConflicts bool
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// Branch : A git branch
|
||||
type Branch struct {
|
||||
Name string
|
||||
Type string
|
||||
BaseBranch string
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
Name string
|
||||
Pushed bool
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// StashEntry : A git stash entry
|
||||
type StashEntry struct {
|
||||
Index int
|
||||
Name string
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// Map (from https://gobyexample.com/collection-functions)
|
||||
func Map(vs []string, f func(string) string) []string {
|
||||
vsm := make([]string, len(vs))
|
||||
for i, v := range vs {
|
||||
vsm[i] = f(v)
|
||||
}
|
||||
return vsm
|
||||
}
|
||||
|
||||
func includesString(list []string, a string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// not sure how to genericise this because []interface{} doesn't accept e.g.
|
||||
// []int arguments
|
||||
func includesInt(list []int, a int) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func mergeGitStatusFiles(oldGitFiles, newGitFiles []GitFile) []GitFile {
|
||||
if len(oldGitFiles) == 0 {
|
||||
return newGitFiles
|
||||
}
|
||||
|
||||
appendedIndexes := make([]int, 0)
|
||||
|
||||
// retain position of files we already could see
|
||||
result := make([]GitFile, 0)
|
||||
for _, oldGitFile := range oldGitFiles {
|
||||
for newIndex, newGitFile := range newGitFiles {
|
||||
if oldGitFile.Name == newGitFile.Name {
|
||||
result = append(result, newGitFile)
|
||||
appendedIndexes = append(appendedIndexes, newIndex)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append any new files to the end
|
||||
for index, newGitFile := range newGitFiles {
|
||||
if !includesInt(appendedIndexes, index) {
|
||||
result = append(result, newGitFile)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func runDirectCommand(command string) (string, error) {
|
||||
timeStart := time.Now()
|
||||
commandLog(command)
|
||||
|
||||
cmdOut, err := exec.
|
||||
Command(state.Platform.shell, state.Platform.shellArg, command).
|
||||
CombinedOutput()
|
||||
devLog("run direct command time for command: ", command, time.Now().Sub(timeStart))
|
||||
return sanitisedCommandOutput(cmdOut, err)
|
||||
}
|
||||
|
||||
func branchStringParts(branchString string) (string, string) {
|
||||
// expect string to be something like '4w master`
|
||||
splitBranchName := strings.Split(branchString, "\t")
|
||||
// if we have no \t then we have no recency, so just output that as blank
|
||||
if len(splitBranchName) == 1 {
|
||||
return "", branchString
|
||||
}
|
||||
return splitBranchName[0], splitBranchName[1]
|
||||
}
|
||||
|
||||
// branchPropertiesFromName : returns branch type, base, and color
|
||||
func branchPropertiesFromName(name string) (string, string, color.Attribute) {
|
||||
if strings.Contains(name, "feature/") {
|
||||
return "feature", "develop", color.FgGreen
|
||||
} else if strings.Contains(name, "bugfix/") {
|
||||
return "bugfix", "develop", color.FgYellow
|
||||
} else if strings.Contains(name, "hotfix/") {
|
||||
return "hotfix", "master", color.FgRed
|
||||
}
|
||||
return "other", name, color.FgWhite
|
||||
}
|
||||
|
||||
func coloredString(str string, colour *color.Color) string {
|
||||
return colour.SprintFunc()(fmt.Sprint(str))
|
||||
}
|
||||
|
||||
func withPadding(str string, padding int) string {
|
||||
if padding-len(str) < 0 {
|
||||
return str
|
||||
}
|
||||
return str + strings.Repeat(" ", padding-len(str))
|
||||
}
|
||||
|
||||
// TODO: DRY up this function and getGitBranches
|
||||
func getGitStashEntries() []StashEntry {
|
||||
stashEntries := make([]StashEntry, 0)
|
||||
rawString, _ := runDirectCommand("git stash list --pretty='%gs'")
|
||||
for i, line := range splitLines(rawString) {
|
||||
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
|
||||
}
|
||||
return stashEntries
|
||||
}
|
||||
|
||||
func stashEntryFromLine(line string, index int) StashEntry {
|
||||
return StashEntry{
|
||||
Name: line,
|
||||
Index: index,
|
||||
DisplayString: line,
|
||||
}
|
||||
}
|
||||
|
||||
func getStashEntryDiff(index int) (string, error) {
|
||||
return runCommand("git stash show -p --color stash@{" + fmt.Sprint(index) + "}")
|
||||
}
|
||||
|
||||
func includes(array []string, str string) bool {
|
||||
for _, arrayStr := range array {
|
||||
if arrayStr == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getGitStatusFiles() []GitFile {
|
||||
statusOutput, _ := getGitStatus()
|
||||
statusStrings := splitLines(statusOutput)
|
||||
gitFiles := make([]GitFile, 0)
|
||||
|
||||
for _, statusString := range statusStrings {
|
||||
change := statusString[0:2]
|
||||
stagedChange := change[0:1]
|
||||
unstagedChange := statusString[1:2]
|
||||
filename := statusString[3:]
|
||||
tracked := !includes([]string{"??", "A "}, change)
|
||||
gitFile := GitFile{
|
||||
Name: filename,
|
||||
DisplayString: statusString,
|
||||
HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange),
|
||||
HasUnstagedChanges: unstagedChange != " ",
|
||||
Tracked: tracked,
|
||||
Deleted: unstagedChange == "D" || stagedChange == "D",
|
||||
HasMergeConflicts: change == "UU",
|
||||
}
|
||||
devLog("tracked", gitFile.Tracked)
|
||||
devLog("hasUnstagedChanges", gitFile.HasUnstagedChanges)
|
||||
devLog("HasStagedChanges", gitFile.HasStagedChanges)
|
||||
devLog("DisplayString", gitFile.DisplayString)
|
||||
gitFiles = append(gitFiles, gitFile)
|
||||
}
|
||||
devLog(gitFiles)
|
||||
return gitFiles
|
||||
}
|
||||
|
||||
func gitStashDo(index int, method string) (string, error) {
|
||||
return runCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}")
|
||||
}
|
||||
|
||||
func gitStashSave(message string) (string, error) {
|
||||
output, err := runCommand("git stash save \"" + message + "\"")
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
// if there are no local changes to save, the exit code is 0, but we want
|
||||
// to raise an error
|
||||
if output == "No local changes to save\n" {
|
||||
return output, errors.New(output)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func gitCheckout(branch string, force bool) (string, error) {
|
||||
forceArg := ""
|
||||
if force {
|
||||
forceArg = "--force "
|
||||
}
|
||||
return runCommand("git checkout " + forceArg + branch)
|
||||
}
|
||||
|
||||
func sanitisedCommandOutput(output []byte, err error) (string, error) {
|
||||
outputString := string(output)
|
||||
if outputString == "" && err != nil {
|
||||
return err.Error(), err
|
||||
}
|
||||
return outputString, err
|
||||
}
|
||||
|
||||
func runCommand(command string) (string, error) {
|
||||
commandStartTime := time.Now()
|
||||
commandLog(command)
|
||||
splitCmd := strings.Split(command, " ")
|
||||
devLog(splitCmd)
|
||||
cmdOut, err := exec.Command(splitCmd[0], splitCmd[1:]...).CombinedOutput()
|
||||
devLog("run command time: ", time.Now().Sub(commandStartTime))
|
||||
return sanitisedCommandOutput(cmdOut, err)
|
||||
}
|
||||
|
||||
func vsCodeOpenFile(g *gocui.Gui, filename string) (string, error) {
|
||||
return runCommand("code -r " + filename)
|
||||
}
|
||||
|
||||
func sublimeOpenFile(g *gocui.Gui, filename string) (string, error) {
|
||||
return runCommand("subl " + filename)
|
||||
}
|
||||
|
||||
func openFile(g *gocui.Gui, filename string) (string, error) {
|
||||
cmdName, cmdTrail, err := getOpenCommand()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return runCommand(cmdName + " " + filename + cmdTrail)
|
||||
}
|
||||
|
||||
func getOpenCommand() (string, string, error) {
|
||||
//NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX)
|
||||
trailMap := map[string]string{
|
||||
"xdg-open": " &>/dev/null &",
|
||||
"cygstart": "",
|
||||
"open": "",
|
||||
}
|
||||
for name, trail := range trailMap {
|
||||
if out, _ := runCommand("which " + name); out != "exit status 1" {
|
||||
return name, trail, nil
|
||||
}
|
||||
}
|
||||
return "", "", ErrNoOpenCommand
|
||||
}
|
||||
|
||||
func gitAddPatch(g *gocui.Gui, filename string) {
|
||||
runSubProcess(g, "git", "add", "--patch", filename)
|
||||
}
|
||||
|
||||
func editFile(g *gocui.Gui, filename string) (string, error) {
|
||||
editor, _ := gitconfig.Global("core.editor")
|
||||
if editor == "" {
|
||||
editor = os.Getenv("VISUAL")
|
||||
}
|
||||
if editor == "" {
|
||||
editor = os.Getenv("EDITOR")
|
||||
}
|
||||
if editor == "" {
|
||||
return "", createErrorPanel(g, "No editor defined in $VISUAL, $EDITOR, or git config.")
|
||||
}
|
||||
runSubProcess(g, editor, filename)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func runSubProcess(g *gocui.Gui, cmdName string, commandArgs ...string) {
|
||||
subprocess = exec.Command(cmdName, commandArgs...)
|
||||
subprocess.Stdin = os.Stdin
|
||||
subprocess.Stdout = os.Stdout
|
||||
subprocess.Stderr = os.Stderr
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
return ErrSubprocess
|
||||
})
|
||||
}
|
||||
|
||||
func getBranchGraph(branch string, baseBranch string) (string, error) {
|
||||
return runCommand("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branch)
|
||||
|
||||
// Leaving this guy commented out in case there's backlash from the design
|
||||
// change and I want to make this configurable
|
||||
// return runCommand("git log -p -30 --color --no-merges " + branch)
|
||||
}
|
||||
|
||||
func verifyInGitRepo() {
|
||||
if output, err := runCommand("git status"); err != nil {
|
||||
fmt.Println(output)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getCommits() []Commit {
|
||||
pushables := gitCommitsToPush()
|
||||
log := getLog()
|
||||
commits := make([]Commit, 0)
|
||||
// now we can split it up and turn it into commits
|
||||
lines := splitLines(log)
|
||||
for _, line := range lines {
|
||||
splitLine := strings.Split(line, " ")
|
||||
sha := splitLine[0]
|
||||
pushed := includesString(pushables, sha)
|
||||
commits = append(commits, Commit{
|
||||
Sha: sha,
|
||||
Name: strings.Join(splitLine[1:], " "),
|
||||
Pushed: pushed,
|
||||
DisplayString: strings.Join(splitLine, " "),
|
||||
})
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
func getLog() string {
|
||||
// currently limiting to 30 for performance reasons
|
||||
// TODO: add lazyloading when you scroll down
|
||||
result, err := runDirectCommand("git log --oneline -30")
|
||||
if err != nil {
|
||||
// assume if there is an error there are no commits yet for this branch
|
||||
return ""
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func gitIgnore(filename string) {
|
||||
if _, err := runDirectCommand("echo '" + filename + "' >> .gitignore"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func gitShow(sha string) string {
|
||||
result, err := runDirectCommand("git show --color " + sha)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getDiff(file GitFile) string {
|
||||
cachedArg := ""
|
||||
if file.HasStagedChanges && !file.HasUnstagedChanges {
|
||||
cachedArg = "--cached "
|
||||
}
|
||||
deletedArg := ""
|
||||
if file.Deleted {
|
||||
deletedArg = "-- "
|
||||
}
|
||||
trackedArg := ""
|
||||
if !file.Tracked && !file.HasStagedChanges {
|
||||
trackedArg = "--no-index /dev/null "
|
||||
}
|
||||
command := "git diff --color " + cachedArg + deletedArg + trackedArg + file.Name
|
||||
// for now we assume an error means the file was deleted
|
||||
s, _ := runCommand(command)
|
||||
return s
|
||||
}
|
||||
|
||||
func catFile(file string) (string, error) {
|
||||
return runDirectCommand("cat " + file)
|
||||
}
|
||||
|
||||
func stageFile(file string) error {
|
||||
_, err := runCommand("git add " + file)
|
||||
return err
|
||||
}
|
||||
|
||||
func unStageFile(file string, tracked bool) error {
|
||||
var command string
|
||||
if tracked {
|
||||
command = "git reset HEAD "
|
||||
} else {
|
||||
command = "git rm --cached "
|
||||
}
|
||||
devLog(command)
|
||||
_, err := runCommand(command + file)
|
||||
return err
|
||||
}
|
||||
|
||||
func getGitStatus() (string, error) {
|
||||
return runCommand("git status --untracked-files=all --short")
|
||||
}
|
||||
|
||||
func isInMergeState() (bool, error) {
|
||||
output, err := runCommand("git status --untracked-files=all")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.Contains(output, "conclude merge") || strings.Contains(output, "unmerged paths"), nil
|
||||
}
|
||||
|
||||
func removeFile(file GitFile) error {
|
||||
// if the file isn't tracked, we assume you want to delete it
|
||||
if !file.Tracked {
|
||||
_, err := runCommand("rm -rf ./" + file.Name)
|
||||
return err
|
||||
}
|
||||
// if the file is tracked, we assume you want to just check it out
|
||||
_, err := runCommand("git checkout " + file.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
func gitCommit(g *gocui.Gui, message string) (string, error) {
|
||||
gpgsign, _ := gitconfig.Global("commit.gpgsign")
|
||||
if gpgsign != "" {
|
||||
runSubProcess(g, "bash", "-c", "git commit -m \""+message+"\"")
|
||||
return "", nil
|
||||
}
|
||||
userName, err := gitconfig.Username()
|
||||
if userName == "" {
|
||||
return "", errNoUsername
|
||||
}
|
||||
userEmail, err := gitconfig.Email()
|
||||
_, err = w.Commit(message, &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: userName,
|
||||
Email: userEmail,
|
||||
When: time.Now(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err.Error(), err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func gitPull() (string, error) {
|
||||
return runDirectCommand("git pull --no-edit")
|
||||
}
|
||||
|
||||
func gitPush() (string, error) {
|
||||
branchName := gitCurrentBranchName()
|
||||
if branchName == "" {
|
||||
return "", ErrNoCheckedOutBranch
|
||||
}
|
||||
return runDirectCommand("git push -u origin " + branchName)
|
||||
}
|
||||
|
||||
func gitSquashPreviousTwoCommits(message string) (string, error) {
|
||||
return runDirectCommand("git reset --soft HEAD^ && git commit --amend -m \"" + message + "\"")
|
||||
}
|
||||
|
||||
func gitRenameCommit(message string) (string, error) {
|
||||
return runDirectCommand("git commit --allow-empty --amend -m \"" + message + "\"")
|
||||
}
|
||||
|
||||
func gitFetch() (string, error) {
|
||||
return runDirectCommand("git fetch")
|
||||
}
|
||||
|
||||
func gitResetToCommit(sha string) (string, error) {
|
||||
return runDirectCommand("git reset " + sha)
|
||||
}
|
||||
|
||||
func gitNewBranch(name string) (string, error) {
|
||||
return runDirectCommand("git checkout -b " + name)
|
||||
}
|
||||
|
||||
func gitListStash() (string, error) {
|
||||
return runDirectCommand("git stash list")
|
||||
}
|
||||
|
||||
func gitMerge(branchName string) (string, error) {
|
||||
return runDirectCommand("git merge --no-edit " + branchName)
|
||||
}
|
||||
|
||||
func gitAbortMerge() (string, error) {
|
||||
return runDirectCommand("git merge --abort")
|
||||
}
|
||||
|
||||
func gitUpstreamDifferenceCount() (string, string) {
|
||||
pushableCount, err := runDirectCommand("git rev-list @{u}..head --count")
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
pullableCount, err := runDirectCommand("git rev-list head..@{u} --count")
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
|
||||
}
|
||||
|
||||
func gitCommitsToPush() []string {
|
||||
pushables, err := runDirectCommand("git rev-list @{u}..head --abbrev-commit")
|
||||
if err != nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
return splitLines(pushables)
|
||||
}
|
||||
|
||||
func gitCurrentBranchName() string {
|
||||
branchName, err := runDirectCommand("git symbolic-ref --short HEAD")
|
||||
// if there is an error, assume there are no branches yet
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(branchName)
|
||||
}
|
||||
|
||||
// A line will have the form '10 days ago master' so we need to strip out the
|
||||
// useful information from that into timeNumber, timeUnit, and branchName
|
||||
func branchInfoFromLine(line string) (string, string, string) {
|
||||
r := regexp.MustCompile("\\|.*\\s")
|
||||
line = r.ReplaceAllString(line, " ")
|
||||
words := strings.Split(line, " ")
|
||||
return words[0], words[1], words[3]
|
||||
}
|
||||
|
||||
func abbreviatedTimeUnit(timeUnit string) string {
|
||||
r := regexp.MustCompile("s$")
|
||||
timeUnit = r.ReplaceAllString(timeUnit, "")
|
||||
timeUnitMap := map[string]string{
|
||||
"hour": "h",
|
||||
"minute": "m",
|
||||
"second": "s",
|
||||
"week": "w",
|
||||
"year": "y",
|
||||
"day": "d",
|
||||
"month": "m",
|
||||
}
|
||||
return timeUnitMap[timeUnit]
|
||||
}
|
||||
|
||||
func getBranches() []Branch {
|
||||
branches := make([]Branch, 0)
|
||||
rawString, err := runDirectCommand("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
|
||||
if err != nil {
|
||||
return branches
|
||||
}
|
||||
|
||||
branchLines := splitLines(rawString)
|
||||
for i, line := range branchLines {
|
||||
timeNumber, timeUnit, branchName := branchInfoFromLine(line)
|
||||
timeUnit = abbreviatedTimeUnit(timeUnit)
|
||||
|
||||
if branchAlreadyStored(branchName, branches) {
|
||||
continue
|
||||
}
|
||||
|
||||
branch := constructBranch(timeNumber+timeUnit, branchName, i)
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return branches
|
||||
}
|
||||
|
||||
func constructBranch(prefix, name string, index int) Branch {
|
||||
branchType, branchBase, colourAttr := branchPropertiesFromName(name)
|
||||
if index == 0 {
|
||||
prefix = " *"
|
||||
}
|
||||
colour := color.New(colourAttr)
|
||||
displayString := withPadding(prefix, 4) + coloredString(name, colour)
|
||||
return Branch{
|
||||
Name: name,
|
||||
Type: branchType,
|
||||
BaseBranch: branchBase,
|
||||
DisplayString: displayString,
|
||||
}
|
||||
}
|
||||
|
||||
func getGitBranches() []Branch {
|
||||
// check if there are any branches
|
||||
branchCheck, _ := runCommand("git branch")
|
||||
if branchCheck == "" {
|
||||
return []Branch{constructBranch("", gitCurrentBranchName(), 0)}
|
||||
}
|
||||
branches := getBranches()
|
||||
if len(branches) == 0 {
|
||||
branches = append(branches, constructBranch("", gitCurrentBranchName(), 0))
|
||||
}
|
||||
branches = getAndMergeFetchedBranches(branches)
|
||||
return branches
|
||||
}
|
||||
|
||||
func branchAlreadyStored(branchName string, branches []Branch) bool {
|
||||
for _, existingBranch := range branches {
|
||||
if existingBranch.Name == branchName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// here branches contains all the branches that we've checked out, along with
|
||||
// the recency. In this function we append the branches that are in our heads
|
||||
// directory i.e. things we've fetched but haven't necessarily checked out.
|
||||
// Worth mentioning this has nothing to do with the 'git merge' operation
|
||||
func getAndMergeFetchedBranches(branches []Branch) []Branch {
|
||||
rawString, err := runDirectCommand("git branch --sort=-committerdate --no-color")
|
||||
if err != nil {
|
||||
return branches
|
||||
}
|
||||
branchLines := splitLines(rawString)
|
||||
for _, line := range branchLines {
|
||||
line = strings.Replace(line, "* ", "", -1)
|
||||
line = strings.TrimSpace(line)
|
||||
if branchAlreadyStored(line, branches) {
|
||||
continue
|
||||
}
|
||||
branches = append(branches, constructBranch("", line, len(branches)))
|
||||
}
|
||||
return branches
|
||||
}
|
||||
|
||||
func gitResetHard() error {
|
||||
return w.Reset(&git.ResetOptions{Mode: git.HardReset})
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
// "io"
|
||||
// "io/ioutil"
|
||||
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// "strings"
|
||||
|
||||
"github.com/golang-collections/collections/stack"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// OverlappingEdges determines if panel edges overlap
|
||||
var OverlappingEdges = false
|
||||
|
||||
type stateType struct {
|
||||
GitFiles []GitFile
|
||||
Branches []Branch
|
||||
Commits []Commit
|
||||
StashEntries []StashEntry
|
||||
PreviousView string
|
||||
HasMergeConflicts bool
|
||||
ConflictIndex int
|
||||
ConflictTop bool
|
||||
Conflicts []conflict
|
||||
EditHistory *stack.Stack
|
||||
Platform platform
|
||||
}
|
||||
|
||||
type conflict struct {
|
||||
start int
|
||||
middle int
|
||||
end int
|
||||
}
|
||||
|
||||
var state = stateType{
|
||||
GitFiles: make([]GitFile, 0),
|
||||
PreviousView: "files",
|
||||
Commits: make([]Commit, 0),
|
||||
StashEntries: make([]StashEntry, 0),
|
||||
ConflictIndex: 0,
|
||||
ConflictTop: true,
|
||||
Conflicts: make([]conflict, 0),
|
||||
EditHistory: stack.New(),
|
||||
Platform: getPlatform(),
|
||||
}
|
||||
|
||||
type platform struct {
|
||||
os string
|
||||
shell string
|
||||
shellArg string
|
||||
escapedQuote string
|
||||
}
|
||||
|
||||
func getPlatform() platform {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return platform{
|
||||
os: "windows",
|
||||
shell: "cmd",
|
||||
shellArg: "/c",
|
||||
escapedQuote: "\\\"",
|
||||
}
|
||||
default:
|
||||
return platform{
|
||||
os: runtime.GOOS,
|
||||
shell: "bash",
|
||||
shellArg: "-c",
|
||||
escapedQuote: "\"",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scrollUpMain(g *gocui.Gui, v *gocui.View) error {
|
||||
mainView, _ := g.View("main")
|
||||
ox, oy := mainView.Origin()
|
||||
if oy >= 1 {
|
||||
return mainView.SetOrigin(ox, oy-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scrollDownMain(g *gocui.Gui, v *gocui.View) error {
|
||||
mainView, _ := g.View("main")
|
||||
ox, oy := mainView.Origin()
|
||||
if oy < len(mainView.BufferLines()) {
|
||||
return mainView.SetOrigin(ox, oy+1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleRefresh(g *gocui.Gui, v *gocui.View) error {
|
||||
return refreshSidePanels(g)
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// layout is called for every screen re-render e.g. when the screen is resized
|
||||
func layout(g *gocui.Gui) error {
|
||||
g.Highlight = true
|
||||
g.SelFgColor = gocui.ColorWhite | gocui.AttrBold
|
||||
width, height := g.Size()
|
||||
leftSideWidth := width / 3
|
||||
statusFilesBoundary := 2
|
||||
filesBranchesBoundary := 2 * height / 5 // height - 20
|
||||
commitsBranchesBoundary := 3 * height / 5 // height - 10
|
||||
commitsStashBoundary := height - 5 // height - 5
|
||||
minimumHeight := 16
|
||||
minimumWidth := 10
|
||||
|
||||
panelSpacing := 1
|
||||
if OverlappingEdges {
|
||||
panelSpacing = 0
|
||||
}
|
||||
|
||||
if height < minimumHeight || width < minimumWidth {
|
||||
v, err := g.SetView("limit", 0, 0, max(width-1, 2), max(height-1, 2), 0)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Not enough space to render panels"
|
||||
v.Wrap = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
g.DeleteView("limit")
|
||||
|
||||
optionsTop := height - 2
|
||||
// hiding options if there's not enough space
|
||||
if height < 30 {
|
||||
optionsTop = height - 1
|
||||
}
|
||||
|
||||
v, err := g.SetView("main", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Diff"
|
||||
v.Wrap = true
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, statusFilesBoundary, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Status"
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
filesView, err := g.SetView("files", 0, statusFilesBoundary+panelSpacing, leftSideWidth, filesBranchesBoundary, gocui.TOP|gocui.BOTTOM)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
filesView.Highlight = true
|
||||
filesView.Title = "Files"
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Branches"
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("commits", 0, commitsBranchesBoundary+panelSpacing, leftSideWidth, commitsStashBoundary, gocui.TOP|gocui.BOTTOM); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Commits"
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("stash", 0, commitsStashBoundary+panelSpacing, leftSideWidth, optionsTop, gocui.TOP|gocui.RIGHT); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Stash"
|
||||
v.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", -1, optionsTop, width-len(version)-2, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorBlue
|
||||
v.Frame = false
|
||||
}
|
||||
|
||||
if err = resizeConfirmationPanel(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorGreen
|
||||
v.Frame = false
|
||||
renderString(g, "version", version)
|
||||
|
||||
// these are only called once
|
||||
handleFileSelect(g, filesView)
|
||||
refreshFiles(g)
|
||||
refreshBranches(g)
|
||||
refreshCommits(g)
|
||||
refreshStashEntries(g)
|
||||
nextView(g, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetch(g *gocui.Gui) error {
|
||||
gitFetch()
|
||||
refreshStatus(g)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateLoader(g *gocui.Gui) error {
|
||||
if confirmationView, _ := g.View("confirmation"); confirmationView != nil {
|
||||
content := trimmedContent(confirmationView)
|
||||
if strings.Contains(content, "...") {
|
||||
staticContent := strings.Split(content, "...")[0] + "..."
|
||||
renderString(g, "confirmation", staticContent+" "+loader())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) {
|
||||
go func() {
|
||||
for range time.Tick(interval) {
|
||||
function(g)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func run() (err error) {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
goEvery(g, time.Second*60, fetch)
|
||||
goEvery(g, time.Second*10, refreshFiles)
|
||||
goEvery(g, time.Millisecond*10, updateLoader)
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err = keybindings(g); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = g.MainLoop()
|
||||
return
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
|
||||
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
|
||||
// is only handled if the given view has focus, or handled globally if the view
|
||||
// is ""
|
||||
type Binding struct {
|
||||
ViewName string
|
||||
Handler func(*gocui.Gui, *gocui.View) error
|
||||
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
||||
Modifier gocui.Modifier
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
bindings := []Binding{
|
||||
Binding{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: quit},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: scrollUpMain},
|
||||
Binding{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: scrollDownMain},
|
||||
Binding{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: pushFiles},
|
||||
Binding{ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: pullFiles},
|
||||
Binding{ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: handleRefresh},
|
||||
Binding{ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: handleCommitPress},
|
||||
Binding{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleFilePress},
|
||||
Binding{ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: handleFileRemove},
|
||||
Binding{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: handleSwitchToMerge},
|
||||
Binding{ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: handleFileEdit},
|
||||
Binding{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: handleFileOpen},
|
||||
Binding{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: handleSublimeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: handleVsCodeFileOpen},
|
||||
Binding{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: handleIgnoreFile},
|
||||
Binding{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: handleRefreshFiles},
|
||||
Binding{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: handleStashSave},
|
||||
Binding{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: handleAbortMerge},
|
||||
Binding{ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: handleAddPatch},
|
||||
Binding{ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: handleResetHard},
|
||||
Binding{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: handleEscapeMerge},
|
||||
Binding{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handlePickHunk},
|
||||
Binding{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: handlePickBothHunks},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: handleSelectTop},
|
||||
Binding{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: handleSelectBottom},
|
||||
Binding{ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: handleSelectPrevConflict},
|
||||
Binding{ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: handleSelectNextConflict},
|
||||
Binding{ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: handleSelectTop},
|
||||
Binding{ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: handleSelectBottom},
|
||||
Binding{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: handlePopFileSnapshot},
|
||||
Binding{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleBranchPress},
|
||||
Binding{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: handleCheckoutByName},
|
||||
Binding{ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: handleForceCheckout},
|
||||
Binding{ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: handleNewBranch},
|
||||
Binding{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: handleMerge},
|
||||
Binding{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: handleCommitSquashDown},
|
||||
Binding{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: handleRenameCommit},
|
||||
Binding{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: handleResetToCommit},
|
||||
Binding{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: handleStashApply},
|
||||
Binding{ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: handleStashPop},
|
||||
Binding{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: handleStashDrop},
|
||||
}
|
||||
|
||||
// Would make these keybindings global but that interferes with editing
|
||||
// input in the confirmation panel
|
||||
for _, viewName := range []string{"files", "branches", "commits", "stash"} {
|
||||
bindings = append(bindings, []Binding{
|
||||
Binding{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: previousView},
|
||||
Binding{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: cursorUp},
|
||||
Binding{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: cursorDown},
|
||||
Binding{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: previousView},
|
||||
Binding{ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: nextView},
|
||||
Binding{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: cursorUp},
|
||||
Binding{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: cursorDown},
|
||||
}...)
|
||||
}
|
||||
|
||||
for _, binding := range bindings {
|
||||
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.8 MiB |
@@ -1,126 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
// ErrSubProcess is raised when we are running a subprocess
|
||||
var (
|
||||
ErrSubprocess = errors.New("running subprocess")
|
||||
subprocess *exec.Cmd
|
||||
|
||||
commit string
|
||||
version = "unversioned"
|
||||
|
||||
date string
|
||||
debuggingFlag = flag.Bool("debug", false, "a boolean")
|
||||
versionFlag = flag.Bool("v", false, "Print the current version")
|
||||
|
||||
w *git.Worktree
|
||||
)
|
||||
|
||||
func homeDirectory() string {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return usr.HomeDir
|
||||
}
|
||||
|
||||
func projectPath(path string) string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
return filepath.FromSlash(gopath + "/src/github.com/jesseduffield/lazygit/" + path)
|
||||
}
|
||||
|
||||
func devLog(objects ...interface{}) {
|
||||
localLog("development.log", objects...)
|
||||
}
|
||||
|
||||
func commandLog(objects ...interface{}) {
|
||||
localLog("commands.log", objects...)
|
||||
}
|
||||
|
||||
func localLog(path string, objects ...interface{}) {
|
||||
if !*debuggingFlag {
|
||||
return
|
||||
}
|
||||
f, err := os.OpenFile(projectPath(path), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
log.SetOutput(f)
|
||||
for _, object := range objects {
|
||||
log.Println(fmt.Sprint(object))
|
||||
}
|
||||
}
|
||||
|
||||
func navigateToRepoRootDirectory() {
|
||||
_, err := os.Stat(".git")
|
||||
for os.IsNotExist(err) {
|
||||
devLog("going up a directory to find the root")
|
||||
os.Chdir("..")
|
||||
_, err = os.Stat(".git")
|
||||
}
|
||||
}
|
||||
|
||||
// when building the binary, `version` is set as a compile-time variable, along
|
||||
// with `date` and `commit`. If this program has been opened directly via go,
|
||||
// we will populate the `version` with VERSION in the lazygit root directory
|
||||
func fallbackVersion() string {
|
||||
path := projectPath("VERSION")
|
||||
byteVersion, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "unversioned"
|
||||
}
|
||||
return string(byteVersion)
|
||||
}
|
||||
|
||||
func setupWorktree() {
|
||||
r, err := git.PlainOpen(".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w, err = r.Worktree()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
devLog("\n\n\n\n\n\n\n\n\n\n")
|
||||
flag.Parse()
|
||||
if version == "unversioned" {
|
||||
version = fallbackVersion()
|
||||
}
|
||||
if *versionFlag {
|
||||
fmt.Printf("commit=%s, build date=%s, version=%s", commit, date, version)
|
||||
os.Exit(0)
|
||||
}
|
||||
verifyInGitRepo()
|
||||
navigateToRepoRootDirectory()
|
||||
setupWorktree()
|
||||
for {
|
||||
if err := run(); err != nil {
|
||||
if err == gocui.ErrQuit {
|
||||
break
|
||||
} else if err == ErrSubprocess {
|
||||
subprocess.Run()
|
||||
} else {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func findConflicts(content string) ([]conflict, error) {
|
||||
conflicts := make([]conflict, 0)
|
||||
var newConflict conflict
|
||||
for i, line := range splitLines(content) {
|
||||
if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" {
|
||||
newConflict = conflict{start: i}
|
||||
} else if line == "=======" {
|
||||
newConflict.middle = i
|
||||
} else if strings.HasPrefix(line, ">>>>>>> ") {
|
||||
newConflict.end = i
|
||||
conflicts = append(conflicts, newConflict)
|
||||
}
|
||||
}
|
||||
return conflicts, nil
|
||||
}
|
||||
|
||||
func shiftConflict(conflicts []conflict) (conflict, []conflict) {
|
||||
return conflicts[0], conflicts[1:]
|
||||
}
|
||||
|
||||
func shouldHighlightLine(index int, conflict conflict, top bool) bool {
|
||||
return (index >= conflict.start && index <= conflict.middle && top) || (index >= conflict.middle && index <= conflict.end && !top)
|
||||
}
|
||||
|
||||
func coloredConflictFile(content string, conflicts []conflict, conflictIndex int, conflictTop, hasFocus bool) (string, error) {
|
||||
if len(conflicts) == 0 {
|
||||
return content, nil
|
||||
}
|
||||
conflict, remainingConflicts := shiftConflict(conflicts)
|
||||
var outputBuffer bytes.Buffer
|
||||
for i, line := range splitLines(content) {
|
||||
colourAttr := color.FgWhite
|
||||
if i == conflict.start || i == conflict.middle || i == conflict.end {
|
||||
colourAttr = color.FgRed
|
||||
}
|
||||
colour := color.New(colourAttr)
|
||||
if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && shouldHighlightLine(i, conflict, conflictTop) {
|
||||
colour.Add(color.Bold)
|
||||
}
|
||||
if i == conflict.end && len(remainingConflicts) > 0 {
|
||||
conflict, remainingConflicts = shiftConflict(remainingConflicts)
|
||||
}
|
||||
outputBuffer.WriteString(coloredString(line, colour) + "\n")
|
||||
}
|
||||
return outputBuffer.String(), nil
|
||||
}
|
||||
|
||||
func handleSelectTop(g *gocui.Gui, v *gocui.View) error {
|
||||
state.ConflictTop = true
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func handleSelectBottom(g *gocui.Gui, v *gocui.View) error {
|
||||
state.ConflictTop = false
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error {
|
||||
if state.ConflictIndex >= len(state.Conflicts)-1 {
|
||||
return nil
|
||||
}
|
||||
state.ConflictIndex++
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error {
|
||||
if state.ConflictIndex <= 0 {
|
||||
return nil
|
||||
}
|
||||
state.ConflictIndex--
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func isIndexToDelete(i int, conflict conflict, pick string) bool {
|
||||
return i == conflict.middle ||
|
||||
i == conflict.start ||
|
||||
i == conflict.end ||
|
||||
pick != "both" &&
|
||||
(pick == "bottom" && i > conflict.start && i < conflict.middle) ||
|
||||
(pick == "top" && i > conflict.middle && i < conflict.end)
|
||||
}
|
||||
|
||||
func resolveConflict(g *gocui.Gui, conflict conflict, pick string) error {
|
||||
gitFile, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Open(gitFile.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
output := ""
|
||||
for i := 0; true; i++ {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if !isIndexToDelete(i, conflict, pick) {
|
||||
output += line
|
||||
}
|
||||
}
|
||||
devLog(output)
|
||||
return ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
|
||||
}
|
||||
|
||||
func pushFileSnapshot(g *gocui.Gui) error {
|
||||
gitFile, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := catFile(gitFile.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.EditHistory.Push(content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
|
||||
if state.EditHistory.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
prevContent := state.EditHistory.Pop().(string)
|
||||
gitFile, err := getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644)
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
conflict := state.Conflicts[state.ConflictIndex]
|
||||
pushFileSnapshot(g)
|
||||
pick := "bottom"
|
||||
if state.ConflictTop {
|
||||
pick = "top"
|
||||
}
|
||||
err := resolveConflict(g, conflict, pick)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
refreshMergePanel(g)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePickBothHunks(g *gocui.Gui, v *gocui.View) error {
|
||||
conflict := state.Conflicts[state.ConflictIndex]
|
||||
pushFileSnapshot(g)
|
||||
err := resolveConflict(g, conflict, "both")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func currentViewName(g *gocui.Gui) string {
|
||||
currentView := g.CurrentView()
|
||||
return currentView.Name()
|
||||
}
|
||||
|
||||
func refreshMergePanel(g *gocui.Gui) error {
|
||||
cat, err := catSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.Conflicts, err = findConflicts(cat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(state.Conflicts) == 0 {
|
||||
return handleCompleteMerge(g)
|
||||
} else if state.ConflictIndex > len(state.Conflicts)-1 {
|
||||
state.ConflictIndex = len(state.Conflicts) - 1
|
||||
}
|
||||
hasFocus := currentViewName(g) == "main"
|
||||
if hasFocus {
|
||||
renderMergeOptions(g)
|
||||
}
|
||||
content, err := coloredConflictFile(cat, state.Conflicts, state.ConflictIndex, state.ConflictTop, hasFocus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := scrollToConflict(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return renderString(g, "main", content)
|
||||
}
|
||||
|
||||
func scrollToConflict(g *gocui.Gui) error {
|
||||
mainView, err := g.View("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(state.Conflicts) == 0 {
|
||||
return nil
|
||||
}
|
||||
conflict := state.Conflicts[state.ConflictIndex]
|
||||
ox, _ := mainView.Origin()
|
||||
_, height := mainView.Size()
|
||||
conflictMiddle := (conflict.end + conflict.start) / 2
|
||||
newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2))))
|
||||
return mainView.SetOrigin(ox, newOriginY)
|
||||
}
|
||||
|
||||
func switchToMerging(g *gocui.Gui) error {
|
||||
state.ConflictIndex = 0
|
||||
state.ConflictTop = true
|
||||
_, err := g.SetCurrentView("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func renderMergeOptions(g *gocui.Gui) error {
|
||||
return renderOptionsMap(g, map[string]string{
|
||||
"↑ ↓": "select hunk",
|
||||
"← →": "navigate conflicts",
|
||||
"space": "pick hunk",
|
||||
"b": "pick both hunks",
|
||||
"z": "undo",
|
||||
})
|
||||
}
|
||||
|
||||
func handleEscapeMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refreshFiles(g)
|
||||
return switchFocus(g, v, filesView)
|
||||
}
|
||||
|
||||
func handleCompleteMerge(g *gocui.Gui) error {
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stageSelectedFile(g)
|
||||
refreshFiles(g)
|
||||
return switchFocus(g, nil, filesView)
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 650 KiB |
@@ -1,93 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func refreshStashEntries(g *gocui.Gui) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("stash")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
state.StashEntries = getGitStashEntries()
|
||||
v.Clear()
|
||||
for _, stashEntry := range state.StashEntries {
|
||||
fmt.Fprintln(v, stashEntry.DisplayString)
|
||||
}
|
||||
return resetOrigin(v)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSelectedStashEntry(v *gocui.View) *StashEntry {
|
||||
if len(state.StashEntries) == 0 {
|
||||
return nil
|
||||
}
|
||||
lineNumber := getItemPosition(v)
|
||||
return &state.StashEntries[lineNumber]
|
||||
}
|
||||
|
||||
func renderStashOptions(g *gocui.Gui) error {
|
||||
return renderOptionsMap(g, map[string]string{
|
||||
"space": "apply",
|
||||
"g": "pop",
|
||||
"d": "drop",
|
||||
"← → ↑ ↓": "navigate",
|
||||
})
|
||||
}
|
||||
|
||||
func handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := renderStashOptions(g); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
stashEntry := getSelectedStashEntry(v)
|
||||
if stashEntry == nil {
|
||||
renderString(g, "main", "No stash entries")
|
||||
return
|
||||
}
|
||||
diff, _ := getStashEntryDiff(stashEntry.Index)
|
||||
renderString(g, "main", diff)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleStashApply(g *gocui.Gui, v *gocui.View) error {
|
||||
return stashDo(g, v, "apply")
|
||||
}
|
||||
|
||||
func handleStashPop(g *gocui.Gui, v *gocui.View) error {
|
||||
return stashDo(g, v, "pop")
|
||||
}
|
||||
|
||||
func handleStashDrop(g *gocui.Gui, v *gocui.View) error {
|
||||
return createConfirmationPanel(g, v, "Stash drop", "Are you sure you want to drop this stash entry?", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return stashDo(g, v, "drop")
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func stashDo(g *gocui.Gui, v *gocui.View, method string) error {
|
||||
stashEntry := getSelectedStashEntry(v)
|
||||
if stashEntry == nil {
|
||||
return createErrorPanel(g, "No stash to "+method)
|
||||
}
|
||||
if output, err := gitStashDo(stashEntry.Index, method); err != nil {
|
||||
createErrorPanel(g, output)
|
||||
}
|
||||
refreshStashEntries(g)
|
||||
return refreshFiles(g)
|
||||
}
|
||||
|
||||
func handleStashSave(g *gocui.Gui, filesView *gocui.View) error {
|
||||
createPromptPanel(g, filesView, "Stash changes", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if output, err := gitStashSave(trimmedContent(v)); err != nil {
|
||||
createErrorPanel(g, output)
|
||||
}
|
||||
refreshStashEntries(g)
|
||||
return refreshFiles(g)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func refreshStatus(g *gocui.Gui) error {
|
||||
v, err := g.View("status")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// for some reason if this isn't wrapped in an update the clear seems to
|
||||
// be applied after the other things or something like that; the panel's
|
||||
// contents end up cleared
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v.Clear()
|
||||
pushables, pullables := gitUpstreamDifferenceCount()
|
||||
fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
|
||||
branches := state.Branches
|
||||
if err := updateHasMergeConflictStatus(); err != nil {
|
||||
return err
|
||||
}
|
||||
if state.HasMergeConflicts {
|
||||
colour := color.New(color.FgYellow)
|
||||
fmt.Fprint(v, coloredString(" (merging)", colour))
|
||||
}
|
||||
if len(branches) == 0 {
|
||||
return nil
|
||||
}
|
||||
branch := branches[0]
|
||||
// utilising the fact these all have padding to only grab the name
|
||||
// from the display string with the existing coloring applied
|
||||
fmt.Fprint(v, " "+branch.DisplayString[4:])
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
After Width: | Height: | Size: 56 KiB |
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# this script will make a repo with a master and develop branch, where we end up
|
||||
# on the master branch and if we try and merge master we get a merge conflict
|
||||
|
||||
# call this command from the test directory:
|
||||
# ./generate_basic_repo.sh; cd testrepo; gg; cd ..
|
||||
|
||||
# -e means exit if something fails
|
||||
# -x means print out simple commands before running them
|
||||
set -ex
|
||||
|
||||
reponame="testrepo"
|
||||
|
||||
rm -rf ${reponame}
|
||||
mkdir ${reponame}
|
||||
cd ${reponame}
|
||||
|
||||
git init
|
||||
|
||||
function add_spacing {
|
||||
for i in {1..60}
|
||||
do
|
||||
echo "..." >> $1
|
||||
done
|
||||
}
|
||||
|
||||
echo "Here is a story that has been told throuhg the ages" >> file1
|
||||
|
||||
git add file1
|
||||
git commit -m "first commit"
|
||||
|
||||
git checkout -b develop
|
||||
|
||||
echo "once upon a time there was a dog" >> file1
|
||||
add_spacing file1
|
||||
echo "once upon a time there was another dog" >> file1
|
||||
git add file1
|
||||
git commit -m "first commit on develop"
|
||||
|
||||
git checkout master
|
||||
|
||||
echo "once upon a time there was a cat" >> file1
|
||||
add_spacing file1
|
||||
echo "once upon a time there was another cat" >> file1
|
||||
git add file1
|
||||
git commit -m "first commit on develop"
|
||||
|
||||
git merge develop # should have a merge conflict here
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
# For testing subprocesses that require input
|
||||
# Ask the user for login details
|
||||
read -p 'Username: ' user
|
||||
read -sp 'Password: ' pass
|
||||
echo
|
||||
echo Hello $user
|
||||
@@ -1,41 +0,0 @@
|
||||
Copyright (c) 2015, Emir Pasic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
AVL Tree:
|
||||
|
||||
Copyright (c) 2017 Benjamin Scher Purcell <benjapurcell@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package containers provides core interfaces and functions for data structures.
|
||||
//
|
||||
// Container is the base interface for all data structures to implement.
|
||||
//
|
||||
// Iterators provide stateful iterators.
|
||||
//
|
||||
// Enumerable provides Ruby inspired (each, select, map, find, any?, etc.) container functions.
|
||||
//
|
||||
// Serialization provides serializers (marshalers) and deserializers (unmarshalers).
|
||||
package containers
|
||||
|
||||
import "github.com/emirpasic/gods/utils"
|
||||
|
||||
// Container is base interface that all data structures implement.
|
||||
type Container interface {
|
||||
Empty() bool
|
||||
Size() int
|
||||
Clear()
|
||||
Values() []interface{}
|
||||
}
|
||||
|
||||
// GetSortedValues returns sorted container's elements with respect to the passed comparator.
|
||||
// Does not effect the ordering of elements within the container.
|
||||
func GetSortedValues(container Container, comparator utils.Comparator) []interface{} {
|
||||
values := container.Values()
|
||||
if len(values) < 2 {
|
||||
return values
|
||||
}
|
||||
utils.Sort(values, comparator)
|
||||
return values
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package containers
|
||||
|
||||
// EnumerableWithIndex provides functions for ordered containers whose values can be fetched by an index.
|
||||
type EnumerableWithIndex interface {
|
||||
// Each calls the given function once for each element, passing that element's index and value.
|
||||
Each(func(index int, value interface{}))
|
||||
|
||||
// Map invokes the given function once for each element and returns a
|
||||
// container containing the values returned by the given function.
|
||||
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
|
||||
// Map(func(index int, value interface{}) interface{}) Container
|
||||
|
||||
// Select returns a new container containing all elements for which the given function returns a true value.
|
||||
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
|
||||
// Select(func(index int, value interface{}) bool) Container
|
||||
|
||||
// Any passes each element of the container to the given function and
|
||||
// returns true if the function ever returns true for any element.
|
||||
Any(func(index int, value interface{}) bool) bool
|
||||
|
||||
// All passes each element of the container to the given function and
|
||||
// returns true if the function returns true for all elements.
|
||||
All(func(index int, value interface{}) bool) bool
|
||||
|
||||
// Find passes each element of the container to the given function and returns
|
||||
// the first (index,value) for which the function is true or -1,nil otherwise
|
||||
// if no element matches the criteria.
|
||||
Find(func(index int, value interface{}) bool) (int, interface{})
|
||||
}
|
||||
|
||||
// EnumerableWithKey provides functions for ordered containers whose values whose elements are key/value pairs.
|
||||
type EnumerableWithKey interface {
|
||||
// Each calls the given function once for each element, passing that element's key and value.
|
||||
Each(func(key interface{}, value interface{}))
|
||||
|
||||
// Map invokes the given function once for each element and returns a container
|
||||
// containing the values returned by the given function as key/value pairs.
|
||||
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
|
||||
// Map(func(key interface{}, value interface{}) (interface{}, interface{})) Container
|
||||
|
||||
// Select returns a new container containing all elements for which the given function returns a true value.
|
||||
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
|
||||
// Select(func(key interface{}, value interface{}) bool) Container
|
||||
|
||||
// Any passes each element of the container to the given function and
|
||||
// returns true if the function ever returns true for any element.
|
||||
Any(func(key interface{}, value interface{}) bool) bool
|
||||
|
||||
// All passes each element of the container to the given function and
|
||||
// returns true if the function returns true for all elements.
|
||||
All(func(key interface{}, value interface{}) bool) bool
|
||||
|
||||
// Find passes each element of the container to the given function and returns
|
||||
// the first (key,value) for which the function is true or nil,nil otherwise if no element
|
||||
// matches the criteria.
|
||||
Find(func(key interface{}, value interface{}) bool) (interface{}, interface{})
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package containers
|
||||
|
||||
// IteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index.
|
||||
type IteratorWithIndex interface {
|
||||
// Next moves the iterator to the next element and returns true if there was a next element in the container.
|
||||
// If Next() returns true, then next element's index and value can be retrieved by Index() and Value().
|
||||
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
|
||||
// Modifies the state of the iterator.
|
||||
Next() bool
|
||||
|
||||
// Value returns the current element's value.
|
||||
// Does not modify the state of the iterator.
|
||||
Value() interface{}
|
||||
|
||||
// Index returns the current element's index.
|
||||
// Does not modify the state of the iterator.
|
||||
Index() int
|
||||
|
||||
// Begin resets the iterator to its initial state (one-before-first)
|
||||
// Call Next() to fetch the first element if any.
|
||||
Begin()
|
||||
|
||||
// First moves the iterator to the first element and returns true if there was a first element in the container.
|
||||
// If First() returns true, then first element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
First() bool
|
||||
}
|
||||
|
||||
// IteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs.
|
||||
type IteratorWithKey interface {
|
||||
// Next moves the iterator to the next element and returns true if there was a next element in the container.
|
||||
// If Next() returns true, then next element's key and value can be retrieved by Key() and Value().
|
||||
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
|
||||
// Modifies the state of the iterator.
|
||||
Next() bool
|
||||
|
||||
// Value returns the current element's value.
|
||||
// Does not modify the state of the iterator.
|
||||
Value() interface{}
|
||||
|
||||
// Key returns the current element's key.
|
||||
// Does not modify the state of the iterator.
|
||||
Key() interface{}
|
||||
|
||||
// Begin resets the iterator to its initial state (one-before-first)
|
||||
// Call Next() to fetch the first element if any.
|
||||
Begin()
|
||||
|
||||
// First moves the iterator to the first element and returns true if there was a first element in the container.
|
||||
// If First() returns true, then first element's key and value can be retrieved by Key() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
First() bool
|
||||
}
|
||||
|
||||
// ReverseIteratorWithIndex is stateful iterator for ordered containers whose values can be fetched by an index.
|
||||
//
|
||||
// Essentially it is the same as IteratorWithIndex, but provides additional:
|
||||
//
|
||||
// Prev() function to enable traversal in reverse
|
||||
//
|
||||
// Last() function to move the iterator to the last element.
|
||||
//
|
||||
// End() function to move the iterator past the last element (one-past-the-end).
|
||||
type ReverseIteratorWithIndex interface {
|
||||
// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
|
||||
// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
Prev() bool
|
||||
|
||||
// End moves the iterator past the last element (one-past-the-end).
|
||||
// Call Prev() to fetch the last element if any.
|
||||
End()
|
||||
|
||||
// Last moves the iterator to the last element and returns true if there was a last element in the container.
|
||||
// If Last() returns true, then last element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
Last() bool
|
||||
|
||||
IteratorWithIndex
|
||||
}
|
||||
|
||||
// ReverseIteratorWithKey is a stateful iterator for ordered containers whose elements are key value pairs.
|
||||
//
|
||||
// Essentially it is the same as IteratorWithKey, but provides additional:
|
||||
//
|
||||
// Prev() function to enable traversal in reverse
|
||||
//
|
||||
// Last() function to move the iterator to the last element.
|
||||
type ReverseIteratorWithKey interface {
|
||||
// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
|
||||
// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
Prev() bool
|
||||
|
||||
// End moves the iterator past the last element (one-past-the-end).
|
||||
// Call Prev() to fetch the last element if any.
|
||||
End()
|
||||
|
||||
// Last moves the iterator to the last element and returns true if there was a last element in the container.
|
||||
// If Last() returns true, then last element's key and value can be retrieved by Key() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
Last() bool
|
||||
|
||||
IteratorWithKey
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package containers
|
||||
|
||||
// JSONSerializer provides JSON serialization
|
||||
type JSONSerializer interface {
|
||||
// ToJSON outputs the JSON representation of containers's elements.
|
||||
ToJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// JSONDeserializer provides JSON deserialization
|
||||
type JSONDeserializer interface {
|
||||
// FromJSON populates containers's elements from the input JSON representation.
|
||||
FromJSON([]byte) error
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package arraylist implements the array list.
|
||||
//
|
||||
// Structure is not thread safe.
|
||||
//
|
||||
// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29
|
||||
package arraylist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emirpasic/gods/lists"
|
||||
"github.com/emirpasic/gods/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func assertListImplementation() {
|
||||
var _ lists.List = (*List)(nil)
|
||||
}
|
||||
|
||||
// List holds the elements in a slice
|
||||
type List struct {
|
||||
elements []interface{}
|
||||
size int
|
||||
}
|
||||
|
||||
const (
|
||||
growthFactor = float32(2.0) // growth by 100%
|
||||
shrinkFactor = float32(0.25) // shrink when size is 25% of capacity (0 means never shrink)
|
||||
)
|
||||
|
||||
// New instantiates a new empty list
|
||||
func New() *List {
|
||||
return &List{}
|
||||
}
|
||||
|
||||
// Add appends a value at the end of the list
|
||||
func (list *List) Add(values ...interface{}) {
|
||||
list.growBy(len(values))
|
||||
for _, value := range values {
|
||||
list.elements[list.size] = value
|
||||
list.size++
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the element at index.
|
||||
// Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false.
|
||||
func (list *List) Get(index int) (interface{}, bool) {
|
||||
|
||||
if !list.withinRange(index) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return list.elements[index], true
|
||||
}
|
||||
|
||||
// Remove removes one or more elements from the list with the supplied indices.
|
||||
func (list *List) Remove(index int) {
|
||||
|
||||
if !list.withinRange(index) {
|
||||
return
|
||||
}
|
||||
|
||||
list.elements[index] = nil // cleanup reference
|
||||
copy(list.elements[index:], list.elements[index+1:list.size]) // shift to the left by one (slow operation, need ways to optimize this)
|
||||
list.size--
|
||||
|
||||
list.shrink()
|
||||
}
|
||||
|
||||
// Contains checks if elements (one or more) are present in the set.
|
||||
// All elements have to be present in the set for the method to return true.
|
||||
// Performance time complexity of n^2.
|
||||
// Returns true if no arguments are passed at all, i.e. set is always super-set of empty set.
|
||||
func (list *List) Contains(values ...interface{}) bool {
|
||||
|
||||
for _, searchValue := range values {
|
||||
found := false
|
||||
for _, element := range list.elements {
|
||||
if element == searchValue {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Values returns all elements in the list.
|
||||
func (list *List) Values() []interface{} {
|
||||
newElements := make([]interface{}, list.size, list.size)
|
||||
copy(newElements, list.elements[:list.size])
|
||||
return newElements
|
||||
}
|
||||
|
||||
// Empty returns true if list does not contain any elements.
|
||||
func (list *List) Empty() bool {
|
||||
return list.size == 0
|
||||
}
|
||||
|
||||
// Size returns number of elements within the list.
|
||||
func (list *List) Size() int {
|
||||
return list.size
|
||||
}
|
||||
|
||||
// Clear removes all elements from the list.
|
||||
func (list *List) Clear() {
|
||||
list.size = 0
|
||||
list.elements = []interface{}{}
|
||||
}
|
||||
|
||||
// Sort sorts values (in-place) using.
|
||||
func (list *List) Sort(comparator utils.Comparator) {
|
||||
if len(list.elements) < 2 {
|
||||
return
|
||||
}
|
||||
utils.Sort(list.elements[:list.size], comparator)
|
||||
}
|
||||
|
||||
// Swap swaps the two values at the specified positions.
|
||||
func (list *List) Swap(i, j int) {
|
||||
if list.withinRange(i) && list.withinRange(j) {
|
||||
list.elements[i], list.elements[j] = list.elements[j], list.elements[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right.
|
||||
// Does not do anything if position is negative or bigger than list's size
|
||||
// Note: position equal to list's size is valid, i.e. append.
|
||||
func (list *List) Insert(index int, values ...interface{}) {
|
||||
|
||||
if !list.withinRange(index) {
|
||||
// Append
|
||||
if index == list.size {
|
||||
list.Add(values...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
l := len(values)
|
||||
list.growBy(l)
|
||||
list.size += l
|
||||
// Shift old to right
|
||||
for i := list.size - 1; i >= index+l; i-- {
|
||||
list.elements[i] = list.elements[i-l]
|
||||
}
|
||||
// Insert new
|
||||
for i, value := range values {
|
||||
list.elements[index+i] = value
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of container
|
||||
func (list *List) String() string {
|
||||
str := "ArrayList\n"
|
||||
values := []string{}
|
||||
for _, value := range list.elements[:list.size] {
|
||||
values = append(values, fmt.Sprintf("%v", value))
|
||||
}
|
||||
str += strings.Join(values, ", ")
|
||||
return str
|
||||
}
|
||||
|
||||
// Check that the index is within bounds of the list
|
||||
func (list *List) withinRange(index int) bool {
|
||||
return index >= 0 && index < list.size
|
||||
}
|
||||
|
||||
func (list *List) resize(cap int) {
|
||||
newElements := make([]interface{}, cap, cap)
|
||||
copy(newElements, list.elements)
|
||||
list.elements = newElements
|
||||
}
|
||||
|
||||
// Expand the array if necessary, i.e. capacity will be reached if we add n elements
|
||||
func (list *List) growBy(n int) {
|
||||
// When capacity is reached, grow by a factor of growthFactor and add number of elements
|
||||
currentCapacity := cap(list.elements)
|
||||
if list.size+n >= currentCapacity {
|
||||
newCapacity := int(growthFactor * float32(currentCapacity+n))
|
||||
list.resize(newCapacity)
|
||||
}
|
||||
}
|
||||
|
||||
// Shrink the array if necessary, i.e. when size is shrinkFactor percent of current capacity
|
||||
func (list *List) shrink() {
|
||||
if shrinkFactor == 0.0 {
|
||||
return
|
||||
}
|
||||
// Shrink when size is at shrinkFactor * capacity
|
||||
currentCapacity := cap(list.elements)
|
||||
if list.size <= int(float32(currentCapacity)*shrinkFactor) {
|
||||
list.resize(list.size)
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package arraylist
|
||||
|
||||
import "github.com/emirpasic/gods/containers"
|
||||
|
||||
func assertEnumerableImplementation() {
|
||||
var _ containers.EnumerableWithIndex = (*List)(nil)
|
||||
}
|
||||
|
||||
// Each calls the given function once for each element, passing that element's index and value.
|
||||
func (list *List) Each(f func(index int, value interface{})) {
|
||||
iterator := list.Iterator()
|
||||
for iterator.Next() {
|
||||
f(iterator.Index(), iterator.Value())
|
||||
}
|
||||
}
|
||||
|
||||
// Map invokes the given function once for each element and returns a
|
||||
// container containing the values returned by the given function.
|
||||
func (list *List) Map(f func(index int, value interface{}) interface{}) *List {
|
||||
newList := &List{}
|
||||
iterator := list.Iterator()
|
||||
for iterator.Next() {
|
||||
newList.Add(f(iterator.Index(), iterator.Value()))
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
// Select returns a new container containing all elements for which the given function returns a true value.
|
||||
func (list *List) Select(f func(index int, value interface{}) bool) *List {
|
||||
newList := &List{}
|
||||
iterator := list.Iterator()
|
||||
for iterator.Next() {
|
||||
if f(iterator.Index(), iterator.Value()) {
|
||||
newList.Add(iterator.Value())
|
||||
}
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
// Any passes each element of the collection to the given function and
|
||||
// returns true if the function ever returns true for any element.
|
||||
func (list *List) Any(f func(index int, value interface{}) bool) bool {
|
||||
iterator := list.Iterator()
|
||||
for iterator.Next() {
|
||||
if f(iterator.Index(), iterator.Value()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// All passes each element of the collection to the given function and
|
||||
// returns true if the function returns true for all elements.
|
||||
func (list *List) All(f func(index int, value interface{}) bool) bool {
|
||||
iterator := list.Iterator()
|
||||
for iterator.Next() {
|
||||
if !f(iterator.Index(), iterator.Value()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Find passes each element of the container to the given function and returns
|
||||
// the first (index,value) for which the function is true or -1,nil otherwise
|
||||
// if no element matches the criteria.
|
||||
func (list *List) Find(f func(index int, value interface{}) bool) (int, interface{}) {
|
||||
iterator := list.Iterator()
|
||||
for iterator.Next() {
|
||||
if f(iterator.Index(), iterator.Value()) {
|
||||
return iterator.Index(), iterator.Value()
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package arraylist
|
||||
|
||||
import "github.com/emirpasic/gods/containers"
|
||||
|
||||
func assertIteratorImplementation() {
|
||||
var _ containers.ReverseIteratorWithIndex = (*Iterator)(nil)
|
||||
}
|
||||
|
||||
// Iterator holding the iterator's state
|
||||
type Iterator struct {
|
||||
list *List
|
||||
index int
|
||||
}
|
||||
|
||||
// Iterator returns a stateful iterator whose values can be fetched by an index.
|
||||
func (list *List) Iterator() Iterator {
|
||||
return Iterator{list: list, index: -1}
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next element and returns true if there was a next element in the container.
|
||||
// If Next() returns true, then next element's index and value can be retrieved by Index() and Value().
|
||||
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) Next() bool {
|
||||
if iterator.index < iterator.list.size {
|
||||
iterator.index++
|
||||
}
|
||||
return iterator.list.withinRange(iterator.index)
|
||||
}
|
||||
|
||||
// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
|
||||
// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) Prev() bool {
|
||||
if iterator.index >= 0 {
|
||||
iterator.index--
|
||||
}
|
||||
return iterator.list.withinRange(iterator.index)
|
||||
}
|
||||
|
||||
// Value returns the current element's value.
|
||||
// Does not modify the state of the iterator.
|
||||
func (iterator *Iterator) Value() interface{} {
|
||||
return iterator.list.elements[iterator.index]
|
||||
}
|
||||
|
||||
// Index returns the current element's index.
|
||||
// Does not modify the state of the iterator.
|
||||
func (iterator *Iterator) Index() int {
|
||||
return iterator.index
|
||||
}
|
||||
|
||||
// Begin resets the iterator to its initial state (one-before-first)
|
||||
// Call Next() to fetch the first element if any.
|
||||
func (iterator *Iterator) Begin() {
|
||||
iterator.index = -1
|
||||
}
|
||||
|
||||
// End moves the iterator past the last element (one-past-the-end).
|
||||
// Call Prev() to fetch the last element if any.
|
||||
func (iterator *Iterator) End() {
|
||||
iterator.index = iterator.list.size
|
||||
}
|
||||
|
||||
// First moves the iterator to the first element and returns true if there was a first element in the container.
|
||||
// If First() returns true, then first element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) First() bool {
|
||||
iterator.Begin()
|
||||
return iterator.Next()
|
||||
}
|
||||
|
||||
// Last moves the iterator to the last element and returns true if there was a last element in the container.
|
||||
// If Last() returns true, then last element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) Last() bool {
|
||||
iterator.End()
|
||||
return iterator.Prev()
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package arraylist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/emirpasic/gods/containers"
|
||||
)
|
||||
|
||||
func assertSerializationImplementation() {
|
||||
var _ containers.JSONSerializer = (*List)(nil)
|
||||
var _ containers.JSONDeserializer = (*List)(nil)
|
||||
}
|
||||
|
||||
// ToJSON outputs the JSON representation of list's elements.
|
||||
func (list *List) ToJSON() ([]byte, error) {
|
||||
return json.Marshal(list.elements[:list.size])
|
||||
}
|
||||
|
||||
// FromJSON populates list's elements from the input JSON representation.
|
||||
func (list *List) FromJSON(data []byte) error {
|
||||
err := json.Unmarshal(data, &list.elements)
|
||||
if err == nil {
|
||||
list.size = len(list.elements)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package lists provides an abstract List interface.
|
||||
//
|
||||
// In computer science, a list or sequence is an abstract data type that represents an ordered sequence of values, where the same value may occur more than once. An instance of a list is a computer representation of the mathematical concept of a finite sequence; the (potentially) infinite analog of a list is a stream. Lists are a basic example of containers, as they contain other values. If the same value occurs multiple times, each occurrence is considered a distinct item.
|
||||
//
|
||||
// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29
|
||||
package lists
|
||||
|
||||
import (
|
||||
"github.com/emirpasic/gods/containers"
|
||||
"github.com/emirpasic/gods/utils"
|
||||
)
|
||||
|
||||
// List interface that all lists implement
|
||||
type List interface {
|
||||
Get(index int) (interface{}, bool)
|
||||
Remove(index int)
|
||||
Add(values ...interface{})
|
||||
Contains(values ...interface{}) bool
|
||||
Sort(comparator utils.Comparator)
|
||||
Swap(index1, index2 int)
|
||||
Insert(index int, values ...interface{})
|
||||
|
||||
containers.Container
|
||||
// Empty() bool
|
||||
// Size() int
|
||||
// Clear()
|
||||
// Values() []interface{}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package binaryheap implements a binary heap backed by array list.
|
||||
//
|
||||
// Comparator defines this heap as either min or max heap.
|
||||
//
|
||||
// Structure is not thread safe.
|
||||
//
|
||||
// References: http://en.wikipedia.org/wiki/Binary_heap
|
||||
package binaryheap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emirpasic/gods/lists/arraylist"
|
||||
"github.com/emirpasic/gods/trees"
|
||||
"github.com/emirpasic/gods/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func assertTreeImplementation() {
|
||||
var _ trees.Tree = (*Heap)(nil)
|
||||
}
|
||||
|
||||
// Heap holds elements in an array-list
|
||||
type Heap struct {
|
||||
list *arraylist.List
|
||||
Comparator utils.Comparator
|
||||
}
|
||||
|
||||
// NewWith instantiates a new empty heap tree with the custom comparator.
|
||||
func NewWith(comparator utils.Comparator) *Heap {
|
||||
return &Heap{list: arraylist.New(), Comparator: comparator}
|
||||
}
|
||||
|
||||
// NewWithIntComparator instantiates a new empty heap with the IntComparator, i.e. elements are of type int.
|
||||
func NewWithIntComparator() *Heap {
|
||||
return &Heap{list: arraylist.New(), Comparator: utils.IntComparator}
|
||||
}
|
||||
|
||||
// NewWithStringComparator instantiates a new empty heap with the StringComparator, i.e. elements are of type string.
|
||||
func NewWithStringComparator() *Heap {
|
||||
return &Heap{list: arraylist.New(), Comparator: utils.StringComparator}
|
||||
}
|
||||
|
||||
// Push adds a value onto the heap and bubbles it up accordingly.
|
||||
func (heap *Heap) Push(values ...interface{}) {
|
||||
if len(values) == 1 {
|
||||
heap.list.Add(values[0])
|
||||
heap.bubbleUp()
|
||||
} else {
|
||||
// Reference: https://en.wikipedia.org/wiki/Binary_heap#Building_a_heap
|
||||
for _, value := range values {
|
||||
heap.list.Add(value)
|
||||
}
|
||||
size := heap.list.Size()/2 + 1
|
||||
for i := size; i >= 0; i-- {
|
||||
heap.bubbleDownIndex(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop removes top element on heap and returns it, or nil if heap is empty.
|
||||
// Second return parameter is true, unless the heap was empty and there was nothing to pop.
|
||||
func (heap *Heap) Pop() (value interface{}, ok bool) {
|
||||
value, ok = heap.list.Get(0)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
lastIndex := heap.list.Size() - 1
|
||||
heap.list.Swap(0, lastIndex)
|
||||
heap.list.Remove(lastIndex)
|
||||
heap.bubbleDown()
|
||||
return
|
||||
}
|
||||
|
||||
// Peek returns top element on the heap without removing it, or nil if heap is empty.
|
||||
// Second return parameter is true, unless the heap was empty and there was nothing to peek.
|
||||
func (heap *Heap) Peek() (value interface{}, ok bool) {
|
||||
return heap.list.Get(0)
|
||||
}
|
||||
|
||||
// Empty returns true if heap does not contain any elements.
|
||||
func (heap *Heap) Empty() bool {
|
||||
return heap.list.Empty()
|
||||
}
|
||||
|
||||
// Size returns number of elements within the heap.
|
||||
func (heap *Heap) Size() int {
|
||||
return heap.list.Size()
|
||||
}
|
||||
|
||||
// Clear removes all elements from the heap.
|
||||
func (heap *Heap) Clear() {
|
||||
heap.list.Clear()
|
||||
}
|
||||
|
||||
// Values returns all elements in the heap.
|
||||
func (heap *Heap) Values() []interface{} {
|
||||
return heap.list.Values()
|
||||
}
|
||||
|
||||
// String returns a string representation of container
|
||||
func (heap *Heap) String() string {
|
||||
str := "BinaryHeap\n"
|
||||
values := []string{}
|
||||
for _, value := range heap.list.Values() {
|
||||
values = append(values, fmt.Sprintf("%v", value))
|
||||
}
|
||||
str += strings.Join(values, ", ")
|
||||
return str
|
||||
}
|
||||
|
||||
// Performs the "bubble down" operation. This is to place the element that is at the root
|
||||
// of the heap in its correct place so that the heap maintains the min/max-heap order property.
|
||||
func (heap *Heap) bubbleDown() {
|
||||
heap.bubbleDownIndex(0)
|
||||
}
|
||||
|
||||
// Performs the "bubble down" operation. This is to place the element that is at the index
|
||||
// of the heap in its correct place so that the heap maintains the min/max-heap order property.
|
||||
func (heap *Heap) bubbleDownIndex(index int) {
|
||||
size := heap.list.Size()
|
||||
for leftIndex := index<<1 + 1; leftIndex < size; leftIndex = index<<1 + 1 {
|
||||
rightIndex := index<<1 + 2
|
||||
smallerIndex := leftIndex
|
||||
leftValue, _ := heap.list.Get(leftIndex)
|
||||
rightValue, _ := heap.list.Get(rightIndex)
|
||||
if rightIndex < size && heap.Comparator(leftValue, rightValue) > 0 {
|
||||
smallerIndex = rightIndex
|
||||
}
|
||||
indexValue, _ := heap.list.Get(index)
|
||||
smallerValue, _ := heap.list.Get(smallerIndex)
|
||||
if heap.Comparator(indexValue, smallerValue) > 0 {
|
||||
heap.list.Swap(index, smallerIndex)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
index = smallerIndex
|
||||
}
|
||||
}
|
||||
|
||||
// Performs the "bubble up" operation. This is to place a newly inserted
|
||||
// element (i.e. last element in the list) in its correct place so that
|
||||
// the heap maintains the min/max-heap order property.
|
||||
func (heap *Heap) bubbleUp() {
|
||||
index := heap.list.Size() - 1
|
||||
for parentIndex := (index - 1) >> 1; index > 0; parentIndex = (index - 1) >> 1 {
|
||||
indexValue, _ := heap.list.Get(index)
|
||||
parentValue, _ := heap.list.Get(parentIndex)
|
||||
if heap.Comparator(parentValue, indexValue) <= 0 {
|
||||
break
|
||||
}
|
||||
heap.list.Swap(index, parentIndex)
|
||||
index = parentIndex
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the index is within bounds of the list
|
||||
func (heap *Heap) withinRange(index int) bool {
|
||||
return index >= 0 && index < heap.list.Size()
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binaryheap
|
||||
|
||||
import "github.com/emirpasic/gods/containers"
|
||||
|
||||
func assertIteratorImplementation() {
|
||||
var _ containers.ReverseIteratorWithIndex = (*Iterator)(nil)
|
||||
}
|
||||
|
||||
// Iterator returns a stateful iterator whose values can be fetched by an index.
|
||||
type Iterator struct {
|
||||
heap *Heap
|
||||
index int
|
||||
}
|
||||
|
||||
// Iterator returns a stateful iterator whose values can be fetched by an index.
|
||||
func (heap *Heap) Iterator() Iterator {
|
||||
return Iterator{heap: heap, index: -1}
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next element and returns true if there was a next element in the container.
|
||||
// If Next() returns true, then next element's index and value can be retrieved by Index() and Value().
|
||||
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) Next() bool {
|
||||
if iterator.index < iterator.heap.Size() {
|
||||
iterator.index++
|
||||
}
|
||||
return iterator.heap.withinRange(iterator.index)
|
||||
}
|
||||
|
||||
// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
|
||||
// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) Prev() bool {
|
||||
if iterator.index >= 0 {
|
||||
iterator.index--
|
||||
}
|
||||
return iterator.heap.withinRange(iterator.index)
|
||||
}
|
||||
|
||||
// Value returns the current element's value.
|
||||
// Does not modify the state of the iterator.
|
||||
func (iterator *Iterator) Value() interface{} {
|
||||
value, _ := iterator.heap.list.Get(iterator.index)
|
||||
return value
|
||||
}
|
||||
|
||||
// Index returns the current element's index.
|
||||
// Does not modify the state of the iterator.
|
||||
func (iterator *Iterator) Index() int {
|
||||
return iterator.index
|
||||
}
|
||||
|
||||
// Begin resets the iterator to its initial state (one-before-first)
|
||||
// Call Next() to fetch the first element if any.
|
||||
func (iterator *Iterator) Begin() {
|
||||
iterator.index = -1
|
||||
}
|
||||
|
||||
// End moves the iterator past the last element (one-past-the-end).
|
||||
// Call Prev() to fetch the last element if any.
|
||||
func (iterator *Iterator) End() {
|
||||
iterator.index = iterator.heap.Size()
|
||||
}
|
||||
|
||||
// First moves the iterator to the first element and returns true if there was a first element in the container.
|
||||
// If First() returns true, then first element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) First() bool {
|
||||
iterator.Begin()
|
||||
return iterator.Next()
|
||||
}
|
||||
|
||||
// Last moves the iterator to the last element and returns true if there was a last element in the container.
|
||||
// If Last() returns true, then last element's index and value can be retrieved by Index() and Value().
|
||||
// Modifies the state of the iterator.
|
||||
func (iterator *Iterator) Last() bool {
|
||||
iterator.End()
|
||||
return iterator.Prev()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binaryheap
|
||||
|
||||
import "github.com/emirpasic/gods/containers"
|
||||
|
||||
func assertSerializationImplementation() {
|
||||
var _ containers.JSONSerializer = (*Heap)(nil)
|
||||
var _ containers.JSONDeserializer = (*Heap)(nil)
|
||||
}
|
||||
|
||||
// ToJSON outputs the JSON representation of list's elements.
|
||||
func (heap *Heap) ToJSON() ([]byte, error) {
|
||||
return heap.list.ToJSON()
|
||||
}
|
||||
|
||||
// FromJSON populates list's elements from the input JSON representation.
|
||||
func (heap *Heap) FromJSON(data []byte) error {
|
||||
return heap.list.FromJSON(data)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package trees provides an abstract Tree interface.
|
||||
//
|
||||
// In computer science, a tree is a widely used abstract data type (ADT) or data structure implementing this ADT that simulates a hierarchical tree structure, with a root value and subtrees of children with a parent node, represented as a set of linked nodes.
|
||||
//
|
||||
// Reference: https://en.wikipedia.org/wiki/Tree_%28data_structure%29
|
||||
package trees
|
||||
|
||||
import "github.com/emirpasic/gods/containers"
|
||||
|
||||
// Tree interface that all trees implement
|
||||
type Tree interface {
|
||||
containers.Container
|
||||
// Empty() bool
|
||||
// Size() int
|
||||
// Clear()
|
||||
// Values() []interface{}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
// Comparator will make type assertion (see IntComparator for example),
|
||||
// which will panic if a or b are not of the asserted type.
|
||||
//
|
||||
// Should return a number:
|
||||
// negative , if a < b
|
||||
// zero , if a == b
|
||||
// positive , if a > b
|
||||
type Comparator func(a, b interface{}) int
|
||||
|
||||
// StringComparator provides a fast comparison on strings
|
||||
func StringComparator(a, b interface{}) int {
|
||||
s1 := a.(string)
|
||||
s2 := b.(string)
|
||||
min := len(s2)
|
||||
if len(s1) < len(s2) {
|
||||
min = len(s1)
|
||||
}
|
||||
diff := 0
|
||||
for i := 0; i < min && diff == 0; i++ {
|
||||
diff = int(s1[i]) - int(s2[i])
|
||||
}
|
||||
if diff == 0 {
|
||||
diff = len(s1) - len(s2)
|
||||
}
|
||||
if diff < 0 {
|
||||
return -1
|
||||
}
|
||||
if diff > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IntComparator provides a basic comparison on int
|
||||
func IntComparator(a, b interface{}) int {
|
||||
aAsserted := a.(int)
|
||||
bAsserted := b.(int)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Int8Comparator provides a basic comparison on int8
|
||||
func Int8Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(int8)
|
||||
bAsserted := b.(int8)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Int16Comparator provides a basic comparison on int16
|
||||
func Int16Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(int16)
|
||||
bAsserted := b.(int16)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Int32Comparator provides a basic comparison on int32
|
||||
func Int32Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(int32)
|
||||
bAsserted := b.(int32)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Int64Comparator provides a basic comparison on int64
|
||||
func Int64Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(int64)
|
||||
bAsserted := b.(int64)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// UIntComparator provides a basic comparison on uint
|
||||
func UIntComparator(a, b interface{}) int {
|
||||
aAsserted := a.(uint)
|
||||
bAsserted := b.(uint)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// UInt8Comparator provides a basic comparison on uint8
|
||||
func UInt8Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(uint8)
|
||||
bAsserted := b.(uint8)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// UInt16Comparator provides a basic comparison on uint16
|
||||
func UInt16Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(uint16)
|
||||
bAsserted := b.(uint16)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// UInt32Comparator provides a basic comparison on uint32
|
||||
func UInt32Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(uint32)
|
||||
bAsserted := b.(uint32)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// UInt64Comparator provides a basic comparison on uint64
|
||||
func UInt64Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(uint64)
|
||||
bAsserted := b.(uint64)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Float32Comparator provides a basic comparison on float32
|
||||
func Float32Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(float32)
|
||||
bAsserted := b.(float32)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Float64Comparator provides a basic comparison on float64
|
||||
func Float64Comparator(a, b interface{}) int {
|
||||
aAsserted := a.(float64)
|
||||
bAsserted := b.(float64)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// ByteComparator provides a basic comparison on byte
|
||||
func ByteComparator(a, b interface{}) int {
|
||||
aAsserted := a.(byte)
|
||||
bAsserted := b.(byte)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// RuneComparator provides a basic comparison on rune
|
||||
func RuneComparator(a, b interface{}) int {
|
||||
aAsserted := a.(rune)
|
||||
bAsserted := b.(rune)
|
||||
switch {
|
||||
case aAsserted > bAsserted:
|
||||
return 1
|
||||
case aAsserted < bAsserted:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// TimeComparator provides a basic comparison on time.Time
|
||||
func TimeComparator(a, b interface{}) int {
|
||||
aAsserted := a.(time.Time)
|
||||
bAsserted := b.(time.Time)
|
||||
|
||||
switch {
|
||||
case aAsserted.After(bAsserted):
|
||||
return 1
|
||||
case aAsserted.Before(bAsserted):
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import "sort"
|
||||
|
||||
// Sort sorts values (in-place) with respect to the given comparator.
|
||||
//
|
||||
// Uses Go's sort (hybrid of quicksort for large and then insertion sort for smaller slices).
|
||||
func Sort(values []interface{}, comparator Comparator) {
|
||||
sort.Sort(sortable{values, comparator})
|
||||
}
|
||||
|
||||
type sortable struct {
|
||||
values []interface{}
|
||||
comparator Comparator
|
||||
}
|
||||
|
||||
func (s sortable) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
func (s sortable) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
}
|
||||
func (s sortable) Less(i, j int) bool {
|
||||
return s.comparator(s.values[i], s.values[j]) < 0
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package utils provides common utility functions.
|
||||
//
|
||||
// Provided functionalities:
|
||||
// - sorting
|
||||
// - comparators
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ToString converts a value to string.
|
||||
func ToString(value interface{}) string {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
return value.(string)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(value.(int8)), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(value.(int16)), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(value.(int32)), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(int64(value.(int64)), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(value.(uint8)), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(value.(uint16)), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(value.(uint32)), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(uint64(value.(uint64)), 10)
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(value.(float32)), 'g', -1, 64)
|
||||
case float64:
|
||||
return strconv.FormatFloat(float64(value.(float64)), 'g', -1, 64)
|
||||
case bool:
|
||||
return strconv.FormatBool(value.(bool))
|
||||
default:
|
||||
return fmt.Sprintf("%+v", value)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Fatih Arslan
|
||||
|
||||
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.
|
||||
@@ -1,603 +0,0 @@
|
||||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. This is a global option and affects all colors. For more control
|
||||
// over each color block use the methods DisableColor() individually.
|
||||
NoColor = os.Getenv("TERM") == "dumb" ||
|
||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
||||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
// Error defines a color supporting writer for os.Stderr.
|
||||
Error = colorable.NewColorableStderr()
|
||||
|
||||
// colorsCache is used to reduce the count of created Color objects and
|
||||
// allows to reuse already created objects with required Attribute.
|
||||
colorsCache = make(map[Attribute]*Color)
|
||||
colorsCacheMu sync.Mutex // protects colorsCache
|
||||
)
|
||||
|
||||
// Color defines a custom color object which is defined by SGR parameters.
|
||||
type Color struct {
|
||||
params []Attribute
|
||||
noColor *bool
|
||||
}
|
||||
|
||||
// Attribute defines a single SGR Code
|
||||
type Attribute int
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
Reset Attribute = iota
|
||||
Bold
|
||||
Faint
|
||||
Italic
|
||||
Underline
|
||||
BlinkSlow
|
||||
BlinkRapid
|
||||
ReverseVideo
|
||||
Concealed
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attribute = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attribute = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attribute = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attribute = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func New(value ...Attribute) *Color {
|
||||
c := &Color{params: make([]Attribute, 0)}
|
||||
c.Add(value...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the given parameters immediately. It will change the color of
|
||||
// output with the given SGR parameters until color.Unset() is called.
|
||||
func Set(p ...Attribute) *Color {
|
||||
c := New(p...)
|
||||
c.Set()
|
||||
return c
|
||||
}
|
||||
|
||||
// Unset resets all escape attributes and clears the output. Usually should
|
||||
// be called after Set().
|
||||
func Unset() {
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Set sets the SGR sequence.
|
||||
func (c *Color) Set() *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unset() {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
Unset()
|
||||
}
|
||||
|
||||
func (c *Color) setWriter(w io.Writer) *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unsetWriter(w io.Writer) {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||
func (c *Color) Add(value ...Attribute) *Color {
|
||||
c.params = append(c.params, value...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) prepend(value Attribute) {
|
||||
c.params = append(c.params, 0)
|
||||
copy(c.params[1:], c.params[0:])
|
||||
c.params[0] = value
|
||||
}
|
||||
|
||||
// Fprint formats using the default formats for its operands and writes to w.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
// string. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprint(Output, a...)
|
||||
}
|
||||
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintf(Output, format, a...)
|
||||
}
|
||||
|
||||
// Fprintln formats using the default formats for its operands and writes to w.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are always added between operands and a newline is
|
||||
// appended. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
func (c *Color) Sprint(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// FprintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprint().
|
||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprint(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Print().
|
||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintf().
|
||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||
return func(w io.Writer, format string, a ...interface{}) {
|
||||
c.Fprintf(w, format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Printf().
|
||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintln().
|
||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprintln(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Println().
|
||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// SprintFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output, example:
|
||||
//
|
||||
// put := New(FgYellow).SprintFunc()
|
||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintfFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
return func(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
||||
// an example output might be: "1;36" -> bold cyan
|
||||
func (c *Color) sequence() string {
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
return strings.Join(format, ";")
|
||||
}
|
||||
|
||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||
// be printed.
|
||||
func (c *Color) wrap(s string) string {
|
||||
if c.isNoColorSet() {
|
||||
return s
|
||||
}
|
||||
|
||||
return c.format() + s + c.unformat()
|
||||
}
|
||||
|
||||
func (c *Color) format() string {
|
||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
// code and still being able to output. Can be used for flags like
|
||||
// "--no-color". To enable back use EnableColor() method.
|
||||
func (c *Color) DisableColor() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
// EnableColor enables the color output. Use it in conjunction with
|
||||
// DisableColor(). Otherwise this method has no side effects.
|
||||
func (c *Color) EnableColor() {
|
||||
c.noColor = boolPtr(false)
|
||||
}
|
||||
|
||||
func (c *Color) isNoColorSet() bool {
|
||||
// check first if we have user setted action
|
||||
if c.noColor != nil {
|
||||
return *c.noColor
|
||||
}
|
||||
|
||||
// if not return the global option, which is disabled by default
|
||||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attribute) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func getCachedColor(p Attribute) *Color {
|
||||
colorsCacheMu.Lock()
|
||||
defer colorsCacheMu.Unlock()
|
||||
|
||||
c, ok := colorsCache[p]
|
||||
if !ok {
|
||||
c = New(p)
|
||||
colorsCache[p] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func colorPrint(format string, p Attribute, a ...interface{}) {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
c.Print(format)
|
||||
} else {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func colorString(format string, p Attribute, a ...interface{}) string {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if len(a) == 0 {
|
||||
return c.SprintFunc()(format)
|
||||
}
|
||||
|
||||
return c.SprintfFunc()(format, a...)
|
||||
}
|
||||
|
||||
// Black is a convenient helper function to print with black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||
|
||||
// Red is a convenient helper function to print with red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||
|
||||
// Green is a convenient helper function to print with green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||
|
||||
// Yellow is a convenient helper function to print with yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||
|
||||
// Blue is a convenient helper function to print with blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||
|
||||
// Magenta is a convenient helper function to print with magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||
|
||||
// Cyan is a convenient helper function to print with cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||
|
||||
// White is a convenient helper function to print with white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||
|
||||
// BlackString is a convenient helper function to return a string with black
|
||||
// foreground.
|
||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||
|
||||
// RedString is a convenient helper function to return a string with red
|
||||
// foreground.
|
||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||
|
||||
// GreenString is a convenient helper function to return a string with green
|
||||
// foreground.
|
||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||
|
||||
// YellowString is a convenient helper function to return a string with yellow
|
||||
// foreground.
|
||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||
|
||||
// BlueString is a convenient helper function to return a string with blue
|
||||
// foreground.
|
||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||
|
||||
// MagentaString is a convenient helper function to return a string with magenta
|
||||
// foreground.
|
||||
func MagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgMagenta, a...)
|
||||
}
|
||||
|
||||
// CyanString is a convenient helper function to return a string with cyan
|
||||
// foreground.
|
||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||
|
||||
// WhiteString is a convenient helper function to return a string with white
|
||||
// foreground.
|
||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
||||
|
||||
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
||||
|
||||
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
||||
|
||||
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
||||
|
||||
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
||||
|
||||
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
||||
|
||||
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
||||
// foreground.
|
||||
func HiBlackString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiBlack, a...)
|
||||
}
|
||||
|
||||
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
||||
// foreground.
|
||||
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
||||
// foreground.
|
||||
func HiGreenString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiGreen, a...)
|
||||
}
|
||||
|
||||
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
||||
// foreground.
|
||||
func HiYellowString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiYellow, a...)
|
||||
}
|
||||
|
||||
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
||||
// foreground.
|
||||
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
||||
// foreground.
|
||||
func HiMagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiMagenta, a...)
|
||||
}
|
||||
|
||||
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
||||
// foreground.
|
||||
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
||||
// foreground.
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
Package color is an ANSI color package to output colorized or SGR defined
|
||||
output to the standard output. The API can be used in several way, pick one
|
||||
that suits you.
|
||||
|
||||
Use simple and default helper functions with predefined foreground colors:
|
||||
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// a newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// More default foreground colors..
|
||||
color.Red("We have red")
|
||||
color.Yellow("Yellow color too!")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
// Hi-intensity colors
|
||||
color.HiGreen("Bright green color.")
|
||||
color.HiBlack("Bright black means gray..")
|
||||
color.HiWhite("Shiny white color!")
|
||||
|
||||
However there are times where custom color mixes are required. Below are some
|
||||
examples to create custom color objects and use the print functions of each
|
||||
separate color object.
|
||||
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with White background.")
|
||||
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(myWriter, "This will print text in blue.")
|
||||
|
||||
You can create PrintXxx functions to simplify even more:
|
||||
|
||||
// Create a custom print function for convenient
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("warning")
|
||||
red("error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("don't forget this...")
|
||||
|
||||
You can also FprintXxx functions to pass your own io.Writer:
|
||||
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, don't forget this...")
|
||||
|
||||
|
||||
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
|
||||
yellow := New(FgYellow).SprintFunc()
|
||||
red := New(FgRed).SprintFunc()
|
||||
|
||||
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Printf("this %s rocks!\n", info("package"))
|
||||
|
||||
Windows support is enabled by default. All Print functions work as intended.
|
||||
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
||||
set the output to color.Output:
|
||||
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
||||
|
||||
Using with existing code is possible. Just use the Set() method to set the
|
||||
standard output to the given parameters. That way a rewrite of an existing
|
||||
code is not required.
|
||||
|
||||
// Use handy standard colors.
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will be now in Yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // use it in your function
|
||||
|
||||
fmt.Println("All text will be now bold magenta.")
|
||||
|
||||
There might be a case where you want to disable color output (for example to
|
||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||
disable colors both globally and for single color definition. For example
|
||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||
the color output with:
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
*/
|
||||
package color
|
||||