Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5ff39ba5f | ||
|
|
31e201ca52 | ||
|
|
0abd7ad6be | ||
|
|
b3522c48d9 | ||
|
|
0fc58a7986 | ||
|
|
54241d8ab9 | ||
|
|
355f1615ab | ||
|
|
113252b0ae | ||
|
|
1cd7d14029 | ||
|
|
87c2fb6a4a | ||
|
|
9912998bb7 | ||
|
|
e223d3d8de | ||
|
|
ec31fc4cc7 | ||
|
|
3ce2b9b79a | ||
|
|
a79182e50d | ||
|
|
6f4c595dde | ||
|
|
0eb3090ad6 | ||
|
|
6ea25bd259 | ||
|
|
fe5f087f9c | ||
|
|
79299be3b2 | ||
|
|
4c9b620bd0 | ||
|
|
a7508a5dfd | ||
|
|
1a3d765c4c | ||
|
|
4058c71ca0 | ||
|
|
3fc22a6010 | ||
|
|
a9fe0b8000 | ||
|
|
5af7b0235e | ||
|
|
bf946200e9 | ||
|
|
890cc87724 | ||
|
|
8eb0b0f4ca | ||
|
|
e6a8dc0bcf | ||
|
|
02c497fad6 | ||
|
|
d0ab747479 | ||
|
|
f94d0be2c9 | ||
|
|
9fd9fd6816 | ||
|
|
b8717d750a | ||
|
|
8ad01fe32f | ||
|
|
fdb543fa7d | ||
|
|
52b5a6410c | ||
|
|
0034cfef5c | ||
|
|
78b62be96f | ||
|
|
1f5ccab1ce | ||
|
|
46be280c92 | ||
|
|
2a5763a771 | ||
|
|
370cec098b | ||
|
|
49a2f0191f | ||
|
|
fabdda0492 | ||
|
|
6fc3290a05 | ||
|
|
66e6369c28 | ||
|
|
0f0da9c32a | ||
|
|
0a69c1a02d | ||
|
|
feaf98bd01 | ||
|
|
0fe9c15ce8 | ||
|
|
f528e12c83 | ||
|
|
8ca9f93ccf | ||
|
|
73d8064837 | ||
|
|
5b1f60b7eb | ||
|
|
2e1344f611 | ||
|
|
5b9996b16f | ||
|
|
6fdc1791e4 | ||
|
|
fd4f37b5c3 | ||
|
|
d76e8887e5 | ||
|
|
eb9134685a | ||
|
|
d929b84786 | ||
|
|
8ef3297b11 | ||
|
|
27c7aeb117 | ||
|
|
c9714600e8 | ||
|
|
665fdded14 | ||
|
|
814a0ea36f | ||
|
|
71e018a3dd | ||
|
|
efb26f8b60 | ||
|
|
d9eb6e2682 | ||
|
|
b74107f2ba | ||
|
|
0cd91a10c6 | ||
|
|
f062e1dcda | ||
|
|
9f5397a2d4 | ||
|
|
0164abbd4a | ||
|
|
e92af63636 | ||
|
|
94501c683b | ||
|
|
047c3cf880 | ||
|
|
47d7d87c82 | ||
|
|
5f53d50492 | ||
|
|
5f71f87496 |
@@ -38,27 +38,28 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^bump'
|
||||
brew:
|
||||
# Reporitory to push the tap to.
|
||||
github:
|
||||
owner: jesseduffield
|
||||
name: homebrew-lazygit
|
||||
brews:
|
||||
-
|
||||
# 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 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'
|
||||
# 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
|
||||
# # 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
|
||||
# test comment to see if goreleaser only releases on new commits
|
||||
|
||||
39
README.md
39
README.md
@@ -1,8 +1,8 @@
|
||||
# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []()
|
||||
# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []() [](https://www.tickgit.com/browse?repo=github.com/jesseduffield/lazygit)
|
||||
|
||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui 'gocui') library.
|
||||
|
||||
Rant time: You've heard it before, git is _powerful_, but what good is that power when everything is so damn hard to do? Interactive rebasing requires you to edit a goddamn TODO file in your editor? *Are you kidding me?* To stage part of a file you need to use a command line program stepping through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, bad luck? *Are you KIDDING me?!* Sometimes you get asked to stash your changes when switching branches only to realise that after you switch and unstash that there weren't even any conflicts and it would have been fine to just checkout the branch directly? *YOU HAVE GOT TO BE KIDDING ME!*
|
||||
Rant time: You've heard it before, git is _powerful_, but what good is that power when everything is so damn hard to do? Interactive rebasing requires you to edit a goddamn TODO file in your editor? *Are you kidding me?* To stage part of a file you need to use a command line program stepping through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, bad luck? *Are you KIDDING me?!* Sometimes you get asked to stash your changes when switching branches only to realise that after you switch and unstash that there weren't even any conflicts and it would have been fine to just checkout the branch directly? *YOU HAVE GOT TO BE KIDDING ME!*
|
||||
|
||||
If you're a mere mortal like me and you're tired of hearing how powerful git is when in your daily life it's a powerful pain in your ass, lazygit might be for you.
|
||||
|
||||
@@ -24,9 +24,11 @@ Github Sponsors is matching all donations dollar-for-dollar for 12 months so if
|
||||
## Installation
|
||||
|
||||
### Homebrew
|
||||
|
||||
Normally the lazygit formula can be found in the Homebrew core but we suggest you tap our formula to get the frequently updated one. It works with Linux, too.
|
||||
|
||||
Tap:
|
||||
|
||||
```
|
||||
brew install jesseduffield/lazygit/lazygit
|
||||
```
|
||||
@@ -38,8 +40,10 @@ brew install lazygit
|
||||
```
|
||||
|
||||
### MacPorts
|
||||
|
||||
Latest version built from github releases.
|
||||
Tap:
|
||||
|
||||
```
|
||||
sudo port install lazygit
|
||||
```
|
||||
@@ -63,6 +67,17 @@ They follow upstream latest releases
|
||||
```sh
|
||||
sudo xbps-install -S lazygit
|
||||
```
|
||||
### Scoop (Windows)
|
||||
|
||||
You can install `lazygit` using [scoop](https://scoop.sh/). It's in the `extras` bucket:
|
||||
|
||||
```sh
|
||||
# Add the extras bucket
|
||||
scoop bucket add extras
|
||||
|
||||
# Install lazygit
|
||||
scoop install lazygit
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
|
||||
@@ -71,11 +86,11 @@ Packages for Arch Linux are available via AUR (Arch User Repository).
|
||||
There are two packages. The stable one which is built with the latest release
|
||||
and the git version which builds from the most recent commit.
|
||||
|
||||
- Stable: https://aur.archlinux.org/packages/lazygit/
|
||||
- Development: https://aur.archlinux.org/packages/lazygit-git/
|
||||
- Stable: <https://aur.archlinux.org/packages/lazygit/>
|
||||
- Development: <https://aur.archlinux.org/packages/lazygit-git/>
|
||||
|
||||
Instruction of how to install AUR content can be found here:
|
||||
https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
<https://wiki.archlinux.org/index.php/Arch_User_Repository>
|
||||
|
||||
### Fedora and CentOS 7
|
||||
|
||||
@@ -88,7 +103,7 @@ sudo dnf install lazygit
|
||||
|
||||
### Conda
|
||||
|
||||
Released versions are available for different platforms, see https://anaconda.org/conda-forge/lazygit
|
||||
Released versions are available for different platforms, see <https://anaconda.org/conda-forge/lazygit>
|
||||
|
||||
```sh
|
||||
conda install -c conda-forge lazygit
|
||||
@@ -120,10 +135,11 @@ whichever rc file you're using).
|
||||
- Rebase Magic tutorial [here](https://youtu.be/4XaToVut_hs)
|
||||
- List of keybindings
|
||||
[here](/docs/keybindings).
|
||||
|
||||
|
||||
## Changing Directory On Exit
|
||||
|
||||
If you change repos in lazygit and want your shell to change directory into that repo on exiting lazygit, add this to your `~/.zshrc` (or other rc file):
|
||||
|
||||
```
|
||||
lg()
|
||||
{
|
||||
@@ -137,8 +153,17 @@ lg()
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
Then `source ~/.zshrc` and from now on when you call `lg` and exit you'll switch directories to whatever you were in inside lazyigt. To override this behaviour you can exit using `shift+Q` rather than just `q`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Check the [configuration docs](docs/Config.md).
|
||||
|
||||
## Custom Pagers
|
||||
|
||||
See the [docs](docs/Custom_Pagers.md)
|
||||
|
||||
## Cool features
|
||||
|
||||
- Adding files easily
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
# User Config:
|
||||
# User Config
|
||||
|
||||
Default path for the config file: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
Default path for the config file:
|
||||
|
||||
## Default:
|
||||
* Linux: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
* MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
||||
|
||||
## Default
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
# stuff relating to the UI
|
||||
scrollHeight: 2 # how many lines you scroll by
|
||||
scrollPastBottom: true # enable scrolling past the bottom
|
||||
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||
theme:
|
||||
lightTheme: false # For terminals with a light background
|
||||
activeBorderColor:
|
||||
- white
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- white
|
||||
- green
|
||||
optionsTextColor:
|
||||
- blue
|
||||
selectedLineBgColor:
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
manualCommit: false
|
||||
@@ -48,6 +57,8 @@ Default path for the config file: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
nextBlock: '<right>' # goto the next block / panel
|
||||
prevBlock-alt: 'h' # goto the previous block / panel
|
||||
nextBlock-alt: 'l' # goto the next block / panel
|
||||
nextMatch: 'n'
|
||||
prevMatch: 'N'
|
||||
optionMenu: 'x' # show help menu
|
||||
optionMenu-alt1: '?' # show help menu
|
||||
select: '<space>'
|
||||
@@ -62,7 +73,7 @@ Default path for the config file: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
scrollDownMain-alt1: 'J' # main panel scrool down
|
||||
scrollUpMain-alt2: '<c-u>' # main panel scrool up
|
||||
scrollDownMain-alt2: '<c-d>' # main panel scrool down
|
||||
executeCustomCommand: 'X'
|
||||
executeCustomCommand: ':'
|
||||
createRebaseOptionsMenu: 'm'
|
||||
pushFiles: 'P'
|
||||
pullFiles: 'p'
|
||||
@@ -70,6 +81,8 @@ Default path for the config file: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
createPatchOptionsMenu: '<c-p>'
|
||||
nextTab: ']'
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
@@ -127,30 +140,30 @@ Default path for the config file: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
undo: 'z'
|
||||
```
|
||||
|
||||
## Platform Defaults:
|
||||
## Platform Defaults
|
||||
|
||||
### Windows:
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'cmd /c "start "" {{filename}}"'
|
||||
```
|
||||
|
||||
### Linux:
|
||||
### Linux
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||
```
|
||||
|
||||
### OSX:
|
||||
### OSX
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Recommended Config Values:
|
||||
### Recommended Config Values
|
||||
|
||||
for users of VSCode
|
||||
|
||||
@@ -159,7 +172,7 @@ for users of VSCode
|
||||
openCommand: 'code -r {{filename}}'
|
||||
```
|
||||
|
||||
## Color Attributes:
|
||||
## Color Attributes
|
||||
|
||||
For color attributes you can choose an array of attributes (with max one color attribute)
|
||||
The available attributes are:
|
||||
@@ -177,7 +190,7 @@ The available attributes are:
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
|
||||
## Light terminal theme:
|
||||
## Light terminal theme
|
||||
|
||||
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||
|
||||
@@ -190,17 +203,20 @@ If you have issues with a light terminal theme where you can't read / see the te
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- black
|
||||
selectedLineBgColor:
|
||||
- blue
|
||||
```
|
||||
|
||||
## Example Coloring:
|
||||
## Example Coloring
|
||||
|
||||

|
||||
|
||||
## Keybindings:
|
||||
## Keybindings
|
||||
|
||||
For all possible keybinding options, check [Custom_Keybinding.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybinding.md)
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
|
||||
#### Example Keybindings For Colemak Users:
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
@@ -208,6 +224,8 @@ For all possible keybinding options, check [Custom_Keybinding.md](https://github
|
||||
nextItem-alt: 'e'
|
||||
prevBlock-alt: 'n'
|
||||
nextBlock-alt: 'i'
|
||||
nextMatch: '='
|
||||
prevMatch: '-'
|
||||
new: 'k'
|
||||
edit: 'o'
|
||||
openFile: 'O'
|
||||
@@ -225,3 +243,19 @@ For all possible keybinding options, check [Custom_Keybinding.md](https://github
|
||||
viewGitFlowOptions: 'I'
|
||||
```
|
||||
|
||||
## Custom pull request URLs
|
||||
|
||||
Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and
|
||||
the web interface/API itself. To work with those, Lazygit needs to know where it needs to create
|
||||
the pull request. You can do so on your `config.yml` file using the following syntax:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
"<gitDomain>": "<provider>:<webDomain>"
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `gitDomain` stands for the domain used by git itself (i.e. the one present on clone URLs), e.g. `git.work.com`
|
||||
- `provider` is one of `github`, `bitbucket` or `gitlab`
|
||||
- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
|
||||
|
||||
64
docs/Custom_Pagers.md
Normal file
64
docs/Custom_Pagers.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Custom Pagers
|
||||
|
||||
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing 'o' in the Status panel).
|
||||
|
||||
Support does not extend to windows users, because we're making use of a package which doesn't have windows support.
|
||||
|
||||
## Default:
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
```
|
||||
|
||||
the `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to always, others want it set to 'never'.
|
||||
|
||||
## Delta:
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
pager: delta --dark --paging=never --24-bit-color=never
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Diff-so-fancy
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
pager: diff-so-fancy
|
||||
```
|
||||
|
||||

|
||||
|
||||
## ydiff
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
|
||||
git:
|
||||
paging:
|
||||
colorArg: never
|
||||
pager: ydiff -p cat -s --wrap --width={{columnWidth}}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working.
|
||||
|
||||
## Using git config
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: true
|
||||
```
|
||||
|
||||
If you set `useConfig: true`, lazygit will use whatever pager is specified in $GIT_PAGER, $PAGER, or your git config. If the pager ends with something like ' | less' we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
|
||||
@@ -1,47 +1,21 @@
|
||||
# Lazygit menu
|
||||
# Lazygit Keybindings
|
||||
|
||||
## Global
|
||||
## Global Keybindings
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: refresh
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>S</kbd>: stash files
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>t</kbd>: add patch
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
## Branches Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout
|
||||
@@ -50,12 +24,56 @@
|
||||
<kbd>F</kbd>: force checkout
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>d</kbd>: delete branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>r</kbd>: rebase checked-out branch onto this branch
|
||||
<kbd>M</kbd>: merge into currently checked out branch
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
## Branches Panel (Remote Branches (in Remotes tab))
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to remotes list
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>space</kbd>: checkout
|
||||
<kbd>M</kbd>: merge into currently checked out branch
|
||||
<kbd>d</kbd>: delete branch
|
||||
<kbd>r</kbd>: rebase checked-out branch onto this branch
|
||||
<kbd>u</kbd>: set as upstream of checked-out branch
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remotes Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: fetch remote
|
||||
<kbd>n</kbd>: add new remote
|
||||
<kbd>d</kbd>: remove remote
|
||||
<kbd>e</kbd>: edit remote
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Tags Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout
|
||||
<kbd>d</kbd>: delete tag
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commit Files Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>space</kbd>: toggle file included in patch
|
||||
<kbd>enter</kbd>: enter file to add selected lines to the patch
|
||||
</pre>
|
||||
|
||||
## Commits Panel (Commits Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash down
|
||||
@@ -76,46 +94,41 @@
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>i</kbd>: select commit to diff with another commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
## Commits Panel (Reflog Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commit files
|
||||
## Files Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>s</kbd>: stash changes
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
</pre>
|
||||
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage line
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
## Main Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
@@ -127,3 +140,63 @@
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd> ̄</kbd>: scroll down (fn+up)
|
||||
<kbd>¦</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Main Panel (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
</pre>
|
||||
|
||||
## Stash Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
@@ -1,46 +1,21 @@
|
||||
# Lazygit menu
|
||||
# Lazygit Keybindings
|
||||
|
||||
## Global
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: bekijk merge/rebase opties
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: verversen
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: voor aangepast commando uit
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>s</kbd>: wissel naar een recente repo
|
||||
</pre>
|
||||
|
||||
## Bestanden
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: voeg toe aan .gitignore
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>S</kbd>: stash-bestanden
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>t</kbd>: bewerkingen toevoegen
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: voor aangepast commando uit
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
## Branches Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: uitchecken
|
||||
@@ -51,10 +26,54 @@
|
||||
<kbd>d</kbd>: verwijder branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: merge in met huidige checked out branch
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
## Branches Panel (Remote Branches (in Remotes tab))
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to remotes list
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>space</kbd>: uitchecken
|
||||
<kbd>M</kbd>: merge in met huidige checked out branch
|
||||
<kbd>d</kbd>: verwijder branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>u</kbd>: set as upstream of checked-out branch
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remotes Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: fetch remote
|
||||
<kbd>n</kbd>: add new remote
|
||||
<kbd>d</kbd>: remove remote
|
||||
<kbd>e</kbd>: edit remote
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Tags Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: uitchecken
|
||||
<kbd>d</kbd>: delete tag
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
</pre>
|
||||
|
||||
## Commit bestanden Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug
|
||||
<kbd>c</kbd>: bestand uitchecken
|
||||
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>space</kbd>: toggle file included in patch
|
||||
<kbd>enter</kbd>: enter file to add selected lines to the patch
|
||||
</pre>
|
||||
|
||||
## Commits Panel (Commits Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash beneden
|
||||
@@ -75,39 +94,41 @@
|
||||
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
|
||||
<kbd>v</kbd>: plak commits (cherry-pick)
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>i</kbd>: select commit to diff with another commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
## Commits Panel (Reflog Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
</pre>
|
||||
|
||||
## Commit bestanden
|
||||
## Bestanden Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug
|
||||
<kbd>c</kbd>: bestand uitchecken
|
||||
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: voeg toe aan .gitignore
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>s</kbd>: stash-bestanden
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
</pre>
|
||||
|
||||
## Hoofd (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: stage lijn
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd (Merging)
|
||||
## Hoofd Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
@@ -120,9 +141,62 @@
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
</pre>
|
||||
|
||||
## Hoofd (Normaal)
|
||||
## Hoofd Panel (Normaal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll omhoog (fn+down)
|
||||
<kbd> ̄</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>¦</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
</pre>
|
||||
|
||||
## Stash Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>enter</kbd>: wissel naar een recente repo
|
||||
</pre>
|
||||
|
||||
@@ -1,46 +1,21 @@
|
||||
# Lazygit menu
|
||||
# Lazygit Keybindings
|
||||
|
||||
## Globalne
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: odśwież
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
<kbd>space</kbd>: przełącz zatwierdzenie
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: dodaj do .gitignore
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>S</kbd>: przechowaj pliki
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>t</kbd>: dodaj łatkę
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Gałęzie
|
||||
## Gałęzie Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: przełącz
|
||||
@@ -51,10 +26,54 @@
|
||||
<kbd>d</kbd>: usuń gałąź
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commity
|
||||
## Gałęzie Panel (Remote Branches (in Remotes tab))
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to remotes list
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>space</kbd>: przełącz
|
||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
||||
<kbd>d</kbd>: usuń gałąź
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>u</kbd>: set as upstream of checked-out branch
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Remotes Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: fetch remote
|
||||
<kbd>n</kbd>: add new remote
|
||||
<kbd>d</kbd>: remove remote
|
||||
<kbd>e</kbd>: edit remote
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Tags Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: przełącz
|
||||
<kbd>d</kbd>: delete tag
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commit files Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>space</kbd>: toggle file included in patch
|
||||
<kbd>enter</kbd>: enter file to add selected lines to the patch
|
||||
</pre>
|
||||
|
||||
## Commity Panel (Commits Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: ściśnij w dół
|
||||
@@ -75,46 +94,41 @@
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>i</kbd>: select commit to diff with another commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
</pre>
|
||||
|
||||
## Schowek
|
||||
## Commity Panel (Reflog Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: zastosuj
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commit files
|
||||
## Pliki Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
<kbd>space</kbd>: przełącz zatwierdzenie
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: dodaj do .gitignore
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>s</kbd>: przechowaj pliki
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
</pre>
|
||||
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Zatwierdzanie)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: zatwierdź linię
|
||||
<kbd>a</kbd>: zatwierdź kawałek
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
## Main Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
@@ -126,3 +140,63 @@
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd> ̄</kbd>: scroll down (fn+up)
|
||||
<kbd>¦</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Main Panel (Zatwierdzanie)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
</pre>
|
||||
|
||||
## Schowek Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: zastosuj
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
3
go.mod
3
go.mod
@@ -11,9 +11,8 @@ require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113
|
||||
github.com/jesseduffield/pty v1.2.1
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
|
||||
33
go.sum
33
go.sum
@@ -54,7 +54,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -77,24 +76,12 @@ github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM
|
||||
github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532 h1:V1Lk2rm5/p27NjnlF2ezzkxDaisHNcveMNueSD7RYgs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351 h1:+sSqd2YotacWt+1MNRN8ZmXnYoiJeblZeptzKiHIyv0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039 h1:CVhilJ8ZdN7GmAI+fbH9829Cp/8hbK7Lijbd4VaNgo0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac h1:vp7I0RpFq4L46nFA9QQokzhFgr68LRGtwDO9xfq4F+A=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5 h1:tE0w3tuL/bj1o5VMhjjE0ep6i7Fva+RYjKcMFcniJEY=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed h1:glGs+mzPZOl1iHiUsBW3918WeFwqsbQQ/jtLkkQXDi4=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113 h1:jHZRVJUWsU8HaQ0crocz0i0BkpOqFLDJEO/AtBp+Ecs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
||||
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00/go.mod h1:cWNQljQAWYBp4wchyGfql4q2jRNZXxiE1KhVQgz+JaM=
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 h1:CRD7bVjlGIiV+M0jlsa+XWpneW0KY0e7Y4z3GWb5S4o=
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7/go.mod h1:VspA3aTkEo0Q7TPCLmX1uHNP+Wb4iSDX09hmTRo1QYc=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e h1:tth7wr6+sfSbdpRWWrwvLYyS56HyIRVfq0Qcl2h28wM=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 h1:iBBk1lhFwjwJw//J2m1yyz9S368GeXQTpMVACTyQMh0=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -108,7 +95,6 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -126,8 +112,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -147,7 +131,6 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
@@ -171,7 +154,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
@@ -180,17 +162,14 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -225,7 +204,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -240,7 +218,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -260,7 +237,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||
@@ -282,7 +258,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Branch : A git branch
|
||||
// duplicating this for now
|
||||
type Branch struct {
|
||||
@@ -17,31 +7,4 @@ type Branch struct {
|
||||
Recency string
|
||||
Pushables string
|
||||
Pullables string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||
}
|
||||
|
||||
return []string{b.Recency, displayName}
|
||||
}
|
||||
|
||||
// GetBranchColor branch color
|
||||
func GetBranchColor(name string) color.Attribute {
|
||||
branchType := strings.Split(name, "/")[0]
|
||||
|
||||
switch branchType {
|
||||
case "feature":
|
||||
return color.FgGreen
|
||||
case "bugfix":
|
||||
return color.FgYellow
|
||||
case "hotfix":
|
||||
return color.FgRed
|
||||
default:
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
@@ -17,52 +9,7 @@ type Commit struct {
|
||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "reflog":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
actionString := ""
|
||||
tagString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
} else if len(c.Tags) > 0 {
|
||||
tagString = utils.ColoredString(strings.Join(c.Tags, " "), color.FgMagenta) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
Author string
|
||||
Date string
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
// CommitFile : A git commit file
|
||||
type CommitFile struct {
|
||||
Sha string
|
||||
@@ -22,21 +17,3 @@ const (
|
||||
// PART is for when you're only talking about specific lines that have been modified
|
||||
PART
|
||||
)
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (f *CommitFile) GetDisplayStrings(isFocused bool) []string {
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
|
||||
var colour *color.Color
|
||||
switch f.Status {
|
||||
case UNSELECTED:
|
||||
colour = defaultColor
|
||||
case WHOLE:
|
||||
colour = green
|
||||
case PART:
|
||||
colour = yellow
|
||||
}
|
||||
return []string{colour.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
// if we find out we need to use one of these functions in the git.go file, we
|
||||
// can just pull them out of here and put them there and then call them from in here
|
||||
|
||||
const SEPARATION_CHAR = "|"
|
||||
|
||||
// CommitListBuilder returns a list of Branch objects for the current repo
|
||||
type CommitListBuilder struct {
|
||||
Log *logrus.Entry
|
||||
@@ -45,6 +47,39 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
|
||||
// then puts them into a commit object
|
||||
// example input:
|
||||
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
|
||||
func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
split := strings.Split(line, SEPARATION_CHAR)
|
||||
|
||||
sha := split[0]
|
||||
date := split[1]
|
||||
author := split[2]
|
||||
extraInfo := strings.TrimSpace(split[3])
|
||||
message := strings.Join(split[4:], SEPARATION_CHAR)
|
||||
tags := []string{}
|
||||
|
||||
if extraInfo != "" {
|
||||
re := regexp.MustCompile(`tag: ([^,\)]+)`)
|
||||
tagMatch := re.FindStringSubmatch(extraInfo)
|
||||
if len(tagMatch) > 1 {
|
||||
tags = append(tags, tagMatch[1])
|
||||
}
|
||||
}
|
||||
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
Name: message,
|
||||
DisplayString: line,
|
||||
Tags: tags,
|
||||
ExtraInfo: extraInfo,
|
||||
Date: date,
|
||||
Author: author,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
@@ -69,17 +104,10 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
|
||||
// now we can split it up and turn it into commits
|
||||
for _, line := range utils.SplitLines(log) {
|
||||
splitLine := strings.Split(line, " ")
|
||||
sha := splitLine[0]
|
||||
_, unpushed := unpushedCommits[sha]
|
||||
status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
commits = append(commits, &Commit{
|
||||
Sha: sha,
|
||||
Name: strings.Join(splitLine[1:], " "),
|
||||
Status: status,
|
||||
DisplayString: strings.Join(splitLine, " "),
|
||||
// TODO: add tags here
|
||||
})
|
||||
commit := c.extractCommitFromLine(line)
|
||||
_, unpushed := unpushedCommits[commit.Sha[:8]]
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
commits = append(commits, commit)
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
currentCommit := commits[len(rebasingCommits)]
|
||||
@@ -190,7 +218,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
|
||||
}
|
||||
splitLine := strings.Split(line, " ")
|
||||
commits = append([]*Commit{{
|
||||
Sha: splitLine[1][0:7],
|
||||
Sha: splitLine[1],
|
||||
Name: strings.Join(splitLine[2:], " "),
|
||||
Status: "rebasing",
|
||||
Action: splitLine[0],
|
||||
@@ -207,7 +235,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
|
||||
// Subject: second commit on master
|
||||
func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
|
||||
lines := strings.Split(content, "\n")
|
||||
sha := strings.Split(lines[0], " ")[1][0:7]
|
||||
sha := strings.Split(lines[0], " ")[1]
|
||||
name := strings.TrimPrefix(lines[3], "Subject: ")
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
@@ -270,7 +298,7 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||
// to the remote branch of the current branch, a map is returned to ease look up
|
||||
func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
pushables := map[string]bool{}
|
||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit")
|
||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit --abbrev=8")
|
||||
if err != nil {
|
||||
return pushables
|
||||
}
|
||||
@@ -288,8 +316,8 @@ func (c *CommitListBuilder) getLog(limit bool) string {
|
||||
limitFlag = "-30"
|
||||
}
|
||||
|
||||
c.Log.Warn(fmt.Sprintf("git log --oneline %s", limitFlag))
|
||||
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline %s", limitFlag))
|
||||
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%ar%s%%aN%s%%d%s%%s\" %s --abbrev=%d", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20))
|
||||
|
||||
if err != nil {
|
||||
// assume if there is an error there are no commits yet for this branch
|
||||
return ""
|
||||
|
||||
@@ -149,170 +149,3 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetLog is a function.
|
||||
func TestCommitListBuilderGetLog(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Retrieves logs",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
|
||||
return exec.Command("echo", "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line")
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"An error occurred when retrieving logs",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(output string) {
|
||||
assert.Empty(t, output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getLog(true))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetCommits is a function.
|
||||
func TestCommitListBuilderGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func([]*Commit, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"No data found",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("test")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
"GetCommits returns 2 commits, 1 unpushed, the other merged",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo", "8a2bb0e")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "78976bc")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, 2)
|
||||
assert.EqualValues(t, []*Commit{
|
||||
{
|
||||
Sha: "8a2bb0e",
|
||||
Name: "commit 1",
|
||||
Status: "unpushed",
|
||||
DisplayString: "8a2bb0e commit 1",
|
||||
},
|
||||
{
|
||||
Sha: "78976bc",
|
||||
Name: "commit 2",
|
||||
Status: "merged",
|
||||
DisplayString: "78976bc commit 2",
|
||||
},
|
||||
}, commits)
|
||||
},
|
||||
},
|
||||
{
|
||||
"GetCommits bubbles up an error from setCommitMergedStatuses",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo", "8a2bb0e")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "78976bc")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
// here's where we are returning the error
|
||||
return exec.Command("test")
|
||||
case "branch":
|
||||
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
||||
// here too
|
||||
return exec.Command("test")
|
||||
case "rev-parse":
|
||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||
// here too
|
||||
return exec.Command("test")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, commits, 0)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.GetCommits(true))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package commands
|
||||
|
||||
import "github.com/fatih/color"
|
||||
|
||||
// File : A file from git status
|
||||
// duplicating this for now
|
||||
type File struct {
|
||||
@@ -16,23 +14,3 @@ type File struct {
|
||||
Type string // one of 'file', 'directory', and 'other'
|
||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of a file
|
||||
func (f *File) GetDisplayStrings(isFocused bool) []string {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !f.Tracked && !f.HasStagedChanges {
|
||||
return []string{red.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
output := green.Sprint(f.DisplayString[0:1])
|
||||
output += red.Sprint(f.DisplayString[1:3])
|
||||
if f.HasUnstagedChanges {
|
||||
output += red.Sprint(f.Name)
|
||||
} else {
|
||||
output += green.Sprint(f.Name)
|
||||
}
|
||||
return []string{output}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -177,7 +178,7 @@ func stashEntryFromLine(line string, index int) *StashEntry {
|
||||
|
||||
// GetStashEntryDiff stash diff
|
||||
func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
||||
return fmt.Sprintf("git stash show -p --color stash@{%d}", index)
|
||||
return fmt.Sprintf("git stash show -p --color=%s stash@{%d}", c.colorArg(), index)
|
||||
}
|
||||
|
||||
// GetStatusFiles git status files
|
||||
@@ -408,6 +409,11 @@ func (c *GitCommand) Pull(args string, ask func(string) string) error {
|
||||
return c.OSCommand.DetectUnamePass("git pull --no-edit "+args, ask)
|
||||
}
|
||||
|
||||
// PullWithoutPasswordCheck assumes that the pull will not prompt the user for a password
|
||||
func (c *GitCommand) PullWithoutPasswordCheck(args string) error {
|
||||
return c.OSCommand.RunCommand("git pull --no-edit " + args)
|
||||
}
|
||||
|
||||
// Push pushes to a branch
|
||||
func (c *GitCommand) Push(branchName string, force bool, upstream string, args string, ask func(string) string) error {
|
||||
forceFlag := ""
|
||||
@@ -553,11 +559,11 @@ func (c *GitCommand) Ignore(filename string) error {
|
||||
}
|
||||
|
||||
func (c *GitCommand) ShowCmdStr(sha string) string {
|
||||
return fmt.Sprintf("git show --color --no-renames %s", sha)
|
||||
return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s", c.colorArg(), sha)
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
|
||||
return fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium %s", branchName)
|
||||
return fmt.Sprintf("git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium %s", branchName)
|
||||
}
|
||||
|
||||
// GetRemoteURL returns current repo remote url
|
||||
@@ -586,20 +592,20 @@ func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
|
||||
func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string {
|
||||
cachedArg := ""
|
||||
trackedArg := "--"
|
||||
colorArg := "--color"
|
||||
colorArg := c.colorArg()
|
||||
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
||||
fileName := c.OSCommand.Quote(split[len(split)-1])
|
||||
if cached {
|
||||
cachedArg = "--cached"
|
||||
}
|
||||
if !file.Tracked && !file.HasStagedChanges {
|
||||
if !file.Tracked && !file.HasStagedChanges && !cached {
|
||||
trackedArg = "--no-index /dev/null"
|
||||
}
|
||||
if plain {
|
||||
colorArg = ""
|
||||
colorArg = "never"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("git diff %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||
return fmt.Sprintf("git diff --color=%s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
||||
@@ -891,12 +897,12 @@ func (c *GitCommand) ShowCommitFile(commitSha, fileName string, plain bool) (str
|
||||
}
|
||||
|
||||
func (c *GitCommand) ShowCommitFileCmdStr(commitSha, fileName string, plain bool) string {
|
||||
colorArg := "--color"
|
||||
colorArg := c.colorArg()
|
||||
if plain {
|
||||
colorArg = ""
|
||||
colorArg = "never"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
||||
return fmt.Sprintf("git show --no-renames --color=%s %s -- %s", colorArg, commitSha, fileName)
|
||||
}
|
||||
|
||||
// CheckoutFile checks out the file for the given commit
|
||||
@@ -940,6 +946,11 @@ func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
|
||||
return c.OSCommand.RunCommand("git checkout -- .")
|
||||
}
|
||||
|
||||
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
|
||||
func (c *GitCommand) RemoveTrackedFiles(name string) error {
|
||||
return c.OSCommand.RunCommand("git rm -r --cached %s", name)
|
||||
}
|
||||
|
||||
// RemoveUntrackedFiles runs `git clean -fd`
|
||||
func (c *GitCommand) RemoveUntrackedFiles() error {
|
||||
return c.OSCommand.RunCommand("git clean -fd")
|
||||
@@ -957,7 +968,7 @@ func (c *GitCommand) ResetSoft(ref string) error {
|
||||
|
||||
// DiffCommits show diff between commits
|
||||
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git diff --color %s %s", sha1, sha2)
|
||||
return c.OSCommand.RunCommandWithOutput("git diff --color=%s --stat -p %s %s", c.colorArg(), sha1, sha2)
|
||||
}
|
||||
|
||||
// CreateFixupCommit creates a commit that fixes up a previous commit
|
||||
@@ -1095,7 +1106,7 @@ func (c *GitCommand) FetchRemote(remoteName string) error {
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git reflog")
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git reflog --abbrev=20")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1105,7 +1116,7 @@ func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
|
||||
re := regexp.MustCompile(`(\w+).*HEAD@\{\d+\}: (.*)`)
|
||||
for i, line := range lines {
|
||||
match := re.FindStringSubmatch(line)
|
||||
if len(match) == 1 {
|
||||
if len(match) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1118,3 +1129,37 @@ func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (c *GitCommand) ConfiguredPager() string {
|
||||
if os.Getenv("GIT_PAGER") != "" {
|
||||
return os.Getenv("GIT_PAGER")
|
||||
}
|
||||
if os.Getenv("PAGER") != "" {
|
||||
return os.Getenv("PAGER")
|
||||
}
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git config --get-all core.pager")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
return strings.Split(trimmedOutput, "\n")[0]
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetPager(width int) string {
|
||||
useConfig := c.Config.GetUserConfig().GetBool("git.paging.useConfig")
|
||||
if useConfig {
|
||||
pager := c.ConfiguredPager()
|
||||
return strings.Split(pager, "| less")[0]
|
||||
}
|
||||
|
||||
templateValues := map[string]string{
|
||||
"columnWidth": strconv.Itoa(width/2 - 6),
|
||||
}
|
||||
|
||||
pagerTemplate := c.Config.GetUserConfig().GetString("git.paging.pager")
|
||||
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
|
||||
}
|
||||
|
||||
func (c *GitCommand) colorArg() string {
|
||||
return c.Config.GetUserConfig().GetString("git.paging.colorArg")
|
||||
}
|
||||
|
||||
@@ -1449,7 +1449,7 @@ func TestGitCommandGetBranchGraph(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test"}, args)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
@@ -1473,7 +1473,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"Default case",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1489,7 +1489,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"cached",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color", "--cached", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--cached", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1505,7 +1505,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"plain",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color=never", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1521,7 +1521,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"File not tracked and file has no staged changes",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color", "--no-index", "/dev/null", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--no-index", "/dev/null", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1871,7 +1871,7 @@ func TestGitCommandShowCommitFile(t *testing.T) {
|
||||
"hello.txt",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git show --no-renames 123456 -- hello.txt",
|
||||
Expect: "git show --no-renames --color=never 123456 -- hello.txt",
|
||||
Replace: "echo -n hello",
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -401,3 +401,11 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Kill(cmd *exec.Cmd) error {
|
||||
if cmd.Process == nil {
|
||||
// somebody got to it before we were able to, poor bastard
|
||||
return nil
|
||||
}
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func coloredString(colorAttr color.Attribute, str string, selected bool, include
|
||||
var cl *color.Color
|
||||
attributes := []color.Attribute{colorAttr}
|
||||
if selected {
|
||||
attributes = append(attributes, color.BgBlue)
|
||||
attributes = append(attributes, theme.SelectedLineBgColor)
|
||||
}
|
||||
cl = color.New(attributes...)
|
||||
var clIncluded *color.Color
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
)
|
||||
|
||||
// Service is a service that repository is on (Github, Bitbucket, ...)
|
||||
@@ -26,27 +27,63 @@ type RepoInformation struct {
|
||||
Repository string
|
||||
}
|
||||
|
||||
func getServices() []*Service {
|
||||
return []*Service{
|
||||
{
|
||||
Name: "github.com",
|
||||
PullRequestURL: "https://github.com/%s/%s/compare/%s?expand=1",
|
||||
},
|
||||
{
|
||||
Name: "bitbucket.org",
|
||||
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?source=%s&t=1",
|
||||
},
|
||||
{
|
||||
Name: "gitlab.com",
|
||||
PullRequestURL: "https://gitlab.com/%s/%s/merge_requests/new?merge_request[source_branch]=%s",
|
||||
},
|
||||
// NewService builds a Service based on the host type
|
||||
func NewService(typeName string, repositoryDomain string, siteDomain string) *Service {
|
||||
var service *Service
|
||||
|
||||
switch typeName {
|
||||
case "github":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/compare/%s?expand=1"),
|
||||
}
|
||||
case "bitbucket":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/pull-requests/new?source=%s&t=1"),
|
||||
}
|
||||
case "gitlab":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/merge_requests/new?merge_request[source_branch]=%s"),
|
||||
}
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func getServices(config config.AppConfigurer) []*Service {
|
||||
services := []*Service{
|
||||
NewService("github", "github.com", "github.com"),
|
||||
NewService("bitbucket", "bitbucket.org", "bitbucket.org"),
|
||||
NewService("gitlab", "gitlab.com", "gitlab.com"),
|
||||
}
|
||||
|
||||
configServices := config.GetUserConfig().GetStringMapString("services")
|
||||
|
||||
for repoDomain, typeAndDomain := range configServices {
|
||||
splitData := strings.Split(typeAndDomain, ":")
|
||||
if len(splitData) != 2 {
|
||||
// TODO log this misconfiguration
|
||||
continue
|
||||
}
|
||||
|
||||
service := NewService(splitData[0], repoDomain, splitData[1])
|
||||
if service == nil {
|
||||
// TODO log this unsupported service
|
||||
continue
|
||||
}
|
||||
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
// NewPullRequest creates new instance of PullRequest
|
||||
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
||||
return &PullRequest{
|
||||
GitServices: getServices(),
|
||||
GitServices: getServices(gitCommand.Config),
|
||||
GitCommand: gitCommand,
|
||||
}
|
||||
}
|
||||
@@ -85,7 +122,7 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
|
||||
if isHTTP {
|
||||
splits := strings.Split(url, "/")
|
||||
owner := splits[len(splits)-2]
|
||||
owner := strings.Join(splits[3:len(splits)-1], "/")
|
||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
@@ -96,8 +133,8 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
|
||||
tmpSplit := strings.Split(url, ":")
|
||||
splits := strings.Split(tmpSplit[1], "/")
|
||||
owner := splits[0]
|
||||
repo := strings.TrimSuffix(splits[1], ".git")
|
||||
owner := strings.Join(splits[0:len(splits)-1], "/")
|
||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
Owner: owner,
|
||||
|
||||
@@ -147,6 +147,13 @@ func TestCreatePullRequest(t *testing.T) {
|
||||
gitCommand := NewDummyGitCommand()
|
||||
gitCommand.OSCommand.command = s.command
|
||||
gitCommand.OSCommand.Config.GetUserConfig().Set("os.openLinkCommand", "open {{link}}")
|
||||
gitCommand.Config.GetUserConfig().Set("services", map[string]string{
|
||||
// valid configuration for a custom service URL
|
||||
"git.work.com": "gitlab:code.work.com",
|
||||
// invalid configurations for a custom service URL
|
||||
"invalid.work.com": "noservice:invalid.work.com",
|
||||
"noservice.work.com": "noservice.work.com",
|
||||
})
|
||||
dummyPullRequest := NewPullRequest(gitCommand)
|
||||
s.test(dummyPullRequest.Create(s.branch))
|
||||
})
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Remote Branch : A git remote branch
|
||||
type RemoteBranch struct {
|
||||
Name string
|
||||
Selected bool
|
||||
RemoteName string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (b *RemoteBranch) GetDisplayStrings(isFocused bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
|
||||
return []string{displayName}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,3 @@ package commands
|
||||
type Tag struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of a remote
|
||||
func (r *Tag) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.Name}
|
||||
}
|
||||
|
||||
@@ -244,18 +244,24 @@ func GetDefaultConfig() []byte {
|
||||
scrollPastBottom: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
sidePanelWidth: 0.3333
|
||||
theme:
|
||||
lightTheme: false
|
||||
activeBorderColor:
|
||||
- white
|
||||
- green
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- white
|
||||
optionsTextColor:
|
||||
- blue
|
||||
selectedLineBgColor:
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
merging:
|
||||
manualCommit: false
|
||||
skipHookPrefix: 'WIP'
|
||||
@@ -281,6 +287,9 @@ keybinding:
|
||||
nextBlock: '<right>'
|
||||
prevBlock-alt: 'h'
|
||||
nextBlock-alt: 'l'
|
||||
nextMatch: 'n'
|
||||
prevMatch: 'N'
|
||||
startSearch: '/'
|
||||
optionMenu: 'x'
|
||||
optionMenu-alt1: '?'
|
||||
select: '<space>'
|
||||
@@ -295,7 +304,7 @@ keybinding:
|
||||
scrollDownMain-alt1: 'J'
|
||||
scrollUpMain-alt2: '<c-u>'
|
||||
scrollDownMain-alt2: '<c-d>'
|
||||
executeCustomCommand: 'X'
|
||||
executeCustomCommand: ':'
|
||||
createRebaseOptionsMenu: 'm'
|
||||
pushFiles: 'P'
|
||||
pullFiles: 'p'
|
||||
@@ -303,6 +312,8 @@ keybinding:
|
||||
createPatchOptionsMenu: '<c-p>'
|
||||
nextTab: ']'
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -57,13 +58,6 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
||||
return gui.newTask("branches", func(stop chan struct{}) error {
|
||||
// here we tell the selected branch that it is selected.
|
||||
// this is necessary for showing stats on a branch that is selected, because
|
||||
// the displaystring function doesn't have access to gui state to tell if it's selected
|
||||
for i, branch := range gui.State.Branches {
|
||||
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
|
||||
}
|
||||
|
||||
branch := gui.getSelectedBranch()
|
||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
||||
|
||||
@@ -73,7 +67,10 @@ func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
||||
default:
|
||||
}
|
||||
|
||||
return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches)
|
||||
branchesView := gui.getBranchesView()
|
||||
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.currentViewName() == "branches", gui.State.Panels.Branches.SelectedLine)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,8 +94,7 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
|
||||
// TODO: if we're in the remotes view and we've just deleted a remote we need to refresh accordingly
|
||||
if gui.getBranchesView().Context == "local-branches" {
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
if err := gui.renderLocalBranchesWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -112,10 +108,10 @@ func (gui *Gui) renderLocalBranchesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.Branches); err != nil {
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "local-branches" {
|
||||
if gui.g.CurrentView() == branchesView {
|
||||
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -373,12 +369,19 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
||||
go func() {
|
||||
_ = gui.createLoaderPanel(gui.g, v, message)
|
||||
|
||||
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
if gui.State.Panels.Branches.SelectedLine == 0 {
|
||||
if err := gui.GitCommand.PullWithoutPasswordCheck("--ff-only"); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
_ = gui.refreshSidePanels(gui.g)
|
||||
} else {
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
||||
}
|
||||
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -394,6 +397,7 @@ func (gui *Gui) onBranchesTabClick(tabIndex int) error {
|
||||
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
branchesView.Context = context
|
||||
gui.onSearchEscape()
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"local-branches": 0,
|
||||
@@ -429,3 +433,28 @@ func (gui *Gui) handlePrevBranchesTab(g *gocui.Gui, v *gocui.View) error {
|
||||
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToBranchMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createResetMenu(branch.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) onBranchesPanelSearchSelect(selectedLine int) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
switch branchesView.Context {
|
||||
case "local-branches":
|
||||
gui.State.Panels.Branches.SelectedLine = selectedLine
|
||||
return gui.handleBranchSelect(gui.g, branchesView)
|
||||
case "remotes":
|
||||
gui.State.Panels.Remotes.SelectedLine = selectedLine
|
||||
return gui.handleRemoteSelect(gui.g, branchesView)
|
||||
case "remote-branches":
|
||||
gui.State.Panels.RemoteBranches.SelectedLine = selectedLine
|
||||
return gui.handleRemoteBranchSelect(gui.g, branchesView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
|
||||
@@ -49,7 +50,7 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -112,11 +113,11 @@ func (gui *Gui) refreshCommitFilesView() error {
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles))
|
||||
|
||||
if err := gui.renderListPanel(gui.getCommitFilesView(), gui.State.CommitFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
commitsFileView := gui.getCommitFilesView()
|
||||
displayStrings := presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles)
|
||||
gui.renderDisplayStrings(commitsFileView, displayStrings)
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
||||
return gui.handleCommitFileSelect(gui.g, commitsFileView)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -212,3 +213,8 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
|
||||
return enterTheFile(selectedLineIdx)
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitFilesPanelSearchSelect(selectedLine int) error {
|
||||
gui.State.Panels.CommitFiles.SelectedLine = selectedLine
|
||||
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -68,7 +67,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -560,53 +559,6 @@ func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) er
|
||||
}, nil)
|
||||
}
|
||||
|
||||
type resetOption struct {
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *resetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
strengths := []string{"soft", "mixed", "hard"}
|
||||
options := make([]*resetOption, len(strengths))
|
||||
for i, strength := range strengths {
|
||||
options[i] = &resetOption{
|
||||
description: fmt.Sprintf("%s reset", strength),
|
||||
command: fmt.Sprintf("reset --%s %s", strength, commit.Sha),
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha, strengths[index]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.handleCommitSelect(g, gui.getCommitsView())
|
||||
}
|
||||
|
||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTagCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
// TODO: bring up menu asking if you want to make a lightweight or annotated tag
|
||||
// if annotated, switch to a subprocess to create the message
|
||||
@@ -649,9 +601,8 @@ func (gui *Gui) renderBranchCommitsWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||
if err := gui.renderListPanel(commitsView, gui.State.Commits); err != nil {
|
||||
return err
|
||||
}
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
||||
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
||||
return err
|
||||
@@ -672,6 +623,7 @@ func (gui *Gui) onCommitsTabClick(tabIndex int) error {
|
||||
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
||||
commitsView := gui.getCommitsView()
|
||||
commitsView.Context = context
|
||||
gui.onSearchEscape()
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"branch-commits": 0,
|
||||
@@ -680,7 +632,13 @@ func (gui *Gui) switchCommitsPanelContext(context string) error {
|
||||
|
||||
commitsView.TabIndex = contextTabIndexMap[context]
|
||||
|
||||
switch context {
|
||||
return gui.refreshCommitsViewWithSelection()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitsViewWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
switch commitsView.Context {
|
||||
case "branch-commits":
|
||||
return gui.renderBranchCommitsWithSelection()
|
||||
case "reflog-commits":
|
||||
@@ -701,3 +659,37 @@ func (gui *Gui) handlePrevCommitsTab(g *gocui.Gui, v *gocui.View) error {
|
||||
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
return gui.createResetMenu(commit.Sha)
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitsPanelSearchSelect(selectedLine int) error {
|
||||
commitsView := gui.getCommitsView()
|
||||
switch commitsView.Context {
|
||||
case "branch-commits":
|
||||
gui.State.Panels.Commits.SelectedLine = selectedLine
|
||||
return gui.handleCommitSelect(gui.g, commitsView)
|
||||
case "reflog-commits":
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = selectedLine
|
||||
return gui.handleReflogCommitSelect(gui.g, commitsView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenSearchForCommitsPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
// we usually lazyload these commits but now that we're searching we need to load them now
|
||||
if gui.State.Panels.Commits.LimitCommits {
|
||||
gui.State.Panels.Commits.LimitCommits = false
|
||||
if err := gui.refreshCommits(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gui.handleOpenSearch(gui.g, v)
|
||||
}
|
||||
|
||||
42
pkg/gui/discard_changes_menu_panel.go
Normal file
42
pkg/gui/discard_changes_menu_panel.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
displayString: gui.Tr.SLocalize("discardAllChanges"),
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
menuItems = append(menuItems, &menuItem{
|
||||
displayString: gui.Tr.SLocalize("discardUnstagedChanges"),
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return gui.createMenu(file.Name, menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -62,7 +62,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, true)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newCmdTask("secondary", cmd); err != nil {
|
||||
if err := gui.newPtyTask("secondary", cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -76,7 +76,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -103,14 +103,8 @@ func (gui *Gui) refreshFiles() error {
|
||||
}
|
||||
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
|
||||
filesView.Clear()
|
||||
isFocused := gui.g.CurrentView().Name() == "files"
|
||||
list, err := utils.RenderList(gui.State.Files, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(filesView, list)
|
||||
displayStrings := presentation.GetFileListDisplayStrings(gui.State.Files)
|
||||
gui.renderDisplayStrings(filesView, displayStrings)
|
||||
|
||||
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") {
|
||||
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||
@@ -242,16 +236,29 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
if file.Tracked {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantIgnoreTrackFiles"))
|
||||
return gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("IgnoreTracked"), gui.Tr.SLocalize("IgnoreTrackedPrompt"),
|
||||
// On confirmation
|
||||
func(_ *gocui.Gui, _ *gocui.View) error {
|
||||
if err := gui.GitCommand.Ignore(file.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.GitCommand.RemoveTrackedFiles(file.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshFiles()
|
||||
}, nil)
|
||||
}
|
||||
|
||||
if err := gui.GitCommand.Ignore(file.Name); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
@@ -527,67 +534,6 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type discardOption struct {
|
||||
handler func(fileName *commands.File) error
|
||||
description string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *discardOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
options := []*discardOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAllChanges"),
|
||||
handler: func(file *commands.File) error {
|
||||
return gui.GitCommand.DiscardAllFileChanges(file)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func(file *commands.File) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
discardUnstagedChanges := &discardOption{
|
||||
description: gui.Tr.SLocalize("discardUnstagedChanges"),
|
||||
handler: func(file *commands.File) error {
|
||||
return gui.GitCommand.DiscardUnstagedFileChanges(file)
|
||||
},
|
||||
}
|
||||
|
||||
options = append(options[:1], append([]*discardOption{discardUnstagedChanges}, options[1:]...)...)
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := options[index].handler(file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
return gui.createMenu(file.Name, options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
command := gui.trimmedContent(v)
|
||||
@@ -596,45 +542,34 @@ func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
||||
})
|
||||
}
|
||||
|
||||
type stashOption struct {
|
||||
description string
|
||||
handler func() error
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (o *stashOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{o.description}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateStashMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*stashOption{
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
description: gui.Tr.SLocalize("stashAllChanges"),
|
||||
handler: func() error {
|
||||
displayString: gui.Tr.SLocalize("stashAllChanges"),
|
||||
onPress: func() error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSave)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("stashStagedChanges"),
|
||||
handler: func() error {
|
||||
displayString: gui.Tr.SLocalize("stashStagedChanges"),
|
||||
onPress: func() error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSaveStagedChanges)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), options, len(options), handleMenuPress)
|
||||
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSave)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToUpstreamMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createResetMenu("@{upstream}")
|
||||
}
|
||||
|
||||
func (gui *Gui) onFilesPanelSearchSelect(selectedLine int) error {
|
||||
gui.State.Panels.Files.SelectedLine = selectedLine
|
||||
return gui.focusAndSelectFile(gui.g, gui.getFilesView())
|
||||
}
|
||||
|
||||
@@ -8,11 +8,6 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type gitFlowOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
}
|
||||
|
||||
func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) error {
|
||||
// need to find out what kind of branch this is
|
||||
prefix := strings.SplitAfterN(branchName, "/", 2)[0]
|
||||
@@ -41,11 +36,6 @@ func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) err
|
||||
return gui.Errors.ErrSubProcess
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *gitFlowOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateGitFlowMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
@@ -70,37 +60,27 @@ func (gui *Gui) handleCreateGitFlowMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
}
|
||||
|
||||
options := []*gitFlowOption{
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
// not localising here because it's one to one with the actual git flow commands
|
||||
description: fmt.Sprintf("finish branch '%s'", branch.Name),
|
||||
handler: func() error {
|
||||
displayString: fmt.Sprintf("finish branch '%s'", branch.Name),
|
||||
onPress: func() error {
|
||||
return gui.gitFlowFinishBranch(gitFlowConfig, branch.Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "start feature",
|
||||
handler: startHandler("feature"),
|
||||
displayString: "start feature",
|
||||
onPress: startHandler("feature"),
|
||||
},
|
||||
{
|
||||
description: "start hotfix",
|
||||
handler: startHandler("hotfix"),
|
||||
displayString: "start hotfix",
|
||||
onPress: startHandler("hotfix"),
|
||||
},
|
||||
{
|
||||
description: "start release",
|
||||
handler: startHandler("release"),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
displayString: "start release",
|
||||
onPress: startHandler("release"),
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu("git flow", options, len(options), handleMenuPress)
|
||||
return gui.createMenu("git flow", menuItems, createMenuOptions{})
|
||||
}
|
||||
|
||||
271
pkg/gui/gui.go
271
pkg/gui/gui.go
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
@@ -33,6 +34,12 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
SCREEN_NORMAL int = iota
|
||||
SCREEN_HALF
|
||||
SCREEN_FULL
|
||||
)
|
||||
|
||||
const StartupPopupVersion = 1
|
||||
|
||||
// OverlappingEdges determines if panel edges overlap
|
||||
@@ -172,6 +179,12 @@ type panelStates struct {
|
||||
Status *statusPanelState
|
||||
}
|
||||
|
||||
type searchingState struct {
|
||||
view *gocui.View
|
||||
isSearching bool
|
||||
searchString string
|
||||
}
|
||||
|
||||
type guiState struct {
|
||||
Files []*commands.File
|
||||
Branches []*commands.Branch
|
||||
@@ -195,6 +208,12 @@ type guiState struct {
|
||||
RetainOriginalDir bool
|
||||
IsRefreshingFiles bool
|
||||
RefreshingFilesMutex sync.Mutex
|
||||
Searching searchingState
|
||||
ScreenMode int
|
||||
SideView *gocui.View
|
||||
Ptmx *os.File
|
||||
PrevMainWidth int
|
||||
PrevMainHeight int
|
||||
}
|
||||
|
||||
// for now the split view will always be on
|
||||
@@ -229,6 +248,9 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
},
|
||||
Status: &statusPanelState{},
|
||||
},
|
||||
ScreenMode: SCREEN_NORMAL,
|
||||
SideView: nil,
|
||||
Ptmx: nil,
|
||||
}
|
||||
|
||||
gui := &Gui{
|
||||
@@ -250,6 +272,26 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
return gui, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.ScreenMode = utils.NextIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
// commits render differently depending on whether we're in fullscreen more or not
|
||||
if err := gui.refreshCommitsViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.ScreenMode = utils.PrevIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
// commits render differently depending on whether we're in fullscreen more or not
|
||||
if err := gui.refreshCommitsViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpView(viewName string) error {
|
||||
mainView, _ := gui.g.View(viewName)
|
||||
ox, oy := mainView.Origin()
|
||||
@@ -338,14 +380,15 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if v.IsSearching() && newView.Name() != "search" {
|
||||
gui.onSearchEscape()
|
||||
}
|
||||
switch v.Name() {
|
||||
case "branches":
|
||||
if v.Context == "local-branches" {
|
||||
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
||||
// inside renderListPanel it checks to see if the panel has focus
|
||||
if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil {
|
||||
return err
|
||||
}
|
||||
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, false, -1)
|
||||
gui.renderDisplayStrings(gui.getBranchesView(), displayStrings)
|
||||
}
|
||||
case "main":
|
||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||
@@ -369,6 +412,75 @@ func (gui *Gui) onFocus(v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getViewHeights() map[string]int {
|
||||
currView := gui.g.CurrentView()
|
||||
currentCyclebleView := gui.State.PreviousView
|
||||
if currView != nil {
|
||||
viewName := currView.Name()
|
||||
usePreviousView := true
|
||||
for _, view := range cyclableViews {
|
||||
if view == viewName {
|
||||
currentCyclebleView = viewName
|
||||
usePreviousView = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if usePreviousView {
|
||||
currentCyclebleView = gui.State.PreviousView
|
||||
}
|
||||
}
|
||||
|
||||
// unfortunate result of the fact that these are separate views, have to map explicitly
|
||||
if currentCyclebleView == "commitFiles" {
|
||||
currentCyclebleView = "commits"
|
||||
}
|
||||
|
||||
_, height := gui.g.Size()
|
||||
|
||||
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
|
||||
vHeights := map[string]int{
|
||||
"status": 0,
|
||||
"files": 0,
|
||||
"branches": 0,
|
||||
"commits": 0,
|
||||
"stash": 0,
|
||||
"options": 0,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - 1
|
||||
return vHeights
|
||||
}
|
||||
|
||||
usableSpace := height - 7
|
||||
extraSpace := usableSpace - (usableSpace/3)*3
|
||||
|
||||
if height >= 28 {
|
||||
return map[string]int{
|
||||
"status": 3,
|
||||
"files": (usableSpace / 3) + extraSpace,
|
||||
"branches": usableSpace / 3,
|
||||
"commits": usableSpace / 3,
|
||||
"stash": 3,
|
||||
"options": 1,
|
||||
}
|
||||
}
|
||||
|
||||
defaultHeight := 3
|
||||
if height < 21 {
|
||||
defaultHeight = 1
|
||||
}
|
||||
vHeights := map[string]int{
|
||||
"status": defaultHeight,
|
||||
"files": defaultHeight,
|
||||
"branches": defaultHeight,
|
||||
"commits": defaultHeight,
|
||||
"stash": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
|
||||
|
||||
return vHeights
|
||||
}
|
||||
|
||||
// layout is called for every screen re-render e.g. when the screen is resized
|
||||
func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
g.Highlight = true
|
||||
@@ -395,50 +507,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
currView := gui.g.CurrentView()
|
||||
currentCyclebleView := gui.State.PreviousView
|
||||
if currView != nil {
|
||||
viewName := currView.Name()
|
||||
usePreviouseView := true
|
||||
for _, view := range cyclableViews {
|
||||
if view == viewName {
|
||||
currentCyclebleView = viewName
|
||||
usePreviouseView = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if usePreviouseView {
|
||||
currentCyclebleView = gui.State.PreviousView
|
||||
}
|
||||
}
|
||||
|
||||
usableSpace := height - 7
|
||||
extraSpace := usableSpace - (usableSpace/3)*3
|
||||
|
||||
vHeights := map[string]int{
|
||||
"status": 3,
|
||||
"files": (usableSpace / 3) + extraSpace,
|
||||
"branches": usableSpace / 3,
|
||||
"commits": usableSpace / 3,
|
||||
"stash": 3,
|
||||
"options": 1,
|
||||
}
|
||||
|
||||
if height < 28 {
|
||||
defaultHeight := 3
|
||||
if height < 21 {
|
||||
defaultHeight = 1
|
||||
}
|
||||
vHeights = map[string]int{
|
||||
"status": defaultHeight,
|
||||
"files": defaultHeight,
|
||||
"branches": defaultHeight,
|
||||
"commits": defaultHeight,
|
||||
"stash": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
|
||||
}
|
||||
vHeights := gui.getViewHeights()
|
||||
|
||||
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||
|
||||
@@ -448,29 +517,47 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
appStatusOptionsBoundary = len(appStatus) + 2
|
||||
}
|
||||
|
||||
panelSpacing := 1
|
||||
if OverlappingEdges {
|
||||
panelSpacing = 0
|
||||
}
|
||||
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
g.DeleteView("limit")
|
||||
|
||||
sidePanelWidthRatio := gui.Config.GetUserConfig().GetFloat64("gui.sidePanelWidth")
|
||||
|
||||
textColor := theme.GocuiDefaultTextColor
|
||||
leftSideWidth := width / 3
|
||||
var leftSideWidth int
|
||||
switch gui.State.ScreenMode {
|
||||
case SCREEN_NORMAL:
|
||||
leftSideWidth = int(float64(width) * sidePanelWidthRatio)
|
||||
case SCREEN_HALF:
|
||||
leftSideWidth = width / 2
|
||||
case SCREEN_FULL:
|
||||
currentView := gui.g.CurrentView()
|
||||
if currentView != nil && currentView.Name() == "main" {
|
||||
leftSideWidth = 0
|
||||
} else {
|
||||
leftSideWidth = width - 1
|
||||
}
|
||||
}
|
||||
|
||||
panelSplitX := width - 1
|
||||
mainPanelLeft := leftSideWidth + 1
|
||||
mainPanelRight := width - 1
|
||||
secondaryPanelLeft := width - 1
|
||||
secondaryPanelTop := 0
|
||||
mainPanelBottom := height - 2
|
||||
if gui.State.SplitMainPanel {
|
||||
if width < 220 {
|
||||
if gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainPanelLeft = 0
|
||||
panelSplitX = width/2 - 4
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
} else if width < 220 {
|
||||
mainPanelBottom = height/2 - 1
|
||||
secondaryPanelTop = mainPanelBottom + 1
|
||||
secondaryPanelLeft = leftSideWidth + 1
|
||||
} else {
|
||||
units := 5
|
||||
leftSideWidth = width / units
|
||||
mainPanelLeft = leftSideWidth + 1
|
||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
@@ -489,7 +576,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
prevMainView, err := gui.g.View("main")
|
||||
if err == nil {
|
||||
_, prevMainHeight := prevMainView.Size()
|
||||
heightDiff := mainPanelBottom - prevMainHeight
|
||||
heightDiff := mainPanelBottom - prevMainHeight - 1
|
||||
if heightDiff > 0 {
|
||||
if manager, ok := gui.viewBufferManagerMap["main"]; ok {
|
||||
manager.ReadLines(heightDiff)
|
||||
@@ -500,7 +587,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
||||
v, err := g.SetView(main, mainPanelLeft, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -508,13 +595,16 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
v.Title = gui.Tr.SLocalize("DiffTitle")
|
||||
v.Wrap = true
|
||||
v.FgColor = textColor
|
||||
v.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
hiddenViewOffset := 0
|
||||
hiddenViewOffset := 9999
|
||||
|
||||
hiddenSecondaryPanelOffset := 0
|
||||
if !gui.State.SplitMainPanel {
|
||||
hiddenViewOffset = 9999
|
||||
hiddenSecondaryPanelOffset = hiddenViewOffset
|
||||
}
|
||||
secondaryView, err := g.SetView(secondary, secondaryPanelLeft+hiddenViewOffset, hiddenViewOffset+secondaryPanelTop, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
||||
secondaryView, err := g.SetView(secondary, secondaryPanelLeft+hiddenSecondaryPanelOffset, hiddenSecondaryPanelOffset+secondaryPanelTop, width-1+hiddenSecondaryPanelOffset, height-2+hiddenSecondaryPanelOffset, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -522,6 +612,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
secondaryView.Title = gui.Tr.SLocalize("DiffTitle")
|
||||
secondaryView.Wrap = true
|
||||
secondaryView.FgColor = gocui.ColorWhite
|
||||
secondaryView.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
@@ -539,7 +630,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
filesView.Highlight = true
|
||||
filesView.Title = gui.Tr.SLocalize("FilesTitle")
|
||||
v.FgColor = textColor
|
||||
filesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onFilesPanelSearchSelect))
|
||||
filesView.ContainsList = true
|
||||
}
|
||||
|
||||
branchesView, err := g.SetViewBeneath("branches", "files", vHeights["branches"])
|
||||
@@ -550,6 +642,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
|
||||
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
|
||||
branchesView.FgColor = textColor
|
||||
branchesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onBranchesPanelSearchSelect))
|
||||
branchesView.ContainsList = true
|
||||
}
|
||||
|
||||
if v, err := g.SetViewBeneath("commitFiles", "branches", vHeights["commits"]); err != nil {
|
||||
@@ -558,6 +652,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("CommitFiles")
|
||||
v.FgColor = textColor
|
||||
v.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitFilesPanelSearchSelect))
|
||||
v.ContainsList = true
|
||||
}
|
||||
|
||||
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
|
||||
@@ -568,6 +664,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
commitsView.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
commitsView.Tabs = []string{"Commits", "Reflog"}
|
||||
commitsView.FgColor = textColor
|
||||
commitsView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitsPanelSearchSelect))
|
||||
commitsView.ContainsList = true
|
||||
}
|
||||
|
||||
stashView, err := g.SetViewBeneath("stash", "commits", vHeights["stash"])
|
||||
@@ -577,6 +675,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
stashView.Title = gui.Tr.SLocalize("StashTitle")
|
||||
stashView.FgColor = textColor
|
||||
stashView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onStashPanelSearchSelect))
|
||||
stashView.ContainsList = true
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
|
||||
@@ -584,8 +684,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
v.Frame = false
|
||||
userConfig := gui.Config.GetUserConfig()
|
||||
v.FgColor = theme.GetColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
v.FgColor = theme.OptionsColor
|
||||
}
|
||||
|
||||
if gui.getCommitMessageView() == nil {
|
||||
@@ -618,6 +717,35 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
searchViewOffset := hiddenViewOffset
|
||||
if gui.State.Searching.isSearching {
|
||||
searchViewOffset = 0
|
||||
}
|
||||
|
||||
// this view takes up one character. Its only purpose is to show the slash when searching
|
||||
searchPrefix := "search: "
|
||||
if searchPrefixView, err := g.SetView("searchPrefix", appStatusOptionsBoundary-1+searchViewOffset, height-2+searchViewOffset, len(searchPrefix)+searchViewOffset, height+searchViewOffset, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
|
||||
searchPrefixView.BgColor = gocui.ColorDefault
|
||||
searchPrefixView.FgColor = gocui.ColorGreen
|
||||
searchPrefixView.Frame = false
|
||||
gui.setViewContent(gui.g, searchPrefixView, searchPrefix)
|
||||
}
|
||||
|
||||
if searchView, err := g.SetView("search", appStatusOptionsBoundary-1+searchViewOffset+len(searchPrefix), height-2+searchViewOffset, optionsVersionBoundary+searchViewOffset, height+searchViewOffset, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
|
||||
searchView.BgColor = gocui.ColorDefault
|
||||
searchView.FgColor = gocui.ColorGreen
|
||||
searchView.Frame = false
|
||||
searchView.Editable = true
|
||||
}
|
||||
|
||||
if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -689,6 +817,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
mainViewWidth, mainViewHeight := gui.getMainView().Size()
|
||||
if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight {
|
||||
gui.State.PrevMainWidth = mainViewWidth
|
||||
gui.State.PrevMainHeight = mainViewHeight
|
||||
if err := gui.onResize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// here is a good place log some stuff
|
||||
// if you download humanlog and do tail -f development.log | humanlog
|
||||
// this will let you see these branches as prettified json
|
||||
@@ -820,11 +957,17 @@ func (gui *Gui) startBackgroundFetch() {
|
||||
|
||||
// Run setup the gui with keybindings and start the mainloop
|
||||
func (gui *Gui) Run() error {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
g, err := gocui.NewGui(gocui.Output256, OverlappingEdges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.OnSearchEscape = gui.onSearchEscape
|
||||
g.SearchEscapeKey = gui.getKey("universal.return")
|
||||
g.NextSearchMatchKey = gui.getKey("universal.nextMatch")
|
||||
g.PrevSearchMatchKey = gui.getKey("universal.prevMatch")
|
||||
|
||||
gui.stopChan = make(chan struct{})
|
||||
|
||||
g.ASCII = runtime.GOOS == "windows" && runewidth.IsEastAsian()
|
||||
|
||||
@@ -225,6 +225,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollUpMain,
|
||||
Alternative: "fn+up",
|
||||
Description: gui.Tr.SLocalize("scrollUpMainPanel"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -232,6 +233,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownMain,
|
||||
Alternative: "fn+down",
|
||||
Description: gui.Tr.SLocalize("scrollDownMainPanel"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -317,6 +319,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleEditConfig,
|
||||
Description: gui.Tr.SLocalize("EditConfig"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: gui.getKey("universal.nextScreenMode"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.nextScreenMode,
|
||||
Description: gui.Tr.SLocalize("nextScreenMode"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: gui.getKey("universal.prevScreenMode"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.prevScreenMode,
|
||||
Description: gui.Tr.SLocalize("prevScreenMode"),
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: gui.getKey("universal.openFile"),
|
||||
@@ -451,12 +467,19 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Description: gui.Tr.SLocalize("fetch"),
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
ViewName: "",
|
||||
Key: gui.getKey("universal.executeCustomCommand"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCustomCommand,
|
||||
Description: gui.Tr.SLocalize("executeCustomCommand"),
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Key: gui.getKey("commits.viewResetOptions"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateResetToUpstreamMenu,
|
||||
Description: gui.Tr.SLocalize("viewResetToUpstreamOptions"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Contexts: []string{"local-branches"},
|
||||
@@ -537,6 +560,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleFastForward,
|
||||
Description: gui.Tr.SLocalize("FastForward"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Contexts: []string{"local-branches"},
|
||||
Key: gui.getKey("commits.viewResetOptions"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateResetToBranchMenu,
|
||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Contexts: []string{"tags"},
|
||||
@@ -569,6 +600,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleCreateTag,
|
||||
Description: gui.Tr.SLocalize("createTag"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Contexts: []string{"tags"},
|
||||
Key: gui.getKey("commits.viewResetOptions"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateResetToTagMenu,
|
||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Key: gui.getKey("universal.nextTab"),
|
||||
@@ -589,6 +628,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleRemoteBranchesEscape,
|
||||
Description: gui.Tr.SLocalize("ReturnToRemotesList"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Contexts: []string{"remote-branches"},
|
||||
Key: gui.getKey("commits.viewResetOptions"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateResetToRemoteBranchMenu,
|
||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Contexts: []string{"remotes"},
|
||||
@@ -609,6 +656,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePrevCommitsTab,
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.startSearch"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleOpenSearchForCommitsPanel,
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"branch-commits"},
|
||||
@@ -962,7 +1015,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Contexts: []string{"staging"},
|
||||
Key: gui.getKey("universal.select"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStageSelection,
|
||||
Handler: gui.handleToggleStagedSelection,
|
||||
Description: gui.Tr.SLocalize("StageSelection"),
|
||||
},
|
||||
{
|
||||
@@ -1084,16 +1137,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Contexts: []string{"patch-building"},
|
||||
Key: gui.getKey("universal.select"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleAddSelectionToPatch,
|
||||
Description: gui.Tr.SLocalize("StageSelection"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"patch-building"},
|
||||
Key: gui.getKey("universal.remove"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleRemoveSelectionFromPatch,
|
||||
Description: gui.Tr.SLocalize("ResetSelection"),
|
||||
Handler: gui.handleToggleSelectionForPatch,
|
||||
Description: gui.Tr.SLocalize("ToggleSelectionForPatch"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
@@ -1148,6 +1193,30 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseScrollDown,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"staging"},
|
||||
Key: gui.getKey("files.commitChanges"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitPress,
|
||||
Description: gui.Tr.SLocalize("CommitChanges"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"staging"},
|
||||
Key: gui.getKey("files.commitChangesWithoutHook"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleWIPCommitPress,
|
||||
Description: gui.Tr.SLocalize("commitChangesWithoutHook"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"staging"},
|
||||
Key: gui.getKey("files.commitChangesWithEditor"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitEditorPress,
|
||||
Description: gui.Tr.SLocalize("CommitChangesWithEditor"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"merging"},
|
||||
@@ -1343,6 +1412,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitFilesClick,
|
||||
},
|
||||
{
|
||||
ViewName: "search",
|
||||
Key: gocui.KeyEnter,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSearch,
|
||||
},
|
||||
{
|
||||
ViewName: "search",
|
||||
Key: gui.getKey("universal.return"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSearchEscape,
|
||||
},
|
||||
}
|
||||
|
||||
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
|
||||
@@ -1370,6 +1451,11 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listView.handleNextLine},
|
||||
{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listView.handleClick},
|
||||
}...)
|
||||
|
||||
// we need a specific keybinding for the commits panel beacuse it usually lazyloads commits
|
||||
if listView.viewName != "commits" {
|
||||
bindings = append(bindings, &Binding{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gui.getKey("universal.startSearch"), Modifier: gocui.ModNone, Handler: gui.handleOpenSearch})
|
||||
}
|
||||
}
|
||||
|
||||
return bindings
|
||||
|
||||
@@ -220,6 +220,10 @@ func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCycleLine(1)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedCommitFileName() string {
|
||||
return gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMainView() error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
@@ -227,7 +231,7 @@ func (gui *Gui) refreshMainView() error {
|
||||
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
||||
// how to get around this
|
||||
if gui.State.MainContext == "patch-building" {
|
||||
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
filename := gui.getSelectedCommitFileName()
|
||||
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
}
|
||||
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices)
|
||||
|
||||
@@ -8,6 +8,12 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type menuItem struct {
|
||||
displayString string
|
||||
displayStrings []string
|
||||
onPress func() error
|
||||
}
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -38,27 +44,49 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.returnFocus(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error {
|
||||
isFocused := gui.g.CurrentView().Name() == "menu"
|
||||
gui.State.MenuItemCount = itemCount
|
||||
list, err := utils.RenderList(items, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
type createMenuOptions struct {
|
||||
showCancel bool
|
||||
}
|
||||
|
||||
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
|
||||
if createMenuOptions.showCancel {
|
||||
// this is mutative but I'm okay with that for now
|
||||
items = append(items, &menuItem{
|
||||
displayStrings: []string{gui.Tr.SLocalize("cancel")},
|
||||
onPress: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
gui.State.MenuItemCount = len(items)
|
||||
|
||||
stringArrays := make([][]string, len(items))
|
||||
for i, item := range items {
|
||||
if item.displayStrings == nil {
|
||||
stringArrays[i] = []string{item.displayString}
|
||||
} else {
|
||||
stringArrays[i] = item.displayStrings
|
||||
}
|
||||
}
|
||||
|
||||
list := utils.RenderDisplayStrings(stringArrays)
|
||||
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, false, list)
|
||||
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
|
||||
menuView.Title = title
|
||||
menuView.FgColor = theme.GocuiDefaultTextColor
|
||||
menuView.ContainsList = true
|
||||
menuView.Clear()
|
||||
fmt.Fprint(menuView, list)
|
||||
gui.State.Panels.Menu.SelectedLine = 0
|
||||
|
||||
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedLine := gui.State.Panels.Menu.SelectedLine
|
||||
if err := handlePress(selectedLine); err != nil {
|
||||
if err := items[selectedLine].onPress(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := gui.g.View("menu"); err == nil {
|
||||
if _, err := gui.g.SetViewOnBottom("menu"); err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,8 +3,6 @@ package gui
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -38,19 +36,23 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
bindings := gui.getBindings(v)
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
if bindings[index].Key == nil {
|
||||
return nil
|
||||
menuItems := make([]*menuItem, len(bindings))
|
||||
|
||||
for i, binding := range bindings {
|
||||
innerBinding := binding // note to self, never close over loop variables
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{GetKeyDisplay(innerBinding.Key), innerBinding.Description},
|
||||
onPress: func() error {
|
||||
if innerBinding.Key == nil {
|
||||
return nil
|
||||
}
|
||||
if err := gui.handleMenuClose(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return innerBinding.Handler(g, v)
|
||||
},
|
||||
}
|
||||
if index >= len(bindings) {
|
||||
return errors.New("Index is greater than size of bindings")
|
||||
}
|
||||
err := gui.handleMenuClose(g, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bindings[index].Handler(g, v)
|
||||
}
|
||||
|
||||
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, len(bindings), handleMenuPress)
|
||||
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), menuItems, createMenuOptions{})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
@@ -42,38 +43,24 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAddSelectionToPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) handleToggleSelectionForPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
toggleFunc := gui.GitCommand.PatchManager.AddFileLineRange
|
||||
filename := gui.getSelectedCommitFileName()
|
||||
includedLineIndices := gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.SelectedLineIdx)
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = gui.GitCommand.PatchManager.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.AddFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoveSelectionFromPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.RemoveFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
toggleFunc(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,48 +6,43 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type patchMenuOption struct {
|
||||
displayName string
|
||||
function func() error
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (o *patchMenuOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{o.displayName}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePatchOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
if !gui.GitCommand.PatchManager.CommitSelected() {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoPatchError"))
|
||||
}
|
||||
|
||||
options := []*patchMenuOption{
|
||||
{displayName: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha), function: gui.handleDeletePatchFromCommit},
|
||||
{displayName: "pull patch out into index", function: gui.handlePullPatchIntoWorkingTree},
|
||||
{displayName: "reset patch", function: gui.handleResetPatch},
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha),
|
||||
onPress: gui.handleDeletePatchFromCommit,
|
||||
},
|
||||
{
|
||||
displayString: "pull patch out into index",
|
||||
onPress: gui.handlePullPatchIntoWorkingTree,
|
||||
},
|
||||
{
|
||||
displayString: "reset patch",
|
||||
onPress: gui.handleResetPatch,
|
||||
},
|
||||
}
|
||||
|
||||
selectedCommit := gui.getSelectedCommit(gui.g)
|
||||
if selectedCommit != nil && gui.GitCommand.PatchManager.CommitSha != selectedCommit.Sha {
|
||||
// adding this option to index 1
|
||||
options = append(
|
||||
options[:1],
|
||||
menuItems = append(
|
||||
menuItems[:1],
|
||||
append(
|
||||
[]*patchMenuOption{
|
||||
[]*menuItem{
|
||||
{
|
||||
displayName: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
function: gui.handleMovePatchToSelectedCommit,
|
||||
displayString: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
onPress: gui.handleMovePatchToSelectedCommit,
|
||||
},
|
||||
}, options[1:]...,
|
||||
}, menuItems[1:]...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].function()
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), options, len(options), handleMenuPress)
|
||||
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
|
||||
func (gui *Gui) getPatchCommitIndex() int {
|
||||
|
||||
48
pkg/gui/presentation/branches.go
Normal file
48
pkg/gui/presentation/branches.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetBranchListDisplayStrings(branches []*commands.Branch, isFocused bool, selectedLine int) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
showUpstreamDifferences := isFocused && i == selectedLine
|
||||
lines[i] = getBranchDisplayStrings(branches[i], showUpstreamDifferences)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getBranchDisplayStrings returns the display string of branch
|
||||
func getBranchDisplayStrings(b *commands.Branch, showUpstreamDifferences bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
if showUpstreamDifferences && b.Pushables != "" && b.Pullables != "" {
|
||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||
}
|
||||
|
||||
return []string{b.Recency, displayName}
|
||||
}
|
||||
|
||||
// GetBranchColor branch color
|
||||
func GetBranchColor(name string) color.Attribute {
|
||||
branchType := strings.Split(name, "/")[0]
|
||||
|
||||
switch branchType {
|
||||
case "feature":
|
||||
return color.FgGreen
|
||||
case "bugfix":
|
||||
return color.FgYellow
|
||||
case "hotfix":
|
||||
return color.FgRed
|
||||
default:
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
}
|
||||
35
pkg/gui/presentation/commit_files.go
Normal file
35
pkg/gui/presentation/commit_files.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
func GetCommitFileListDisplayStrings(branches []*commands.CommitFile) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
lines[i] = getCommitFileDisplayStrings(branches[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getCommitFileDisplayStrings returns the display string of branch
|
||||
func getCommitFileDisplayStrings(f *commands.CommitFile) []string {
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
|
||||
var colour *color.Color
|
||||
switch f.Status {
|
||||
case commands.UNSELECTED:
|
||||
colour = defaultColor
|
||||
case commands.WHOLE:
|
||||
colour = green
|
||||
case commands.PART:
|
||||
colour = yellow
|
||||
}
|
||||
return []string{colour.Sprint(f.DisplayString)}
|
||||
}
|
||||
126
pkg/gui/presentation/commits.go
Normal file
126
pkg/gui/presentation/commits.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetCommitListDisplayStrings(commits []*commands.Commit, fullDescription bool) [][]string {
|
||||
lines := make([][]string, len(commits))
|
||||
|
||||
var displayFunc func(*commands.Commit) []string
|
||||
if fullDescription {
|
||||
displayFunc = getFullDescriptionDisplayStringsForCommit
|
||||
} else {
|
||||
displayFunc = getDisplayStringsForCommit
|
||||
}
|
||||
|
||||
for i := range commits {
|
||||
lines[i] = displayFunc(commits[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "reflog":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
tagString := ""
|
||||
truncatedDate := utils.TruncateWithEllipsis(c.Date, 15)
|
||||
secondColumnString := blue.Sprint(truncatedDate)
|
||||
if c.Action != "" {
|
||||
secondColumnString = cyan.Sprint(c.Action)
|
||||
} else if c.ExtraInfo != "" {
|
||||
tagColor := color.New(color.FgMagenta, color.Bold)
|
||||
tagString = utils.ColoredStringDirect(c.ExtraInfo, tagColor) + " "
|
||||
}
|
||||
|
||||
truncatedAuthor := utils.TruncateWithEllipsis(c.Author, 17)
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha[:8]), secondColumnString, yellow.Sprint(truncatedAuthor), tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
func getDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "reflog":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
actionString := ""
|
||||
tagString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
} else if len(c.Tags) > 0 {
|
||||
tagColor := color.New(color.FgMagenta, color.Bold)
|
||||
tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha[:8]), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
36
pkg/gui/presentation/files.go
Normal file
36
pkg/gui/presentation/files.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetFileListDisplayStrings(files []*commands.File) [][]string {
|
||||
lines := make([][]string, len(files))
|
||||
|
||||
for i := range files {
|
||||
lines[i] = getFileDisplayStrings(files[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getFileDisplayStrings returns the display string of branch
|
||||
func getFileDisplayStrings(f *commands.File) []string {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !f.Tracked && !f.HasStagedChanges {
|
||||
return []string{red.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
output := green.Sprint(f.DisplayString[0:1])
|
||||
output += red.Sprint(f.DisplayString[1:3])
|
||||
if f.HasUnstagedChanges {
|
||||
output += red.Sprint(f.Name)
|
||||
} else {
|
||||
output += green.Sprint(f.Name)
|
||||
}
|
||||
return []string{output}
|
||||
}
|
||||
23
pkg/gui/presentation/remote_branches.go
Normal file
23
pkg/gui/presentation/remote_branches.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetRemoteBranchListDisplayStrings(branches []*commands.RemoteBranch) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
lines[i] = getRemoteBranchDisplayStrings(branches[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getRemoteBranchDisplayStrings returns the display string of branch
|
||||
func getRemoteBranchDisplayStrings(b *commands.RemoteBranch) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
|
||||
return []string{displayName}
|
||||
}
|
||||
20
pkg/gui/presentation/remotes.go
Normal file
20
pkg/gui/presentation/remotes.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetRemoteListDisplayStrings(remotes []*commands.Remote) [][]string {
|
||||
lines := make([][]string, len(remotes))
|
||||
|
||||
for i := range remotes {
|
||||
lines[i] = getRemoteDisplayStrings(remotes[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getRemoteDisplayStrings returns the display string of branch
|
||||
func getRemoteDisplayStrings(r *commands.Remote) []string {
|
||||
return []string{r.Name}
|
||||
}
|
||||
20
pkg/gui/presentation/stash_entries.go
Normal file
20
pkg/gui/presentation/stash_entries.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetStashEntryListDisplayStrings(stashEntries []*commands.StashEntry) [][]string {
|
||||
lines := make([][]string, len(stashEntries))
|
||||
|
||||
for i := range stashEntries {
|
||||
lines[i] = getStashEntryDisplayStrings(stashEntries[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getStashEntryDisplayStrings returns the display string of branch
|
||||
func getStashEntryDisplayStrings(s *commands.StashEntry) []string {
|
||||
return []string{s.DisplayString}
|
||||
}
|
||||
20
pkg/gui/presentation/tags.go
Normal file
20
pkg/gui/presentation/tags.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetTagListDisplayStrings(tags []*commands.Tag) [][]string {
|
||||
lines := make([][]string, len(tags))
|
||||
|
||||
for i := range tags {
|
||||
lines[i] = getTagDisplayStrings(tags[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getTagDisplayStrings returns the display string of branch
|
||||
func getTagDisplayStrings(t *commands.Tag) []string {
|
||||
return []string{t.Name}
|
||||
}
|
||||
74
pkg/gui/pty.go
Normal file
74
pkg/gui/pty.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// +build !windows
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/jesseduffield/pty"
|
||||
)
|
||||
|
||||
func (gui *Gui) onResize() error {
|
||||
if gui.State.Ptmx == nil {
|
||||
return nil
|
||||
}
|
||||
mainView := gui.getMainView()
|
||||
width, height := mainView.Size()
|
||||
|
||||
if err := pty.Setsize(gui.State.Ptmx, &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: handle resizing properly
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some commands need to output for a terminal to active certain behaviour.
|
||||
// For example, git won't invoke the GIT_PAGER env var unless it thinks it's
|
||||
// talking to a terminal. We typically write cmd outputs straight to a view,
|
||||
// which is just an io.Reader. the pty package lets us wrap a command in a
|
||||
// pseudo-terminal meaning we'll get the behaviour we want from the underlying
|
||||
// command.
|
||||
func (gui *Gui) newPtyTask(viewName string, cmd *exec.Cmd) error {
|
||||
width, _ := gui.getMainView().Size()
|
||||
pager := gui.GitCommand.GetPager(width)
|
||||
|
||||
if pager == "" {
|
||||
// if we're not using a custom pager we don't need to use a pty
|
||||
return gui.newCmdTask(viewName, cmd)
|
||||
}
|
||||
|
||||
cmd.Env = append(cmd.Env, "GIT_PAGER="+pager)
|
||||
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil // swallowing for now
|
||||
}
|
||||
|
||||
_, height := view.Size()
|
||||
_, oy := view.Origin()
|
||||
|
||||
manager := gui.getManager(view)
|
||||
|
||||
ptmx, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Ptmx = ptmx
|
||||
onClose := func() {
|
||||
ptmx.Close()
|
||||
gui.State.Ptmx = nil
|
||||
}
|
||||
|
||||
if err := gui.onResize(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := manager.NewTask(manager.NewCmdTask(ptmx, cmd, height+oy+10, onClose)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
13
pkg/gui/pty_windows.go
Normal file
13
pkg/gui/pty_windows.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build windows
|
||||
|
||||
package gui
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func (gui *Gui) onResize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) newPtyTask(viewName string, cmd *exec.Cmd) error {
|
||||
return gui.newCmdTask(viewName, cmd)
|
||||
}
|
||||
@@ -7,33 +7,23 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type option struct {
|
||||
value string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *option) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.value}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*option{
|
||||
{value: "continue"},
|
||||
{value: "abort"},
|
||||
}
|
||||
options := []string{"continue", "abort"}
|
||||
|
||||
if gui.State.WorkingTreeState == "rebasing" {
|
||||
options = append(options, &option{value: "skip"})
|
||||
options = append(options, "skip")
|
||||
}
|
||||
|
||||
options = append(options, &option{value: "cancel"})
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
command := options[index].value
|
||||
if command == "cancel" {
|
||||
return nil
|
||||
menuItems := make([]*menuItem, len(options))
|
||||
for i, option := range options {
|
||||
// note to self. Never, EVER, close over loop variables in a function
|
||||
innerOption := option
|
||||
menuItems[i] = &menuItem{
|
||||
displayString: innerOption,
|
||||
onPress: func() error {
|
||||
return gui.genericMergeCommand(innerOption)
|
||||
},
|
||||
}
|
||||
return gui.genericMergeCommand(command)
|
||||
}
|
||||
|
||||
var title string
|
||||
@@ -43,7 +33,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
|
||||
title = gui.Tr.SLocalize("RebaseOptionsTitle")
|
||||
}
|
||||
|
||||
return gui.createMenu(title, options, len(options), handleMenuPress)
|
||||
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
|
||||
func (gui *Gui) genericMergeCommand(command string) error {
|
||||
|
||||
@@ -10,41 +10,34 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type recentRepo struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the path from a recent repo.
|
||||
func (r *recentRepo) GetDisplayStrings(isFocused bool) []string {
|
||||
yellow := color.New(color.FgMagenta)
|
||||
base := filepath.Base(r.path)
|
||||
path := yellow.Sprint(r.path)
|
||||
return []string{base, path}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
recentRepoPaths := gui.Config.GetAppState().RecentRepos
|
||||
reposCount := utils.Min(len(recentRepoPaths), 20)
|
||||
yellow := color.New(color.FgMagenta)
|
||||
// we won't show the current repo hence the -1
|
||||
recentRepos := make([]*recentRepo, reposCount-1)
|
||||
menuItems := make([]*menuItem, reposCount-1)
|
||||
for i, path := range recentRepoPaths[1:reposCount] {
|
||||
recentRepos[i] = &recentRepo{path: path}
|
||||
innerPath := path
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{
|
||||
filepath.Base(innerPath),
|
||||
yellow.Sprint(innerPath),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := os.Chdir(innerPath); err != nil {
|
||||
return err
|
||||
}
|
||||
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.GitCommand = newGitCommand
|
||||
return gui.Errors.ErrSwitchRepo
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
repo := recentRepos[index]
|
||||
if err := os.Chdir(repo.path); err != nil {
|
||||
return err
|
||||
}
|
||||
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.GitCommand = newGitCommand
|
||||
return gui.Errors.ErrSwitchRepo
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, len(recentRepos), handleMenuPress)
|
||||
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
|
||||
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||
|
||||
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -40,7 +41,7 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -66,9 +67,8 @@ func (gui *Gui) renderReflogCommitsWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits))
|
||||
if err := gui.renderListPanel(commitsView, gui.State.ReflogCommits); err != nil {
|
||||
return err
|
||||
}
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.ReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "reflog-commits" {
|
||||
if err := gui.handleReflogCommitSelect(gui.g, commitsView); err != nil {
|
||||
return err
|
||||
@@ -95,3 +95,9 @@ func (gui *Gui) handleCheckoutReflogCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedReflogCommit()
|
||||
|
||||
return gui.createResetMenu(commit.Sha)
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type reflogResetOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *reflogResetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedReflogCommit()
|
||||
|
||||
resetFunction := func(reset func(string) error) func() error {
|
||||
return func() error {
|
||||
if err := reset(commit.Sha); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = 0
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
}
|
||||
}
|
||||
|
||||
options := []*reflogResetOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardReset"),
|
||||
command: fmt.Sprintf("reset --hard %s", commit.Sha),
|
||||
handler: resetFunction(gui.GitCommand.ResetHard),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("softReset"),
|
||||
command: fmt.Sprintf("reset --soft %s", commit.Sha),
|
||||
handler: resetFunction(gui.GitCommand.ResetSoft),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -62,9 +63,8 @@ func (gui *Gui) renderRemoteBranchesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
displayStrings := presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "remote-branches" {
|
||||
if err := gui.handleRemoteBranchSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
@@ -132,3 +132,12 @@ func (gui *Gui) handleSetBranchUpstream(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToRemoteBranchMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranch := gui.getSelectedRemoteBranch()
|
||||
if selectedBranch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createResetMenu(fmt.Sprintf("%s/%s", selectedBranch.RemoteName, selectedBranch.Name))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -80,9 +81,10 @@ func (gui *Gui) renderRemotesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
displayStrings := presentation.GetRemoteListDisplayStrings(gui.State.Remotes)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "remotes" {
|
||||
if err := gui.handleRemoteSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
|
||||
46
pkg/gui/reset_menu_panel.go
Normal file
46
pkg/gui/reset_menu_panel.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
func (gui *Gui) createResetMenu(ref string) error {
|
||||
strengths := []string{"soft", "mixed", "hard"}
|
||||
menuItems := make([]*menuItem, len(strengths))
|
||||
for i, strength := range strengths {
|
||||
innerStrength := strength
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{
|
||||
fmt.Sprintf("%s reset", strength),
|
||||
color.New(color.FgRed).Sprint(
|
||||
fmt.Sprintf("reset --%s %s", strength, ref),
|
||||
),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetToCommit(ref, innerStrength); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.switchCommitsPanelContext("branch-commits")
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = 0
|
||||
|
||||
if err := gui.refreshCommits(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleCommitSelect(gui.g, gui.getCommitsView())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), ref), menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
91
pkg/gui/searching.go
Normal file
91
pkg/gui/searching.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.isSearching = true
|
||||
gui.State.Searching.view = v
|
||||
gui.renderString(gui.g, "search", "")
|
||||
gui.switchFocus(gui.g, v, gui.getSearchView())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.searchString = gui.getSearchView().Buffer()
|
||||
gui.switchFocus(gui.g, nil, gui.State.Searching.view)
|
||||
if err := gui.State.Searching.view.Search(gui.State.Searching.searchString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
gui.renderString(
|
||||
gui.g,
|
||||
"search",
|
||||
fmt.Sprintf(
|
||||
"no matches for '%s' %s",
|
||||
gui.State.Searching.searchString,
|
||||
utils.ColoredString(
|
||||
fmt.Sprintf("%s: exit search mode", gui.getKeyDisplay("universal.return")),
|
||||
theme.OptionsFgColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
gui.renderString(
|
||||
gui.g,
|
||||
"search",
|
||||
fmt.Sprintf(
|
||||
"matches for '%s' (%d of %d) %s",
|
||||
gui.State.Searching.searchString,
|
||||
index+1,
|
||||
total,
|
||||
utils.ColoredString(
|
||||
fmt.Sprintf(
|
||||
"%s: next match, %s: previous match, %s: exit search mode",
|
||||
gui.getKeyDisplay("universal.nextMatch"),
|
||||
gui.getKeyDisplay("universal.prevMatch"),
|
||||
gui.getKeyDisplay("universal.return"),
|
||||
),
|
||||
theme.OptionsFgColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
if err := innerFunc(y); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onSearchEscape() error {
|
||||
gui.State.Searching.isSearching = false
|
||||
if gui.State.Searching.view != nil {
|
||||
gui.State.Searching.view.ClearSearch()
|
||||
gui.State.Searching.view = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.switchFocus(gui.g, nil, gui.State.Searching.view); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.onSearchEscape()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -99,26 +99,27 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelectionWithPrompt(false)
|
||||
func (gui *Gui) handleToggleStagedSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
return gui.applySelection(state.SecondaryFocused)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelectionWithPrompt(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) applySelectionWithPrompt(reverse bool) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if !reverse && state.SecondaryFocused {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
||||
} else if reverse && !state.SecondaryFocused && !gui.Config.GetUserConfig().GetBool("gui.skipUnstageLineWarning") {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getMainView(), false, "unstage lines", "Are you sure you want to unstage these lines? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipUnstageLineWarning' to true", func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.applySelection(reverse)
|
||||
}, nil)
|
||||
if state.SecondaryFocused {
|
||||
// for backwards compatibility
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
|
||||
return gui.applySelection(reverse)
|
||||
if gui.Config.GetUserConfig().GetBool("gui.skipUnstageLineWarning") {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getMainView(), false, "unstage lines", "Are you sure you want to delete the selected lines (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipUnstageLineWarning' to true", func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.applySelection(true)
|
||||
}, nil)
|
||||
} else {
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) applySelection(reverse bool) error {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -43,7 +41,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -56,17 +54,12 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
|
||||
|
||||
isFocused := gui.g.CurrentView().Name() == "stash"
|
||||
list, err := utils.RenderList(gui.State.StashEntries, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stashView := gui.getStashView()
|
||||
|
||||
v := gui.getStashView()
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
displayStrings := presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries)
|
||||
gui.renderDisplayStrings(stashView, displayStrings)
|
||||
|
||||
if err := gui.resetOrigin(v); err != nil {
|
||||
if err := gui.resetOrigin(stashView); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -122,3 +115,8 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
|
||||
return gui.refreshFiles()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onStashPanelSearchSelect(selectedLine int) error {
|
||||
gui.State.Panels.Stash.SelectedLine = selectedLine
|
||||
return gui.handleStashEntrySelect(gui.g, gui.getStashView())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
|
||||
if len(branches) > 0 {
|
||||
branch := branches[0]
|
||||
name := utils.ColoredString(branch.Name, commands.GetBranchColor(branch.Name))
|
||||
name := utils.ColoredString(branch.Name, presentation.GetBranchColor(branch.Name))
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
status += fmt.Sprintf(" %s → %s", repoName, name)
|
||||
}
|
||||
@@ -136,14 +136,6 @@ func lazygitTitle() string {
|
||||
}
|
||||
|
||||
func (gui *Gui) updateWorkTreeState() error {
|
||||
merging, err := gui.GitCommand.IsInMergeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if merging {
|
||||
gui.State.WorkingTreeState = "merging"
|
||||
return nil
|
||||
}
|
||||
rebaseMode, err := gui.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -152,6 +144,14 @@ func (gui *Gui) updateWorkTreeState() error {
|
||||
gui.State.WorkingTreeState = "rebasing"
|
||||
return nil
|
||||
}
|
||||
merging, err := gui.GitCommand.IsInMergeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if merging {
|
||||
gui.State.WorkingTreeState = "merging"
|
||||
return nil
|
||||
}
|
||||
gui.State.WorkingTreeState = "normal"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -66,9 +67,8 @@ func (gui *Gui) renderTagsWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.Tags); err != nil {
|
||||
return err
|
||||
}
|
||||
displayStrings := presentation.GetTagListDisplayStrings(gui.State.Tags)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "tags" {
|
||||
if err := gui.handleTagSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
@@ -106,7 +106,13 @@ func (gui *Gui) handleDeleteTag(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.DeleteTag(tag.Name); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.refreshTags()
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshTags(); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -137,6 +143,21 @@ func (gui *Gui) handleCreateTag(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), ""); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.refreshTags()
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshTags(); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToTagMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
tag := gui.getSelectedTag()
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createResetMenu(tag.Name)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,17 @@ func (gui *Gui) newCmdTask(viewName string, cmd *exec.Cmd) error {
|
||||
|
||||
manager := gui.getManager(view)
|
||||
|
||||
if err := manager.NewTask(manager.NewCmdTask(cmd, height+oy+10)); err != nil {
|
||||
r, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := manager.NewTask(manager.NewCmdTask(r, cmd, height+oy+10, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -69,7 +79,10 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
||||
view.Clear()
|
||||
},
|
||||
func() {
|
||||
gui.g.Update(func(*gocui.Gui) error { return nil })
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
gui.Log.Warn("updating view")
|
||||
return nil
|
||||
})
|
||||
})
|
||||
gui.viewBufferManagerMap[view.Name()] = manager
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
v.Highlight = false
|
||||
return nil
|
||||
case "search":
|
||||
return nil
|
||||
default:
|
||||
panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement"))
|
||||
}
|
||||
@@ -218,32 +220,7 @@ func (gui *Gui) resetOrigin(v *gocui.View) error {
|
||||
|
||||
// if the cursor down past the last item, move it to the last line
|
||||
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
|
||||
if cy < 0 || cy > lineCount {
|
||||
return nil
|
||||
}
|
||||
ox, oy := v.Origin()
|
||||
_, height := v.Size()
|
||||
|
||||
ly := height - 1
|
||||
if ly == -1 {
|
||||
ly = 0
|
||||
}
|
||||
|
||||
// if line is above origin, move origin and set cursor to zero
|
||||
// if line is below origin + height, move origin and set cursor to max
|
||||
// otherwise set cursor to value - origin
|
||||
if ly > lineCount {
|
||||
_ = v.SetCursor(cx, cy)
|
||||
_ = v.SetOrigin(ox, 0)
|
||||
} else if cy < oy {
|
||||
_ = v.SetCursor(cx, 0)
|
||||
_ = v.SetOrigin(ox, cy)
|
||||
} else if cy > oy+ly {
|
||||
_ = v.SetCursor(cx, ly)
|
||||
_ = v.SetOrigin(ox, cy-ly)
|
||||
} else {
|
||||
_ = v.SetCursor(cx, cy-oy)
|
||||
}
|
||||
v.FocusPoint(cx, cy)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -268,6 +245,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
||||
if err := v.SetOrigin(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v.SetCursor(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.setViewContent(gui.g, v, s)
|
||||
})
|
||||
return nil
|
||||
@@ -333,6 +313,11 @@ func (gui *Gui) getMenuView() *gocui.View {
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getSearchView() *gocui.View {
|
||||
v, _ := gui.g.View("search")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
||||
return strings.TrimSpace(v.Buffer())
|
||||
}
|
||||
@@ -402,18 +387,13 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
|
||||
func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
isFocused := gui.g.CurrentView().Name() == v.Name()
|
||||
list, err := utils.RenderList(items, isFocused)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
list := utils.RenderDisplayStrings(displayStrings)
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderPanelOptions() error {
|
||||
|
||||
@@ -5,23 +5,16 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type workspaceResetOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *workspaceResetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*workspaceResetOption{
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAllChangesToAllFiles"),
|
||||
command: "reset --hard HEAD && git clean -fd",
|
||||
handler: func() error {
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("discardAllChangesToAllFiles"),
|
||||
red.Sprint("reset --hard HEAD && git clean -fd"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetAndClean(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
@@ -30,9 +23,11 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAnyUnstagedChanges"),
|
||||
command: "git checkout -- .",
|
||||
handler: func() error {
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("discardAnyUnstagedChanges"),
|
||||
red.Sprint("git checkout -- ."),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.DiscardAnyUnstagedFileChanges(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
@@ -41,9 +36,11 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardUntrackedFiles"),
|
||||
command: "git clean -fd",
|
||||
handler: func() error {
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("discardUntrackedFiles"),
|
||||
red.Sprint("git clean -fd"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.RemoveUntrackedFiles(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
@@ -52,9 +49,11 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("softReset"),
|
||||
command: "git reset --soft HEAD",
|
||||
handler: func() error {
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("softReset"),
|
||||
red.Sprint("git reset --soft HEAD"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
@@ -63,10 +62,12 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardReset"),
|
||||
command: "git reset --hard HEAD",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
|
||||
displayStrings: []string{
|
||||
"mixed reset",
|
||||
red.Sprint("git reset --mixed HEAD"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
@@ -74,27 +75,19 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardResetUpstream"),
|
||||
command: "git reset --hard @{upstream}",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.ResetHard("@{upstream}"); err != nil {
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("hardReset"),
|
||||
red.Sprint("git reset --hard HEAD"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||
return gui.createMenu("", menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
|
||||
@@ -136,9 +136,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CannotGitAdd",
|
||||
Other: "Kan commando niet uitvoeren git add --path untracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "CantIgnoreTrackFiles",
|
||||
Other: "Kan gevolgde bestanden niet negeren",
|
||||
}, &i18n.Message{
|
||||
ID: "NoStagedFilesToCommit",
|
||||
Other: "Er zijn geen staged bestanden om te commiten",
|
||||
@@ -760,6 +757,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTracked",
|
||||
Other: "Ignore tracked file",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTrackedPrompt",
|
||||
Other: "Are you sure you want to ignore a tracked file?",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -162,9 +162,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CannotGitAdd",
|
||||
Other: "Cannot git add --patch untracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "CantIgnoreTrackFiles",
|
||||
Other: "Cannot ignore tracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "NoStagedFilesToCommit",
|
||||
Other: "There are no staged files to commit",
|
||||
@@ -504,16 +501,19 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: `select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageSelection",
|
||||
Other: `stage selection`,
|
||||
Other: `toggle line staged / unstaged`,
|
||||
}, &i18n.Message{
|
||||
ID: "ResetSelection",
|
||||
Other: `reset selection`,
|
||||
Other: `delete change (git reset)`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleDragSelect",
|
||||
Other: `toggle drag select`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectHunk",
|
||||
Other: `toggle select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectionForPatch",
|
||||
Other: `add/remove line(s) to patch`,
|
||||
},
|
||||
&i18n.Message{
|
||||
ID: "TogglePanel",
|
||||
@@ -567,6 +567,54 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "RebaseOptionsTitle",
|
||||
Other: "Rebase Options",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitMessageTitle",
|
||||
Other: "Commit Message",
|
||||
}, &i18n.Message{
|
||||
ID: "Local-BranchesTitle",
|
||||
Other: "Branches Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "SearchTitle",
|
||||
Other: "Search",
|
||||
}, &i18n.Message{
|
||||
ID: "TagsTitle",
|
||||
Other: "Tags Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "Branch-CommitsTitle",
|
||||
Other: "Commits Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "MenuTitle",
|
||||
Other: "Menu",
|
||||
}, &i18n.Message{
|
||||
ID: "RemotesTitle",
|
||||
Other: "Remotes Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsTitle",
|
||||
Other: "Credentials",
|
||||
}, &i18n.Message{
|
||||
ID: "Remote-BranchesTitle",
|
||||
Other: "Remote Branches (in Remotes tab)",
|
||||
}, &i18n.Message{
|
||||
ID: "Patch-BuildingTitle",
|
||||
Other: "Patch Building",
|
||||
}, &i18n.Message{
|
||||
ID: "InformationTitle",
|
||||
Other: "Information",
|
||||
}, &i18n.Message{
|
||||
ID: "SecondaryTitle",
|
||||
Other: "Secondary",
|
||||
}, &i18n.Message{
|
||||
ID: "Reflog-CommitsTitle",
|
||||
Other: "Reflog Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "Title",
|
||||
Other: "Title",
|
||||
}, &i18n.Message{
|
||||
ID: "GlobalTitle",
|
||||
Other: "Global Keybindings",
|
||||
}, &i18n.Message{
|
||||
ID: "MerginTitle",
|
||||
Other: "Mergin",
|
||||
}, &i18n.Message{
|
||||
ID: "ConflictsResolved",
|
||||
Other: "all merge conflicts resolved. Continue?",
|
||||
@@ -654,6 +702,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollUp",
|
||||
Other: "scroll up",
|
||||
}, &i18n.Message{
|
||||
ID: "scrollUpMainPanel",
|
||||
Other: "scroll up main panel",
|
||||
}, &i18n.Message{
|
||||
ID: "scrollDownMainPanel",
|
||||
Other: "scroll down main panel",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitTitle",
|
||||
Other: "Amend Commit",
|
||||
@@ -695,7 +749,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: "view commit's files",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFilesTitle",
|
||||
Other: "Commit files",
|
||||
Other: "Commit Files",
|
||||
}, &i18n.Message{
|
||||
ID: "goBack",
|
||||
Other: "go back",
|
||||
@@ -939,6 +993,27 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "NewBranchNamePrompt",
|
||||
Other: "new {{.branchType}} name:",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTracked",
|
||||
Other: "Ignore tracked file",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTrackedPrompt",
|
||||
Other: "Are you sure you want to ignore a tracked file?",
|
||||
}, &i18n.Message{
|
||||
ID: "viewResetToUpstreamOptions",
|
||||
Other: "view upstream reset options",
|
||||
}, &i18n.Message{
|
||||
ID: "nextScreenMode",
|
||||
Other: "next screen mode (normal/half/fullscreen)",
|
||||
}, &i18n.Message{
|
||||
ID: "prevScreenMode",
|
||||
Other: "prev screen mode",
|
||||
}, &i18n.Message{
|
||||
ID: "Panel",
|
||||
Other: "Panel",
|
||||
}, &i18n.Message{
|
||||
ID: "Keybindings",
|
||||
Other: "Keybindings",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,9 +128,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CannotGitAdd",
|
||||
Other: "Nie można git add --patch nieśledzonych plików",
|
||||
}, &i18n.Message{
|
||||
ID: "CantIgnoreTrackFiles",
|
||||
Other: "Nie można zignorować nieśledzonych plików",
|
||||
}, &i18n.Message{
|
||||
ID: "NoStagedFilesToCommit",
|
||||
Other: "Brak zatwierdzonych plików do commita",
|
||||
@@ -743,6 +740,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTracked",
|
||||
Other: "Ignore tracked file",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTrackedPrompt",
|
||||
Other: "Are you sure you want to ignore a tracked file?",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -45,27 +46,20 @@ func (m *ViewBufferManager) ReadLines(n int) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan struct{}) error {
|
||||
func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, linesToRead int, onDone func()) func(chan struct{}) error {
|
||||
return func(stop chan struct{}) error {
|
||||
r, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-stop
|
||||
if cmd.ProcessState.ExitCode() == -1 {
|
||||
if err := kill(cmd); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
if err := commands.Kill(cmd); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
if onDone != nil {
|
||||
onDone()
|
||||
}
|
||||
}()
|
||||
|
||||
loadingMutex := sync.Mutex{}
|
||||
|
||||
// not sure if it's the right move to redefine this or not
|
||||
m.readLines = make(chan int, 1024)
|
||||
|
||||
@@ -82,11 +76,13 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
defer ticker.Stop()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
m.writer.Write([]byte("loading..."))
|
||||
m.refreshView()
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
@@ -98,13 +94,16 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
case linesToRead := <-m.readLines:
|
||||
for i := 0; i < linesToRead; i++ {
|
||||
ok := scanner.Scan()
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
loaded = true
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
m.refreshView()
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
@@ -116,6 +115,7 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
}
|
||||
m.refreshView()
|
||||
case <-stop:
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,12 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
|
||||
m.refreshView()
|
||||
|
||||
if onDone != nil {
|
||||
onDone()
|
||||
}
|
||||
|
||||
close(done)
|
||||
}()
|
||||
|
||||
@@ -141,7 +147,7 @@ func (t *ViewBufferManager) Close() {
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan struct{}, 1)
|
||||
c := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
t.currentTask.Stop()
|
||||
@@ -170,7 +176,10 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error {
|
||||
|
||||
m.waitingMutex.Lock()
|
||||
defer m.waitingMutex.Unlock()
|
||||
|
||||
m.Log.Infof("done waiting")
|
||||
if taskID < m.newTaskId {
|
||||
m.Log.Infof("returning cos the task is obsolete")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -216,13 +225,3 @@ func (t *Task) Stop() {
|
||||
t.stopped = true
|
||||
return
|
||||
}
|
||||
|
||||
// kill kills a process
|
||||
func kill(cmd *exec.Cmd) error {
|
||||
if cmd.Process == nil {
|
||||
// somebody got to it before we were able to, poor bastard
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
|
||||
@@ -20,12 +20,22 @@ var (
|
||||
|
||||
// InactiveBorderColor is the border color of the inactive active frames
|
||||
InactiveBorderColor gocui.Attribute
|
||||
|
||||
// SelectedLineBgColor is the background color for the selected line
|
||||
SelectedLineBgColor color.Attribute
|
||||
|
||||
OptionsFgColor color.Attribute
|
||||
|
||||
OptionsColor gocui.Attribute
|
||||
)
|
||||
|
||||
// UpdateTheme updates all theme variables
|
||||
func UpdateTheme(userConfig *viper.Viper) {
|
||||
ActiveBorderColor = getColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
||||
InactiveBorderColor = getColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
||||
ActiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
||||
InactiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
||||
SelectedLineBgColor = GetBgColor(userConfig.GetStringSlice("gui.theme.selectedLineBgColor"))
|
||||
OptionsColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
OptionsFgColor = GetFgColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
|
||||
isLightTheme := userConfig.GetBool("gui.theme.lightTheme")
|
||||
if isLightTheme {
|
||||
@@ -39,8 +49,8 @@ func UpdateTheme(userConfig *viper.Viper) {
|
||||
}
|
||||
}
|
||||
|
||||
// getAttribute gets the gocui color attribute from the string
|
||||
func getAttribute(key string) gocui.Attribute {
|
||||
// GetAttribute gets the gocui color attribute from the string
|
||||
func GetGocuiAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
@@ -62,43 +72,75 @@ func getAttribute(key string) gocui.Attribute {
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// getColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func getColor(keys []string) gocui.Attribute {
|
||||
// GetFgAttribute gets the color foreground attribute from the string
|
||||
func GetFgAttribute(key string) color.Attribute {
|
||||
colorMap := map[string]color.Attribute{
|
||||
"default": color.FgWhite,
|
||||
"black": color.FgBlack,
|
||||
"red": color.FgRed,
|
||||
"green": color.FgGreen,
|
||||
"yellow": color.FgYellow,
|
||||
"blue": color.FgBlue,
|
||||
"magenta": color.FgMagenta,
|
||||
"cyan": color.FgCyan,
|
||||
"white": color.FgWhite,
|
||||
"bold": color.Bold,
|
||||
"reverse": color.ReverseVideo,
|
||||
"underline": color.Underline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return color.FgWhite
|
||||
}
|
||||
|
||||
// GetBgAttribute gets the color background attribute from the string
|
||||
func GetBgAttribute(key string) color.Attribute {
|
||||
colorMap := map[string]color.Attribute{
|
||||
"default": color.BgWhite,
|
||||
"black": color.BgBlack,
|
||||
"red": color.BgRed,
|
||||
"green": color.BgGreen,
|
||||
"yellow": color.BgYellow,
|
||||
"blue": color.BgBlue,
|
||||
"magenta": color.BgMagenta,
|
||||
"cyan": color.BgCyan,
|
||||
"white": color.BgWhite,
|
||||
"bold": color.Bold,
|
||||
"reverse": color.ReverseVideo,
|
||||
"underline": color.Underline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return color.FgWhite
|
||||
}
|
||||
|
||||
// GetGocuiColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetGocuiColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= getAttribute(key)
|
||||
attribute |= GetGocuiAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
// GetAttribute gets the gocui color attribute from the string
|
||||
func GetAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
"red": gocui.ColorRed,
|
||||
"green": gocui.ColorGreen,
|
||||
"yellow": gocui.ColorYellow,
|
||||
"blue": gocui.ColorBlue,
|
||||
"magenta": gocui.ColorMagenta,
|
||||
"cyan": gocui.ColorCyan,
|
||||
"white": gocui.ColorWhite,
|
||||
"bold": gocui.AttrBold,
|
||||
"reverse": gocui.AttrReverse,
|
||||
"underline": gocui.AttrUnderline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// GetColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
func GetBgColor(keys []string) color.Attribute {
|
||||
var attribute color.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= GetAttribute(key)
|
||||
attribute |= GetBgAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
// GetColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetFgColor(keys []string) color.Attribute {
|
||||
var attribute color.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= GetFgAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
@@ -6,13 +6,10 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@@ -114,51 +111,11 @@ func Min(x, y int) int {
|
||||
return y
|
||||
}
|
||||
|
||||
type Displayable interface {
|
||||
GetDisplayStrings(bool) []string
|
||||
}
|
||||
func RenderDisplayStrings(displayStringsArr [][]string) string {
|
||||
padWidths := getPadWidths(displayStringsArr)
|
||||
paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths)
|
||||
|
||||
// RenderList takes a slice of items, confirms they implement the Displayable
|
||||
// interface, then generates a list of their displaystrings to write to a panel's
|
||||
// buffer
|
||||
func RenderList(slice interface{}, isFocused bool) (string, error) {
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
return "", errors.New("RenderList given a non-slice type")
|
||||
}
|
||||
|
||||
displayables := make([]Displayable, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
value, ok := s.Index(i).Interface().(Displayable)
|
||||
if !ok {
|
||||
return "", errors.New("item does not implement the Displayable interface")
|
||||
}
|
||||
displayables[i] = value
|
||||
}
|
||||
|
||||
return renderDisplayableList(displayables, isFocused)
|
||||
}
|
||||
|
||||
// renderDisplayableList takes a list of displayable items, obtains their display
|
||||
// strings via GetDisplayStrings() and then returns a single string containing
|
||||
// each item's string representation on its own line, with appropriate horizontal
|
||||
// padding between the item's own strings
|
||||
func renderDisplayableList(items []Displayable, isFocused bool) (string, error) {
|
||||
if len(items) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
stringArrays := getDisplayStringArrays(items, isFocused)
|
||||
|
||||
if !displayArraysAligned(stringArrays) {
|
||||
return "", errors.New("Each item must return the same number of strings to display")
|
||||
}
|
||||
|
||||
padWidths := getPadWidths(stringArrays)
|
||||
paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths)
|
||||
|
||||
return strings.Join(paddedDisplayStrings, "\n"), nil
|
||||
return strings.Join(paddedDisplayStrings, "\n")
|
||||
}
|
||||
|
||||
// Decolorise strips a string of color
|
||||
@@ -168,10 +125,16 @@ func Decolorise(str string) string {
|
||||
}
|
||||
|
||||
func getPadWidths(stringArrays [][]string) []int {
|
||||
if len(stringArrays[0]) <= 1 {
|
||||
maxWidth := 0
|
||||
for _, stringArray := range stringArrays {
|
||||
if len(stringArray) > maxWidth {
|
||||
maxWidth = len(stringArray)
|
||||
}
|
||||
}
|
||||
if maxWidth-1 < 0 {
|
||||
return []int{}
|
||||
}
|
||||
padWidths := make([]int, len(stringArrays[0])-1)
|
||||
padWidths := make([]int, maxWidth-1)
|
||||
for i := range padWidths {
|
||||
for _, strings := range stringArrays {
|
||||
uncoloredString := Decolorise(strings[i])
|
||||
@@ -190,8 +153,14 @@ func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string
|
||||
continue
|
||||
}
|
||||
for j, padWidth := range padWidths {
|
||||
if len(stringArray)-1 < j {
|
||||
continue
|
||||
}
|
||||
paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " "
|
||||
}
|
||||
if len(stringArray)-1 < len(padWidths) {
|
||||
continue
|
||||
}
|
||||
paddedDisplayStrings[i] += stringArray[len(padWidths)]
|
||||
}
|
||||
return paddedDisplayStrings
|
||||
@@ -208,14 +177,6 @@ func displayArraysAligned(stringArrays [][]string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func getDisplayStringArrays(displayables []Displayable, isFocused bool) [][]string {
|
||||
stringArrays := make([][]string, len(displayables))
|
||||
for i, item := range displayables {
|
||||
stringArrays[i] = item.GetDisplayStrings(isFocused)
|
||||
}
|
||||
return stringArrays
|
||||
}
|
||||
|
||||
// IncludesString if the list contains the string
|
||||
func IncludesString(list []string, a string) bool {
|
||||
for _, b := range list {
|
||||
@@ -310,3 +271,48 @@ func ModuloWithWrap(n, max int) int {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
// NextIntInCycle returns the next int in a slice, returning to the first index if we've reached the end
|
||||
func NextIntInCycle(sl []int, current int) int {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i == len(sl)-1 {
|
||||
return sl[0]
|
||||
}
|
||||
return sl[i+1]
|
||||
}
|
||||
}
|
||||
return sl[0]
|
||||
}
|
||||
|
||||
// PrevIntInCycle returns the prev int in a slice, returning to the first index if we've reached the end
|
||||
func PrevIntInCycle(sl []int, current int) int {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i > 0 {
|
||||
return sl[i-1]
|
||||
}
|
||||
return sl[len(sl)-1]
|
||||
}
|
||||
}
|
||||
return sl[len(sl)-1]
|
||||
}
|
||||
|
||||
// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
|
||||
func TruncateWithEllipsis(str string, limit int) string {
|
||||
if limit == 1 && len(str) > 1 {
|
||||
return "."
|
||||
}
|
||||
|
||||
if limit == 2 && len(str) > 2 {
|
||||
return ".."
|
||||
}
|
||||
|
||||
ellipsis := "..."
|
||||
if len(str) <= limit {
|
||||
return str
|
||||
}
|
||||
|
||||
remainingLength := limit - len(ellipsis)
|
||||
return str[0:remainingLength] + "..."
|
||||
}
|
||||
|
||||
@@ -196,166 +196,8 @@ func TestDisplayArraysAligned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type myDisplayable struct {
|
||||
strings []string
|
||||
}
|
||||
|
||||
type myStruct struct{}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (d *myDisplayable) GetDisplayStrings(isFocused bool) []string {
|
||||
if isFocused {
|
||||
return append(d.strings, "blah")
|
||||
}
|
||||
return d.strings
|
||||
}
|
||||
|
||||
// TestGetDisplayStringArrays is a function.
|
||||
func TestGetDisplayStringArrays(t *testing.T) {
|
||||
type scenario struct {
|
||||
input []Displayable
|
||||
isFocused bool
|
||||
expected [][]string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
false,
|
||||
[][]string{{"a", "b"}, {"c", "d"}},
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
true,
|
||||
[][]string{{"a", "b", "blah"}, {"c", "d", "blah"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, getDisplayStringArrays(s.input, s.isFocused))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderDisplayableList is a function.
|
||||
func TestRenderDisplayableList(t *testing.T) {
|
||||
type scenario struct {
|
||||
input []Displayable
|
||||
isFocused bool
|
||||
expectedString string
|
||||
expectedErrorMessage string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{}}),
|
||||
Displayable(&myDisplayable{[]string{}}),
|
||||
},
|
||||
false,
|
||||
"\n",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"aa", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
false,
|
||||
"aa b\nc d",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a"}}),
|
||||
Displayable(&myDisplayable{[]string{"b", "c"}}),
|
||||
},
|
||||
false,
|
||||
"",
|
||||
"Each item must return the same number of strings to display",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a"}}),
|
||||
Displayable(&myDisplayable{[]string{"b"}}),
|
||||
},
|
||||
true,
|
||||
"a blah\nb blah",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
str, err := renderDisplayableList(s.input, s.isFocused)
|
||||
assert.EqualValues(t, s.expectedString, str)
|
||||
if s.expectedErrorMessage != "" {
|
||||
assert.EqualError(t, err, s.expectedErrorMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderList is a function.
|
||||
func TestRenderList(t *testing.T) {
|
||||
type scenario struct {
|
||||
input interface{}
|
||||
isFocused bool
|
||||
expectedString string
|
||||
expectedErrorMessage string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]*myDisplayable{
|
||||
{[]string{"aa", "b"}},
|
||||
{[]string{"c", "d"}},
|
||||
},
|
||||
false,
|
||||
"aa b\nc d",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]*myStruct{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
false,
|
||||
"",
|
||||
"item does not implement the Displayable interface",
|
||||
},
|
||||
{
|
||||
&myStruct{},
|
||||
false,
|
||||
"",
|
||||
"RenderList given a non-slice type",
|
||||
},
|
||||
{
|
||||
[]*myDisplayable{
|
||||
{[]string{"a"}},
|
||||
},
|
||||
true,
|
||||
"a blah",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
str, err := RenderList(s.input, s.isFocused)
|
||||
assert.EqualValues(t, s.expectedString, str)
|
||||
if s.expectedErrorMessage != "" {
|
||||
assert.EqualError(t, err, s.expectedErrorMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPaddedDisplayStrings is a function.
|
||||
func TestGetPaddedDisplayStrings(t *testing.T) {
|
||||
type scenario struct {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
@@ -60,33 +61,87 @@ func formatTitle(title string) string {
|
||||
|
||||
func formatBinding(binding *gui.Binding) string {
|
||||
if binding.Alternative != "" {
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", binding.GetKey(), binding.Description, binding.Alternative)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative)
|
||||
}
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", binding.GetKey(), binding.Description)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", gui.GetKeyDisplay(binding.Key), binding.Description)
|
||||
}
|
||||
|
||||
func getBindingSections(mApp *app.App) []*bindingSection {
|
||||
bindingSections := []*bindingSection{}
|
||||
|
||||
// TODO: add context-based keybindings
|
||||
for _, binding := range mApp.Gui.GetInitialKeybindings() {
|
||||
if binding.Description == "" {
|
||||
continue
|
||||
bindings := mApp.Gui.GetInitialKeybindings()
|
||||
|
||||
type contextAndViewType struct {
|
||||
context string
|
||||
viewName string
|
||||
}
|
||||
|
||||
contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{}
|
||||
|
||||
for _, binding := range bindings {
|
||||
contexts := []string{}
|
||||
if len(binding.Contexts) == 0 {
|
||||
contexts = append(contexts, "")
|
||||
} else {
|
||||
for _, context := range binding.Contexts {
|
||||
contexts = append(contexts, context)
|
||||
}
|
||||
}
|
||||
|
||||
viewName := binding.ViewName
|
||||
for _, context := range contexts {
|
||||
key := contextAndViewType{context: context, viewName: binding.ViewName}
|
||||
existing := contextAndViewBindingMap[key]
|
||||
if existing == nil {
|
||||
contextAndViewBindingMap[key] = []*gui.Binding{binding}
|
||||
} else {
|
||||
contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type groupedBindingsType struct {
|
||||
contextAndView contextAndViewType
|
||||
bindings []*gui.Binding
|
||||
}
|
||||
|
||||
groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap))
|
||||
|
||||
for contextAndView, contextBindings := range contextAndViewBindingMap {
|
||||
groupedBindings = append(groupedBindings, groupedBindingsType{contextAndView: contextAndView, bindings: contextBindings})
|
||||
}
|
||||
|
||||
sort.Slice(groupedBindings, func(i, j int) bool {
|
||||
first := groupedBindings[i].contextAndView
|
||||
second := groupedBindings[j].contextAndView
|
||||
if first.viewName == "" {
|
||||
return true
|
||||
}
|
||||
if second.viewName == "" {
|
||||
return false
|
||||
}
|
||||
return first.viewName < second.viewName || (first.viewName == second.viewName && first.context < second.context)
|
||||
})
|
||||
|
||||
for _, group := range groupedBindings {
|
||||
contextAndView := group.contextAndView
|
||||
contextBindings := group.bindings
|
||||
mApp.Log.Warn("viewname: " + contextAndView.viewName + ", context: " + contextAndView.context)
|
||||
viewName := contextAndView.viewName
|
||||
if viewName == "" {
|
||||
viewName = "global"
|
||||
}
|
||||
title := localisedTitle(mApp, viewName)
|
||||
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
}
|
||||
|
||||
for contextName, contextBindings := range mApp.Gui.GetContextMap() {
|
||||
translatedView := localisedTitle(mApp, contextBindings[0].ViewName)
|
||||
translatedContextName := localisedTitle(mApp, contextName)
|
||||
title := fmt.Sprintf("%s (%s)", translatedView, translatedContextName)
|
||||
translatedView := localisedTitle(mApp, viewName)
|
||||
var title string
|
||||
if contextAndView.context == "" {
|
||||
addendum := " " + mApp.Tr.SLocalize("Panel")
|
||||
if viewName == "global" {
|
||||
addendum = ""
|
||||
}
|
||||
title = fmt.Sprintf("%s%s", translatedView, addendum)
|
||||
} else {
|
||||
translatedContextName := localisedTitle(mApp, contextAndView.context)
|
||||
title = fmt.Sprintf("%s %s (%s)", translatedView, mApp.Tr.SLocalize("Panel"), translatedContextName)
|
||||
}
|
||||
|
||||
for _, binding := range contextBindings {
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
@@ -117,7 +172,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
|
||||
}
|
||||
|
||||
func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("menu"))
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("Keybindings"))
|
||||
|
||||
for _, section := range bindingSections {
|
||||
content += formatTitle(section.title)
|
||||
|
||||
22
vendor/github.com/jesseduffield/gocui/escape.go
generated
vendored
22
vendor/github.com/jesseduffield/gocui/escape.go
generated
vendored
@@ -6,7 +6,6 @@ package gocui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
)
|
||||
@@ -17,7 +16,6 @@ type escapeInterpreter struct {
|
||||
csiParam []string
|
||||
curFgColor, curBgColor Attribute
|
||||
mode OutputMode
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type escapeState int
|
||||
@@ -37,9 +35,6 @@ var (
|
||||
|
||||
// runes in case of error will output the non-parsed runes as a string.
|
||||
func (ei *escapeInterpreter) runes() []rune {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
switch ei.state {
|
||||
case stateNone:
|
||||
return []rune{0x1b}
|
||||
@@ -72,9 +67,6 @@ func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
|
||||
|
||||
// reset sets the escapeInterpreter in initial state.
|
||||
func (ei *escapeInterpreter) reset() {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
ei.state = stateNone
|
||||
ei.curFgColor = ColorDefault
|
||||
ei.curBgColor = ColorDefault
|
||||
@@ -85,9 +77,6 @@ func (ei *escapeInterpreter) reset() {
|
||||
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
||||
// it's not an escape sequence.
|
||||
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
// Sanity checks
|
||||
if len(ei.csiParam) > 20 {
|
||||
return false, errCSITooLong
|
||||
@@ -163,13 +152,13 @@ func (ei *escapeInterpreter) outputNormal() error {
|
||||
|
||||
switch {
|
||||
case p >= 30 && p <= 37:
|
||||
ei.curFgColor = Attribute(p - 30 + 1)
|
||||
ei.curFgColor |= Attribute(p - 30 + 1)
|
||||
case p == 39:
|
||||
ei.curFgColor = ColorDefault
|
||||
ei.curFgColor |= ColorDefault
|
||||
case p >= 40 && p <= 47:
|
||||
ei.curBgColor = Attribute(p - 40 + 1)
|
||||
ei.curBgColor |= Attribute(p - 40 + 1)
|
||||
case p == 49:
|
||||
ei.curBgColor = ColorDefault
|
||||
ei.curBgColor |= ColorDefault
|
||||
case p == 1:
|
||||
ei.curFgColor |= AttrBold
|
||||
case p == 4:
|
||||
@@ -191,9 +180,6 @@ func (ei *escapeInterpreter) outputNormal() error {
|
||||
// 0x11 - 0xe8: 216 different colors
|
||||
// 0xe9 - 0x1ff: 24 different shades of grey
|
||||
func (ei *escapeInterpreter) output256() error {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
if len(ei.csiParam) < 3 {
|
||||
return ei.outputNormal()
|
||||
}
|
||||
|
||||
62
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
62
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@@ -6,6 +6,7 @@ package gocui
|
||||
|
||||
import (
|
||||
standardErrors "errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -94,6 +95,12 @@ type Gui struct {
|
||||
// tickingMutex ensures we don't have two loops ticking. The point of 'ticking'
|
||||
// is to refresh the gui rapidly so that loader characters can be animated.
|
||||
tickingMutex sync.Mutex
|
||||
|
||||
OnSearchEscape func() error
|
||||
// these keys must either be of type Key of rune
|
||||
SearchEscapeKey interface{}
|
||||
NextSearchMatchKey interface{}
|
||||
PrevSearchMatchKey interface{}
|
||||
}
|
||||
|
||||
// NewGui returns a new Gui object with a given output mode.
|
||||
@@ -124,6 +131,11 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
|
||||
// view edges
|
||||
g.SupportOverlaps = supportOverlaps
|
||||
|
||||
// default keys for when searching strings in a view
|
||||
g.SearchEscapeKey = KeyEsc
|
||||
g.NextSearchMatchKey = 'n'
|
||||
g.PrevSearchMatchKey = 'N'
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
@@ -531,6 +543,11 @@ func (g *Gui) flush() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v.ContainsList {
|
||||
if err := g.drawListFooter(v, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := g.draw(v); err != nil {
|
||||
return err
|
||||
@@ -718,6 +735,34 @@ func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawListFooter draws the footer of a list view, showing something like '1 of 10'
|
||||
func (g *Gui) drawListFooter(v *View, fgColor, bgColor Attribute) error {
|
||||
if len(v.lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("%d of %d", v.cy+v.oy+1, len(v.lines))
|
||||
|
||||
if v.y1 < 0 || v.y1 >= g.maxY {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := v.x1 - 1 - len(message)
|
||||
if start < v.x0 {
|
||||
return nil
|
||||
}
|
||||
for i, ch := range message {
|
||||
x := start + i
|
||||
if x >= v.x1 {
|
||||
break
|
||||
}
|
||||
if err := g.SetRune(x, v.y1, ch, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// draw manages the cursor and calls the draw function of a view.
|
||||
func (g *Gui) draw(v *View) error {
|
||||
if g.Cursor {
|
||||
@@ -803,6 +848,23 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err
|
||||
var globalKb *keybinding
|
||||
var matchingParentViewKb *keybinding
|
||||
|
||||
// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
|
||||
if v.IsSearching() && Modifier(ev.Mod) == ModNone {
|
||||
if eventMatchesKey(ev, g.NextSearchMatchKey) {
|
||||
return true, v.gotoNextMatch()
|
||||
} else if eventMatchesKey(ev, g.PrevSearchMatchKey) {
|
||||
return true, v.gotoPreviousMatch()
|
||||
} else if eventMatchesKey(ev, g.SearchEscapeKey) {
|
||||
v.searcher.clearSearch()
|
||||
if g.OnSearchEscape != nil {
|
||||
if err := g.OnSearchEscape(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, kb := range g.keybindings {
|
||||
if kb.handler == nil {
|
||||
continue
|
||||
|
||||
14
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
14
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
@@ -29,6 +29,20 @@ func newKeybinding(viewname string, contexts []string, key Key, ch rune, mod Mod
|
||||
return kb
|
||||
}
|
||||
|
||||
func eventMatchesKey(ev *termbox.Event, key interface{}) bool {
|
||||
// assuming ModNone for now
|
||||
if Modifier(ev.Mod) != ModNone {
|
||||
return false
|
||||
}
|
||||
|
||||
k, ch, err := getKey(key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return k == Key(ev.Key) && ch == ev.Ch
|
||||
}
|
||||
|
||||
// matchKeypress returns if the keybinding matches the keypress.
|
||||
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||
|
||||
199
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
199
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@@ -105,6 +105,140 @@ type View struct {
|
||||
ParentView *View
|
||||
|
||||
Context string // this is for assigning keybindings to a view only in certain contexts
|
||||
|
||||
searcher *searcher
|
||||
|
||||
// when ContainsList is true, we show the current index and total count in the view
|
||||
ContainsList bool
|
||||
}
|
||||
|
||||
type searcher struct {
|
||||
searchString string
|
||||
searchPositions []cellPos
|
||||
currentSearchIndex int
|
||||
onSelectItem func(int, int, int) error
|
||||
}
|
||||
|
||||
func (v *View) SetOnSelectItem(onSelectItem func(int, int, int) error) {
|
||||
v.searcher.onSelectItem = onSelectItem
|
||||
}
|
||||
|
||||
func (v *View) gotoNextMatch() error {
|
||||
if len(v.searcher.searchPositions) == 0 {
|
||||
return nil
|
||||
}
|
||||
if v.searcher.currentSearchIndex == len(v.searcher.searchPositions)-1 {
|
||||
v.searcher.currentSearchIndex = 0
|
||||
} else {
|
||||
v.searcher.currentSearchIndex++
|
||||
}
|
||||
return v.SelectSearchResult(v.searcher.currentSearchIndex)
|
||||
}
|
||||
|
||||
func (v *View) gotoPreviousMatch() error {
|
||||
if len(v.searcher.searchPositions) == 0 {
|
||||
return nil
|
||||
}
|
||||
if v.searcher.currentSearchIndex == 0 {
|
||||
if len(v.searcher.searchPositions) > 0 {
|
||||
v.searcher.currentSearchIndex = len(v.searcher.searchPositions) - 1
|
||||
}
|
||||
} else {
|
||||
v.searcher.currentSearchIndex--
|
||||
}
|
||||
return v.SelectSearchResult(v.searcher.currentSearchIndex)
|
||||
}
|
||||
|
||||
func (v *View) SelectSearchResult(index int) error {
|
||||
y := v.searcher.searchPositions[index].y
|
||||
v.FocusPoint(0, y)
|
||||
if v.searcher.onSelectItem != nil {
|
||||
return v.searcher.onSelectItem(y, index, len(v.searcher.searchPositions))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *View) Search(str string) error {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.searcher.search(str)
|
||||
v.updateSearchPositions()
|
||||
if len(v.searcher.searchPositions) > 0 {
|
||||
// get the first result past the current cursor
|
||||
currentIndex := 0
|
||||
adjustedY := v.oy + v.cy
|
||||
adjustedX := v.ox + v.cx
|
||||
for i, pos := range v.searcher.searchPositions {
|
||||
if pos.y > adjustedY || (pos.y == adjustedY && pos.x > adjustedX) {
|
||||
currentIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
v.searcher.currentSearchIndex = currentIndex
|
||||
return v.SelectSearchResult(currentIndex)
|
||||
} else {
|
||||
return v.searcher.onSelectItem(-1, -1, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *View) ClearSearch() {
|
||||
v.searcher.clearSearch()
|
||||
}
|
||||
|
||||
func (v *View) IsSearching() bool {
|
||||
return v.searcher.searchString != ""
|
||||
}
|
||||
|
||||
func (v *View) FocusPoint(cx int, cy int) {
|
||||
lineCount := len(v.lines)
|
||||
if cy < 0 || cy > lineCount {
|
||||
return
|
||||
}
|
||||
_, height := v.Size()
|
||||
|
||||
ly := height - 1
|
||||
if ly == -1 {
|
||||
ly = 0
|
||||
}
|
||||
|
||||
// if line is above origin, move origin and set cursor to zero
|
||||
// if line is below origin + height, move origin and set cursor to max
|
||||
// otherwise set cursor to value - origin
|
||||
if ly > lineCount {
|
||||
v.cx = cx
|
||||
v.cy = cy
|
||||
v.oy = 0
|
||||
} else if cy < v.oy {
|
||||
v.cx = cx
|
||||
v.cy = 0
|
||||
v.oy = cy
|
||||
} else if cy > v.oy+ly {
|
||||
v.cx = cx
|
||||
v.cy = ly
|
||||
v.oy = cy - ly
|
||||
} else {
|
||||
v.cx = cx
|
||||
v.cy = cy - v.oy
|
||||
}
|
||||
}
|
||||
|
||||
func (s *searcher) search(str string) {
|
||||
s.searchString = str
|
||||
s.searchPositions = []cellPos{}
|
||||
s.currentSearchIndex = 0
|
||||
}
|
||||
|
||||
func (s *searcher) clearSearch() {
|
||||
s.searchString = ""
|
||||
s.searchPositions = []cellPos{}
|
||||
s.currentSearchIndex = 0
|
||||
}
|
||||
|
||||
type cellPos struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
type viewLine struct {
|
||||
@@ -131,15 +265,16 @@ func (l lineType) String() string {
|
||||
// newView returns a new View object.
|
||||
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
v := &View{
|
||||
name: name,
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
Frame: true,
|
||||
Editor: DefaultEditor,
|
||||
tainted: true,
|
||||
ei: newEscapeInterpreter(mode),
|
||||
name: name,
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
Frame: true,
|
||||
Editor: DefaultEditor,
|
||||
tainted: true,
|
||||
ei: newEscapeInterpreter(mode),
|
||||
searcher: &searcher{},
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -331,8 +466,35 @@ func (v *View) Rewind() {
|
||||
v.readOffset = 0
|
||||
}
|
||||
|
||||
func (v *View) updateSearchPositions() {
|
||||
if v.searcher.searchString != "" {
|
||||
v.searcher.searchPositions = []cellPos{}
|
||||
for y, line := range v.lines {
|
||||
lineLoop:
|
||||
for x, _ := range line {
|
||||
if line[x].chr == rune(v.searcher.searchString[0]) {
|
||||
for offset := 1; offset < len(v.searcher.searchString); offset++ {
|
||||
if len(line)-1 < x+offset {
|
||||
continue lineLoop
|
||||
}
|
||||
if line[x+offset].chr != rune(v.searcher.searchString[offset]) {
|
||||
continue lineLoop
|
||||
}
|
||||
}
|
||||
v.searcher.searchPositions = append(v.searcher.searchPositions, cellPos{x: x, y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// draw re-draws the view's contents.
|
||||
func (v *View) draw() error {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.updateSearchPositions()
|
||||
maxX, maxY := v.Size()
|
||||
|
||||
if v.Wrap {
|
||||
@@ -392,6 +554,13 @@ func (v *View) draw() error {
|
||||
if bgColor == ColorDefault {
|
||||
bgColor = v.BgColor
|
||||
}
|
||||
if matched, selected := v.isPatternMatchedRune(x, y); matched {
|
||||
if selected {
|
||||
bgColor = ColorCyan
|
||||
} else {
|
||||
bgColor = ColorYellow
|
||||
}
|
||||
}
|
||||
|
||||
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
@@ -403,6 +572,18 @@ func (v *View) draw() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *View) isPatternMatchedRune(x, y int) (bool, bool) {
|
||||
searchStringLength := len(v.searcher.searchString)
|
||||
for i, pos := range v.searcher.searchPositions {
|
||||
adjustedY := y + v.oy
|
||||
adjustedX := x + v.ox
|
||||
if adjustedY == pos.y && adjustedX >= pos.x && adjustedX < pos.x+searchStringLength {
|
||||
return true, i == v.searcher.currentSearchIndex
|
||||
}
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// realPosition returns the position in the internal buffer corresponding to the
|
||||
// point (x, y) of the view.
|
||||
func (v *View) realPosition(vx, vy int) (x, y int, err error) {
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -32,7 +32,7 @@ github.com/hashicorp/hcl/json/token
|
||||
github.com/integrii/flaggy
|
||||
# github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||
github.com/jbenet/go-context/io
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113
|
||||
github.com/jesseduffield/gocui
|
||||
# github.com/jesseduffield/pty v1.2.1
|
||||
github.com/jesseduffield/pty
|
||||
|
||||
Reference in New Issue
Block a user