mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-01 19:46:44 -04:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
759ddd3e80 | ||
|
|
b431a354aa | ||
|
|
24a3b815c5 | ||
|
|
7efb900336 | ||
|
|
ab8e1f0265 | ||
|
|
8cf3d8b269 | ||
|
|
e6b5fb93eb | ||
|
|
649a8b7276 | ||
|
|
345c533d64 | ||
|
|
5e542c3992 | ||
|
|
0b33634c73 | ||
|
|
8d6da58bfa | ||
|
|
cd6d92da1d | ||
|
|
46a333407d |
@@ -10,10 +10,6 @@ before making a change.
|
||||
|
||||
[This video](https://www.youtube.com/watch?v=kNavnhzZHtk) walks through the process of adding a small feature to lazygit. If you have no idea where to start, watching that video is a good first step.
|
||||
|
||||
## Design principles
|
||||
|
||||
See [here](./VISION.md) for a set of design principles that we want to consider when building a feature or making a change.
|
||||
|
||||
## Codebase guide
|
||||
|
||||
[This doc](./docs/dev/Codebase_Guide.md) explains:
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# Vision and Design Principles
|
||||
|
||||
## Vision
|
||||
|
||||
Lazygit's vision is to be the most enjoyable UI for git.
|
||||
|
||||
## Design Principles
|
||||
|
||||
There are seven (sometimes contradictory) design principles we follow:
|
||||
|
||||
- Dicoverability
|
||||
- Simplicity
|
||||
- Safety
|
||||
- Power
|
||||
- Speed
|
||||
- Conformity with git
|
||||
- Think of the codebase
|
||||
|
||||
### Discoverability
|
||||
|
||||
TUI's are notoriously hard to learn, thanks to limited screen real-estate to provide contextual help and a general lack of effort on the part of developers to make things obvious. We want Lazygit to buck the trend and be easy for a new user to grok.
|
||||
|
||||
Examples:
|
||||
|
||||
- Clearly document all the features/configuration options
|
||||
- e.g. gifs in the README
|
||||
- Document how to solve various git problems with Lazygit
|
||||
- This is something we don't have yet but should: a section in the docs explaining how Lazygit can help you in various scenarios
|
||||
- Use tooltips to explain what actions will do
|
||||
- Make it easy for users to ask questions and get answers from the community
|
||||
- Make it easy to find entities and actions from within Lazygit
|
||||
- Use visual elements to make things obvious
|
||||
- e.g. '<-- YOU ARE HERE' label when rebasing
|
||||
- Don't require the user to memorise keybindings
|
||||
- e.g. when the user is mid-rebase, we prominently show that the keybinding for viewing rebase options is 'm'
|
||||
- When the user performs an action in Lazygit, make the impact obvious
|
||||
- If the affected entity isn't visible, show a toast notification
|
||||
- If a keybinding is disabled, give a reason why
|
||||
|
||||
### Simplicity
|
||||
|
||||
The git CLI is very complex but most git use cases are simple. Lazygit needs to ensure that simple use cases are easy to satisfy.
|
||||
|
||||
- Make the most common use cases dead-simple (staging files, committing, pulling/pushing)
|
||||
- Don't overwhelm the user with options
|
||||
- Use sensible defaults
|
||||
- We already have too many configuration options: think hard before adding any new ones
|
||||
|
||||
### Safety
|
||||
|
||||
It's easy to screw things up in git so Lazygit should try to protect the user from screwing things up.
|
||||
|
||||
- Prompt for a confirmation before doing anything that's hard to reverse
|
||||
- Make it easy to correct mistakes
|
||||
- e.g. undo action
|
||||
- the escape key should get you out of most transient situations (rebasing, diffing, etc)
|
||||
|
||||
## Power
|
||||
|
||||
Users shouldn't have to drop down the CLI _too_ often. Lazygit should be able to handle some complex use cases.
|
||||
|
||||
- Make complex (but common) CLI flows simple
|
||||
- e.g. interactive rebasing
|
||||
- Use the custom commands system to handle the really rare complex edge-cases
|
||||
|
||||
### Speed
|
||||
|
||||
Pro users should be able to move at lightning speed with Lazygit.
|
||||
|
||||
- Always think about the number of keypresses involved in a given UX flow
|
||||
- Make lazygit performant and responsive
|
||||
- Think about the individual commands being run and how fast they are
|
||||
- Startup should be FAST. If you want to run something at startup that is slow, make it non-blocking.
|
||||
- Support muscle-memory
|
||||
- Prefer disabling menu items instead of hiding them so that muscle memory can be used to select the desired menu item
|
||||
- Try to make keybinding intuitions to transfer across contexts (e.g. 'd' for destroy)
|
||||
- When changing keybindings in a new release, always consider what will happen if a user does not read the release notes and relies on muscle memory.
|
||||
|
||||
### Conformity with git
|
||||
|
||||
Satisfying the use-cases of git users is more important than perfectly conforming to git's API, but even obscure parts of git's API were motivated by real use-cases.
|
||||
|
||||
- Users should only have to drop down to the git CLI in rare circumstances
|
||||
- Honour the git config
|
||||
- Don't override anything set in the git config without the user's permission
|
||||
- Work with git, not against it.
|
||||
- Too much magic will get us into trouble
|
||||
- Avoid storing Lazygit-specific session state that could instead be stored in git
|
||||
- Ensure that Lazygit can represent the state of any repo
|
||||
- Sometimes git's default behaviour is just silly and we'll make the call to override but it should be a well-considered decision.
|
||||
|
||||
### Think of the codebase
|
||||
|
||||
Will somebody PLEASE think of the codebase!
|
||||
|
||||
Some features are not worth the added complexity in the codebase. The more this codebase grows, the harder it will be to make the changes that everybody wants.
|
||||
|
||||
## Resolving conflicts
|
||||
|
||||
Many of the above objectives are directly antithetical to one another. If you add an extra confirmation prompt for the sake of _safety_, you're sacrificing _speed_. If you support toggling various git flags in the name of _power_, you're sacrificing _simplicity_. There are a few things to say here.
|
||||
|
||||
When there are conflicts, we need to make a judgement call. In general we should err on the side of safety and simplicity as the default, with the ability for users to make things faster / more powerful either through configuration or separate keybindings.
|
||||
|
||||
This does not mean for example that force pushes should be impossible without being manually enabled: force pushes are table stakes for anybody who rebases. But it does mean that a confirmation popup should appear when force pushing.
|
||||
+4
-44
@@ -87,11 +87,6 @@ gui:
|
||||
# - 'top': split the window vertically (side panel on top, main view below)
|
||||
enlargedSideViewLocation: left
|
||||
|
||||
# If true, wrap lines in the staging view to the width of the view. This
|
||||
# makes it much easier to work with diffs that have long lines, e.g.
|
||||
# paragraphs of markdown text.
|
||||
wrapLinesInStagingView: true
|
||||
|
||||
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
language: auto
|
||||
|
||||
@@ -169,9 +164,6 @@ gui:
|
||||
# This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
showFileTree: true
|
||||
|
||||
# If true, show the number of lines changed per file in the Files view
|
||||
showNumstatInFilesView: false
|
||||
|
||||
# If true, show a random tip in the command log when Lazygit starts
|
||||
showRandomTip: true
|
||||
|
||||
@@ -219,9 +211,9 @@ gui:
|
||||
# If 'auto', only split the main window when a file has both staged and unstaged changes
|
||||
splitDiff: auto
|
||||
|
||||
# Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
# Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
# One of: 'normal' (default) | 'half' | 'full'
|
||||
screenMode: normal
|
||||
windowSize: normal
|
||||
|
||||
# Window border style.
|
||||
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
@@ -332,7 +324,7 @@ git:
|
||||
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
|
||||
|
||||
# Command used to display git log of all branches in the main window.
|
||||
# Deprecated: Use `allBranchesLogCmds` instead.
|
||||
# Deprecated: User `allBranchesLogCmds` instead.
|
||||
allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
|
||||
|
||||
# If true, do not spawn a separate process when using GPG
|
||||
@@ -659,13 +651,6 @@ os:
|
||||
open: 'open {{filename}}'
|
||||
```
|
||||
|
||||
## Custom Command for Opening a Link
|
||||
```yaml
|
||||
os:
|
||||
openLink: 'bash -C /path/to/your/shell-script.sh {{link}}'
|
||||
```
|
||||
Specify the external command to invoke when opening URL links (i.e. creating MR/PR in GitLab, BitBucket or GitHub). `{{link}}` will be replaced by the URL to be opened. A simple shell script can be used to further mangle the passed URL.
|
||||
|
||||
## Custom Command for Copying to and Pasting from Clipboard
|
||||
```yaml
|
||||
os:
|
||||
@@ -674,26 +659,9 @@ os:
|
||||
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
|
||||
|
||||
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty
|
||||
```
|
||||
|
||||
For tmux you need to wrap it with the [tmux escape sequence](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it), and enable passthrough in tmux config with `set -g allow-passthrough on`:
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty
|
||||
```
|
||||
|
||||
For the best of both worlds, we can let the command determine if we are running in a tmux session and send the correct sequence:
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: >
|
||||
if [[ "$TERM" =~ ^(screen|tmux) ]]; then
|
||||
printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty
|
||||
else
|
||||
printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty
|
||||
fi
|
||||
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty
|
||||
```
|
||||
|
||||
A custom command for reading from the clipboard can be set using
|
||||
@@ -940,14 +908,6 @@ git:
|
||||
replace: '[$1] '
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The way golang regex works is when you use `$n` in the replacement string, where `n` is a number, it puts the nth captured subgroup at that place. If `n` is out of range because there aren't that many capture groups in the regex, it puts an empty string there.
|
||||
>
|
||||
> So make sure you are capturing group or groups in your regex.
|
||||
>
|
||||
> For example `^[A-Z]+-\d+$` won't work on branch name like BRANCH-1111
|
||||
> But `^([A-Z]+-\d+)$` will
|
||||
|
||||
## Predefined branch name prefix
|
||||
|
||||
In situations where certain naming pattern is used for branches, this can be used to populate new branch creation with a static prefix.
|
||||
|
||||
@@ -50,7 +50,7 @@ Custom command keybindings will appear alongside inbuilt keybindings when you vi
|
||||
For a given custom command, here are the allowed fields:
|
||||
| _field_ | _description_ | required |
|
||||
|-----------------|----------------------|-|
|
||||
| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no |
|
||||
| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) | yes |
|
||||
| command | The command to run (using Go template syntax for placeholder values) | yes |
|
||||
| context | The context in which to listen for the key (see [below](#contexts)) | yes |
|
||||
| subprocess | Whether you want the command to run in a subprocess (e.g. if the command requires user input) | no |
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## The use-case
|
||||
|
||||
This topic deserves its own doc because there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
|
||||
This topic deserves its own doc because there there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
|
||||
1) press a key
|
||||
2) wait until Lazygit is idle
|
||||
3) run assertion / press another key
|
||||
|
||||
@@ -348,7 +348,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | Checkout | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -180,7 +180,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | チェックアウト | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | チェックアウト | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | タグを作成 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | タグをpush | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -321,7 +321,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | 체크아웃 | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -348,7 +348,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Uitchecken | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | Uitchecken | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -286,7 +286,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | Переключить | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -76,9 +76,9 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` i `` | 显示 git-flow 选项 | |
|
||||
| `` <space> `` | 检出 | 检出选中的项目 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` o `` | 创建拉取请求 | |
|
||||
| `` O `` | 创建拉取请求选项 | |
|
||||
| `` <c-y> `` | 将拉取请求 URL 复制到剪贴板 | |
|
||||
| `` o `` | 创建抓取请求 | |
|
||||
| `` O `` | 创建抓取请求选项 | |
|
||||
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | |
|
||||
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
|
||||
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
|
||||
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
|
||||
|
||||
@@ -282,7 +282,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 檢出 | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | 檢出 | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | 刪除 | View delete options for local/remote tag. |
|
||||
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -16,8 +16,8 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
@@ -38,7 +38,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sync v0.8.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -73,10 +73,10 @@ require (
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
@@ -188,10 +188,10 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4 h1:hSAimLVb4b5ktU3uJRtBsZW0P2dtXECReTcsHYfOy58=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g=
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f h1:ZzsAUDwPFLPITKLcJpMSqt/3rERdI8YRZKr2l0plrls=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
|
||||
@@ -327,9 +327,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -402,8 +401,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -425,8 +424,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -476,14 +475,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -494,8 +493,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@@ -38,7 +39,6 @@ const (
|
||||
DaemonKindMoveTodosDown
|
||||
DaemonKindInsertBreak
|
||||
DaemonKindChangeTodoActions
|
||||
DaemonKindDropMergeCommit
|
||||
DaemonKindMoveFixupCommitDown
|
||||
DaemonKindWriteRebaseTodo
|
||||
)
|
||||
@@ -58,7 +58,6 @@ func getInstruction() Instruction {
|
||||
DaemonKindRemoveUpdateRefsForCopiedBranch: deserializeInstruction[*RemoveUpdateRefsForCopiedBranchInstruction],
|
||||
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
|
||||
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
|
||||
DaemonKindDropMergeCommit: deserializeInstruction[*DropMergeCommitInstruction],
|
||||
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
|
||||
DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction],
|
||||
DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction],
|
||||
@@ -236,6 +235,7 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: c.Hash,
|
||||
OldAction: todo.Pick,
|
||||
NewAction: c.NewAction,
|
||||
}
|
||||
})
|
||||
@@ -244,30 +244,6 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
})
|
||||
}
|
||||
|
||||
type DropMergeCommitInstruction struct {
|
||||
Hash string
|
||||
}
|
||||
|
||||
func NewDropMergeCommitInstruction(hash string) Instruction {
|
||||
return &DropMergeCommitInstruction{
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) Kind() DaemonKind {
|
||||
return DaemonKindDropMergeCommit
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.DropMergeCommit(path, self.Hash, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
// Takes the hash of some commit, and the hash of a fixup commit that was created
|
||||
// at the end of the branch, then moves the fixup commit down to right after the
|
||||
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
|
||||
@@ -320,12 +296,13 @@ func (self *MoveTodosUpInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosUpInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
Hash: hash,
|
||||
Action: todo.Pick,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodosUp(path, todosToMove, false, getCommentChar())
|
||||
return utils.MoveTodosUp(path, todosToMove, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -350,12 +327,13 @@ func (self *MoveTodosDownInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosDownInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
Hash: hash,
|
||||
Action: todo.Pick,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodosDown(path, todosToMove, false, getCommentChar())
|
||||
return utils.MoveTodosDown(path, todosToMove, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+5
-10
@@ -29,17 +29,16 @@ type cliArgs struct {
|
||||
RepoPath string
|
||||
FilterPath string
|
||||
GitArg string
|
||||
UseConfigDir string
|
||||
WorkTree string
|
||||
GitDir string
|
||||
CustomConfigFile string
|
||||
ScreenMode string
|
||||
PrintVersionInfo bool
|
||||
Debug bool
|
||||
TailLogs bool
|
||||
Profile bool
|
||||
PrintDefaultConfig bool
|
||||
PrintConfigDir bool
|
||||
UseConfigDir string
|
||||
WorkTree string
|
||||
GitDir string
|
||||
CustomConfigFile string
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
@@ -165,7 +164,7 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
|
||||
parsedGitArg := parseGitArg(cliArgs.GitArg)
|
||||
|
||||
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, cliArgs.ScreenMode, integrationTest))
|
||||
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest))
|
||||
}
|
||||
|
||||
func parseCliArgsAndEnvVars() *cliArgs {
|
||||
@@ -210,9 +209,6 @@ func parseCliArgsAndEnvVars() *cliArgs {
|
||||
customConfigFile := ""
|
||||
flaggy.String(&customConfigFile, "ucf", "use-config-file", "Comma separated list to custom config file(s)")
|
||||
|
||||
screenMode := ""
|
||||
flaggy.String(&screenMode, "sm", "screen-mode", "The initial screen-mode, which determines the size of the focused panel. Valid options: 'normal' (default), 'half', 'full'")
|
||||
|
||||
flaggy.Parse()
|
||||
|
||||
if os.Getenv("DEBUG") == "TRUE" {
|
||||
@@ -233,7 +229,6 @@ func parseCliArgsAndEnvVars() *cliArgs {
|
||||
WorkTree: workTree,
|
||||
GitDir: gitDir,
|
||||
CustomConfigFile: customConfigFile,
|
||||
ScreenMode: screenMode,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,12 @@ import (
|
||||
|
||||
// StartArgs is the struct that represents some things we want to do on program start
|
||||
type StartArgs struct {
|
||||
// FilterPath determines which path we're going to filter on so that we only see commits from that file.
|
||||
FilterPath string
|
||||
// GitArg determines what context we open in
|
||||
GitArg GitArg
|
||||
// integration test (only relevant when invoking lazygit in the context of an integration test)
|
||||
IntegrationTest integrationTypes.IntegrationTest
|
||||
// FilterPath determines which path we're going to filter on so that we only see commits from that file.
|
||||
FilterPath string
|
||||
// ScreenMode determines the initial Screen Mode (normal, half or full) to use
|
||||
ScreenMode string
|
||||
}
|
||||
|
||||
type GitArg string
|
||||
@@ -26,11 +24,10 @@ const (
|
||||
GitArgStash GitArg = "stash"
|
||||
)
|
||||
|
||||
func NewStartArgs(filterPath string, gitArg GitArg, screenMode string, test integrationTypes.IntegrationTest) StartArgs {
|
||||
func NewStartArgs(filterPath string, gitArg GitArg, test integrationTypes.IntegrationTest) StartArgs {
|
||||
return StartArgs{
|
||||
FilterPath: filterPath,
|
||||
GitArg: gitArg,
|
||||
ScreenMode: screenMode,
|
||||
IntegrationTest: test,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +109,10 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
|
||||
}
|
||||
|
||||
// LocalDelete delete branch locally
|
||||
func (self *BranchCommands) LocalDelete(branches []string, force bool) error {
|
||||
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
|
||||
cmdArgs := NewGitCmd("branch").
|
||||
ArgIfElse(force, "-D", "-d").
|
||||
Arg(branches...).
|
||||
Arg(branch).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
|
||||
@@ -62,57 +62,36 @@ func TestBranchNewBranch(t *testing.T) {
|
||||
|
||||
func TestBranchDeleteBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
branchNames []string
|
||||
force bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
testName string
|
||||
force bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Delete a branch",
|
||||
[]string{"test"},
|
||||
false,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Delete multiple branches",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
false,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test1", "test2", "test3"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Force delete a branch",
|
||||
[]string{"test"},
|
||||
true,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Force delete multiple branches",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
true,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test1", "test2", "test3"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
|
||||
s.test(instance.LocalDelete(s.branchNames, s.force))
|
||||
s.test(instance.LocalDelete("test", s.force))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,12 +16,9 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
||||
}
|
||||
}
|
||||
|
||||
// This is for generating diffs to be shown in the UI (e.g. rendering a range
|
||||
// diff to the main view). It uses a custom pager if one is configured.
|
||||
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != ""
|
||||
ignoreWhitespace := self.AppState.IgnoreWhitespaceInDiffView
|
||||
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
@@ -30,29 +27,33 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv(),
|
||||
)
|
||||
}
|
||||
|
||||
// This is a basic generic diff command that can be used for any diff operation
|
||||
// (e.g. copying a diff to the clipboard). It will not use a custom pager, and
|
||||
// does not use user configs such as ignore whitespace.
|
||||
// If you want to diff specific refs (one or two), you need to add them yourself
|
||||
// in additionalArgs; it is recommended to also pass `--` after that. If you
|
||||
// want to restrict the diff to specific paths, pass them in additionalArgs
|
||||
// after the `--`.
|
||||
func (self *DiffCommands) GetDiff(staged bool, additionalArgs ...string) (string, error) {
|
||||
func (self *DiffCommands) internalDiffCmdObj(diffArgs ...string) *GitCommandBuilder {
|
||||
return NewGitCmd("diff").
|
||||
Config("diff.noprefix=false").
|
||||
Arg("--no-ext-diff", "--no-color").
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath)
|
||||
}
|
||||
|
||||
func (self *DiffCommands) GetPathDiff(path string, staged bool) (string, error) {
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Config("diff.noprefix=false").
|
||||
Arg("--no-ext-diff", "--no-color").
|
||||
self.internalDiffCmdObj().
|
||||
ArgIf(staged, "--staged").
|
||||
Arg(path).
|
||||
ToArgv(),
|
||||
).RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *DiffCommands) GetAllDiff(staged bool) (string, error) {
|
||||
return self.cmd.New(
|
||||
self.internalDiffCmdObj().
|
||||
ArgIf(staged, "--staged").
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
Arg(additionalArgs...).
|
||||
ToArgv(),
|
||||
).RunWithOutput()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -49,14 +48,6 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
}
|
||||
files := []*models.File{}
|
||||
|
||||
fileDiffs := map[string]FileDiff{}
|
||||
if self.GitCommon.Common.UserConfig().Gui.ShowNumstatInFilesView {
|
||||
fileDiffs, err = self.getFileDiffs()
|
||||
if err != nil {
|
||||
self.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
if strings.HasPrefix(status.StatusString, "warning") {
|
||||
self.Log.Warningf("warning when calling git status: %s", status.StatusString)
|
||||
@@ -69,11 +60,6 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
DisplayString: status.StatusString,
|
||||
}
|
||||
|
||||
if diff, ok := fileDiffs[status.Name]; ok {
|
||||
file.LinesAdded = diff.LinesAdded
|
||||
file.LinesDeleted = diff.LinesDeleted
|
||||
}
|
||||
|
||||
models.SetStatusFields(file, status.Change)
|
||||
files = append(files, file)
|
||||
}
|
||||
@@ -101,45 +87,6 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
return files
|
||||
}
|
||||
|
||||
type FileDiff struct {
|
||||
LinesAdded int
|
||||
LinesDeleted int
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
|
||||
diffs, err := fileLoader.gitDiffNumStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
splitLines := strings.Split(diffs, "\x00")
|
||||
|
||||
fileDiffs := map[string]FileDiff{}
|
||||
for _, line := range splitLines {
|
||||
splitLine := strings.Split(line, "\t")
|
||||
if len(splitLine) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
linesAdded, err := strconv.Atoi(splitLine[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
linesDeleted, err := strconv.Atoi(splitLine[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := splitLine[2]
|
||||
fileDiffs[fileName] = FileDiff{
|
||||
LinesAdded: linesAdded,
|
||||
LinesDeleted: linesDeleted,
|
||||
}
|
||||
}
|
||||
|
||||
return fileDiffs, nil
|
||||
}
|
||||
|
||||
// GitStatus returns the file status of the repo
|
||||
type GitStatusOptions struct {
|
||||
NoRenames bool
|
||||
@@ -153,16 +100,6 @@ type FileStatus struct {
|
||||
PreviousName string
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) gitDiffNumStat() (string, error) {
|
||||
return fileLoader.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Arg("--numstat").
|
||||
Arg("-z").
|
||||
Arg("HEAD").
|
||||
ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
cmdArgs := NewGitCmd("status").
|
||||
Arg(opts.UntrackedFilesArg).
|
||||
|
||||
@@ -11,35 +11,29 @@ import (
|
||||
|
||||
func TestFileGetStatusFiles(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
similarityThreshold int
|
||||
runner oscommands.ICmdObjRunner
|
||||
showNumstatInFilesView bool
|
||||
expectedFiles []*models.File
|
||||
testName string
|
||||
similarityThreshold int
|
||||
runner oscommands.ICmdObjRunner
|
||||
expectedFiles []*models.File
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "No files found",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"No files found",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "", nil),
|
||||
expectedFiles: []*models.File{},
|
||||
[]*models.File{},
|
||||
},
|
||||
{
|
||||
testName: "Several files found",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"Several files found",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
"MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
|
||||
nil,
|
||||
).
|
||||
ExpectGitArgs([]string{"diff", "--numstat", "-z", "HEAD"},
|
||||
"4\t1\tfile1.txt\x001\t0\tfile2.txt\x002\t2\tfile3.txt\x000\t2\tfile4.txt\x002\t2\tfile5.txt",
|
||||
nil,
|
||||
),
|
||||
showNumstatInFilesView: true,
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "file1.txt",
|
||||
HasStagedChanges: true,
|
||||
@@ -51,8 +45,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "MM file1.txt",
|
||||
ShortStatus: "MM",
|
||||
LinesAdded: 4,
|
||||
LinesDeleted: 1,
|
||||
},
|
||||
{
|
||||
Name: "file3.txt",
|
||||
@@ -65,8 +57,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "A file3.txt",
|
||||
ShortStatus: "A ",
|
||||
LinesAdded: 2,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
{
|
||||
Name: "file2.txt",
|
||||
@@ -79,8 +69,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "AM file2.txt",
|
||||
ShortStatus: "AM",
|
||||
LinesAdded: 1,
|
||||
LinesDeleted: 0,
|
||||
},
|
||||
{
|
||||
Name: "file4.txt",
|
||||
@@ -93,8 +81,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "?? file4.txt",
|
||||
ShortStatus: "??",
|
||||
LinesAdded: 0,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
{
|
||||
Name: "file5.txt",
|
||||
@@ -107,17 +93,15 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: true,
|
||||
DisplayString: "UU file5.txt",
|
||||
ShortStatus: "UU",
|
||||
LinesAdded: 2,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "File with new line char",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"File with new line char",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil),
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "a\nb.txt",
|
||||
HasStagedChanges: true,
|
||||
@@ -133,14 +117,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Renamed files",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"Renamed files",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
"R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
|
||||
nil,
|
||||
),
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "after1.txt",
|
||||
PreviousName: "before1.txt",
|
||||
@@ -170,14 +154,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "File with arrow in name",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"File with arrow in name",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
`?? a -> b.txt`,
|
||||
nil,
|
||||
),
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "a -> b.txt",
|
||||
HasStagedChanges: false,
|
||||
@@ -201,14 +185,8 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
appState := &config.AppState{}
|
||||
appState.RenameSimilarityThreshold = s.similarityThreshold
|
||||
|
||||
userConfig := &config.UserConfig{
|
||||
Gui: config.GuiConfig{
|
||||
ShowNumstatInFilesView: s.showNumstatInFilesView,
|
||||
},
|
||||
}
|
||||
|
||||
loader := &FileLoader{
|
||||
GitCommon: buildGitCommon(commonDeps{appState: appState, userConfig: userConfig}),
|
||||
GitCommon: buildGitCommon(commonDeps{appState: appState}),
|
||||
cmd: cmd,
|
||||
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
|
||||
getFileType: func(string) string { return "file" },
|
||||
|
||||
@@ -76,14 +76,6 @@ func (self *GitCommandBuilder) Worktree(path string) *GitCommandBuilder {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) WorktreePathIf(condition bool, path string) *GitCommandBuilder {
|
||||
if condition {
|
||||
return self.Worktree(path)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// Note, you may prefer to use the Dir method instead of this one
|
||||
func (self *GitCommandBuilder) GitDir(path string) *GitCommandBuilder {
|
||||
// git dir arg comes before the command
|
||||
|
||||
@@ -324,9 +324,9 @@ func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, target
|
||||
|
||||
func todoFromCommit(commit *models.Commit) utils.Todo {
|
||||
if commit.Action == todo.UpdateRef {
|
||||
return utils.Todo{Ref: commit.Name}
|
||||
return utils.Todo{Ref: commit.Name, Action: commit.Action}
|
||||
} else {
|
||||
return utils.Todo{Hash: commit.Hash}
|
||||
return utils.Todo{Hash: commit.Hash, Action: commit.Action}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo
|
||||
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: commit.Hash,
|
||||
OldAction: commit.Action,
|
||||
NewAction: action,
|
||||
}
|
||||
})
|
||||
@@ -369,7 +370,7 @@ func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
|
||||
return todoFromCommit(commit)
|
||||
})
|
||||
|
||||
return utils.MoveTodosDown(fileName, todosToMove, true, self.config.GetCoreCommentChar())
|
||||
return utils.MoveTodosDown(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
@@ -378,7 +379,7 @@ func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
return todoFromCommit(commit)
|
||||
})
|
||||
|
||||
return utils.MoveTodosUp(fileName, todosToMove, true, self.config.GetCoreCommentChar())
|
||||
return utils.MoveTodosUp(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -564,13 +565,6 @@ func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Comm
|
||||
return utils.PrependStrToTodoFile(filePath, []byte(todo))
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) DropMergeCommit(commits []*models.Commit, commitIndex int) error {
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
||||
instruction: daemon.NewDropMergeCommitInstruction(commits[commitIndex].Hash),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// we can't start an interactive rebase from the first commit without passing the
|
||||
// '--root' arg
|
||||
func getBaseHashOrRoot(commits []*models.Commit, index int) string {
|
||||
|
||||
@@ -49,10 +49,9 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchNames []string) error {
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchName string) error {
|
||||
cmdArgs := NewGitCmd("push").
|
||||
Arg(remoteName, "--delete").
|
||||
Arg(branchNames...).
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
|
||||
@@ -88,7 +88,6 @@ type PullOptions struct {
|
||||
BranchName string
|
||||
FastForwardOnly bool
|
||||
WorktreeGitDir string
|
||||
WorktreePath string
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
@@ -98,7 +97,6 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
ArgIf(opts.RemoteName != "", opts.RemoteName).
|
||||
ArgIf(opts.BranchName != "", "refs/heads/"+opts.BranchName).
|
||||
GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir).
|
||||
WorktreePathIf(opts.WorktreePath != "", opts.WorktreePath).
|
||||
ToArgv()
|
||||
|
||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -99,7 +100,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
Added: true,
|
||||
},
|
||||
removeFile: func(string) error {
|
||||
return errors.New("an error occurred when removing file")
|
||||
return fmt.Errorf("an error occurred when removing file")
|
||||
},
|
||||
runner: oscommands.NewFakeRunner(t),
|
||||
expectedError: "an error occurred when removing file",
|
||||
|
||||
@@ -48,7 +48,6 @@ var azdoServiceDef = ServiceDefinition{
|
||||
regexStrings: []string{
|
||||
`^git@ssh.dev.azure.com.*/(?P<org>.*)/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^https://.*@dev.azure.com/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^https://.*/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: "https://{{.webDomain}}/{{.org}}/{{.project}}/_git/{{.repo}}",
|
||||
}
|
||||
|
||||
@@ -210,19 +210,6 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew&targetRef=dev", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Azure DevOps Server (HTTP)",
|
||||
from: "feature/new",
|
||||
remoteUrl: "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo",
|
||||
configServiceDomains: map[string]string{
|
||||
// valid configuration for a azure devops server URL
|
||||
"mycompany.azuredevops.com": "azuredevops:mycompany.azuredevops.com",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Bitbucket Server (SSH)",
|
||||
from: "feature/new",
|
||||
|
||||
@@ -19,8 +19,6 @@ type File struct {
|
||||
HasInlineMergeConflicts bool
|
||||
DisplayString string
|
||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
||||
LinesDeleted int
|
||||
LinesAdded int
|
||||
|
||||
// If true, this must be a worktree folder
|
||||
IsWorktree bool
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
|
||||
quotedCommand := self.quotedCommandString(commandStr + self.platform.InteractiveShellExit)
|
||||
quotedCommand := self.quotedCommandString(commandStr)
|
||||
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
|
||||
|
||||
return self.New(cmdArgs)
|
||||
|
||||
@@ -51,14 +51,13 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
|
||||
}
|
||||
|
||||
var dummyPlatform = &Platform{
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
InteractiveShellExit: "; exit $?",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
|
||||
|
||||
@@ -35,14 +35,13 @@ type OSCommand struct {
|
||||
|
||||
// Platform stores the os state
|
||||
type Platform struct {
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
InteractiveShellExit string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
}
|
||||
|
||||
// NewOSCommand os command runner
|
||||
|
||||
@@ -6,31 +6,17 @@ package oscommands
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
shell := getUserShell()
|
||||
|
||||
interactiveShell := shell
|
||||
interactiveShellArg := "-i"
|
||||
interactiveShellExit := "; exit $?"
|
||||
|
||||
if !(strings.HasSuffix(shell, "bash") || strings.HasSuffix(shell, "zsh")) {
|
||||
interactiveShell = "bash"
|
||||
interactiveShellArg = ""
|
||||
interactiveShellExit = ""
|
||||
}
|
||||
|
||||
return &Platform{
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: interactiveShell,
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: interactiveShellArg,
|
||||
InteractiveShellExit: interactiveShellExit,
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: getUserShell(),
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestOSCommandFileType(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"nonExistent",
|
||||
"nonExistant",
|
||||
func() {},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "other", output)
|
||||
|
||||
@@ -2,11 +2,10 @@ package oscommands
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
return &Platform{
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
InteractiveShellExit: "",
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,24 +154,3 @@ func (self *Patch) LineCount() int {
|
||||
func (self *Patch) HunkCount() int {
|
||||
return len(self.hunks)
|
||||
}
|
||||
|
||||
// Adjust the given line number (one-based) according to the current patch. The
|
||||
// patch is supposed to be a diff of an old file state against the working
|
||||
// directory; the line number is a line number in that old file, and the
|
||||
// function returns the corresponding line number in the working directory file.
|
||||
func (self *Patch) AdjustLineNumber(lineNumber int) int {
|
||||
adjustedLineNumber := lineNumber
|
||||
for _, hunk := range self.hunks {
|
||||
if hunk.oldStart >= lineNumber {
|
||||
break
|
||||
}
|
||||
|
||||
if hunk.oldStart+hunk.oldLength() > lineNumber {
|
||||
return hunk.newStart
|
||||
}
|
||||
|
||||
adjustedLineNumber += hunk.newLength() - hunk.oldLength()
|
||||
}
|
||||
|
||||
return adjustedLineNumber
|
||||
}
|
||||
|
||||
@@ -639,59 +639,3 @@ func TestGetNextStageableLineIndex(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdjustLineNumber(t *testing.T) {
|
||||
type scenario struct {
|
||||
oldLineNumbers []int
|
||||
expectedResults []int
|
||||
}
|
||||
scenarios := []scenario{
|
||||
{
|
||||
oldLineNumbers: []int{1, 2, 3, 4, 5, 6, 7},
|
||||
expectedResults: []int{1, 2, 2, 3, 4, 7, 8},
|
||||
},
|
||||
}
|
||||
|
||||
// The following diff was generated from old.txt:
|
||||
// 1
|
||||
// 2a
|
||||
// 2b
|
||||
// 3
|
||||
// 4
|
||||
// 7
|
||||
// 8
|
||||
// against new.txt:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
// 4
|
||||
// 5
|
||||
// 6
|
||||
// 7
|
||||
// 8
|
||||
|
||||
// This test setup makes the test easy to understand, because the resulting
|
||||
// adjusted line numbers are the same as the content of the lines in new.txt.
|
||||
|
||||
diff := `--- old.txt 2024-12-16 18:04:29
|
||||
+++ new.txt 2024-12-16 18:04:27
|
||||
@@ -2,2 +2 @@
|
||||
-2a
|
||||
-2b
|
||||
+2
|
||||
@@ -5,0 +5,2 @@
|
||||
+5
|
||||
+6
|
||||
`
|
||||
|
||||
patch := Parse(diff)
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run("TestAdjustLineNumber", func(t *testing.T) {
|
||||
for idx, oldLineNumber := range s.oldLineNumbers {
|
||||
result := patch.AdjustLineNumber(oldLineNumber)
|
||||
assert.Equal(t, s.expectedResults[idx], result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,23 +217,16 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
|
||||
// from one container to another, or changing the type of a key (e.g. from bool
|
||||
// to an enum).
|
||||
func migrateUserConfig(path string, content []byte) ([]byte, error) {
|
||||
changedContent := content
|
||||
|
||||
pathsToReplace := []struct {
|
||||
oldPath []string
|
||||
newName string
|
||||
}{
|
||||
{[]string{"gui", "skipUnstageLineWarning"}, "skipDiscardChangeWarning"},
|
||||
{[]string{"keybinding", "universal", "executeCustomCommand"}, "executeShellCommand"},
|
||||
{[]string{"gui", "windowSize"}, "screenMode"},
|
||||
changedContent, err := yaml_utils.RenameYamlKey(content, []string{"gui", "skipUnstageLineWarning"},
|
||||
"skipDiscardChangeWarning")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, pathToReplace := range pathsToReplace {
|
||||
changedContent, err = yaml_utils.RenameYamlKey(changedContent, pathToReplace.oldPath, pathToReplace.newName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %s", path, strings.Join(pathToReplace.oldPath, "."), err)
|
||||
}
|
||||
changedContent, err = yaml_utils.RenameYamlKey(changedContent, []string{"keybinding", "universal", "executeCustomCommand"},
|
||||
"executeShellCommand")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
changedContent, err = changeNullKeybindingsToDisabled(changedContent)
|
||||
|
||||
@@ -21,8 +21,8 @@ func isContainer() bool {
|
||||
func GetPlatformDefaultConfig() OSConfig {
|
||||
if isWSL() && !isContainer() {
|
||||
return OSConfig{
|
||||
Open: `powershell.exe start explorer.exe "$(wslpath -w {{filename}})" >/dev/null`,
|
||||
OpenLink: `powershell.exe start '{{link}}' >/dev/null`,
|
||||
Open: `powershell.exe start explorer.exe {{filename}} >/dev/null`,
|
||||
OpenLink: `powershell.exe start {{link}} >/dev/null`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,15 +66,9 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
return !ok
|
||||
},
|
||||
},
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": {
|
||||
editTemplate: "micro {{filename}}",
|
||||
editAtLineTemplate: "micro +{{line}} {{filename}}",
|
||||
editAtLineAndWaitTemplate: "micro +{{line}} {{filename}}",
|
||||
openDirInEditorTemplate: "micro {{dir}}",
|
||||
suspend: returnBool(true),
|
||||
},
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": standardTerminalEditorPreset("micro"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kak"),
|
||||
"helix": {
|
||||
|
||||
@@ -91,10 +91,6 @@ type GuiConfig struct {
|
||||
// - 'left': split the window horizontally (side panel on the left, main view on the right)
|
||||
// - 'top': split the window vertically (side panel on top, main view below)
|
||||
EnlargedSideViewLocation string `yaml:"enlargedSideViewLocation"`
|
||||
// If true, wrap lines in the staging view to the width of the view. This
|
||||
// makes it much easier to work with diffs that have long lines, e.g.
|
||||
// paragraphs of markdown text.
|
||||
WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"`
|
||||
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"`
|
||||
// Format used when displaying time e.g. commit time.
|
||||
@@ -113,8 +109,6 @@ type GuiConfig struct {
|
||||
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
|
||||
// This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
// If true, show the number of lines changed per file in the Files view
|
||||
ShowNumstatInFilesView bool `yaml:"showNumstatInFilesView"`
|
||||
// If true, show a random tip in the command log when Lazygit starts
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
// If true, show the command log
|
||||
@@ -148,9 +142,9 @@ type GuiConfig struct {
|
||||
// One of: 'auto' | 'always'
|
||||
// If 'auto', only split the main window when a file has both staged and unstaged changes
|
||||
SplitDiff string `yaml:"splitDiff" jsonschema:"enum=auto,enum=always"`
|
||||
// Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
// Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
// One of: 'normal' (default) | 'half' | 'full'
|
||||
ScreenMode string `yaml:"screenMode" jsonschema:"enum=normal,enum=half,enum=full"`
|
||||
WindowSize string `yaml:"windowSize" jsonschema:"enum=normal,enum=half,enum=full"`
|
||||
// Window border style.
|
||||
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
Border string `yaml:"border" jsonschema:"enum=single,enum=double,enum=rounded,enum=hidden"`
|
||||
@@ -244,7 +238,7 @@ type GitConfig struct {
|
||||
// Command used when displaying the current branch git log in the main window
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
// Command used to display git log of all branches in the main window.
|
||||
// Deprecated: Use `allBranchesLogCmds` instead.
|
||||
// Deprecated: User `allBranchesLogCmds` instead.
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
// Commands used to display git log of all branches in the main window, they will be cycled in order of appearance
|
||||
AllBranchesLogCmds []string `yaml:"allBranchesLogCmds"`
|
||||
@@ -696,7 +690,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
ExpandedSidePanelWeight: 2,
|
||||
MainPanelSplitMode: "flexible",
|
||||
EnlargedSideViewLocation: "left",
|
||||
WrapLinesInStagingView: true,
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
@@ -721,7 +714,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowNumstatInFilesView: false,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
@@ -734,7 +726,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
ScreenMode: "normal",
|
||||
WindowSize: "normal",
|
||||
Border: "rounded",
|
||||
AnimateExplosion: true,
|
||||
PortraitMode: "auto",
|
||||
|
||||
+15
-11
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -75,18 +76,21 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
fetch := func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
}
|
||||
|
||||
// We want an immediate fetch at startup, and since goEvery starts by
|
||||
// waiting for the interval, we need to trigger one manually first
|
||||
_ = fetch()
|
||||
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig()
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, fetch)
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) {
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
c.State().GetItemOperation,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.InnerWidth(),
|
||||
c.Views().Branches.Width(),
|
||||
c.Tr,
|
||||
c.UserConfig(),
|
||||
c.Model().Worktrees,
|
||||
@@ -80,14 +80,6 @@ func (self *BranchesContext) GetDiffTerminals() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
branch := self.GetSelected()
|
||||
if branch != nil {
|
||||
return branch.ID()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *BranchesContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -77,13 +77,6 @@ func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
if refs := self.GetRefRange(); refs != nil {
|
||||
return refs.To.RefName()
|
||||
}
|
||||
return self.GetRef().RefName()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) {
|
||||
if refs := self.GetRefRange(); refs != nil {
|
||||
return refs.From.ParentRefName(), refs.To.RefName()
|
||||
|
||||
@@ -30,9 +30,6 @@ type CommitMessageViewModel struct {
|
||||
// if true, then upon escaping from the commit message panel, we will preserve
|
||||
// the message so that it's still shown next time we open the panel
|
||||
preserveMessage bool
|
||||
// we remember the initial message so that we can tell whether we should preserve
|
||||
// the message; if it's still identical to the initial message, we don't
|
||||
initialMessage string
|
||||
// the full preserved message (combined summary and description)
|
||||
preservedMessage string
|
||||
// invoked when pressing enter in the commit message panel
|
||||
@@ -87,10 +84,6 @@ func (self *CommitMessageContext) SetPreservedMessage(message string) {
|
||||
self.viewModel.preservedMessage = message
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetInitialMessage() string {
|
||||
return strings.TrimSpace(self.viewModel.initialMessage)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetHistoryMessage() string {
|
||||
return self.viewModel.historyMessage
|
||||
}
|
||||
@@ -108,13 +101,11 @@ func (self *CommitMessageContext) SetPanelState(
|
||||
summaryTitle string,
|
||||
descriptionTitle string,
|
||||
preserveMessage bool,
|
||||
initialMessage string,
|
||||
onConfirm func(string, string) error,
|
||||
onSwitchToEditor func(string) error,
|
||||
) {
|
||||
self.viewModel.selectedindex = index
|
||||
self.viewModel.preserveMessage = preserveMessage
|
||||
self.viewModel.initialMessage = initialMessage
|
||||
self.viewModel.onConfirm = onConfirm
|
||||
self.viewModel.onSwitchToEditor = onSwitchToEditor
|
||||
self.GetView().Title = summaryTitle
|
||||
|
||||
@@ -131,7 +131,7 @@ func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// By default, list contexts supports range select
|
||||
// By default, list contexts supporta range select
|
||||
func (self *ListContextTrait) RangeSelectEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -170,14 +170,6 @@ func (self *LocalCommitsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
commits, _, _ := self.GetSelectedItems()
|
||||
if commits == nil {
|
||||
return ""
|
||||
}
|
||||
return commits[0].Hash
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
|
||||
}
|
||||
|
||||
@@ -115,5 +115,5 @@ func (self *MergeConflictsContext) SetSelectedLineRange() {
|
||||
func (self *MergeConflictsContext) GetOriginY() int {
|
||||
view := self.GetView()
|
||||
conflictMiddle := self.GetState().GetConflictMiddle()
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.InnerHeight()/2))))
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
|
||||
}
|
||||
|
||||
@@ -39,13 +39,12 @@ func NewPatchExplorerContext(
|
||||
mutex: &deadlock.Mutex{},
|
||||
getIncludedLineIndices: getIncludedLineIndices,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES,
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
})),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
}
|
||||
@@ -59,8 +58,6 @@ func NewPatchExplorerContext(
|
||||
}),
|
||||
)
|
||||
|
||||
ctx.SetHandleRenderFunc(ctx.OnViewWidthChanged)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -107,15 +104,16 @@ func (self *PatchExplorerContext) setContent() {
|
||||
func (self *PatchExplorerContext) FocusSelection() {
|
||||
view := self.GetView()
|
||||
state := self.GetState()
|
||||
bufferHeight := view.InnerHeight()
|
||||
_, viewHeight := view.Size()
|
||||
bufferHeight := viewHeight - 1
|
||||
_, origin := view.Origin()
|
||||
numLines := view.ViewLinesHeight()
|
||||
numLines := view.LinesHeight()
|
||||
|
||||
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
|
||||
|
||||
view.SetOriginY(newOriginY)
|
||||
|
||||
startIdx, endIdx := state.SelectedViewRange()
|
||||
startIdx, endIdx := state.SelectedRange()
|
||||
// As far as the view is concerned, we are always selecting a range
|
||||
view.SetRangeSelectStart(startIdx)
|
||||
view.SetCursorY(endIdx - newOriginY)
|
||||
@@ -143,11 +141,3 @@ func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) OnViewWidthChanged() {
|
||||
if state := self.GetState(); state != nil {
|
||||
state.OnViewWidthChanged(self.GetView())
|
||||
self.setContent()
|
||||
self.RenderAndFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +86,6 @@ func (self *ReflogCommitsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -78,10 +78,6 @@ func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -53,7 +53,3 @@ func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *RemotesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
type SimpleContext struct {
|
||||
*BaseContext
|
||||
handleRenderFunc func()
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
|
||||
@@ -55,13 +54,6 @@ func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() {
|
||||
if self.handleRenderFunc != nil {
|
||||
self.handleRenderFunc()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SimpleContext) SetHandleRenderFunc(f func()) {
|
||||
self.handleRenderFunc = f
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() {
|
||||
|
||||
@@ -71,7 +71,3 @@ func (self *StashContext) GetDiffTerminals() []string {
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *StashContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
@@ -217,14 +217,6 @@ func (self *SubCommitsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
commits, _, _ := self.GetSelectedItems()
|
||||
if commits == nil {
|
||||
return ""
|
||||
}
|
||||
return commits[0].Hash
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
|
||||
}
|
||||
|
||||
@@ -66,10 +66,6 @@ func (self *TagsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *TagsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (self *TagsContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func (self *ViewTrait) SetOriginX(value int) {
|
||||
// tells us the start of line indexes shown in the view currently as well as the capacity of lines shown in the viewport.
|
||||
func (self *ViewTrait) ViewPortYBounds() (int, int) {
|
||||
_, start := self.view.Origin()
|
||||
length := self.view.InnerHeight()
|
||||
length := self.view.InnerHeight() + 1
|
||||
return start, length
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func (self *ViewTrait) ScrollDown(value int) {
|
||||
|
||||
// this returns the amount we'll scroll if we want to scroll by a page.
|
||||
func (self *ViewTrait) PageDelta() int {
|
||||
height := self.view.InnerHeight()
|
||||
_, height := self.view.Size()
|
||||
|
||||
delta := height - 1
|
||||
if delta == 0 {
|
||||
|
||||
@@ -30,8 +30,7 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
|
||||
showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat)
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
})
|
||||
|
||||
@@ -299,7 +299,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilderSecondary,
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilderSecondary),
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
|
||||
|
||||
@@ -280,7 +280,15 @@ func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
return self.c.Helpers().Refs.CreateCheckoutMenu(commit)
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.CheckoutCommit,
|
||||
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||
return self.c.Helpers().Refs.CheckoutRef(commit.Hash, types.CheckoutRefOptions{})
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||
|
||||
@@ -91,8 +91,8 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.withItems(self.delete),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected(self.branchesAreReal)),
|
||||
Handler: self.withItem(self.delete),
|
||||
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
|
||||
Description: self.c.Tr.Delete,
|
||||
Tooltip: self.c.Tr.BranchDeleteTooltip,
|
||||
OpensMenu: true,
|
||||
@@ -194,10 +194,6 @@ func (self *BranchesController) GetOnRenderToMain() func() {
|
||||
}
|
||||
|
||||
func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branch) error {
|
||||
upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(),
|
||||
selectedBranch.ShortUpstreamRefName(),
|
||||
self.c.Tr.UpstreamGenericName)
|
||||
|
||||
viewDivergenceItem := &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream},
|
||||
OnPress: func() error {
|
||||
@@ -208,7 +204,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: branch,
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), upstream),
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), branch.ShortUpstreamRefName()),
|
||||
RefToShowDivergenceFrom: branch.FullUpstreamRefName(),
|
||||
Context: self.context(),
|
||||
ShowBranchHeads: false,
|
||||
@@ -297,6 +293,9 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
Key: 's',
|
||||
}
|
||||
|
||||
upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(),
|
||||
fmt.Sprintf("%s/%s", selectedBranch.UpstreamRemote, selectedBranch.Name),
|
||||
self.c.Tr.UpstreamGenericName)
|
||||
upstreamResetOptions := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewUpstreamResetOptions,
|
||||
map[string]string{"upstream": upstream},
|
||||
@@ -333,7 +332,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
LabelColumns: []string{upstreamRebaseOptions},
|
||||
OpensMenu: true,
|
||||
OnPress: func() error {
|
||||
if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(upstream); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.ShortUpstreamRefName()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -521,80 +520,62 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
|
||||
}
|
||||
|
||||
func (self *BranchesController) localDelete(branches []*models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branches)
|
||||
func (self *BranchesController) localDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) remoteDelete(branches []*models.Branch) error {
|
||||
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
|
||||
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
|
||||
})
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches)
|
||||
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branches)
|
||||
func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branches []*models.Branch) error {
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
isBranchCheckedOut := lo.SomeBy(branches, func(branch *models.Branch) bool {
|
||||
return checkedOutBranch.Name == branch.Name
|
||||
})
|
||||
hasUpstream := lo.EveryBy(branches, func(branch *models.Branch) bool {
|
||||
return branch.IsTrackingRemote() && !branch.UpstreamGone
|
||||
})
|
||||
|
||||
localDeleteItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalBranches, self.c.Tr.DeleteLocalBranch),
|
||||
Label: self.c.Tr.DeleteLocalBranch,
|
||||
Key: 'c',
|
||||
OnPress: func() error {
|
||||
return self.localDelete(branches)
|
||||
return self.localDelete(branch)
|
||||
},
|
||||
}
|
||||
if isBranchCheckedOut {
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
localDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
}
|
||||
|
||||
remoteDeleteItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteRemoteBranches, self.c.Tr.DeleteRemoteBranch),
|
||||
Label: self.c.Tr.DeleteRemoteBranch,
|
||||
Key: 'r',
|
||||
OnPress: func() error {
|
||||
return self.remoteDelete(branches)
|
||||
return self.remoteDelete(branch)
|
||||
},
|
||||
}
|
||||
if !hasUpstream {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{
|
||||
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
|
||||
}
|
||||
if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
}
|
||||
|
||||
deleteBothItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalAndRemoteBranches, self.c.Tr.DeleteLocalAndRemoteBranch),
|
||||
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
|
||||
Key: 'b',
|
||||
OnPress: func() error {
|
||||
return self.localAndRemoteDelete(branches)
|
||||
return self.localAndRemoteDelete(branch)
|
||||
},
|
||||
}
|
||||
if isBranchCheckedOut {
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
} else if !hasUpstream {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{
|
||||
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
|
||||
}
|
||||
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
}
|
||||
|
||||
var menuTitle string
|
||||
if len(branches) == 1 {
|
||||
menuTitle = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
menuTitle = self.c.Tr.DeleteBranchesTitle
|
||||
}
|
||||
menuTitle := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: menuTitle,
|
||||
@@ -630,11 +611,9 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
self.c.LogAction(action)
|
||||
|
||||
worktreeGitDir := ""
|
||||
worktreePath := ""
|
||||
// if it is the current worktree path, no need to specify the path
|
||||
if !worktree.IsCurrent {
|
||||
worktreeGitDir = worktree.GitDir
|
||||
worktreePath = worktree.Path
|
||||
}
|
||||
|
||||
err := self.c.Git().Sync.Pull(
|
||||
@@ -644,7 +623,6 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
BranchName: branch.UpstreamBranch,
|
||||
FastForwardOnly: true,
|
||||
WorktreeGitDir: worktreeGitDir,
|
||||
WorktreePath: worktreePath,
|
||||
},
|
||||
)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
@@ -841,16 +819,6 @@ func (self *BranchesController) branchIsReal(branch *models.Branch) *types.Disab
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) branchesAreReal(selectedBranches []*models.Branch, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !lo.EveryBy(selectedBranches, func(branch *models.Branch) bool {
|
||||
return branch.IsRealBranch()
|
||||
}) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) notMergingIntoYourself(branch *models.Branch) *types.DisabledReason {
|
||||
selectedBranchName := branch.Name
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
|
||||
|
||||
@@ -3,9 +3,7 @@ package controllers
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type CommitDescriptionController struct {
|
||||
@@ -61,15 +59,6 @@ func (self *CommitDescriptionController) GetMouseKeybindings(opts types.Keybindi
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(types.OnFocusOpts) {
|
||||
self.c.Views().CommitDescription.Footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
|
||||
map[string]string{
|
||||
"confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToCommitMessage() error {
|
||||
self.c.Context().Replace(self.c.Contexts().CommitMessage)
|
||||
return nil
|
||||
|
||||
@@ -69,12 +69,6 @@ func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(types.OnFocusOpts) {
|
||||
self.c.Views().CommitDescription.Footer = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(types.OnFocusLostOpts) {
|
||||
self.context().RenderCommitLength()
|
||||
|
||||
@@ -664,7 +664,7 @@ func (self *FilesController) handleAmendCommitPress() error {
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
if len(self.c.Model().Commits) == 0 {
|
||||
return errors.New(self.c.Tr.NoCommitToAmend)
|
||||
}
|
||||
@@ -869,7 +869,7 @@ func (self *FilesController) openCopyMenu() error {
|
||||
OnPress: func() error {
|
||||
path := self.context().GetSelectedPath()
|
||||
hasStaged := self.hasPathStagedChanges(node)
|
||||
diff, err := self.c.Git().Diff.GetDiff(hasStaged, "--", path)
|
||||
diff, err := self.c.Git().Diff.GetPathDiff(path, hasStaged)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -894,7 +894,7 @@ func (self *FilesController) openCopyMenu() error {
|
||||
Tooltip: self.c.Tr.CopyFileDiffTooltip,
|
||||
OnPress: func() error {
|
||||
hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles()
|
||||
diff, err := self.c.Git().Diff.GetDiff(hasStaged, "--")
|
||||
diff, err := self.c.Git().Diff.GetAllDiff(hasStaged)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -69,11 +69,10 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
Modifier: gocui.ModNone,
|
||||
// we have the description on the alt key and not the main key for legacy reasons
|
||||
// (the original main key was 'x' but we've reassigned that to other purposes)
|
||||
Description: self.c.Tr.OpenKeybindingsMenu,
|
||||
Handler: self.createOptionsMenu,
|
||||
ShortDescription: self.c.Tr.Keybindings,
|
||||
DisplayOnScreen: true,
|
||||
GetDisabledReason: self.optionsMenuDisabledReason,
|
||||
Description: self.c.Tr.OpenKeybindingsMenu,
|
||||
Handler: self.createOptionsMenu,
|
||||
ShortDescription: self.c.Tr.Keybindings,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -157,17 +156,6 @@ func (self *GlobalController) createOptionsMenu() error {
|
||||
return (&OptionsMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) optionsMenuDisabledReason() *types.DisabledReason {
|
||||
ctx := self.c.Context().Current()
|
||||
// Don't show options menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
// The empty error text is intentional. We don't want to show an error
|
||||
// toast for this, but only hide it from the options map.
|
||||
return &types.DisabledReason{Text: ""}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *GlobalController) createFilteringMenu() error {
|
||||
return (&FilteringMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type BranchesHelper struct {
|
||||
@@ -25,16 +23,12 @@ func NewBranchesHelper(c *HelperCommon, worktreeHelper *WorktreeHelper) *Branche
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error {
|
||||
if len(branches) > 1 {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
}
|
||||
} else if self.checkedOutByOtherWorktree(branches[0]) {
|
||||
return self.promptWorktreeBranchDelete(branches[0])
|
||||
func (self *BranchesHelper) ConfirmLocalDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,32 +36,24 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
|
||||
doDelete := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
})
|
||||
}
|
||||
|
||||
if allBranchesMerged {
|
||||
if isMerged {
|
||||
return doDelete()
|
||||
}
|
||||
|
||||
title := self.c.Tr.ForceDeleteBranchTitle
|
||||
var message string
|
||||
if len(branches) == 1 {
|
||||
message = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
message = self.c.Tr.ForceDeleteBranchesMessage
|
||||
}
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
@@ -80,36 +66,27 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch) error {
|
||||
var title string
|
||||
if len(remoteBranches) == 1 {
|
||||
title = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": remoteBranches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
title = self.c.Tr.DeleteBranchesTitle
|
||||
}
|
||||
var prompt string
|
||||
if len(remoteBranches) == 1 {
|
||||
prompt = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"selectedBranchName": remoteBranches[0].Name,
|
||||
"upstream": remoteBranches[0].RemoteName,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt = self.c.Tr.DeleteRemoteBranchesPrompt
|
||||
}
|
||||
func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName string) error {
|
||||
title := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branchName,
|
||||
},
|
||||
)
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"selectedBranchName": branchName,
|
||||
"upstream": remoteName,
|
||||
},
|
||||
)
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, remoteName, branchName); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
@@ -120,41 +97,32 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteB
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branch) error {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var prompt string
|
||||
if len(branches) == 1 {
|
||||
prompt = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"localBranchName": branch.Name,
|
||||
"remoteBranchName": branch.UpstreamBranch,
|
||||
"remoteName": branch.UpstreamRemote,
|
||||
},
|
||||
)
|
||||
|
||||
if !isMerged {
|
||||
prompt += "\n\n" + utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"localBranchName": branches[0].Name,
|
||||
"remoteBranchName": branches[0].UpstreamBranch,
|
||||
"remoteName": branches[0].UpstreamRemote,
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt = self.c.Tr.DeleteLocalAndRemoteBranchesPrompt
|
||||
}
|
||||
|
||||
if !allBranchesMerged {
|
||||
if len(branches) == 1 {
|
||||
prompt += "\n\n" + utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt += "\n\n" + self.c.Tr.ForceDeleteBranchesMessage
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
@@ -162,24 +130,18 @@ func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branc
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
// Delete the remote branches first so that we keep the local ones
|
||||
// Delete the remote branch first so that we keep the local one
|
||||
// in case of failure
|
||||
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
|
||||
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
|
||||
})
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, branch.UpstreamRemote, branch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
})
|
||||
},
|
||||
@@ -236,30 +198,3 @@ func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Br
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) {
|
||||
allBranchesMerged := true
|
||||
for _, branch := range branches {
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isMerged {
|
||||
allBranchesMerged = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return allBranchesMerged, nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) deleteRemoteBranches(remoteBranches []*models.RemoteBranch, task gocui.Task) error {
|
||||
remotes := lo.GroupBy(remoteBranches, func(branch *models.RemoteBranch) string { return branch.RemoteName })
|
||||
for remote, branches := range remotes {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.RemoteBranch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, remote, branchNames); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -70,12 +67,8 @@ func (self *CherryPickHelper) CopyRange(commitsList []*models.Commit, context ty
|
||||
// Only to be called from the branch commits controller
|
||||
func (self *CherryPickHelper) Paste() error {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.CherryPick,
|
||||
Prompt: utils.ResolvePlaceholderString(
|
||||
self.c.Tr.SureCherryPick,
|
||||
map[string]string{
|
||||
"numCommits": strconv.Itoa(len(self.getData().CherryPickedCommits)),
|
||||
}),
|
||||
Title: self.c.Tr.CherryPick,
|
||||
Prompt: self.c.Tr.SureCherryPick,
|
||||
HandleConfirm: func() error {
|
||||
isInRebase, err := self.c.Git().Status.IsInInteractiveRebase()
|
||||
if err != nil {
|
||||
|
||||
@@ -143,7 +143,6 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
opts.SummaryTitle,
|
||||
opts.DescriptionTitle,
|
||||
opts.PreserveMessage,
|
||||
opts.InitialMessage,
|
||||
onConfirm,
|
||||
opts.OnSwitchToEditor,
|
||||
)
|
||||
@@ -178,9 +177,8 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||
func (self *CommitsHelper) CloseCommitMessagePanel() {
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
message := self.JoinCommitMessageAndUnwrappedDescription()
|
||||
if message != self.c.Contexts().CommitMessage.GetInitialMessage() {
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
}
|
||||
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
} else {
|
||||
self.SetMessageAndDescriptionInView("")
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package helpers
|
||||
import (
|
||||
goContext "context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
type ConfirmationHelper struct {
|
||||
@@ -56,9 +57,61 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
|
||||
self.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
// Temporary hack: we're just duplicating the logic in `gocui.lineWrap`
|
||||
func getMessageHeight(wrap bool, message string, width int) int {
|
||||
wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, message, width)
|
||||
return len(wrappedLines)
|
||||
return len(wrapMessageToWidth(wrap, message, width))
|
||||
}
|
||||
|
||||
func wrapMessageToWidth(wrap bool, message string, width int) []string {
|
||||
lines := strings.Split(message, "\n")
|
||||
if !wrap {
|
||||
return lines
|
||||
}
|
||||
|
||||
wrappedLines := make([]string, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
n := 0
|
||||
offset := 0
|
||||
lastWhitespaceIndex := -1
|
||||
for i, currChr := range line {
|
||||
rw := runewidth.RuneWidth(currChr)
|
||||
n += rw
|
||||
|
||||
if n > width {
|
||||
if currChr == ' ' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i + 1
|
||||
n = 0
|
||||
} else if currChr == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
|
||||
if line[lastWhitespaceIndex] == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex+1])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex + 1
|
||||
}
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
}
|
||||
lastWhitespaceIndex = -1
|
||||
} else if currChr == ' ' || currChr == '-' {
|
||||
lastWhitespaceIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
wrappedLines = append(wrappedLines, line[offset:])
|
||||
}
|
||||
|
||||
return wrappedLines
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int, parentPopupContext types.Context) (int, int, int, int) {
|
||||
@@ -276,7 +329,7 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
|
||||
var promptLines []string
|
||||
prompt := self.c.Contexts().Menu.GetPrompt()
|
||||
if len(prompt) > 0 {
|
||||
promptLines, _, _ = utils.WrapViewLinesToWidth(true, prompt, contentWidth)
|
||||
promptLines = wrapMessageToWidth(true, prompt, contentWidth)
|
||||
promptLines = append(promptLines, "")
|
||||
}
|
||||
self.c.Contexts().Menu.SetPromptLines(promptLines)
|
||||
@@ -304,14 +357,13 @@ func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
prompt := self.c.Views().Confirmation.Buffer()
|
||||
wrap := true
|
||||
if self.c.Views().Confirmation.Editable {
|
||||
prompt = self.c.Views().Confirmation.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := getMessageHeight(wrap, prompt, contentWidth) + suggestionsViewHeight
|
||||
panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@@ -35,6 +34,10 @@ func (self *DiffHelper) DiffArgs() []string {
|
||||
output = append(output, "-R")
|
||||
}
|
||||
|
||||
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
|
||||
output = append(output, "--ignore-all-space")
|
||||
}
|
||||
|
||||
output = append(output, "--")
|
||||
|
||||
file := self.currentlySelectedFilename()
|
||||
@@ -56,6 +59,9 @@ func (self *DiffHelper) GetUpdateTaskForRenderingCommitsDiff(commit *models.Comm
|
||||
if refRange != nil {
|
||||
from, to := refRange.From, refRange.To
|
||||
args := []string{from.ParentRefName(), to.RefName(), "--stat", "-p"}
|
||||
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
|
||||
args = append(args, "--ignore-all-space")
|
||||
}
|
||||
args = append(args, "--")
|
||||
if path := self.c.Modes().Filtering.GetPath(); path != "" {
|
||||
args = append(args, path)
|
||||
@@ -165,45 +171,3 @@ func (self *DiffHelper) OpenDiffToolForRef(selectedRef types.Ref) error {
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// AdjustLineNumber is used to adjust a line number in the diff that's currently
|
||||
// being viewed, so that it corresponds to the line number in the actual working
|
||||
// copy state of the file. It is used when clicking on a delta hyperlink in a
|
||||
// diff, or when pressing `e` in the staging or patch building panels. It works
|
||||
// by getting a diff of what's being viewed in the main view against the working
|
||||
// copy, and then using that diff to adjust the line number.
|
||||
// path is the file path of the file being viewed
|
||||
// linenumber is the line number to adjust (one-based)
|
||||
// viewname is the name of the view that shows the diff. We need to pass it
|
||||
// because the diff adjustment is slightly different depending on which view is
|
||||
// showing the diff.
|
||||
func (self *DiffHelper) AdjustLineNumber(path string, linenumber int, viewname string) int {
|
||||
switch viewname {
|
||||
|
||||
case "main", "patchBuilding":
|
||||
if diffableContext, ok := self.c.Context().CurrentSide().(types.DiffableContext); ok {
|
||||
ref := diffableContext.RefForAdjustingLineNumberInDiff()
|
||||
if len(ref) != 0 {
|
||||
return self.adjustLineNumber(linenumber, ref, "--", path)
|
||||
}
|
||||
}
|
||||
// if the type cast to DiffableContext returns false, we are in the
|
||||
// unstaged changes view of the Files panel; no need to adjust line
|
||||
// numbers in this case
|
||||
|
||||
case "secondary", "stagingSecondary":
|
||||
return self.adjustLineNumber(linenumber, "--", path)
|
||||
}
|
||||
|
||||
return linenumber
|
||||
}
|
||||
|
||||
func (self *DiffHelper) adjustLineNumber(linenumber int, diffArgs ...string) int {
|
||||
args := append([]string{"--unified=0"}, diffArgs...)
|
||||
diff, err := self.c.Git().Diff.GetDiff(false, args...)
|
||||
if err != nil {
|
||||
return linenumber
|
||||
}
|
||||
patch := patch.Parse(diff)
|
||||
return patch.AdjustLineNumber(linenumber)
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
|
||||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState)
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, self.c.Log)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
self.Escape()
|
||||
|
||||
@@ -18,7 +18,6 @@ type IRefsHelper interface {
|
||||
CheckoutRef(ref string, options types.CheckoutRefOptions) error
|
||||
GetCheckedOutRef() *models.Branch
|
||||
CreateGitResetMenu(ref string) error
|
||||
CreateCheckoutMenu(commit *models.Commit) error
|
||||
ResetToRef(ref string, strength string, envVars []string) error
|
||||
NewBranch(from string, fromDescription string, suggestedBranchname string) error
|
||||
}
|
||||
@@ -75,7 +74,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
return options.OnRefNotFound(ref)
|
||||
}
|
||||
|
||||
if IsSwitchBranchUncommittedChangesError(err) {
|
||||
if IsSwitchBranchUncommitedChangesError(err) {
|
||||
// offer to autostash changes
|
||||
self.c.OnUIThread(func() error {
|
||||
// (Before showing the prompt, render again to remove the inline status)
|
||||
@@ -272,53 +271,6 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error {
|
||||
branches := lo.Filter(self.c.Model().Branches, func(branch *models.Branch, _ int) bool {
|
||||
return commit.Hash == branch.CommitHash && branch.Name != self.c.Model().CheckedOutBranch
|
||||
})
|
||||
|
||||
hash := commit.Hash
|
||||
var menuItems []*types.MenuItem
|
||||
|
||||
if len(branches) > 0 {
|
||||
menuItems = append(menuItems, lo.Map(branches, func(branch *models.Branch, index int) *types.MenuItem {
|
||||
var key types.Key
|
||||
if index < 9 {
|
||||
key = rune(index + 1 + '0') // Convert 1-based index to key
|
||||
}
|
||||
return &types.MenuItem{
|
||||
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutBranchAtCommit, branch.Name)},
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
|
||||
return self.CheckoutRef(branch.RefName(), types.CheckoutRefOptions{})
|
||||
},
|
||||
Key: key,
|
||||
}
|
||||
})...)
|
||||
} else {
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.Actions.CheckoutBranch},
|
||||
OnPress: func() error { return nil },
|
||||
DisabledReason: &types.DisabledReason{Text: self.c.Tr.NoBranchesFoundAtCommitTooltip},
|
||||
Key: '1',
|
||||
})
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutCommitAsDetachedHead, utils.ShortHash(hash))},
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||
return self.CheckoutRef(hash, types.CheckoutRefOptions{})
|
||||
},
|
||||
Key: 'd',
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.CheckoutBranchOrCommit,
|
||||
Items: menuItems,
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.NewBranchNameBranchOff,
|
||||
@@ -353,7 +305,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
|
||||
newBranchFunc = self.c.Git().Branch.NewWithoutTracking
|
||||
}
|
||||
if err := newBranchFunc(newBranchName, from); err != nil {
|
||||
if IsSwitchBranchUncommittedChangesError(err) {
|
||||
if IsSwitchBranchUncommitedChangesError(err) {
|
||||
// offer to autostash changes
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.AutoStashTitle,
|
||||
@@ -413,6 +365,6 @@ func (self *RefsHelper) ParseRemoteBranchName(fullBranchName string) (string, st
|
||||
return remoteName, branchName, true
|
||||
}
|
||||
|
||||
func IsSwitchBranchUncommittedChangesError(err error) bool {
|
||||
func IsSwitchBranchUncommitedChangesError(err error) bool {
|
||||
return strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch")
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func NewSnakeHelper(c *HelperCommon) *SnakeHelper {
|
||||
func (self *SnakeHelper) StartGame() {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
game := snake.NewGame(view.InnerWidth(), view.InnerHeight(), self.renderSnakeGame, self.c.LogAction)
|
||||
game := snake.NewGame(view.Width(), view.Height(), self.renderSnakeGame, self.c.LogAction)
|
||||
self.game = game
|
||||
game.Start()
|
||||
}
|
||||
|
||||
@@ -63,11 +63,11 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
|
||||
secondaryContext.GetMutex().Lock()
|
||||
|
||||
mainContext.SetState(
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState()),
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetState(), self.c.Log),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState()),
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), self.c.Log),
|
||||
)
|
||||
|
||||
mainState := mainContext.GetState()
|
||||
|
||||
@@ -53,7 +53,7 @@ type WindowArrangementArgs struct {
|
||||
// staged and unstaged changes)
|
||||
SplitMainPanel bool
|
||||
// The current screen mode (normal, half, full)
|
||||
ScreenMode types.ScreenMode
|
||||
ScreenMode types.WindowMaximisation
|
||||
// The content shown on the bottom left of the screen when showing a loader
|
||||
// or toast e.g. 'Rebasing /'
|
||||
AppStatus string
|
||||
|
||||
@@ -422,7 +422,7 @@ func renderLayout(windows map[string]boxlayout.Dimensions) string {
|
||||
return dimensionsA.X0 < dimensionsB.X0
|
||||
})
|
||||
|
||||
// Uniquify windows by dimensions (so perfectly overlapping windows are de-duped). This prevents getting 'fileshes' as a label where the files and branches windows overlap.
|
||||
// Uniquefy windows by dimensions (so perfectly overlapping windows are de-duped). This prevents getting 'fileshes' as a label where the files and branches windows overlap.
|
||||
// branches windows overlap.
|
||||
windowNames = lo.UniqBy(windowNames, func(windowName string) boxlayout.Dimensions {
|
||||
return windows[windowName]
|
||||
|
||||
@@ -87,7 +87,7 @@ func (self *WorkingTreeHelper) OpenMergeTool() error {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage string) error {
|
||||
return self.WithEnsureCommittableFiles(func() error {
|
||||
return self.WithEnsureCommitableFiles(func() error {
|
||||
self.commitsHelper.OpenCommitMessagePanel(
|
||||
&OpenCommitMessagePanelOpts{
|
||||
CommitIndex: context.NoCommitIndex,
|
||||
@@ -131,7 +131,7 @@ func (self *WorkingTreeHelper) switchFromCommitMessagePanelToEditor(filepath str
|
||||
// HandleCommitEditorPress - handle when the user wants to commit changes via
|
||||
// their editor rather than via the popup panel
|
||||
func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
|
||||
return self.WithEnsureCommittableFiles(func() error {
|
||||
return self.WithEnsureCommitableFiles(func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.c.Git().Commit.CommitEditorCmdObj(),
|
||||
@@ -172,7 +172,7 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
|
||||
return self.HandleCommitPressWithMessage(message)
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) WithEnsureCommittableFiles(handler func() error) error {
|
||||
func (self *WorkingTreeHelper) WithEnsureCommitableFiles(handler func() error) error {
|
||||
if err := self.prepareFilesForCommit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -497,17 +497,12 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return self.updateTodos(todo.Drop, selectedCommits)
|
||||
}
|
||||
|
||||
isMerge := selectedCommits[0].IsMerge()
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DropCommitTitle,
|
||||
Prompt: lo.Ternary(isMerge, self.c.Tr.DropMergeCommitPrompt, self.c.Tr.DropCommitPrompt),
|
||||
Prompt: self.c.Tr.DropCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DroppingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
if isMerge {
|
||||
return self.dropMergeCommit(startIdx)
|
||||
}
|
||||
return self.interactiveRebase(todo.Drop, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
@@ -516,11 +511,6 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) dropMergeCommit(commitIdx int) error {
|
||||
err := self.c.Git().Rebase.DropMergeCommit(self.c.Model().Commits, commitIdx)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Edit, selectedCommits)
|
||||
@@ -736,7 +726,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
if err := self.c.Helpers().AmendHelper.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -752,7 +742,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx())
|
||||
@@ -928,7 +918,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
Label: self.c.Tr.FixupMenu_Fixup,
|
||||
Key: 'f',
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
|
||||
if err := self.c.Git().Commit.CreateFixupCommit(commit.Hash); err != nil {
|
||||
@@ -951,7 +941,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
Label: self.c.Tr.FixupMenu_AmendWithChanges,
|
||||
Key: 'a',
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.createAmendCommit(commit, true)
|
||||
})
|
||||
},
|
||||
@@ -1282,11 +1272,6 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
OnPress: onPress("author-date-order"),
|
||||
Widget: types.MakeMenuRadioButton(currentValue == "author-date-order"),
|
||||
},
|
||||
{
|
||||
Label: "default",
|
||||
OnPress: onPress("default"),
|
||||
Widget: types.MakeMenuRadioButton(currentValue == "default"),
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -1368,15 +1353,11 @@ func (self *LocalCommitsController) canFindCommitForSquashFixupsInCurrentBranch(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canSquashOrFixup(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
func (self *LocalCommitsController) canSquashOrFixup(_selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if endIdx >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
|
||||
}
|
||||
|
||||
if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupMergeCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1434,10 +1415,6 @@ func (self *LocalCommitsController) midRebaseCommandEnabled(selectedCommits []*m
|
||||
// Ensures that if we are mid-rebase, we're only selecting commits that can be moved
|
||||
func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveMergeCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1458,10 +1435,6 @@ func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits
|
||||
|
||||
func (self *LocalCommitsController) canDropCommits(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
if len(selectedCommits) > 1 && lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.DroppingMergeRequiresSingleSelection}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ type OptionsMenuAction struct {
|
||||
|
||||
func (self *OptionsMenuAction) Call() error {
|
||||
ctx := self.c.Context().Current()
|
||||
// Don't show menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
return nil
|
||||
}
|
||||
|
||||
local, global, navigation := self.getBindings(ctx)
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
|
||||
@@ -65,7 +65,7 @@ func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(opts types.OnFocusOpts) {
|
||||
// no need to change wrap on the secondary view because it can't be interacted with
|
||||
self.c.Views().PatchBuilding.Wrap = self.c.UserConfig().Gui.WrapLinesInStagingView
|
||||
self.c.Views().PatchBuilding.Wrap = false
|
||||
|
||||
self.c.Helpers().PatchBuilding.RefreshPatchBuildingPanel(opts)
|
||||
}
|
||||
@@ -73,8 +73,6 @@ func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
|
||||
func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(opts types.OnFocusLostOpts) {
|
||||
self.context().SetState(nil)
|
||||
|
||||
self.c.Views().PatchBuilding.Wrap = true
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
@@ -107,7 +105,6 @@ func (self *PatchBuildingController) EditFile() error {
|
||||
}
|
||||
|
||||
lineNumber := self.context().GetState().CurrentLineNumber()
|
||||
lineNumber = self.c.Helpers().Diff.AdjustLineNumber(path, lineNumber, self.context().GetViewName())
|
||||
return self.c.Helpers().Files.EditFileAtLine(path, lineNumber)
|
||||
}
|
||||
|
||||
@@ -137,13 +134,13 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedPatchLineIdx())
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
firstLineIdx, lastLineIdx := state.SelectedPatchRange()
|
||||
firstLineIdx, lastLineIdx := state.SelectedRange()
|
||||
|
||||
if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
|
||||
// might actually want to return an error here
|
||||
|
||||
@@ -170,9 +170,9 @@ func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
}
|
||||
|
||||
func (self *PatchExplorerController) HandlePrevLine() error {
|
||||
before := self.context.GetState().GetSelectedViewLineIdx()
|
||||
before := self.context.GetState().GetSelectedLineIdx()
|
||||
self.context.GetState().CycleSelection(false)
|
||||
after := self.context.GetState().GetSelectedViewLineIdx()
|
||||
after := self.context.GetState().GetSelectedLineIdx()
|
||||
|
||||
if self.context.GetState().SelectingLine() {
|
||||
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
|
||||
@@ -182,9 +182,9 @@ func (self *PatchExplorerController) HandlePrevLine() error {
|
||||
}
|
||||
|
||||
func (self *PatchExplorerController) HandleNextLine() error {
|
||||
before := self.context.GetState().GetSelectedViewLineIdx()
|
||||
before := self.context.GetState().GetSelectedLineIdx()
|
||||
self.context.GetState().CycleSelection(true)
|
||||
after := self.context.GetState().GetSelectedViewLineIdx()
|
||||
after := self.context.GetState().GetSelectedLineIdx()
|
||||
|
||||
if self.context.GetState().SelectingLine() {
|
||||
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
|
||||
|
||||
@@ -66,8 +66,8 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.withItems(self.delete),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected()),
|
||||
Handler: self.withItem(self.delete),
|
||||
GetDisabledReason: self.require(self.singleItemSelected()),
|
||||
Description: self.c.Tr.Delete,
|
||||
Tooltip: self.c.Tr.DeleteRemoteBranchTooltip,
|
||||
DisplayOnScreen: true,
|
||||
@@ -132,8 +132,8 @@ func (self *RemoteBranchesController) context() *context.RemoteBranchesContext {
|
||||
return self.c.Contexts().RemoteBranches
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) delete(selectedBranches []*models.RemoteBranch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranches)
|
||||
func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranch.RemoteName, selectedBranch.Name)
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) merge(selectedBranch *models.RemoteBranch) error {
|
||||
|
||||
@@ -12,7 +12,7 @@ type ScreenModeActions struct {
|
||||
func (self *ScreenModeActions) Next() error {
|
||||
self.c.State().GetRepoState().SetScreenMode(
|
||||
nextIntInCycle(
|
||||
[]types.ScreenMode{types.SCREEN_NORMAL, types.SCREEN_HALF, types.SCREEN_FULL},
|
||||
[]types.WindowMaximisation{types.SCREEN_NORMAL, types.SCREEN_HALF, types.SCREEN_FULL},
|
||||
self.c.State().GetRepoState().GetScreenMode(),
|
||||
),
|
||||
)
|
||||
@@ -24,7 +24,7 @@ func (self *ScreenModeActions) Next() error {
|
||||
func (self *ScreenModeActions) Prev() error {
|
||||
self.c.State().GetRepoState().SetScreenMode(
|
||||
prevIntInCycle(
|
||||
[]types.ScreenMode{types.SCREEN_NORMAL, types.SCREEN_HALF, types.SCREEN_FULL},
|
||||
[]types.WindowMaximisation{types.SCREEN_NORMAL, types.SCREEN_HALF, types.SCREEN_FULL},
|
||||
self.c.State().GetRepoState().GetScreenMode(),
|
||||
),
|
||||
)
|
||||
@@ -53,7 +53,7 @@ func (self *ScreenModeActions) rerenderView(view *gocui.View) {
|
||||
context.HandleRender()
|
||||
}
|
||||
|
||||
func nextIntInCycle(sl []types.ScreenMode, current types.ScreenMode) types.ScreenMode {
|
||||
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i == len(sl)-1 {
|
||||
@@ -65,7 +65,7 @@ func nextIntInCycle(sl []types.ScreenMode, current types.ScreenMode) types.Scree
|
||||
return sl[0]
|
||||
}
|
||||
|
||||
func prevIntInCycle(sl []types.ScreenMode, current types.ScreenMode) types.ScreenMode {
|
||||
func prevIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i > 0 {
|
||||
|
||||
@@ -118,9 +118,8 @@ func (self *StagingController) GetMouseKeybindings(opts types.KeybindingsOpts) [
|
||||
|
||||
func (self *StagingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(opts types.OnFocusOpts) {
|
||||
wrap := self.c.UserConfig().Gui.WrapLinesInStagingView
|
||||
self.c.Views().Staging.Wrap = wrap
|
||||
self.c.Views().StagingSecondary.Wrap = wrap
|
||||
self.c.Views().Staging.Wrap = false
|
||||
self.c.Views().StagingSecondary.Wrap = false
|
||||
|
||||
self.c.Helpers().Staging.RefreshStagingPanel(opts)
|
||||
}
|
||||
@@ -161,7 +160,6 @@ func (self *StagingController) EditFile() error {
|
||||
}
|
||||
|
||||
lineNumber := self.context.GetState().CurrentLineNumber()
|
||||
lineNumber = self.c.Helpers().Diff.AdjustLineNumber(path, lineNumber, self.context.GetViewName())
|
||||
return self.c.Helpers().Files.EditFileAtLine(path, lineNumber)
|
||||
}
|
||||
|
||||
@@ -222,7 +220,7 @@ func (self *StagingController) applySelection(reverse bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
firstLineIdx, lastLineIdx := state.SelectedPatchRange()
|
||||
firstLineIdx, lastLineIdx := state.SelectedRange()
|
||||
patchToApply := patch.
|
||||
Parse(state.GetDiff()).
|
||||
Transform(patch.TransformOpts{
|
||||
@@ -251,7 +249,7 @@ func (self *StagingController) applySelection(reverse bool) error {
|
||||
}
|
||||
|
||||
if state.SelectingRange() {
|
||||
firstLine, _ := state.SelectedViewRange()
|
||||
firstLine, _ := state.SelectedRange()
|
||||
state.SelectLine(firstLine)
|
||||
}
|
||||
|
||||
@@ -292,7 +290,7 @@ func (self *StagingController) editHunk() error {
|
||||
}
|
||||
|
||||
lineOffset := 3
|
||||
lineIdxInHunk := state.GetSelectedPatchLineIdx() - hunkStartIdx
|
||||
lineIdxInHunk := state.GetSelectedLineIdx() - hunkStartIdx
|
||||
if err := self.c.Helpers().Files.EditFileAtLineAndWait(patchFilepath, lineIdxInHunk+lineOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ func (self *FilesController) animateExplosion() {
|
||||
// Animates an explosion within the view by drawing a bunch of flamey characters
|
||||
func (self *FilesController) Explode(v *gocui.View, onDone func()) {
|
||||
width := v.InnerWidth()
|
||||
height := v.InnerHeight()
|
||||
height := v.InnerHeight() + 1
|
||||
styles := []style.TextStyle{
|
||||
style.FgLightWhite.SetBold(),
|
||||
style.FgYellow.SetBold(),
|
||||
|
||||
+19
-22
@@ -108,6 +108,8 @@ type Gui struct {
|
||||
|
||||
PopupHandler types.IPopupHandler
|
||||
|
||||
IsNewRepo bool
|
||||
|
||||
IsRefreshingFiles bool
|
||||
|
||||
// we use this to decide whether we'll return to the original directory that
|
||||
@@ -244,7 +246,7 @@ type GuiRepoState struct {
|
||||
// back in sync with the repo state
|
||||
ViewsSetup bool
|
||||
|
||||
ScreenMode types.ScreenMode
|
||||
ScreenMode types.WindowMaximisation
|
||||
|
||||
CurrentPopupOpts *types.CreatePopupPanelOpts
|
||||
}
|
||||
@@ -275,11 +277,11 @@ func (self *GuiRepoState) SetCurrentPopupOpts(value *types.CreatePopupPanelOpts)
|
||||
self.CurrentPopupOpts = value
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetScreenMode() types.ScreenMode {
|
||||
func (self *GuiRepoState) GetScreenMode() types.WindowMaximisation {
|
||||
return self.ScreenMode
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) SetScreenMode(value types.ScreenMode) {
|
||||
func (self *GuiRepoState) SetScreenMode(value types.WindowMaximisation) {
|
||||
self.ScreenMode = value
|
||||
}
|
||||
|
||||
@@ -358,7 +360,7 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
|
||||
return nil
|
||||
})
|
||||
|
||||
gui.g.SetOpenHyperlinkFunc(func(url string, viewname string) error {
|
||||
gui.g.SetOpenHyperlinkFunc(func(url string) error {
|
||||
if strings.HasPrefix(url, "lazygit-edit:") {
|
||||
re := regexp.MustCompile(`^lazygit-edit://(.+?)(?::(\d+))?$`)
|
||||
matches := re.FindStringSubmatch(url)
|
||||
@@ -368,7 +370,6 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
|
||||
filepath := matches[1]
|
||||
if matches[2] != "" {
|
||||
lineNumber := utils.MustConvertToInt(matches[2])
|
||||
lineNumber = gui.helpers.Diff.AdjustLineNumber(filepath, lineNumber, viewname)
|
||||
return gui.helpers.Files.EditFileAtLine(filepath, lineNumber)
|
||||
}
|
||||
return gui.helpers.Files.EditFiles([]string{filepath})
|
||||
@@ -580,24 +581,20 @@ func initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSaf
|
||||
return result
|
||||
}
|
||||
|
||||
func initialScreenMode(startArgs appTypes.StartArgs, config config.AppConfigurer) types.ScreenMode {
|
||||
if startArgs.ScreenMode != "" {
|
||||
return parseScreenModeArg(startArgs.ScreenMode)
|
||||
} else if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone {
|
||||
return types.SCREEN_HALF
|
||||
} else {
|
||||
return parseScreenModeArg(config.GetUserConfig().Gui.ScreenMode)
|
||||
}
|
||||
}
|
||||
|
||||
func parseScreenModeArg(screenModeArg string) types.ScreenMode {
|
||||
switch screenModeArg {
|
||||
case "half":
|
||||
return types.SCREEN_HALF
|
||||
case "full":
|
||||
func initialScreenMode(startArgs appTypes.StartArgs, config config.AppConfigurer) types.WindowMaximisation {
|
||||
if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone {
|
||||
return types.SCREEN_FULL
|
||||
default:
|
||||
return types.SCREEN_NORMAL
|
||||
} else {
|
||||
defaultWindowSize := config.GetUserConfig().Gui.WindowSize
|
||||
|
||||
switch defaultWindowSize {
|
||||
case "half":
|
||||
return types.SCREEN_HALF
|
||||
case "full":
|
||||
return types.SCREEN_FULL
|
||||
default:
|
||||
return types.SCREEN_NORMAL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ func (gui *Gui) handleInfoClick() error {
|
||||
view := gui.Views.Information
|
||||
|
||||
cx, _ := view.Cursor()
|
||||
width := view.Width()
|
||||
width, _ := view.Size()
|
||||
|
||||
if activeMode, ok := gui.helpers.Mode.GetActiveMode(); ok {
|
||||
if width-cx > utils.StringWidth(gui.c.Tr.ResetInParentheses) {
|
||||
|
||||
+13
-9
@@ -193,6 +193,18 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Tooltip: self.c.Tr.OpenCommandLogMenuTooltip,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollUpSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollDownSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
@@ -207,12 +219,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Description: self.c.Tr.ScrollUp,
|
||||
Alternative: "fn+down",
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollDownSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelUp,
|
||||
@@ -453,9 +459,7 @@ func (gui *Gui) callKeybindingHandler(binding *types.Binding) error {
|
||||
return errors.New(disabledReason.Text)
|
||||
}
|
||||
|
||||
if len(disabledReason.Text) > 0 {
|
||||
gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text)
|
||||
}
|
||||
gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text)
|
||||
return nil
|
||||
}
|
||||
return binding.Handler()
|
||||
|
||||
+8
-6
@@ -30,8 +30,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
// reading more lines into main view buffers upon resize
|
||||
prevMainView := gui.Views.Main
|
||||
if prevMainView != nil {
|
||||
prevMainHeight := prevMainView.Height()
|
||||
newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 + 1
|
||||
_, prevMainHeight := prevMainView.Size()
|
||||
newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 - 1
|
||||
heightDiff := newMainHeight - prevMainHeight
|
||||
if heightDiff > 0 {
|
||||
if manager, ok := gui.viewBufferManagerMap["main"]; ok {
|
||||
@@ -87,15 +87,17 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES {
|
||||
oldWidth := view.Width()
|
||||
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 1
|
||||
// view.Width() returns the width -1 for some reason
|
||||
oldWidth := view.Width() + 1
|
||||
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
|
||||
if oldWidth != newWidth {
|
||||
mustRerender = true
|
||||
}
|
||||
}
|
||||
if context.NeedsRerenderOnHeightChange() {
|
||||
oldHeight := view.Height()
|
||||
newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 1
|
||||
// view.Height() returns the height -1 for some reason
|
||||
oldHeight := view.Height() + 1
|
||||
newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 2*frameOffset
|
||||
if oldHeight != newHeight {
|
||||
mustRerender = true
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
|
||||
}
|
||||
|
||||
func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string {
|
||||
width := self.c.Views().Options.InnerWidth() - 2 // -2 for some padding
|
||||
width := self.c.Views().Options.Width() - 4 // -4 for the padding
|
||||
var builder strings.Builder
|
||||
ellipsis := "…"
|
||||
separator := " | "
|
||||
|
||||
@@ -11,8 +11,8 @@ func calculateOrigin(currentOrigin int, bufferHeight int, numLines int, firstLin
|
||||
// is as close to being in view as possible.
|
||||
func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, numLines int, needToSeeIdx int, wantToSeeIdx int) int {
|
||||
origin := currentOrigin
|
||||
if needToSeeIdx < currentOrigin || needToSeeIdx >= currentOrigin+bufferHeight {
|
||||
origin = max(min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight), 0)
|
||||
if needToSeeIdx < currentOrigin || needToSeeIdx > currentOrigin+bufferHeight {
|
||||
origin = max(min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight-1), 0)
|
||||
}
|
||||
|
||||
bottom := origin + bufferHeight
|
||||
@@ -21,7 +21,7 @@ func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight in
|
||||
requiredChange := origin - wantToSeeIdx
|
||||
allowedChange := bottom - needToSeeIdx
|
||||
return origin - min(requiredChange, allowedChange)
|
||||
} else if wantToSeeIdx >= bottom {
|
||||
} else if wantToSeeIdx > origin+bufferHeight {
|
||||
requiredChange := wantToSeeIdx - bottom
|
||||
allowedChange := needToSeeIdx - origin
|
||||
return origin + min(requiredChange, allowedChange)
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestNewOrigin(t *testing.T) {
|
||||
lastLineIdx: 199,
|
||||
selectedLineIdx: 199,
|
||||
selectMode: LINE,
|
||||
expected: 100,
|
||||
expected: 99,
|
||||
},
|
||||
{
|
||||
name: "selection within scroll window",
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package patch_exploring
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// State represents the current state of the patch explorer context i.e. when
|
||||
// you're staging a file or you're building a patch from an existing commit
|
||||
// this struct holds the info about the diff you're interacting with and what's currently selected.
|
||||
type State struct {
|
||||
// These are in terms of view lines (wrapped), not patch lines
|
||||
selectedLineIdx int
|
||||
rangeStartLineIdx int
|
||||
// If a range is sticky, it means we expand the range when we move up or down.
|
||||
@@ -22,11 +18,6 @@ type State struct {
|
||||
diff string
|
||||
patch *patch.Patch
|
||||
selectMode selectMode
|
||||
|
||||
// Array of indices of the wrapped lines indexed by a patch line index
|
||||
viewLineIndices []int
|
||||
// Array of indices of the original patch lines indexed by a wrapped view line index
|
||||
patchLineIndices []int
|
||||
}
|
||||
|
||||
// these represent what select mode we're in
|
||||
@@ -38,10 +29,10 @@ const (
|
||||
HUNK
|
||||
)
|
||||
|
||||
func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State) *State {
|
||||
func NewState(diff string, selectedLineIdx int, oldState *State, log *logrus.Entry) *State {
|
||||
if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 {
|
||||
// if we're here then we can return the old state. If selectedLineIdx was not -1
|
||||
// then that would mean we were trying to click and potentially drag a range, which
|
||||
// then that would mean we were trying to click and potentiall drag a range, which
|
||||
// is why in that case we continue below
|
||||
return oldState
|
||||
}
|
||||
@@ -52,8 +43,6 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
return nil
|
||||
}
|
||||
|
||||
viewLineIndices, patchLineIndices := wrapPatchLines(diff, view)
|
||||
|
||||
rangeStartLineIdx := 0
|
||||
if oldState != nil {
|
||||
rangeStartLineIdx = oldState.rangeStartLineIdx
|
||||
@@ -62,10 +51,6 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
selectMode := LINE
|
||||
// if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line
|
||||
if selectedLineIdx >= 0 {
|
||||
// Clamp to the number of wrapped view lines; index might be out of
|
||||
// bounds if a custom pager is being used which produces more lines
|
||||
selectedLineIdx = min(selectedLineIdx, len(viewLineIndices)-1)
|
||||
|
||||
selectMode = RANGE
|
||||
rangeStartLineIdx = selectedLineIdx
|
||||
} else if oldState != nil {
|
||||
@@ -73,9 +58,9 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
if oldState.selectMode == HUNK {
|
||||
selectMode = HUNK
|
||||
}
|
||||
selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(oldState.patchLineIndices[oldState.selectedLineIdx])]
|
||||
selectedLineIdx = patch.GetNextChangeIdx(oldState.selectedLineIdx)
|
||||
} else {
|
||||
selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(0)]
|
||||
selectedLineIdx = patch.GetNextChangeIdx(0)
|
||||
}
|
||||
|
||||
return &State{
|
||||
@@ -85,33 +70,10 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
rangeStartLineIdx: rangeStartLineIdx,
|
||||
rangeIsSticky: false,
|
||||
diff: diff,
|
||||
viewLineIndices: viewLineIndices,
|
||||
patchLineIndices: patchLineIndices,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) OnViewWidthChanged(view *gocui.View) {
|
||||
if !view.Wrap {
|
||||
return
|
||||
}
|
||||
|
||||
selectedPatchLineIdx := s.patchLineIndices[s.selectedLineIdx]
|
||||
var rangeStartPatchLineIdx int
|
||||
if s.selectMode == RANGE {
|
||||
rangeStartPatchLineIdx = s.patchLineIndices[s.rangeStartLineIdx]
|
||||
}
|
||||
s.viewLineIndices, s.patchLineIndices = wrapPatchLines(s.diff, view)
|
||||
s.selectedLineIdx = s.viewLineIndices[selectedPatchLineIdx]
|
||||
if s.selectMode == RANGE {
|
||||
s.rangeStartLineIdx = s.viewLineIndices[rangeStartPatchLineIdx]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) GetSelectedPatchLineIdx() int {
|
||||
return s.patchLineIndices[s.selectedLineIdx]
|
||||
}
|
||||
|
||||
func (s *State) GetSelectedViewLineIdx() int {
|
||||
func (s *State) GetSelectedLineIdx() int {
|
||||
return s.selectedLineIdx
|
||||
}
|
||||
|
||||
@@ -181,8 +143,8 @@ func (s *State) SelectLine(newSelectedLineIdx int) {
|
||||
func (s *State) selectLineWithoutRangeCheck(newSelectedLineIdx int) {
|
||||
if newSelectedLineIdx < 0 {
|
||||
newSelectedLineIdx = 0
|
||||
} else if newSelectedLineIdx > len(s.patchLineIndices)-1 {
|
||||
newSelectedLineIdx = len(s.patchLineIndices) - 1
|
||||
} else if newSelectedLineIdx > s.patch.LineCount()-1 {
|
||||
newSelectedLineIdx = s.patch.LineCount() - 1
|
||||
}
|
||||
|
||||
s.selectedLineIdx = newSelectedLineIdx
|
||||
@@ -216,12 +178,12 @@ func (s *State) CycleHunk(forward bool) {
|
||||
change = -1
|
||||
}
|
||||
|
||||
hunkIdx := s.patch.HunkContainingLine(s.patchLineIndices[s.selectedLineIdx])
|
||||
hunkIdx := s.patch.HunkContainingLine(s.selectedLineIdx)
|
||||
if hunkIdx != -1 {
|
||||
newHunkIdx := hunkIdx + change
|
||||
if newHunkIdx >= 0 && newHunkIdx < s.patch.HunkCount() {
|
||||
start := s.patch.HunkStartIdx(newHunkIdx)
|
||||
s.selectedLineIdx = s.viewLineIndices[s.patch.GetNextChangeIdx(start)]
|
||||
s.selectedLineIdx = s.patch.GetNextChangeIdx(start)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,17 +216,16 @@ func (s *State) CycleRange(forward bool) {
|
||||
|
||||
// returns first and last patch line index of current hunk
|
||||
func (s *State) CurrentHunkBounds() (int, int) {
|
||||
hunkIdx := s.patch.HunkContainingLine(s.patchLineIndices[s.selectedLineIdx])
|
||||
hunkIdx := s.patch.HunkContainingLine(s.selectedLineIdx)
|
||||
start := s.patch.HunkStartIdx(hunkIdx)
|
||||
end := s.patch.HunkEndIdx(hunkIdx)
|
||||
return start, end
|
||||
}
|
||||
|
||||
func (s *State) SelectedViewRange() (int, int) {
|
||||
func (s *State) SelectedRange() (int, int) {
|
||||
switch s.selectMode {
|
||||
case HUNK:
|
||||
start, end := s.CurrentHunkBounds()
|
||||
return s.viewLineIndices[start], s.viewLineIndices[end]
|
||||
return s.CurrentHunkBounds()
|
||||
case RANGE:
|
||||
if s.rangeStartLineIdx > s.selectedLineIdx {
|
||||
return s.selectedLineIdx, s.rangeStartLineIdx
|
||||
@@ -279,13 +240,8 @@ func (s *State) SelectedViewRange() (int, int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) SelectedPatchRange() (int, int) {
|
||||
start, end := s.SelectedViewRange()
|
||||
return s.patchLineIndices[start], s.patchLineIndices[end]
|
||||
}
|
||||
|
||||
func (s *State) CurrentLineNumber() int {
|
||||
return s.patch.LineNumberOfLine(s.patchLineIndices[s.selectedLineIdx])
|
||||
return s.patch.LineNumberOfLine(s.selectedLineIdx)
|
||||
}
|
||||
|
||||
func (s *State) AdjustSelectedLineIdx(change int) {
|
||||
@@ -301,13 +257,13 @@ func (s *State) RenderForLineIndices(includedLineIndices []int) string {
|
||||
}
|
||||
|
||||
func (s *State) PlainRenderSelected() string {
|
||||
firstLineIdx, lastLineIdx := s.SelectedPatchRange()
|
||||
firstLineIdx, lastLineIdx := s.SelectedRange()
|
||||
return s.patch.FormatRangePlain(firstLineIdx, lastLineIdx)
|
||||
}
|
||||
|
||||
func (s *State) SelectBottom() {
|
||||
s.DismissHunkSelectMode()
|
||||
s.SelectLine(len(s.patchLineIndices) - 1)
|
||||
s.SelectLine(s.patch.LineCount() - 1)
|
||||
}
|
||||
|
||||
func (s *State) SelectTop() {
|
||||
@@ -316,13 +272,15 @@ func (s *State) SelectTop() {
|
||||
}
|
||||
|
||||
func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int, numLines int) int {
|
||||
firstLineIdx, lastLineIdx := s.SelectedViewRange()
|
||||
firstLineIdx, lastLineIdx := s.SelectedRange()
|
||||
|
||||
return calculateOrigin(currentOrigin, bufferHeight, numLines, firstLineIdx, lastLineIdx, s.GetSelectedViewLineIdx(), s.selectMode)
|
||||
return calculateOrigin(currentOrigin, bufferHeight, numLines, firstLineIdx, lastLineIdx, s.GetSelectedLineIdx(), s.selectMode)
|
||||
}
|
||||
|
||||
func wrapPatchLines(diff string, view *gocui.View) ([]int, []int) {
|
||||
_, viewLineIndices, patchLineIndices := utils.WrapViewLinesToWidth(
|
||||
view.Wrap, strings.TrimSuffix(diff, "\n"), view.InnerWidth())
|
||||
return viewLineIndices, patchLineIndices
|
||||
func (s *State) RangeStartLineIdx() (int, bool) {
|
||||
if s.selectMode == RANGE {
|
||||
return s.rangeStartLineIdx, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -22,13 +22,12 @@ func RenderFileTree(
|
||||
tree filetree.IFileTree,
|
||||
submoduleConfigs []*models.SubmoduleConfig,
|
||||
showFileIcons bool,
|
||||
showNumstat bool,
|
||||
) []string {
|
||||
collapsedPaths := tree.CollapsedPaths()
|
||||
return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.File], treeDepth int, visualDepth int, isCollapsed bool) string {
|
||||
fileNode := filetree.NewFileNode(node)
|
||||
|
||||
return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node)
|
||||
return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showFileIcons, submoduleConfigs, node)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -112,7 +111,6 @@ func getFileLine(
|
||||
hasStagedChanges bool,
|
||||
treeDepth int,
|
||||
visualDepth int,
|
||||
showNumstat,
|
||||
showFileIcons bool,
|
||||
submoduleConfigs []*models.SubmoduleConfig,
|
||||
node *filetree.Node[models.File],
|
||||
@@ -157,7 +155,7 @@ func getFileLine(
|
||||
|
||||
if showFileIcons {
|
||||
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
|
||||
paint := color.HEX(icon.Color, false)
|
||||
paint := color.C256(icon.Color, false)
|
||||
output += paint.Sprint(icon.Icon) + nameColor.Sprint(" ")
|
||||
}
|
||||
|
||||
@@ -167,12 +165,6 @@ func getFileLine(
|
||||
output += theme.DefaultTextColor.Sprint(" (submodule)")
|
||||
}
|
||||
|
||||
if file != nil && showNumstat {
|
||||
if lineChanges := formatLineChanges(file.LinesAdded, file.LinesDeleted); lineChanges != "" {
|
||||
output += " " + lineChanges
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -194,23 +186,6 @@ func formatFileStatus(file *models.File, restColor style.TextStyle) string {
|
||||
return firstCharCl.Sprint(firstChar) + secondCharCl.Sprint(secondChar)
|
||||
}
|
||||
|
||||
func formatLineChanges(linesAdded, linesDeleted int) string {
|
||||
output := ""
|
||||
|
||||
if linesAdded != 0 {
|
||||
output += style.FgGreen.Sprintf("+%d", linesAdded)
|
||||
}
|
||||
|
||||
if linesDeleted != 0 {
|
||||
if output != "" {
|
||||
output += " "
|
||||
}
|
||||
output += style.FgRed.Sprintf("-%d", linesDeleted)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func getCommitFileLine(
|
||||
isCollapsed bool,
|
||||
treeDepth int,
|
||||
@@ -267,7 +242,7 @@ func getCommitFileLine(
|
||||
|
||||
if showFileIcons {
|
||||
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
|
||||
paint := color.HEX(icon.Color, false)
|
||||
paint := color.C256(icon.Color, false)
|
||||
output += paint.Sprint(icon.Icon) + " "
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,11 @@ func toStringSlice(str string) []string {
|
||||
|
||||
func TestRenderFileTree(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
root *filetree.FileNode
|
||||
files []*models.File
|
||||
collapsedPaths []string
|
||||
showLineChanges bool
|
||||
expected []string
|
||||
name string
|
||||
root *filetree.FileNode
|
||||
files []*models.File
|
||||
collapsedPaths []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "nil node",
|
||||
@@ -38,22 +37,6 @@ func TestRenderFileTree(t *testing.T) {
|
||||
},
|
||||
expected: []string{" M test"},
|
||||
},
|
||||
{
|
||||
name: "numstat",
|
||||
files: []*models.File{
|
||||
{Name: "test", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1, LinesDeleted: 1},
|
||||
{Name: "test2", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1},
|
||||
{Name: "test3", ShortStatus: " M", HasStagedChanges: true, LinesDeleted: 1},
|
||||
{Name: "test4", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 0, LinesDeleted: 0},
|
||||
},
|
||||
showLineChanges: true,
|
||||
expected: []string{
|
||||
" M test +1 -1",
|
||||
" M test2 +1",
|
||||
" M test3 -1",
|
||||
" M test4",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "big example",
|
||||
files: []*models.File{
|
||||
@@ -89,7 +72,7 @@ M file1
|
||||
for _, path := range s.collapsedPaths {
|
||||
viewModel.ToggleCollapsed(path)
|
||||
}
|
||||
result := RenderFileTree(viewModel, nil, false, s.showLineChanges)
|
||||
result := RenderFileTree(viewModel, nil, false)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user