Compare commits

...
62 Commits
Author SHA1 Message Date
Stefan Haller 1d0db51caf Update docs and schema for release (#5276) 2026-02-07 09:33:09 +01:00
Stefan Haller 980a8ca53e Update docs and schema for release 2026-02-07 09:29:34 +01:00
Stefan Haller aa7d26be59 Auto-update cheatsheets after updating translation (#5275)
I keep forgetting to do this, and I think there's no reason to update
translations without also updating cheatsheets, so do it automatically.
2026-02-07 09:24:01 +01:00
Stefan Haller 4b69d93660 Auto-update cheatsheets after updating translation
I keep forgetting to do this, and I think there's no reason to update
translations without also updating cheatsheets, so do it automatically.
2026-02-07 09:21:22 +01:00
Stefan Haller 82e283429a Update translations from Crowdin (#5274) 2026-02-07 09:13:06 +01:00
Stefan Haller 6edf29b0dc Update translations from Crowdin 2026-02-07 09:10:45 +01:00
Stefan Haller c9a883b273 fix(config): add Nushell support for nvim-remote preset (#5250)
Using Nushell's native if/else syntax to avoid '&&' syntax errors in the
nvim-remote editor preset.
Closes #5211
2026-02-07 09:03:18 +01:00
baiyei_dk 7eb561921b fix(config): add Nushell support for nvim-remote preset
Using Nushell's native if/else syntax to avoid '&&' syntax errors
in the nvim-remote editor preset.
Closes #5211
2026-02-07 09:00:10 +01:00
Stefan Haller 48f2e1dfc1 Update README to add GitArbor TUI as an alternative (#5257) 2026-02-07 08:55:32 +01:00
cadamsdev 2f575a0b37 Update README to mention GitArbor as an alternative 2026-02-07 08:53:14 +01:00
Stefan Haller 641740fdec Disable the automatic, scheduled release (#5272)
There's too much manual work involved in releasing now (updating
translations, copying docs-master) to make automatic releases
infeasible. I'm not worried that I forget to release on the first
Saturday of every month.

Keeping the code commented out in case we want to re-enable it at some
point.
2026-02-06 19:39:51 +01:00
Stefan Haller bdc780a890 Disable the automatic, scheduled release
There's too much manual work involved in releasing now (updating translations,
copying docs-master) to make automatic releases infeasible. I'm not worried that
I forget to release on the first Saturday of every month.

Keeping the code commented out in case we want to re-enable it at some point.
2026-02-06 19:36:58 +01:00
Stefan Haller 443452ad6a Fix rendering of the Reflog panel when using up/down to scroll it (#5248)
This is a regression introduced with #5135. I didn't notice it because I
am using `gui.scrollOffBehavior: jump`, in which case the problem
wouldn't appear; it only happened with `scrollOffBehavior: margin`
(which is the default). In that case, if you used the arrow keys (or
j/k) to move the selection so that the view would start to scroll, empty
space would appear.

Fixes #5235.
2026-01-29 14:26:21 +01:00
Stefan Haller d97531aeef Fix rendering of the Reflog panel when using up/down to scroll it
This is a regression introduced with #5135. I didn't notice it because I am
using `gui.scrollOffBehavior: jump`, in which case the problem wouldn't appear;
it only happened with `scrollOffBehavior: margin` (which is the default). In
that case, if you used the arrow keys (or j/k) to move the selection so that the
view would start to scroll, empty space would appear.
2026-01-29 14:23:16 +01:00
Stefan Haller 819212b758 Fix gitignore path collisions (#5245)
### PR Description

Paths added to ignore/exclude files need to be prefixed with a forward
slash to point to a specific file in the directory tree.

Without that prefix a file at root called `file` (added to `.gitignore`
as `file`) would match with `./file` and `./src/file`.

Example flow:
1. User creates a directory called `tests` in root
2. User sees that it's tracked
3. User ignores the directory in Lazygit
4. Changes in `src/tests` are now missing - because `tests` without `/`
matches also directories lower in the tree
2026-01-28 21:12:35 +01:00
Dawid Pietrykowski 48a8a71d31 Fix gitignore path collisions
Paths added to ignore/exclude files need to be prefixed with a forward
slash to point to a specific file in the directory tree.

Without that prefix a file at root called `file` (added to `.gitignore`
as `file`) would match with `./file` and `./src/file`.
2026-01-28 21:10:29 +01:00
Stefan Haller be703f3d27 Fix rendering of CRLF sequence ('\r\n') (#5237)
Fixes #5188.
2026-01-28 20:59:56 +01:00
Stefan Haller 06a9ff12ab Bump gocui
This brings in https://github.com/jesseduffield/gocui/pull/98 with the following
fix:

Fix rendering of CRLF sequence ('\r\n')

The FirstGraphemeCluster call returns this as a single character; we want to
treat it the same way as a single \n.

This would be a problem if e.g. a progress bar used \r repeatedly to paint over
the same line, and then printed a \n to move on to the next line; the last pair
of \r and \n was swallowed.

Another scenario where this was a problem was if you stream output of a command
to the log, and the command used \r\n as line feeds. This happens for example
for a background fetch that fails with an error; in that case we print the
combined output (stdout plus stderr) to the log after the command finished, and
for some reason it uses \r\n in that case (I can't actually explain why; when I
do `git fetch --all | xxd` I see only bare \n characters). All output would
appear on one line then.

Also, filter out escape sequences for character set designation; there's nothing
useful we can do with them. In practice, the only one that you are likely to see
is `ESC ( B`, which is sent as part of tput sgr0, which is sometimes used in
scripts to reset all graphics attributes to defaults.
2026-01-28 20:57:09 +01:00
Stefan Haller 4c9cc1ab7f When moving commits up/down and the selection leaves the view, scroll to make it visible again (#5247)
This used to work before 0.58, but broke with efd4298b5e (#5134).

Fixes #5244.
2026-01-28 20:47:53 +01:00
Stefan Haller 02997d30d9 When moving commits up/down and the selection leaves the view, scroll to make it visible again
This used to work before 0.58, but broke with efd4298b5e
(#5134).
2026-01-28 20:44:09 +01:00
Stefan Haller ce202a5612 Support using the selected commit's message in a fixup (#5233)
### PR Description

I had a spare couple hours so figured I'd whip up a PR for a feature
I've wanted for a while.

I've optimised for muscle memory backwards compatibility here:
- Outside interactive rebase: press 'f' then instead of a confirmation
panel, a menu appears where you can choose to keep the selected commit's
message
- Inside interactive rebase: press 'f' then press 'c' to see the menu
for keeping the message, where if you press 'c' again it will retain the
current message. so 'fcc' is the chord to press.

We're also now showing the -C flag (which is what enables the behaviour)
against the todo.

I've picked the 'c' keybinding because 'C' was taken and it corresponds
to the flag. Previously that showed a warning about a change in
keybinding for cherry picking but it's been ages since we've made that
change so I'm happy to retire it.
2026-01-28 10:31:47 +01:00
Jesse Duffield f317a97ac1 Support using the selected commit's message in a fixup
I've optimised for muscle memory backwards compatibility here:
  - Outside interactive rebase: press 'f' then instead of a confirmation
panel, a menu appears where you can choose to keep the selected commit's
message
  - Inside interactive rebase: press 'f' then press 'c' to see the menu
for keeping the message, where if you press 'c' again it will retain the
current message. so 'fcc' is the chord to press.

We're also now showing the -C flag (which is what enables the behaviour)
against the todo.

I've picked the 'c' keybinding because 'C' was taken and it corresponds
to the flag. Previously that showed a warning about a change in
keybinding for cherry picking but it's been ages since we've made that
change so I'm happy to retire it.
2026-01-28 10:28:52 +01:00
Jesse DuffieldandStefan Haller 6f6545e44f Pass todo flag to EditRebaseTodo
Not used yet, we pass an empty string everywhere, to match the previous
behavior. Just extracting this into a separate commit to make the next one
smaller.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:51 +01:00
Jesse DuffieldandStefan Haller 99e26be3d9 Show -C for fixup todos in rebase if present
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:48 +01:00
Jesse DuffieldandStefan Haller de98258e39 Add ActionFlag field to Commit
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:45 +01:00
Jesse DuffieldandStefan Haller 3bf833aea7 Remove the "old cherry-pick key" warning
It's been ages since we changed the key, users should hopefully be used to it by
now, and we want to reuse the key for something else later in the branch.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:15 +01:00
Stefan Haller 39cd4746d5 Limit popup panel widths to a maximum width (#5231)
On very large screens they would get ridiculously wide.

The limits we chose here are a little arbitrary:
- 80 for confirmations and prompts
- 90 for menus
- auto-wrap width plus 25 for the commit message editor (or 100 if
auto-wrap is off)
2026-01-28 09:04:30 +01:00
Stefan Haller 1ede4e6eb9 Limit popup panel widths to a maximum width
On very large screens they would get ridiculously wide.

The limits we chose here are a little arbitrary:
- 80 for confirmations and prompts
- 90 for menus
- auto-wrap width plus 25 for the commit message editor (or 100 if auto-wrap is
  off)
2026-01-28 09:02:27 +01:00
Stefan Haller 4c731abedd Fix opening a menu when a previous menu was scrolled down (#5238)
To reproduce: open the keybindings menu ('?'), press '>' to select the
last line, esc to close it, open it again. The view would appear still
scrolled down, so the selected first line was out of view. Even worse,
if you open a different, shorter menu (e.g. the "View upstream reset
options..." menu in the Files panel), you'd only see the Cancel item,
the other ones were scrolled out of view.

This is a regression introduced with efd4298b5e (#5134).
2026-01-25 16:21:22 +01:00
Stefan Haller c10dfcefcf Fix opening a menu when a previous menu was scrolled down
To reproduce: open the keybindings menu ('?'), press '>' to select the last
line, esc to close it, open it again. The view would appear still scrolled down,
so the selected first line was out of view. Even worse, if you open a different,
shorter menu (e.g. the "View upstream reset options..." menu in the Files
panel), you'd only see the Cancel item, the other ones were scrolled out of
view.

This is a regression introduced with efd4298b5e (#5134).
2026-01-25 16:17:13 +01:00
Stefan Haller 50b5c17d93 Don't log the "git rev-list" call when marking bisect commits (#5236)
When marking commits as good or bad during a bisect, a "git rev-list"
call would appear in the Command log. This is confusing, it's an
internal detail that is not interesting for the user to see.
2026-01-25 11:50:21 +01:00
Stefan Haller 8d36c8b3c1 Don't log the "git rev-list" call when marking bisect commits
When marking commits as good or bad during a bisect, a "git rev-list" call would
appear in the Command log. This is confusing, it's an internal detail that is
not interesting for the user to see.
2026-01-25 11:47:07 +01:00
Stefan Haller 9f44caa568 Exclude commit trailers from line wrapping (#5230)
Bump gocui, which brings in
https://github.com/jesseduffield/gocui/pull/97.

#Fixes #5216.
2026-01-25 11:25:21 +01:00
Stefan Haller 39477172b8 Bump gocui
This brings in https://github.com/jesseduffield/gocui/pull/97 ("Exclude commit
trailers from line wrapping").
2026-01-25 11:23:05 +01:00
Stefan Haller 7545fc48e5 chore: Update outdated GitHub Actions versions (#5220)
### PR Description

- Updated `actions/github-script` from `v7` to `v8` in
`.github/workflows/close-issues.yml`
- Updated `actions/setup-go` from `v5` to `v6` in
`.github/workflows/release.yml`
- Updated `golangci/golangci-lint-action` from `v8` to `v9` in
`.github/workflows/ci.yml`
- Updated `actions/checkout` from `v4` to `v6` in
`.github/workflows/codespell.yml`
- Updated `actions/checkout` from `v4` to `v6` in
`.github/workflows/release.yml`
- Updated `peter-evans/create-pull-request` from `v7` to `v8` in
`.github/workflows/sponsors.yml`
- Updated `actions/download-artifact` from `v4` to `v7` in
`.github/workflows/ci.yml`
- Updated `actions/checkout` from `v4` to `v6` in
`.github/workflows/ci.yml`
- Updated `actions/checkout` from `v4` to `v6` in
`.github/workflows/sponsors.yml`
- Updated `actions/setup-go` from `v5` to `v6` in
`.github/workflows/ci.yml`

The changes ensure that the GitHub Action workflows are using the latest
versions of the actions, which include improved functionality and
compatibility. These updates will be tested in the CI pipeline of the
pull request.
2026-01-24 16:28:57 +01:00
Padraic Slattery 3d78263e0c chore: Update outdated GitHub Actions versions 2026-01-24 16:26:11 +01:00
Stefan Haller 11e877cbad Have "Find base commit for fixup" ignore fixup commits for the found base commit (#5210)
If the ctrl-f function (Find base commit for fixup) finds a base commit
plus a bunch of fixup! commits for that base commit, it ignores those
fixups and selects the base commit. For the purpose of creating another
fixup commit on top this is always what you want, so I'm doing this
without adding a confirmation. If the user presses shift-A after that to
amend their changes though, they are guaranteed to get conflicts, so for
that case a warning might be useful; however, I find it unlikely that
users will want to amend changes to a commit that they already created
fixups for, so I'm just hoping that this won't happen in practice.

This implementation is a bit stricter than #5201, in that it only
ignores fixups if the first found commit is not a fixup itself; because
if it is, it's not clear whether the user wants to create another fixup
for both on top, or amend the changes into each one, in which case they
need to be staged individually.
2026-01-24 16:06:38 +01:00
Stefan Haller 268d1934f8 Ignore fixup commits for a found base commit when doing ctrl-f 2026-01-24 16:04:35 +01:00
Stefan Haller 4bcf3e181c Extract a helper method getCommitsForHashes 2026-01-24 16:04:35 +01:00
Stefan Haller df1e5512da Make getHashesAndSubjects a free-standing function
There's no reason for this to be a method.
2026-01-24 16:04:35 +01:00
Stefan Haller 2ba3c1dc3f Move isFixupCommit to FixupHelper 2026-01-24 16:04:35 +01:00
Stefan Haller b41bb8f845 Add a test that documents the current behavior
Currently we get an annoying error message, but we'd like the base commit to be
selected, disregarding the fixup.
2026-01-24 16:04:35 +01:00
Jesse Duffield 0339b414d9 README.md: Update Sponsors (#5095)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action
2026-01-24 17:33:11 +11:00
github-actions[bot] cb719c11c5 README.md: Update Sponsors 2026-01-17 10:59:48 +00:00
Stefan Haller 19605ad476 Update devcontainer VARIANT to 1-trixie (#5204)
### PR Description

The previous `1-bullseye` did not have the required go version 1.25.0
2026-01-17 11:59:37 +01:00
mricherzhagen e0c1d1c752 Update devcontainer VARIANT to 1-trixie
The previous `1-bullseye` did not have the required go version 1.25.0
2026-01-13 19:04:57 +00:00
Stefan Haller 4486c86271 Fix rendering artefacts after double-width characters (e.g. "⚠️") on some terminals (#5198)
Fixes
https://github.com/jesseduffield/lazygit/issues/5171#issuecomment-3710757175.
2026-01-12 10:02:27 +01:00
Stefan Haller b44a369295 Bump tcell
This fixes rendering artefacts after double-width characters such as "⚠️" on
some terminals.
2026-01-12 09:57:50 +01:00
Stefan Haller 498bc457fc Don't log the "git ls-remote" call when opening a PR (#5195)
When opening a PR, a "git ls-remote" call would appear in the Command
log. This is confusing, it's an internal detail that is not interesting
for the user to see.
2026-01-11 20:04:54 +01:00
Stefan Haller 7f957f059b Don't log the "git ls-remote" call when opening a PR
When opening a PR, a "git ls-remote" call would appear in the Command log. This
is confusing, it's an internal detail that is not interesting for the user to
see.
2026-01-11 20:02:08 +01:00
Stefan Haller 1a4af359ec Request info about terminal in issue template (#5194)
For some types of bugs it can be relevant what terminal is being used,
so ask users to provide this information when they file an issue.
2026-01-11 20:00:34 +01:00
Stefan Haller f16bec2ce3 Ask users to provide information about their terminal 2026-01-11 19:56:47 +01:00
Stefan Haller 798fe4a1ef Remove git version
It's unnecessary to ask users to provide this; the output of `lazygit --version`
contains it already.
2026-01-11 19:56:47 +01:00
Stefan Haller bb2e5fa886 Update tcell dependency (#5192)
This provides two fixes:
- proper handling of keypad keys on certain terminals (e.g. iTerm2)
- fix problems pasting certain emojis or east asian text on Windows
Terminal

Fixes #5171
Fixes #5173
2026-01-11 18:30:51 +01:00
Stefan Haller 3067ff1cac bump tcell
This provides two fixes:
- proper handling of keypad keys on certain terminals (e.g. iTerm2)
- fix problems pasting certain emojis or east asian text on Windows Terminal
2026-01-11 18:15:54 +01:00
Stefan Haller 312feff130 Fix pasting multi-line text into the commit description (#5186)
This fixes a regression in 0.58.

Fixes #5181.
2026-01-11 18:10:17 +01:00
Stefan Haller ee60102fdc Bump gocui
This fixes pasting multi-line text into the commit message editor; see
https://github.com/jesseduffield/gocui/pull/96.
2026-01-11 18:07:30 +01:00
Stefan Haller 7b969a03ad When doing ctrl-f, and the resulting commit is not visible, scroll it into view (#5182)
This used to work in the last version, but broke with efd4298b5e
(#5134).
2026-01-11 18:01:54 +01:00
Stefan Haller 1f7e5d7767 When doing ctrl-f, and the resulting commit is not visible, scroll it into view
This used to work in the last version, but broke with efd4298b5e (#5134).
2026-01-06 16:17:33 +01:00
Stefan Haller 80dd695d7a Update search position (match x of y) when changing the selection in a list view (#5169)
When searching (not filtering) in a list view using `/`, moving the
selection now updates the "x of y" indicator of the search results to
the last one before the selection. Pressing `n` again searches from
there rather than from the previously shown result.

Also see corresponding gocui PR at
https://github.com/jesseduffield/gocui/pull/95.
2026-01-04 18:54:28 +01:00
Stefan Haller 65edd99fd0 Update search position (match x of y) when changing the selection in a list view
or in the staging view.
2026-01-04 18:51:45 +01:00
Stefan Haller 98bf8f86d1 Bump gocui and adapt code accordingly 2026-01-04 18:48:47 +01:00
91 changed files with 1278 additions and 382 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
ARG VARIANT=1-bullseye
ARG VARIANT=1-trixie
FROM golang:${VARIANT}
RUN go install mvdan.cc/gofumpt@latest
+3 -1
View File
@@ -26,7 +26,9 @@ If applicable, add screenshots to help explain your problem.
### Version info:
* _Run `lazygit --version` and paste the result here_
* _Run `git --version` and paste the result here_
### Terminal info:
What terminal are you using, and which version? For some types of bugs this information can be relevant.
### Additional context
Add any other context about the problem here.
+17 -17
View File
@@ -28,9 +28,9 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
- name: Test code
@@ -39,7 +39,7 @@ jobs:
mkdir -p /tmp/code_coverage
go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -59,7 +59,7 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Restore Git cache
if: matrix.git-version != 'latest'
id: cache-git-restore
@@ -85,7 +85,7 @@ jobs:
path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}}
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
- name: Print git version
@@ -98,7 +98,7 @@ jobs:
mkdir -p /tmp/code_coverage
./scripts/run_integration_tests.sh
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -109,9 +109,9 @@ jobs:
GOARCH: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
- name: Build linux binary
@@ -136,9 +136,9 @@ jobs:
GOARCH: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
- name: Check Vendor Directory
@@ -162,13 +162,13 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
- name: Lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v9
with:
# If you change this, make sure to also update scripts/golangci-lint-shim.sh
version: v2.4.0
@@ -182,15 +182,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
- name: Download all coverage artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
path: /tmp/code_coverage
@@ -219,7 +219,7 @@ jobs:
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
- name: "Checkout PR branch and all PR commits"
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request == null && startsWith(github.event.comment.body, '/close') }}
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@v8
with:
script: |
const trustedUsers = ['ChrisMcD1', 'jesseduffield', 'stefanhaller']
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@v1
- name: Codespell
+7 -6
View File
@@ -1,10 +1,11 @@
name: Release
on:
schedule:
# Runs at 8:00 AM UTC on every Saturday
# We'll check below if it's the first Saturday of the month, and fail if not
- cron: '0 8 * * 6'
# schedule:
# # Runs at 8:00 AM UTC on every Saturday
# # We'll check below if it's the first Saturday of the month, and fail if not
# - cron: '0 8 * * 6'
# Allow manual triggering of the workflow
workflow_dispatch:
inputs:
@@ -45,7 +46,7 @@ jobs:
fi
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: jesseduffield/lazygit
token: ${{ secrets.LAZYGIT_RELEASE_PAT }}
@@ -146,7 +147,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }}
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
if: ${{ github.repository == 'jesseduffield/lazygit' }}
steps:
- name: Checkout 🛎️
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1.2.2
@@ -19,7 +19,7 @@ jobs:
file: "README.md"
- name: Create Pull Request 🚀
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v8
with:
commit-message: "README.md: Update Sponsors"
title: "README.md: Update Sponsors"
+2 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -713,6 +713,7 @@ keybinding:
renameCommitWithEditor: R
viewResetOptions: g
markCommitAsFixup: f
setFixupMessage: c
createFixupCommit: F
squashAboveCommits: S
moveDownCommit: <c-j>
@@ -89,6 +89,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Reword | Reword the selected commit's message. |
| `` R `` | Reword with editor | |
| `` d `` | Drop | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+3 -2
View File
@@ -69,6 +69,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` b `` | bisectオプションを表示 | |
| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 |
| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 |
| `` R `` | エディタでメッセージ変更 | |
| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 |
@@ -114,7 +115,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` <space> `` | パッチに含めるファイルを切り替え | ファイルがカスタムパッチに含まれるかどうかを切り替えます。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
| `` a `` | すべてのファイルを切り替え | コミットのすべてのファイルをカスタムパッチに追加/削除します。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
| `` <enter> `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 |
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
| `` 0 `` | メインビューにフォーカス | |
@@ -234,7 +235,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 |
| `` g `` | アップストリームへのリセットオプションを表示 | |
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
@@ -298,6 +298,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | Bisect 옵션 보기 | |
| `` s `` | 스쿼시 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. |
| `` R `` | 에디터에서 커밋메시지 수정 | |
| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -160,6 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Hernoem commit | Reword the selected commit's message. |
| `` R `` | Hernoem commit met editor | |
| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
@@ -62,6 +62,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` b `` | Zobacz opcje bisect | |
| `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. |
| `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. |
| `` R `` | Przeformułuj za pomocą edytora | |
| `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. |
@@ -164,6 +164,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash o commit selecionado no commit abaixo dele. A mensagem do commit selecionado será anexada ao commit abaixo dele. |
| `` f `` | Fixup | Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Reword | Repetir a mensagem de submissão selecionada. |
| `` R `` | Republicar com o editor | |
| `` d `` | Descartar | Solte o commit selecionado. Isso irá remover o commit do branch através de uma rebase. Se o commit faz com que as alterações em commits posteriores dependem, você pode precisar resolver conflitos de merge. |
@@ -170,6 +170,7 @@ _Связки клавиш_
| `` b `` | Просмотреть параметры бинарного поиска | |
| `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Перефразировать коммит | Reword the selected commit's message. |
| `` R `` | Переписать коммит с помощью редактора | |
| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+3 -2
View File
@@ -126,6 +126,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
| `` f `` | 修正 (fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | 改写提交 | 重写所选提交的消息。 |
| `` R `` | 使用编辑器重命名提交 | |
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
@@ -234,7 +235,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
| `` T `` | 创建标签 | |
| `` s `` | 排序 | |
@@ -401,7 +402,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <c-o> `` | 复制分支名称到剪贴板 | |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 |
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
@@ -184,6 +184,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` b `` | 查看二分選項 | |
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+1
View File
@@ -713,6 +713,7 @@ keybinding:
renameCommitWithEditor: R
viewResetOptions: g
markCommitAsFixup: f
setFixupMessage: c
createFixupCommit: F
squashAboveCommits: S
moveDownCommit: <c-j>
+1
View File
@@ -89,6 +89,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Reword | Reword the selected commit's message. |
| `` R `` | Reword with editor | |
| `` d `` | Drop | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+3 -2
View File
@@ -69,6 +69,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` b `` | bisectオプションを表示 | |
| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 |
| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 |
| `` R `` | エディタでメッセージ変更 | |
| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 |
@@ -114,7 +115,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` <space> `` | パッチに含めるファイルを切り替え | ファイルがカスタムパッチに含まれるかどうかを切り替えます。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
| `` a `` | すべてのファイルを切り替え | コミットのすべてのファイルをカスタムパッチに追加/削除します。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
| `` <enter> `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 |
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
| `` 0 `` | メインビューにフォーカス | |
@@ -234,7 +235,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 |
| `` g `` | アップストリームへのリセットオプションを表示 | |
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
+1
View File
@@ -298,6 +298,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | Bisect 옵션 보기 | |
| `` s `` | 스쿼시 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. |
| `` R `` | 에디터에서 커밋메시지 수정 | |
| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+1
View File
@@ -160,6 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Hernoem commit | Reword the selected commit's message. |
| `` R `` | Hernoem commit met editor | |
| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+1
View File
@@ -62,6 +62,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` b `` | Zobacz opcje bisect | |
| `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. |
| `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. |
| `` R `` | Przeformułuj za pomocą edytora | |
| `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. |
+1
View File
@@ -164,6 +164,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` b `` | View bisect options | |
| `` s `` | Squash | Squash o commit selecionado no commit abaixo dele. A mensagem do commit selecionado será anexada ao commit abaixo dele. |
| `` f `` | Fixup | Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Reword | Repetir a mensagem de submissão selecionada. |
| `` R `` | Republicar com o editor | |
| `` d `` | Descartar | Solte o commit selecionado. Isso irá remover o commit do branch através de uma rebase. Se o commit faz com que as alterações em commits posteriores dependem, você pode precisar resolver conflitos de merge. |
+1
View File
@@ -170,6 +170,7 @@ _Связки клавиш_
| `` b `` | Просмотреть параметры бинарного поиска | |
| `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Перефразировать коммит | Reword the selected commit's message. |
| `` R `` | Переписать коммит с помощью редактора | |
| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+3 -2
View File
@@ -126,6 +126,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
| `` f `` | 修正 (fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | 改写提交 | 重写所选提交的消息。 |
| `` R `` | 使用编辑器重命名提交 | |
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
@@ -234,7 +235,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
| `` T `` | 创建标签 | |
| `` s `` | 排序 | |
@@ -401,7 +402,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <c-o> `` | 复制分支名称到剪贴板 | |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 |
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
+1
View File
@@ -184,6 +184,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` b `` | 查看二分選項 | |
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
+5 -5
View File
@@ -12,13 +12,13 @@ require (
github.com/aybabtme/humanlog v0.4.1
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/gdamore/tcell/v2 v2.13.5
github.com/gdamore/tcell/v2 v2.13.8
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.4.2
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd
github.com/jesseduffield/gocui v0.3.1-0.20260103133810-b7e030324985
github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
@@ -39,7 +39,7 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sync v0.19.0
golang.org/x/sys v0.39.0
golang.org/x/sys v0.40.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -78,8 +78,8 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+10 -10
View File
@@ -95,8 +95,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.13.5 h1:YvWYCSr6gr2Ovs84dXbZLjDuOfQchhj8buOEqY52rpA=
github.com/gdamore/tcell/v2 v2.13.5/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
@@ -189,8 +189,8 @@ github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2Paiis
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE=
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q=
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA=
github.com/jesseduffield/gocui v0.3.1-0.20260103133810-b7e030324985 h1:qdjGSiNnlGtoi+nzyERQJvee50JpJjeQ6sEhP7jCfMo=
github.com/jesseduffield/gocui v0.3.1-0.20260103133810-b7e030324985/go.mod h1:lQCd2TvvNXVKFBowy4A7xxZbUp+1KEiGs4j0Q5Zt9gQ=
github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18 h1:+Q17GqvNaGzuvIR1JCdIS0khVjMzdwrhXBBDxnsdN8Y=
github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18/go.mod h1:lQCd2TvvNXVKFBowy4A7xxZbUp+1KEiGs4j0Q5Zt9gQ=
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/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@@ -458,13 +458,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
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=
@@ -475,8 +475,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.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
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=
+1
View File
@@ -199,6 +199,7 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
return utils.TodoChange{
Hash: c.Hash,
NewAction: c.NewAction,
Flag: c.Flag,
}
})
+1
View File
@@ -14,6 +14,7 @@ import (
type ChangeTodoAction struct {
Hash string
NewAction todo.TodoCommand
Flag string
}
func handleInteractiveRebase(common *common.Common, f func(path string) error) error {
+1 -1
View File
@@ -154,7 +154,7 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
candidates := []string{}
cmdArgs := NewGitCmd("rev-list").Arg(newHash).ToArgv()
err := self.cmd.New(cmdArgs).RunAndProcessLines(func(line string) (bool, error) {
err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) {
hash := strings.TrimSpace(line)
if status, ok := info.statusMap[hash]; ok {
+6 -4
View File
@@ -325,6 +325,7 @@ func (self *CommitLoader) getHydratedTodoCommits(hashPool *utils.StringPool, tod
hydratedCommits = append(hydratedCommits, rebasingCommit)
} else if commit := findFullCommit(rebasingCommit.Hash()); commit != nil {
commit.Action = rebasingCommit.Action
commit.ActionFlag = rebasingCommit.ActionFlag
commit.Status = rebasingCommit.Status
hydratedCommits = append(hydratedCommits, commit)
}
@@ -371,10 +372,11 @@ func (self *CommitLoader) getRebasingCommits(hashPool *utils.StringPool, addConf
continue
}
commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{
Hash: t.Commit,
Name: t.Msg,
Status: models.StatusRebasing,
Action: t.Command,
Hash: t.Commit,
Name: t.Msg,
Status: models.StatusRebasing,
Action: t.Command,
ActionFlag: t.Flag,
}))
}
+4 -2
View File
@@ -139,7 +139,7 @@ func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int
}).Run()
}
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand) error {
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand, flag string) error {
baseIndex := endIdx + 1
if action == todo.Squash || action == todo.Fixup {
baseIndex++
@@ -151,6 +151,7 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx
return daemon.ChangeTodoAction{
Hash: commit.Hash(),
NewAction: action,
Flag: flag,
}, !commit.IsMerge()
})
@@ -334,11 +335,12 @@ func todoFromCommit(commit *models.Commit) utils.Todo {
}
// Sets the action for the given commits in the git-rebase-todo file
func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand) error {
func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand, flag string) error {
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
return utils.TodoChange{
Hash: commit.Hash(),
NewAction: action,
Flag: flag,
}
})
+1 -1
View File
@@ -85,6 +85,6 @@ func (self *RemoteCommands) GetRemoteURL(remoteName string) (string, error) {
Arg("--get-url", remoteName).
ToArgv()
url, err := self.cmd.New(cmdArgs).RunWithOutput()
url, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return strings.TrimSpace(url), err
}
+2 -2
View File
@@ -233,10 +233,10 @@ func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) e
return self.cmd.New(cmdArgs).Run()
}
// Escapes special characters in a filename for gitignore and exclude files
// Escapes special characters in a filename for gitignore and exclude files, and prepends `/`
func escapeFilename(filename string) string {
re := regexp.MustCompile(`^[!#]|[\[\]*]`)
return re.ReplaceAllString(filename, `\${0}`)
return "/" + re.ReplaceAllString(filename, `\${0}`)
}
// Ignore adds a file to the gitignore for the repo
+3
View File
@@ -60,6 +60,7 @@ type Commit struct {
Status CommitStatus
Action todo.TodoCommand
ActionFlag string // e.g. "-C" for fixup -C
Divergence Divergence // set to DivergenceNone unless we are showing the divergence view
}
@@ -68,6 +69,7 @@ type NewCommitOpts struct {
Name string
Status CommitStatus
Action todo.TodoCommand
ActionFlag string
Tags []string
ExtraInfo string
AuthorName string
@@ -83,6 +85,7 @@ func NewCommit(hashPool *utils.StringPool, opts NewCommitOpts) *Commit {
Name: opts.Name,
Status: opts.Status,
Action: opts.Action,
ActionFlag: opts.ActionFlag,
Tags: opts.Tags,
ExtraInfo: opts.ExtraInfo,
AuthorName: opts.AuthorName,
+4
View File
@@ -60,6 +60,10 @@ func getPreset(shell string, osConfig *OSConfig, guessDefaultEditor func() strin
nvimRemoteEditTemplate = `begin; if test -z "$NVIM"; nvim -- {{filename}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{filename}}; end; end`
nvimRemoteEditAtLineTemplate = `begin; if test -z "$NVIM"; nvim +{{line}} -- {{filename}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{filename}}; nvim --server "$NVIM" --remote-send ":{{line}}<CR>"; end; end`
nvimRemoteOpenDirInEditorTemplate = `begin; if test -z "$NVIM"; nvim -- {{dir}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{dir}}; end; end`
} else if strings.HasSuffix(shell, "nu") || strings.HasSuffix(shell, "nushell") || os.Getenv("NU_VERSION") != "" {
nvimRemoteEditTemplate = `if ($env | get -i NVIM | is-empty) { nvim -- {{filename}} } else { nvim --server $env.NVIM --remote-send "q"; nvim --server $env.NVIM --remote-tab {{filename}} }`
nvimRemoteEditAtLineTemplate = `if ($env | get -i NVIM | is-empty) { nvim +{{line}} -- {{filename}} } else { nvim --server $env.NVIM --remote-send "q"; nvim --server $env.NVIM --remote-tab {{filename}}; nvim --server $env.NVIM --remote-send ":{{line}}<CR>" }`
nvimRemoteOpenDirInEditorTemplate = `if ($env | get -i NVIM | is-empty) { nvim -- {{dir}} } else { nvim --server $env.NVIM --remote-send "q"; nvim --server $env.NVIM --remote-tab {{dir}} }`
} else {
nvimRemoteEditTemplate = `[ -z "$NVIM" ] && (nvim -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}})`
nvimRemoteEditAtLineTemplate = `[ -z "$NVIM" ] && (nvim +{{line}} -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}} && nvim --server "$NVIM" --remote-send ":{{line}}<CR>")`
+2
View File
@@ -551,6 +551,7 @@ type KeybindingCommitsConfig struct {
RenameCommitWithEditor string `yaml:"renameCommitWithEditor"`
ViewResetOptions string `yaml:"viewResetOptions"`
MarkCommitAsFixup string `yaml:"markCommitAsFixup"`
SetFixupMessage string `yaml:"setFixupMessage"`
CreateFixupCommit string `yaml:"createFixupCommit"`
SquashAboveCommits string `yaml:"squashAboveCommits"`
MoveDownCommit string `yaml:"moveDownCommit"`
@@ -1008,6 +1009,7 @@ func GetDefaultConfig() *UserConfig {
RenameCommitWithEditor: "R",
ViewResetOptions: "g",
MarkCommitAsFixup: "f",
SetFixupMessage: "c",
CreateFixupCommit: "F",
SquashAboveCommits: "S",
MoveDownCommit: "<c-j>",
+2 -1
View File
@@ -68,7 +68,8 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
},
}
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect)
return ctx
}
+14 -2
View File
@@ -24,6 +24,10 @@ type ListContextTrait struct {
// If renderOnlyVisibleLines is true, needRerenderVisibleLines indicates whether we need to
// rerender the visible lines e.g. because the scroll position changed
needRerenderVisibleLines bool
// true if we're inside the OnSearchSelect call; in that case we don't want to update the search
// result index.
inOnSearchSelect bool
}
func (self *ListContextTrait) IsListContext() {}
@@ -31,6 +35,10 @@ func (self *ListContextTrait) IsListContext() {}
func (self *ListContextTrait) FocusLine(scrollIntoView bool) {
self.Context.FocusLine(scrollIntoView)
// Need to capture this in a local variable because by the time the AfterLayout function runs,
// the field will have been reset to false already
inOnSearchSelect := self.inOnSearchSelect
// Doing this at the end of the layout function because we need the view to be
// resized before we focus the line, otherwise if we're in accordion mode
// the view could be squashed and won't know how to adjust the cursor/origin.
@@ -40,6 +48,9 @@ func (self *ListContextTrait) FocusLine(scrollIntoView bool) {
self.GetViewTrait().FocusPoint(
self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()), scrollIntoView)
if !inOnSearchSelect {
self.GetView().SetNearestSearchPosition()
}
selectRangeIndex, isSelectingRange := self.list.GetRangeStartIdx()
if isSelectingRange {
@@ -117,10 +128,11 @@ func (self *ListContextTrait) HandleRender() {
self.setFooter()
}
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) {
self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx))
self.inOnSearchSelect = true
self.HandleFocus(types.OnFocusOpts{})
return nil
self.inOnSearchSelect = false
}
func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
+2 -1
View File
@@ -134,7 +134,8 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
},
}
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect)
return ctx
}
+2 -1
View File
@@ -31,7 +31,8 @@ func NewMainContext(
SearchTrait: NewSearchTrait(c),
}
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(int) error { return nil }))
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
ctx.GetView().SetOnSelectItem(func(int) {})
return ctx
}
+16 -8
View File
@@ -16,6 +16,10 @@ type PatchExplorerContext struct {
getIncludedLineIndices func() []int
c *ContextCommon
mutex deadlock.Mutex
// true if we're inside the OnSelectItem callback; in that case we don't want to update the
// search result index.
inOnSelectItemCallback bool
}
var (
@@ -49,14 +53,14 @@ func NewPatchExplorerContext(
SearchTrait: NewSearchTrait(c),
}
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(
func(selectedLineIdx int) error {
ctx.GetMutex().Lock()
defer ctx.GetMutex().Unlock()
ctx.NavigateTo(selectedLineIdx)
return nil
}),
)
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
ctx.GetView().SetOnSelectItem(func(selectedLineIdx int) {
ctx.GetMutex().Lock()
defer ctx.GetMutex().Unlock()
ctx.inOnSelectItemCallback = true
ctx.NavigateTo(selectedLineIdx)
ctx.inOnSelectItemCallback = false
})
ctx.SetHandleRenderFunc(ctx.OnViewWidthChanged)
@@ -113,6 +117,10 @@ func (self *PatchExplorerContext) FocusSelection() {
// As far as the view is concerned, we are always selecting a range
view.SetRangeSelectStart(startIdx)
view.SetCursorY(endIdx - newOriginY)
if !self.inOnSelectItemCallback {
view.SetNearestSearchPosition()
}
}
func (self *PatchExplorerContext) GetContentToRender() string {
-14
View File
@@ -36,20 +36,6 @@ func (self *SearchTrait) ClearSearchString() {
// used for type switch
func (self *SearchTrait) IsSearchableContext() {}
func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
return func(selectedLineIdx int, index int, total int) error {
self.RenderSearchStatus(index, total)
if total != 0 {
if err := innerFunc(selectedLineIdx); err != nil {
return err
}
}
return nil
}
}
func (self *SearchTrait) RenderSearchStatus(index int, total int) {
keybindingConfig := self.c.UserConfig().Keybinding
+2 -1
View File
@@ -134,7 +134,8 @@ func NewSubCommitsContext(
},
}
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect)
return ctx
}
+2 -1
View File
@@ -56,7 +56,8 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
},
}
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(ctx.OnSearchSelect))
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect)
return ctx
}
@@ -1,7 +1,6 @@
package controllers
import (
"errors"
"fmt"
"strings"
@@ -130,14 +129,6 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
GetDisabledReason: self.require(self.canSelectCommitsOfCurrentBranch),
Description: self.c.Tr.SelectCommitsOfCurrentBranch,
},
// Putting this at the bottom of the list so that it has the lowest priority,
// meaning that if the user has configured another keybinding to the same key
// then that will take precedence.
{
// Hardcoding this key because it's not configurable
Key: opts.GetKey("c"),
Handler: self.handleOldCherryPickKey,
},
}
return bindings
@@ -382,16 +373,6 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com
return nil
}
func (self *BasicCommitsController) handleOldCherryPickKey() error {
msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning,
map[string]string{
"copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy),
"paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits),
})
return errors.New(msg)
}
func (self *BasicCommitsController) openDiffTool(commit *models.Commit) error {
to := commit.RefName()
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(commit.ParentRefName())
@@ -125,10 +125,10 @@ func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panel
height/2 + panelHeight/2
}
func (self *ConfirmationHelper) getPopupPanelWidth() int {
func (self *ConfirmationHelper) getPopupPanelWidth(maxWidth int) int {
width, _ := self.c.GocuiGui().Size()
// we want a minimum width up to a point, then we do it based on ratio.
panelWidth := 4 * width / 7
// we want a minimum width up to a point, then we do it based on ratio, but only up to the given max width
panelWidth := min(4*width/7, maxWidth)
minWidth := 80
if panelWidth < minWidth {
panelWidth = min(width-2, minWidth)
@@ -328,7 +328,7 @@ func (self *ConfirmationHelper) resizeMenu(parentPopupContext types.Context) {
// resize the window
itemCount := self.c.Contexts().Menu.UnfilteredLen()
offset := 3
panelWidth := self.getPopupPanelWidth()
panelWidth := self.getPopupPanelWidth(90)
contentWidth := panelWidth - 2 // minus 2 for the frame
promptLinesCount := self.layoutMenuPrompt(contentWidth)
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset+promptLinesCount, parentPopupContext)
@@ -375,7 +375,7 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
}
func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types.Context) {
panelWidth := self.getPopupPanelWidth()
panelWidth := self.getPopupPanelWidth(80)
contentWidth := panelWidth - 2 // minus 2 for the frame
confirmationView := self.c.Views().Confirmation
prompt := confirmationView.Buffer()
@@ -389,7 +389,7 @@ func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Conte
if self.c.Views().Suggestions.Visible {
suggestionsViewHeight = 11
}
panelWidth := self.getPopupPanelWidth()
panelWidth := self.getPopupPanelWidth(80)
contentWidth := panelWidth - 2 // minus 2 for the frame
promptView := self.c.Views().Prompt
prompt := promptView.TextArea.GetContent()
@@ -403,7 +403,13 @@ func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Conte
}
func (self *ConfirmationHelper) ResizeCommitMessagePanels(parentPopupContext types.Context) {
panelWidth := self.getPopupPanelWidth()
maxWidth := 100
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
// Adding some extra space to make it very obvious that we're wrapping the commit message
// for real, not just soft-wrapping for display at the window width.
maxWidth = self.c.UserConfig().Git.Commit.AutoWrapWidth + 25
}
panelWidth := self.getPopupPanelWidth(maxWidth)
content := self.c.Views().CommitDescription.TextArea.GetContent()
summaryViewHeight := 3
panelHeight := getMessageHeight(false, true, content, panelWidth, self.c.Views().CommitDescription.TabWidth)
+70 -9
View File
@@ -110,20 +110,24 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
}
if len(hashGroups[NOT_MERGED]) > 1 {
// If there are multiple commits that could be the base commit, list
foundCommits := getCommitsForHashes(commits, hashGroups[NOT_MERGED])
// If there are multiple commits that could be the base commit, remove all
// those that are fixups for the last one.
foundCommits = removeFixupCommits(foundCommits)
if len(foundCommits) > 1 {
// If there are still multiple commits that could be the base commit, list
// them in the error message. But only the candidates from the current
// branch, not including any that are already merged.
subjects := self.getHashesAndSubjects(commits, hashGroups[NOT_MERGED])
subjects := getHashesAndSubjects(foundCommits)
message := lo.Ternary(hasStagedChanges,
self.c.Tr.MultipleBaseCommitsFoundStaged,
self.c.Tr.MultipleBaseCommitsFoundUnstaged)
return fmt.Errorf("%s\n\n%s", message, subjects)
}
// At this point we know that the NOT_MERGED bucket has exactly one commit,
// and that's the one we want to select.
_, index, _ := self.findCommit(commits, hashGroups[NOT_MERGED][0])
// Now we know that foundCommits has exactly one commit, so find its index
_, index, _ := self.findCommit(commits, foundCommits[0].Hash())
return self.c.ConfirmIf(warnAboutAddedLines, types.ConfirmOpts{
Title: self.c.Tr.FindBaseCommitForFixup,
@@ -137,29 +141,59 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
}
self.c.Contexts().LocalCommits.SetSelection(index)
self.c.Contexts().LocalCommits.FocusLine(true)
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
return nil
},
})
}
func (self *FixupHelper) getHashesAndSubjects(commits []*models.Commit, hashes []string) string {
func getCommitsForHashes(commits []*models.Commit, hashes []string) []*models.Commit {
// This is called only for the NOT_MERGED commits, and we know that all of them are contained in
// the commits slice.
commitsSet := set.NewFromSlice(hashes)
subjects := make([]string, 0, len(hashes))
result := make([]*models.Commit, 0, len(hashes))
for _, c := range commits {
if commitsSet.Includes(c.Hash()) {
subjects = append(subjects, fmt.Sprintf("%s %s", c.ShortRefName(), c.Name))
result = append(result, c)
commitsSet.Remove(c.Hash())
if commitsSet.Len() == 0 {
break
}
}
}
return result
}
func getHashesAndSubjects(commits []*models.Commit) string {
subjects := lo.Map(commits, func(c *models.Commit, _ int) string {
return fmt.Sprintf("%s %s", c.ShortRefName(), c.Name)
})
return strings.Join(subjects, "\n")
}
func removeFixupCommits(commits []*models.Commit) []*models.Commit {
if len(commits) <= 1 {
return commits
}
// If the last found commit is itself a fixup, don't eliminate anything.
baseSubject, lastIsFixup := IsFixupCommit(commits[len(commits)-1].Name)
if lastIsFixup {
return commits
}
// need to go backwards because we are mutating the slice as we go
for i := len(commits) - 2; i >= 0; i-- {
subject, isFixup := IsFixupCommit(commits[i].Name)
if isFixup && subject == baseSubject {
commits = utils.Remove(commits, i)
}
}
return commits
}
func (self *FixupHelper) getDiff() (string, bool, error) {
args := []string{"-U0", "--ignore-submodules=all", "HEAD", "--"}
@@ -349,3 +383,30 @@ func (self *FixupHelper) findCommit(commits []*models.Commit, hash string) (*mod
return commit.Hash() == hash
})
}
// Check whether the given subject line is the subject of a fixup commit, and
// returns (trimmedSubject, true) if so (where trimmedSubject is the subject
// with all fixup prefixes removed), or (subject, false) if not.
func IsFixupCommit(subject string) (string, bool) {
prefixes := []string{"fixup! ", "squash! ", "amend! "}
trimPrefix := func(s string) (string, bool) {
for _, prefix := range prefixes {
if trimmedSubject, ok := strings.CutPrefix(s, prefix); ok {
return trimmedSubject, true
}
}
return s, false
}
if subject, wasTrimmed := trimPrefix(subject); wasTrimmed {
for {
// handle repeated prefixes like "fixup! amend! fixup! Subject"
if subject, wasTrimmed = trimPrefix(subject); !wasTrimmed {
break
}
}
return subject, true
}
return subject, false
}
@@ -3,6 +3,9 @@ package helpers
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
@@ -155,3 +158,184 @@ index 9ce8efb33..0632e41b0 100644
})
}
}
func TestFixupHelper_IsFixupCommit(t *testing.T) {
scenarios := []struct {
subject string
expectedTrimmedSubject string
expectedIsFixup bool
}{
{
subject: "Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: false,
},
{
subject: "fixup Bla",
expectedTrimmedSubject: "fixup Bla",
expectedIsFixup: false,
},
{
subject: "fixup! Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: true,
},
{
subject: "fixup! fixup! Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: true,
},
{
subject: "amend! squash! Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: true,
},
{
subject: "fixup!",
expectedTrimmedSubject: "fixup!",
expectedIsFixup: false,
},
}
for _, s := range scenarios {
t.Run(s.subject, func(t *testing.T) {
trimmedSubject, isFixupCommit := IsFixupCommit(s.subject)
assert.Equal(t, s.expectedTrimmedSubject, trimmedSubject)
assert.Equal(t, s.expectedIsFixup, isFixupCommit)
})
}
}
func TestFixupHelper_removeFixupCommits(t *testing.T) {
hashPool := &utils.StringPool{}
type commitDesc struct {
Hash string
Name string
}
scenarios := []struct {
name string
commits []commitDesc
expectedResult []commitDesc
}{
{
name: "empty list",
commits: []commitDesc{},
expectedResult: []commitDesc{},
},
{
name: "single commit",
commits: []commitDesc{
{"abc123", "Some feature"},
},
expectedResult: []commitDesc{
{"abc123", "Some feature"},
},
},
{
name: "two unrelated commits",
commits: []commitDesc{
{"abc123", "First feature"},
{"def456", "Second feature"},
},
expectedResult: []commitDesc{
{"abc123", "First feature"},
{"def456", "Second feature"},
},
},
{
name: "fixup commit for last commit",
commits: []commitDesc{
{"abc123", "fixup! Some feature"},
{"def456", "Some feature"},
},
expectedResult: []commitDesc{
{"def456", "Some feature"},
},
},
{
name: "amend and squash commits for last commit",
commits: []commitDesc{
{"abc123", "squash! Some feature"},
{"def456", "amend! Some feature"},
{"ghi789", "Some feature"},
},
expectedResult: []commitDesc{
{"ghi789", "Some feature"},
},
},
{
name: "fixup commit for different commit",
commits: []commitDesc{
{"abc123", "fixup! Other feature"},
{"def456", "Some feature"},
},
expectedResult: []commitDesc{
{"abc123", "fixup! Other feature"},
{"def456", "Some feature"},
},
},
{
name: "last commit is a fixup itself",
commits: []commitDesc{
{"abc123", "fixup! Some feature"},
{"def456", "fixup! Some feature"},
},
expectedResult: []commitDesc{
{"abc123", "fixup! Some feature"},
{"def456", "fixup! Some feature"},
},
},
{
name: "nested fixup commit",
commits: []commitDesc{
{"abc123", "fixup! fixup! Some feature"},
{"def456", "amend! squash! fixup! Some feature"},
{"ghi789", "Some feature"},
},
expectedResult: []commitDesc{
{"ghi789", "Some feature"},
},
},
{
name: "fixup commits mixed with unrelated commits",
commits: []commitDesc{
{Hash: "abc123", Name: "fixup! Base commit"},
{Hash: "def456", Name: "Unrelated commit"},
{Hash: "ghi789", Name: "fixup! Base commit"},
{Hash: "jkl012", Name: "Base commit"},
},
expectedResult: []commitDesc{
{Hash: "def456", Name: "Unrelated commit"},
{Hash: "jkl012", Name: "Base commit"},
},
},
{
name: "only fixup commits for last commit removed, others preserved",
commits: []commitDesc{
{Hash: "abc123", Name: "fixup! First feature"},
{Hash: "def456", Name: "fixup! Second feature"},
{Hash: "ghi789", Name: "Second feature"},
{Hash: "jkl012", Name: "First feature"},
},
expectedResult: []commitDesc{
{Hash: "def456", Name: "fixup! Second feature"},
{Hash: "ghi789", Name: "Second feature"},
{Hash: "jkl012", Name: "First feature"},
},
},
}
makeCommitFromDesc := func(desc commitDesc, _ int) *models.Commit {
return models.NewCommit(hashPool, models.NewCommitOpts{Hash: desc.Hash, Name: desc.Name})
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
commits := lo.Map(s.commits, makeCommitFromDesc)
result := removeFixupCommits(commits)
expectedCommits := lo.Map(s.expectedResult, makeCommitFromDesc)
assert.Equal(t, expectedCommits, result)
})
}
}
+4 -9
View File
@@ -108,20 +108,15 @@ func (self *SearchHelper) Confirm() error {
return self.CancelPrompt()
}
var err error
switch state.SearchType() {
case types.SearchTypeFilter:
self.ConfirmFilter()
case types.SearchTypeSearch:
err = self.ConfirmSearch()
self.ConfirmSearch()
case types.SearchTypeNone:
self.c.Context().Pop()
}
if err != nil {
return err
}
return self.c.ResetKeybindings()
}
@@ -144,13 +139,13 @@ func (self *SearchHelper) ConfirmFilter() {
self.c.Context().Pop()
}
func (self *SearchHelper) ConfirmSearch() error {
func (self *SearchHelper) ConfirmSearch() {
state := self.searchState()
context, ok := state.Context.(types.ISearchableContext)
if !ok {
self.c.Log.Warnf("Context %s is searchable", state.Context.GetKey())
return nil
return
}
searchString := self.promptContent()
@@ -161,7 +156,7 @@ func (self *SearchHelper) ConfirmSearch() error {
self.c.Context().Pop()
return context.GetView().Search(searchString, modelSearchResults(context))
context.GetView().Search(searchString, modelSearchResults(context))
}
func modelSearchResults(context types.ISearchableContext) []gocui.SearchPosition {
+10
View File
@@ -104,6 +104,7 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
// doing this check so that if we're holding the up key at the start of the list
// we're not constantly re-rendering the main view.
cursorMoved := before != after
originYBefore := self.context.GetView().OriginY()
if cursorMoved {
switch change {
case -1:
@@ -116,6 +117,15 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
}
if cursorMoved || rangeBefore != rangeAfter {
if originYBefore != self.context.GetView().OriginY() {
// Since we already scrolled the view above, the normal mechanism that
// ListContextTrait.FocusLine uses for deciding whether rerendering is needed won't
// work. It is based on checking whether the origin was changed by the call to
// FocusPoint in that function, but since we scrolled the view directly above, the
// origin has already been updated. So we must tell it explicitly to rerender.
self.context.SetNeedRerenderVisibleLines()
}
self.context.HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
} else {
// If the selection did not change (because, for example, we are at the top of the list and
+84 -39
View File
@@ -82,6 +82,15 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Tooltip: self.c.Tr.FixupTooltip,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Commits.SetFixupMessage),
Handler: self.withItem(self.setFixupMessage),
GetDisabledReason: self.require(
self.singleItemSelected(self.canSetFixupMessage),
),
Description: self.c.Tr.SetFixupMessage,
Tooltip: self.c.Tr.SetFixupMessageTooltip,
},
{
Key: opts.GetKey(opts.Config.Commits.RenameCommit),
Handler: self.withItem(self.reword),
@@ -321,20 +330,71 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
return self.updateTodos(todo.Fixup, selectedCommits)
}
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Fixup,
Prompt: self.c.Tr.SureFixupThisCommit,
HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
return self.interactiveRebase(todo.Fixup, startIdx, endIdx)
})
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Fixup,
Items: []*types.MenuItem{
{
Label: self.c.Tr.Fixup,
Key: 'f',
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
return self.interactiveRebase(todo.Fixup, startIdx, endIdx)
})
},
Tooltip: self.c.Tr.FixupTooltip,
},
{
Label: self.c.Tr.FixupKeepMessage,
Key: 'c',
OnPress: func() error {
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage)
return self.interactiveRebaseWithFlag(todo.Fixup, startIdx, endIdx, "-C")
})
},
Tooltip: self.c.Tr.FixupKeepMessageTooltip,
},
},
})
}
func (self *LocalCommitsController) canSetFixupMessage(commit *models.Commit) *types.DisabledReason {
if !self.isRebasing() {
return &types.DisabledReason{Text: self.c.Tr.NotMidRebase}
}
if commit.Action != todo.Fixup {
return &types.DisabledReason{Text: self.c.Tr.MustSelectFixupCommit}
}
return nil
}
func (self *LocalCommitsController) setFixupMessage(commit *models.Commit) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.SetFixupMessage,
Items: []*types.MenuItem{
{
Label: self.c.Tr.FixupDiscardMessage,
Key: 'f',
OnPress: func() error {
return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "")
},
Tooltip: self.c.Tr.FixupDiscardMessageTooltip,
},
{
Label: self.c.Tr.FixupKeepMessage,
Key: 'c',
OnPress: func() error {
return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "-C")
},
Tooltip: self.c.Tr.FixupKeepMessageTooltip,
},
},
})
}
func (self *LocalCommitsController) reword(commit *models.Commit) error {
commitIdx := self.context().GetSelectedLineIdx()
if self.c.Git().Config.NeedsGpgSubprocessForCommit() && !self.isHeadCommit(commitIdx) {
@@ -505,7 +565,7 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start
commits := self.c.Model().Commits
if !commits[endIdx].IsMerge() {
selectionRangeAndMode := self.getSelectionRangeAndMode()
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit)
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "")
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
types.RefreshOptions{
@@ -611,13 +671,17 @@ func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error
}
func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, startIdx int, endIdx int) error {
return self.interactiveRebaseWithFlag(action, startIdx, endIdx, "")
}
func (self *LocalCommitsController) interactiveRebaseWithFlag(action todo.TodoCommand, startIdx int, endIdx int, flag string) error {
// When performing an action that will remove the selected commits, we need to select the
// next commit down (which will end up at the start index after the action is performed)
if action == todo.Drop || action == todo.Fixup || action == todo.Squash {
self.context().SetSelection(startIdx)
}
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action)
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action, flag)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
}
@@ -626,7 +690,11 @@ func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, s
// commit meaning you are trying to edit the todo file rather than actually
// begin a rebase. It then updates the todo file with that action
func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selectedCommits []*models.Commit) error {
if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action); err != nil {
return self.updateTodosWithFlag(action, selectedCommits, "")
}
func (self *LocalCommitsController) updateTodosWithFlag(action todo.TodoCommand, selectedCommits []*models.Commit, flag string) error {
if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action, flag); err != nil {
return err
}
@@ -670,6 +738,7 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
return err
}
self.context().MoveSelection(1)
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
@@ -682,6 +751,7 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
err := self.c.Git().Rebase.MoveCommitsDown(self.c.Model().Commits, startIdx, endIdx)
if err == nil {
self.context().MoveSelection(1)
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
}
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err, types.RefreshOptions{Mode: types.SYNC})
@@ -694,6 +764,7 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
return err
}
self.context().MoveSelection(-1)
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
@@ -706,6 +777,7 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
err := self.c.Git().Rebase.MoveCommitsUp(self.c.Model().Commits, startIdx, endIdx)
if err == nil {
self.context().MoveSelection(-1)
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
}
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err, types.RefreshOptions{Mode: types.SYNC})
@@ -1096,7 +1168,7 @@ func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, reba
// For each commit _above_ the selection, ...
for i, commit := range commits[0:selectedIdx] {
// ... see if it is a fixup commit, and get the base subject it applies to
if baseSubject, isFixup := isFixupCommit(commit.Name); isFixup {
if baseSubject, isFixup := helpers.IsFixupCommit(commit.Name); isFixup {
// Then, for each commit after the fixup, up to and including the
// rebase start commit, see if we find the base commit
for _, baseCommit := range commits[i+1 : rebaseStartIdx+1] {
@@ -1109,33 +1181,6 @@ func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, reba
return result
}
// Check whether the given subject line is the subject of a fixup commit, and
// returns (trimmedSubject, true) if so (where trimmedSubject is the subject
// with all fixup prefixes removed), or (subject, false) if not.
func isFixupCommit(subject string) (string, bool) {
prefixes := []string{"fixup! ", "squash! ", "amend! "}
trimPrefix := func(s string) (string, bool) {
for _, prefix := range prefixes {
if trimmedSubject, ok := strings.CutPrefix(s, prefix); ok {
return trimmedSubject, true
}
}
return s, false
}
if subject, wasTrimmed := trimPrefix(subject); wasTrimmed {
for {
// handle repeated prefixes like "fixup! amend! fixup! Subject"
if subject, wasTrimmed = trimPrefix(subject); !wasTrimmed {
break
}
}
return subject, true
}
return subject, false
}
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Hash(), func() {})
}
@@ -93,49 +93,3 @@ func Test_countSquashableCommitsAbove(t *testing.T) {
})
}
}
func Test_isFixupCommit(t *testing.T) {
scenarios := []struct {
subject string
expectedTrimmedSubject string
expectedIsFixup bool
}{
{
subject: "Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: false,
},
{
subject: "fixup Bla",
expectedTrimmedSubject: "fixup Bla",
expectedIsFixup: false,
},
{
subject: "fixup! Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: true,
},
{
subject: "fixup! fixup! Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: true,
},
{
subject: "amend! squash! Bla",
expectedTrimmedSubject: "Bla",
expectedIsFixup: true,
},
{
subject: "fixup!",
expectedTrimmedSubject: "fixup!",
expectedIsFixup: false,
},
}
for _, s := range scenarios {
t.Run(s.subject, func(t *testing.T) {
trimmedSubject, isFixupCommit := isFixupCommit(s.subject)
assert.Equal(t, s.expectedTrimmedSubject, trimmedSubject)
assert.Equal(t, s.expectedIsFixup, isFixupCommit)
})
}
}
+2
View File
@@ -61,6 +61,8 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
gui.State.Contexts.Menu.SetKeybindingsTakePrecedence(!opts.KeepConflictingKeybindings)
gui.State.Contexts.Menu.SetSelection(0)
gui.Views.Menu.SetOriginY(0)
gui.Views.Menu.Title = opts.Title
gui.Views.Menu.FgColor = theme.GocuiDefaultTextColor
+6 -1
View File
@@ -385,7 +385,12 @@ func displayCommit(
actionString := ""
if commit.Action != models.ActionNone {
actionString = actionColorMap(commit.Action, commit.Status).Sprint(commit.Action.String())
actionStr := commit.Action.String()
// Only show the flag for fixup commands (where -C changes the meaning)
if commit.ActionFlag != "" && commit.Action == todo.Fixup {
actionStr += " " + commit.ActionFlag
}
actionString = actionColorMap(commit.Action, commit.Status).Sprint(actionStr)
}
tagString := ""
-1
View File
@@ -102,7 +102,6 @@ func (gui *Gui) createAllViews() error {
for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} {
view.Wrap = true
view.IgnoreCarriageReturns = true
view.UnderlineHyperLinksOnlyOnHover = true
view.AutoRenderHyperLinks = true
}
+18 -4
View File
@@ -164,7 +164,12 @@ type TranslationSet struct {
CannotSquashOrFixupMergeCommit string
Fixup string
FixupTooltip string
SureFixupThisCommit string
FixupKeepMessage string
FixupKeepMessageTooltip string
SetFixupMessage string
SetFixupMessageTooltip string
FixupDiscardMessage string
FixupDiscardMessageTooltip string
SureSquashThisCommit string
Squash string
PickCommitTooltip string
@@ -316,6 +321,8 @@ type TranslationSet struct {
ViewRevertOptions string
NotMergingOrRebasing string
AlreadyRebasing string
NotMidRebase string
MustSelectFixupCommit string
RecentRepos string
MergeOptionsTitle string
RebaseOptionsTitle string
@@ -897,7 +904,6 @@ type TranslationSet struct {
SelectedItemIsNotABranch string
SelectedItemDoesNotHaveFiles string
MultiSelectNotSupportedForSubmodules string
OldCherryPickKeyWarning string
CommandDoesNotSupportOpeningInEditor string
CustomCommands string
NoApplicableCommandsInThisContext string
@@ -966,6 +972,7 @@ type Actions struct {
CheckoutFile string
SquashCommitDown string
FixupCommit string
FixupCommitKeepMessage string
RewordCommit string
DropCommit string
EditCommit string
@@ -1260,7 +1267,12 @@ func EnglishTranslationSet() *TranslationSet {
CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
CannotSquashOrFixupMergeCommit: "Cannot squash or fixup a merge commit",
Fixup: "Fixup",
SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?",
FixupKeepMessage: "Fixup and use this commit's message",
FixupKeepMessageTooltip: "Squash the selected commit into the commit below, using this commit's message, discarding the message of the commit below.",
SetFixupMessage: "Set fixup message",
SetFixupMessageTooltip: "Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message.",
FixupDiscardMessage: "Fixup and discard this commit's message",
FixupDiscardMessageTooltip: "Squash the selected commit into the commit below, discarding this commit's message.",
SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?",
Squash: "Squash",
PickCommitTooltip: "Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase.",
@@ -1412,6 +1424,8 @@ func EnglishTranslationSet() *TranslationSet {
ViewRevertOptions: "View revert options",
NotMergingOrRebasing: "You are currently neither rebasing nor merging",
AlreadyRebasing: "Can't perform this action during a rebase",
NotMidRebase: "This action only works during an interactive rebase",
MustSelectFixupCommit: "This action only works on fixup commits",
RecentRepos: "Recent repositories",
MergeOptionsTitle: "Merge options",
RebaseOptionsTitle: "Rebase options",
@@ -1993,7 +2007,6 @@ func EnglishTranslationSet() *TranslationSet {
SelectedItemIsNotABranch: "Selected item is not a branch",
SelectedItemDoesNotHaveFiles: "Selected item does not have files to view",
MultiSelectNotSupportedForSubmodules: "Multiselection not supported for submodules",
OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
CommandDoesNotSupportOpeningInEditor: "This command doesn't support switching to the editor",
CustomCommands: "Custom commands",
NoApplicableCommandsInThisContext: "(No applicable commands in this context)",
@@ -2025,6 +2038,7 @@ func EnglishTranslationSet() *TranslationSet {
CheckoutFile: "Checkout file",
SquashCommitDown: "Squash commit down",
FixupCommit: "Fixup commit",
FixupCommitKeepMessage: "Fixup commit (keep message)",
RewordCommit: "Reword commit",
DropCommit: "Drop commit",
EditCommit: "Edit commit",
+1 -2
View File
@@ -44,6 +44,7 @@
"ToggleStagedAll": "すべてステージ",
"ToggleStagedAllTooltip": "ワーキングツリー内のすべてのファイルのステージ/アンステージを切り替えます。",
"ToggleTreeView": "ファイルツリービューを切り替え",
"ToggleTreeViewTooltip": "ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。\n\nデフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。",
"OpenDiffTool": "外部差分ツールを開く(git difftool)",
"OpenMergeTool": "外部マージツールを開く",
"Refresh": "更新",
@@ -144,7 +145,6 @@
"CannotSquashOrFixupMergeCommit": "マージコミットをスカッシュまたはフィックスアップすることはできません",
"Fixup": "フィックスアップ",
"FixupTooltip": "選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。",
"SureFixupThisCommit": "選択したコミットを下のコミットに'フィックスアップ'してもよろしいですか?",
"SureSquashThisCommit": "選択したコミットを下のコミットにスカッシュしてもよろしいですか?",
"Squash": "スカッシュ",
"PickCommitTooltip": "選択したコミットをピックするようにマークします(リベース中)。これは、リベースを続行すると、コミットが保持されることを意味します。",
@@ -814,7 +814,6 @@
"SelectedItemIsNotABranch": "選択された項目はブランチではありません",
"SelectedItemDoesNotHaveFiles": "選択された項目には表示するファイルがありません",
"MultiSelectNotSupportedForSubmodules": "サブモジュールでは複数選択はサポートされていません",
"OldCherryPickKeyWarning": "'c'キーは現在、コミットのコピー(チェリーピック)のデフォルトキーではなくなりました。代わりに`{{.copy}}`キーを使用してコピーし、`{{.paste}}`キーでペーストしてください。\n\nこの変更理由は以下の通りです:\n- 以前は'v'キーがステージング時の行選択にのみ使用されていましたが、現在はすべてのリストビューで範囲選択に使われています\n- そのため、コミットペースト用の新しいキーが必要になり、`{{.paste}}`をペーストに使うなら、`{{.copy}}`をコピーに使う方が直感的です\n\n以前の動作に戻したい場合は、設定ファイルに以下を追加してください:\n\nkeybinding:\n universal:\n toggleRangeSelect: <v以外の何か>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
"CommandDoesNotSupportOpeningInEditor": "このコマンドはエディタへの切り替えをサポートしていません",
"CustomCommands": "カスタムコマンド",
"NoApplicableCommandsInThisContext": "(このコンテキストでは適用可能なコマンドはありません)",
-1
View File
@@ -77,7 +77,6 @@
"CloseCancel": "닫기/취소",
"Confirm": "확인",
"Quit": "종료",
"SureFixupThisCommit": "Are you sure you want to 'fixup' this commit? It will be merged into the commit below",
"SureSquashThisCommit": "Are you sure you want to squash this commit into the commit below?",
"Squash": "스쿼시",
"PickCommitTooltip": "Pick commit (when mid-rebase)",
-1
View File
@@ -49,7 +49,6 @@
"Close": "Sluiten",
"CloseCancel": "Sluiten",
"Confirm": "Bevestig",
"SureFixupThisCommit": "Weet je zeker dat je fixup wil uitvoeren op deze commit? De commit hieronder zol worden squashed in deze",
"SureSquashThisCommit": "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?",
"PickCommitTooltip": "Kies commit (wanneer midden in rebase)",
"Reword": "Hernoem commit",
-2
View File
@@ -100,7 +100,6 @@
"CannotSquashOrFixupFirstCommit": "Nie ma commita poniżej do scalenia",
"Fixup": "Poprawka",
"FixupTooltip": "Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona.",
"SureFixupThisCommit": "Czy na pewno chcesz 'poprawić' wybrane commit(y) do commita poniżej?",
"SureSquashThisCommit": "Czy na pewno chcesz scalić wybrane commit(y) do commita poniżej?",
"Squash": "Scal",
"PickCommitTooltip": "Oznacz wybrany commit do wybrania (podczas rebazowania). Oznacza to, że commit zostanie zachowany po kontynuacji rebazowania.",
@@ -668,7 +667,6 @@
"NoItemSelected": "Nie wybrano elementu",
"SelectedItemIsNotABranch": "Wybrany element nie jest branch'em",
"SelectedItemDoesNotHaveFiles": "Wybrany element nie ma plików do wyświetlenia",
"OldCherryPickKeyWarning": "Klawisz 'c' nie jest już domyślnym klawiszem do kopiowania commitów do cherry pick. Proszę użyj `{{.copy}}` zamiast tego (i `{{.paste}}` aby wkleić). Powodem tej zmiany jest to, że klawisz 'v' do wybierania zakresu linii podczas stagingu jest teraz używany również do wybierania zakresu linii w każdym widoku listy, co oznacza, że musieliśmy znaleźć nowy klawisz do wklejania commitów, i jeśli zamierzamy teraz używać `{{.paste}}` do wklejania commitów, możemy równie dobrze użyć `{{.copy}}` do ich kopiowania. Jeśli chcesz skonfigurować klawisze, aby uzyskać stare zachowanie, ustaw następujące w swojej konfiguracji:\n\nkeybinding:\n universal:\n toggleRangeSelect: <coś innego niż v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
"Actions": {
"CheckoutCommit": "Przełącz commit",
"CheckoutTag": "Przełącz tag",
-1
View File
@@ -137,7 +137,6 @@
"CannotSquashOrFixupMergeCommit": "Não é possível squash ou corrigir um commit de merge",
"Fixup": "Fixup",
"FixupTooltip": "Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada.",
"SureFixupThisCommit": "Tem certeza que deseja 'corrigir' o(s) commit(s) selecionado(s) no commit abaixo?",
"SureSquashThisCommit": "Tem certeza que deseja esmagar o(s) commit(s) selecionado(s) no commit abaixo?",
"Squash": "Squash",
"PickCommitTooltip": "Marque o commit selecionado para ser escolhido (quando meados da base). Isso significa que o commit será mantido ao continuar o rebase.",
-1
View File
@@ -63,7 +63,6 @@
"Quit": "Выйти",
"CannotSquashOrFixupFirstCommit": "Ниже нет коммита, который можно было бы объединить",
"Fixup": "Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) ",
"SureFixupThisCommit": "Вы уверены, что хотите объединить несколько коммитов, отбросив сообщение коммита? Он будет объединён с коммитом ниже",
"SureSquashThisCommit": "Вы уверены, что хотите объединить несколько коммитов в нижний коммит?",
"Squash": "Объединить коммиты (Squash)",
"PickCommitTooltip": "Выбрать коммит (в середине перебазирования)",
+4 -5
View File
@@ -152,7 +152,6 @@
"CannotSquashOrFixupMergeCommit": "无法对合并提交进行压缩或修正",
"Fixup": "修正 (fixup)",
"FixupTooltip": "将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。",
"SureFixupThisCommit": "您确定要“修正”此提交吗?它将合并到下面的提交中",
"SureSquashThisCommit": "您确定要将这个提交压缩到下面的提交中吗?",
"Squash": "压缩(Squash)",
"PickCommitTooltip": "标记选中的提交为 picked(变基过程中)。这意味该提交将在后续的变基中保留。",
@@ -249,7 +248,7 @@
"RefreshFiles": "刷新文件",
"FocusMainView": "聚焦主视图",
"Merge": "合并到当前检出的分支",
"MergeBranchTooltip": "Merge selected branch into currently checked out branch.",
"MergeBranchTooltip": "查看将选中项合并到当前分支的选项(正常合并,压缩合并)",
"RegularMergeFastForward": "常规合并(快进)",
"RegularMergeFastForwardTooltip": "将 '{{.checkedOutBranch}}' 快进到 '{{.selectedBranch}}',不创建合并提交。",
"CannotFastForwardMerge": "无法将 '{{.checkedOutBranch}}' 快进到 '{{.selectedBranch}}'",
@@ -413,6 +412,7 @@
"ViewItemFiles": "查看提交的文件",
"CommitFilesTitle": "提交文件",
"CheckoutCommitFileTooltip": "检出文件",
"CannotCheckoutWithModifiedFilesErr": "您已有对您试图签出的文件作出的本地修改。您需要先保存或丢弃这些文件。",
"CanOnlyDiscardFromLocalCommits": "只能从本地提交中丢弃更改",
"Remove": "删除",
"DiscardOldFileChangeTooltip": "放弃对此文件的提交变更",
@@ -482,7 +482,7 @@
"StashTooltip": "贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键",
"StashAllChanges": "将所有变更加入贮藏",
"StashStagedChanges": "贮藏已暂存变更",
"StashAllChangesKeepIndex": "将已暂存的变更加入贮藏",
"StashAllChangesKeepIndex": "贮藏所有更改并保留暂存区",
"StashUnstagedChanges": "贮藏未暂存变更",
"StashIncludeUntrackedChanges": "贮藏所有变更,包括未跟踪的文件",
"StashOptions": "贮藏选项",
@@ -882,7 +882,6 @@
"SelectedItemIsNotABranch": "选中的条目不是一个分支",
"SelectedItemDoesNotHaveFiles": "选中的条目中没有",
"MultiSelectNotSupportedForSubmodules": "子模块不支持多选操作",
"OldCherryPickKeyWarning": "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
"CommandDoesNotSupportOpeningInEditor": "该命令不支持切换到编辑器",
"CustomCommands": "自定义命令",
"NoApplicableCommandsInThisContext": "(当前上下文无可用命令)",
@@ -951,7 +950,7 @@
"Pull": "拉取(Pull)",
"OpenFile": "打开文件",
"StashAllChanges": "贮藏所有变更",
"StashAllChangesKeepIndex": "贮藏所有更改并保持索引",
"StashAllChangesKeepIndex": "贮藏所有更改并保留暂存区",
"StashStagedChanges": "贮藏暂存的变更",
"StashUnstagedChanges": "贮藏所有未暂存更改",
"StashIncludeUntrackedChanges": "贮藏所有更改包括未跟踪的文件",
-1
View File
@@ -83,7 +83,6 @@
"Quit": "結束",
"CannotSquashOrFixupFirstCommit": "沒有可以壓縮的提交",
"Fixup": "修復 (Fixup)",
"SureFixupThisCommit": "是否對此提交進行 '修復' ? 其將被合併於以下之提交中",
"SureSquashThisCommit": "是否要把這個提交壓縮到下面的提交中?",
"Squash": "壓縮 (Squash)",
"PickCommitTooltip": "挑選提交 (於變基過程中)",
@@ -0,0 +1,46 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var FindBaseCommitForFixupDisregardFixupsForSameBaseCommit = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Finds the base commit to create a fixup for, disregarding fixup commits for the same base commit",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
EmptyCommit("1st commit").
NewBranch("mybranch").
CreateFileAndAdd("file1", "1\n2\n3\n").
Commit("2nd commit").
UpdateFileAndAdd("file1", "1\n2\n3a\n").
Commit("fixup! 2nd commit").
EmptyCommit("3rd commit").
UpdateFileAndAdd("file1", "1a\n2\n3b\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Lines(
Contains("3rd commit").IsSelected(),
Contains("fixup! 2nd commit"),
Contains("2nd commit"),
Contains("1st commit"),
)
t.Views().Files().
Focus().
Press(keys.Files.FindBaseCommitForFixup)
t.Views().Commits().
IsFocused().
Lines(
Contains("3rd commit"),
Contains("fixup! 2nd commit"),
Contains("2nd commit").IsSelected(),
Contains("1st commit"),
)
},
})
+52
View File
@@ -11,6 +11,10 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
// Creating a branch avoids that searching for 't' will unexpectedly match the first commit
// (since it finds it in the extra info line, which is "HEAD -> master")
shell.NewBranch("branch")
shell.EmptyCommit("one")
shell.EmptyCommit("two")
shell.EmptyCommit("three")
@@ -103,6 +107,54 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("three"),
Contains("two"),
Contains("one").IsSelected(),
).
NavigateToLine(Contains("three")).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)"))
}).
Press("N").
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)"))
}).
Lines(
Contains("four").IsSelected(),
Contains("three"),
Contains("two"),
Contains("one"),
).
Press(keys.Universal.StartSearch).
Tap(func() {
t.ExpectSearch().
Type("t").
Confirm()
t.Views().Search().IsVisible().Content(Contains("matches for 't' (1 of 2)"))
}).
Lines(
Contains("four"),
Contains("three").IsSelected(),
Contains("two"),
Contains("one"),
).
SelectPreviousItem().
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 't' (1 of 2)"))
}).
Lines(
Contains("four").IsSelected(),
Contains("three"),
Contains("two"),
Contains("one"),
).
Press("n").
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 't' (1 of 2)"))
}).
Lines(
Contains("four"),
Contains("three").IsSelected(),
Contains("two"),
Contains("one"),
)
},
})
+3 -3
View File
@@ -50,7 +50,7 @@ var Gitignore = NewIntegrationTest(NewIntegrationTestArgs{
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .git/info/exclude")).Confirm()
t.FileSystem().FileContent(".gitignore", Equals(""))
t.FileSystem().FileContent(".git/info/exclude", Contains("toExclude"))
t.FileSystem().FileContent(".git/info/exclude", Contains("/toExclude"))
}).
SelectNextItem().
Press(keys.Files.IgnoreFile).
@@ -58,8 +58,8 @@ var Gitignore = NewIntegrationTest(NewIntegrationTestArgs{
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .gitignore")).Confirm()
t.FileSystem().FileContent(".gitignore", Equals("toIgnore\n"))
t.FileSystem().FileContent(".git/info/exclude", Contains("toExclude"))
t.FileSystem().FileContent(".gitignore", Equals("/toIgnore\n"))
t.FileSystem().FileContent(".git/info/exclude", Contains("/toExclude"))
})
},
})
@@ -61,6 +61,6 @@ var GitignoreSpecialCharacters = NewIntegrationTest(NewIntegrationTestArgs{
Equals(" ?? abc_def"),
)
t.FileSystem().FileContent(".gitignore", Equals("\\#file\nfile#abc\n\\!file\nfile!abc\nabc\\*def\nfile\\[x\\]\n"))
t.FileSystem().FileContent(".gitignore", Equals("/\\#file\n/file#abc\n/\\!file\n/file!abc\n/abc\\*def\n/file\\[x\\]\n"))
},
})
@@ -0,0 +1,48 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var FixupKeepMessage = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Fixup a commit, keeping its commit message",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
CreateFileAndAdd("file1.txt", "File1 Content\n").Commit("First Commit").
CreateFileAndAdd("file2.txt", "File2 Content\n").Commit("Second Commit").
CreateFileAndAdd("file3.txt", "File3 Content\n").Commit("Third Commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("Third Commit"),
Contains("Second Commit"),
Contains("First Commit"),
).
NavigateToLine(Contains("Second Commit")).
Press(keys.Commits.MarkCommitAsFixup).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Fixup")).
Select(Contains("use this commit's message")).
Confirm()
}).
Lines(
Contains("Third Commit"),
Contains("Second Commit").IsSelected(),
)
t.Views().Main().
// The resulting commit should have the message from the fixup commit,
// not the target commit
Content(Contains("Second Commit")).
Content(DoesNotContain("First Commit")).
Content(Contains("+File1 Content")).
Content(Contains("+File2 Content"))
},
})
@@ -0,0 +1,78 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var FixupKeepMessageRebase = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Set fixup -C flag on a fixup commit during interactive rebase",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
CreateFileAndAdd("file1.txt", "File1 Content\n").Commit("First Commit").
CreateFileAndAdd("file2.txt", "File2 Content\n").Commit("Second Commit").
CreateFileAndAdd("file3.txt", "File3 Content\n").Commit("Third Commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("Third Commit"),
Contains("Second Commit"),
Contains("First Commit"),
).
// Start interactive rebase from the first commit
NavigateToLine(Contains("First Commit")).
Press(keys.Universal.Edit).
Lines(
Contains("--- Pending rebase todos ---"),
Contains("pick CI Third Commit"),
Contains("pick CI Second Commit"),
Contains("--- Commits ---"),
Contains("First Commit").IsSelected(),
).
// Mark second commit as fixup
NavigateToLine(Contains("Second Commit")).
Press(keys.Commits.MarkCommitAsFixup).
Lines(
Contains("--- Pending rebase todos ---"),
Contains("pick CI Third Commit"),
Contains("fixup CI Second Commit").IsSelected(),
Contains("--- Commits ---"),
Contains("First Commit"),
).
// Now set the -C flag using the SetFixupMessage keybinding
Press(keys.Commits.SetFixupMessage).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Set fixup message")).
Select(Contains("use this commit's message")).
Confirm()
}).
Lines(
Contains("--- Pending rebase todos ---"),
Contains("pick CI Third Commit"),
Contains("fixup -C CI Second Commit").IsSelected(),
Contains("--- Commits ---"),
Contains("First Commit"),
).
// Continue the rebase
Tap(func() {
t.Common().ContinueRebase()
}).
Lines(
Contains("Third Commit"),
Contains("Second Commit").IsSelected(),
)
t.Views().Main().
// The resulting commit should have the message from the fixup commit
Content(Contains("Second Commit")).
Content(DoesNotContain("First Commit")).
Content(Contains("+File1 Content")).
Content(Contains("+File2 Content"))
},
})
@@ -27,9 +27,8 @@ var FixupSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("Fixup Commit Message")).
Press(keys.Commits.MarkCommitAsFixup).
Tap(func() {
t.ExpectPopup().Confirmation().
t.ExpectPopup().Menu().
Title(Equals("Fixup")).
Content(Equals("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")).
Confirm()
}).
Lines(
@@ -77,9 +77,8 @@ var OutsideRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
).
Press(keys.Commits.MarkCommitAsFixup).
Tap(func() {
t.ExpectPopup().Confirmation().
t.ExpectPopup().Menu().
Title(Equals("Fixup")).
Content(Contains("Are you sure you want to 'fixup' the selected commit(s) into the commit below?")).
Confirm()
}).
TopLines(
+3
View File
@@ -130,6 +130,7 @@ var tests = []*components.IntegrationTest{
commit.DoNotShowBranchMarkerForHeadCommit,
commit.FailHooksThenCommitNoHooks,
commit.FindBaseCommitForFixup,
commit.FindBaseCommitForFixupDisregardFixupsForSameBaseCommit,
commit.FindBaseCommitForFixupDisregardMainBranch,
commit.FindBaseCommitForFixupOnlyAddedLines,
commit.FindBaseCommitForFixupWarningForAddedLines,
@@ -278,6 +279,8 @@ var tests = []*components.IntegrationTest{
interactive_rebase.EditRangeSelectOutsideRebase,
interactive_rebase.EditTheConflCommit,
interactive_rebase.FixupFirstCommit,
interactive_rebase.FixupKeepMessage,
interactive_rebase.FixupKeepMessageRebase,
interactive_rebase.FixupSecondCommit,
interactive_rebase.InteractiveRebaseOfCopiedBranch,
interactive_rebase.InteractiveRebaseWithConflictForEditCommand,
+2
View File
@@ -19,6 +19,7 @@ type Todo struct {
type TodoChange struct {
Hash string
NewAction todo.TodoCommand
Flag string
}
// Read a git-rebase-todo file, change the actions for the given commits,
@@ -37,6 +38,7 @@ func EditRebaseTodo(filePath string, changes []TodoChange, commentChar byte) err
if equalHash(t.Commit, change.Hash) {
matchCount++
t.Command = change.NewAction
t.Flag = change.Flag
}
}
}
+4
View File
@@ -946,6 +946,10 @@
"type": "string",
"default": "f"
},
"setFixupMessage": {
"type": "string",
"default": "c"
},
"createFixupCommit": {
"type": "string",
"default": "F"
+4
View File
@@ -946,6 +946,10 @@
"type": "string",
"default": "f"
},
"setFixupMessage": {
"type": "string",
"default": "c"
},
"createFixupCommit": {
"type": "string",
"default": "F"
+6
View File
@@ -18,6 +18,8 @@ fi
download_dir="$1"
echo "Updating translations..."
# The Portuguese translation is named pt-PT, but we want to use pt instead (it
# is used both for Brasilian and European Portuguese). I couldn't figure out how
# to change this in Crowdin, so we'll do it here.
@@ -30,3 +32,7 @@ do
# exporting, but unfortunately it doesn't work for json files.
jq 'del(..|select(. == ""))' < "$d/en.json" > pkg/i18n/translations/$(basename "$d").json
done
# Update cheatsheets, which might be affected by changes in the translations.
echo "Regenerating auto-generated files..."
go generate ./...
+83 -41
View File
@@ -84,6 +84,7 @@ type inputProcessor struct {
evch chan<- Event
rows int // used for clipping mouse coordinates
cols int // used for clipping mouse coordinates
surrogate rune
nested *inputProcessor
}
@@ -135,8 +136,9 @@ type csiParamMode struct {
}
type keyMap struct {
Key Key
Mod ModMask
Key Key
Mod ModMask
Rune rune
}
var csiAllKeys = map[csiParamMode]keyMap{
@@ -275,41 +277,70 @@ var csiAllKeys = map[csiParamMode]keyMap{
}
// keys reported using Kitty csi-u protocol
var csiUKeys = map[int]Key{
27: KeyESC,
9: KeyTAB,
13: KeyEnter,
127: KeyBS,
57358: KeyCapsLock,
57359: KeyScrollLock,
57360: KeyNumLock,
57361: KeyPrint,
57362: KeyPause,
57363: KeyMenu,
57376: KeyF13,
57377: KeyF14,
57378: KeyF15,
57379: KeyF16,
57380: KeyF17,
57381: KeyF18,
57382: KeyF19,
57383: KeyF20,
57384: KeyF21,
57385: KeyF22,
57386: KeyF23,
57387: KeyF24,
57388: KeyF25,
57389: KeyF26,
57390: KeyF27,
57391: KeyF28,
57392: KeyF29,
57393: KeyF30,
57394: KeyF31,
57395: KeyF32,
57396: KeyF33,
57397: KeyF34,
57398: KeyF35,
// TODO: KP keys
var csiUKeys = map[int]keyMap{
27: {Key: KeyESC},
9: {Key: KeyTAB},
13: {Key: KeyEnter},
127: {Key: KeyBS},
57358: {Key: KeyCapsLock},
57359: {Key: KeyScrollLock},
57360: {Key: KeyNumLock},
57361: {Key: KeyPrint},
57362: {Key: KeyPause},
57363: {Key: KeyMenu},
57376: {Key: KeyF13},
57377: {Key: KeyF14},
57378: {Key: KeyF15},
57379: {Key: KeyF16},
57380: {Key: KeyF17},
57381: {Key: KeyF18},
57382: {Key: KeyF19},
57383: {Key: KeyF20},
57384: {Key: KeyF21},
57385: {Key: KeyF22},
57386: {Key: KeyF23},
57387: {Key: KeyF24},
57388: {Key: KeyF25},
57389: {Key: KeyF26},
57390: {Key: KeyF27},
57391: {Key: KeyF28},
57392: {Key: KeyF29},
57393: {Key: KeyF30},
57394: {Key: KeyF31},
57395: {Key: KeyF32},
57396: {Key: KeyF33},
57397: {Key: KeyF34},
57398: {Key: KeyF35},
57399: {Key: KeyRune, Rune: '0'}, // KP 0
57400: {Key: KeyRune, Rune: '1'}, // KP 1
57401: {Key: KeyRune, Rune: '2'}, // KP 2
57402: {Key: KeyRune, Rune: '3'}, // KP 3
57403: {Key: KeyRune, Rune: '4'}, // KP 4
57404: {Key: KeyRune, Rune: '5'}, // KP 5
57405: {Key: KeyRune, Rune: '6'}, // KP 6
57406: {Key: KeyRune, Rune: '7'}, // KP 7
57407: {Key: KeyRune, Rune: '8'}, // KP 8
57408: {Key: KeyRune, Rune: '9'}, // KP 9
57409: {Key: KeyRune, Rune: '.'}, // KP_DECIMAL
57410: {Key: KeyRune, Rune: '/'}, // KP_DIVIDE
57411: {Key: KeyRune, Rune: '*'}, // KP_MULTIPLY
57412: {Key: KeyRune, Rune: '-'}, // KP_SUBTRACT
57413: {Key: KeyRune, Rune: '+'}, // KP_ADD
57414: {Key: KeyEnter}, // KP_ENTER
57415: {Key: KeyRune, Rune: '='}, // KP_EQUAL
57416: {Key: KeyClear}, // KP_SEPARATOR
57417: {Key: KeyLeft}, // KP_LEFT
57418: {Key: KeyRight}, // KP_RIGHT
57419: {Key: KeyUp}, // KP_UP
57420: {Key: KeyDown}, // KP_DOWN
57421: {Key: KeyPgUp}, // KP_PG_UP
57422: {Key: KeyPgDn}, // KP_PG_DN
57423: {Key: KeyHome}, // KP_HOME
57424: {Key: KeyEnd}, // KP_END
57425: {Key: KeyInsert}, // KP_INSERT
57426: {Key: KeyDelete}, // KP_DELETE
// 57427: {Key: KeyBegin}, // KP_BEGIN
// TODO: Media keys
}
@@ -318,8 +349,8 @@ var winKeys = map[int]Key{
0x03: KeyCancel, // vkCancel
0x08: KeyBackspace, // vkBackspace
0x09: KeyTab, // vkTab
0x0c: KeyClear, // vClear
0x0d: KeyEnter, // vkReturn
0x12: KeyClear, // vClear
0x13: KeyPause, // vkPause
0x1b: KeyEscape, // vkEscape
0x21: KeyPgUp, // vkPrior
@@ -701,11 +732,22 @@ func (ip *inputProcessor) handleWinKey(P []int) {
} else if chr < ' ' && P[0] >= 0x41 && P[0] <= 0x5a {
key = Key(P[0])
chr = 0
} else if key == 0x11 || key == 0x13 || key == 0x14 {
} else if chr >= 0xD800 && chr <= 0xDBFF {
// high surrogate pair
ip.surrogate = chr
return
} else if chr >= 0xDC00 && chr <= 0xDFFF {
// low surrogate pair
chr = utf16.DecodeRune(ip.surrogate, chr)
} else if P[0] == 0x10 || P[0] == 0x11 || P[0] == 0x12 || P[0] == 0x14 {
// lone modifiers
ip.surrogate = 0
return
}
ip.surrogate = 0
// Modifiers
if P[4]&0x010 != 0 {
mod |= ModShift
@@ -791,8 +833,8 @@ func (ip *inputProcessor) handleCsi(mode rune, params []byte, intermediate []byt
key := KeyRune
chr := rune(0)
if k1, ok := csiUKeys[P0]; ok {
key = k1
chr = 0
key = k1.Key
chr = k1.Rune
} else {
chr = rune(P0)
}
+14 -1
View File
@@ -622,6 +622,14 @@ func (t *tScreen) drawCell(x, y int) int {
width = 1
str = " "
}
if width > 1 && x+width < t.w {
// Clobber over any content in the next cell.
// This fixes a problem with some terminals where overwriting two
// adjacent single cells with a wide rune would leave an image
// of the second cell. This is a workaround for buggy terminals.
t.writeString(" \b\b")
}
t.writeString(str)
t.cx += width
t.cells.SetDirty(x, y, false)
@@ -989,9 +997,14 @@ func (t *tScreen) buildAcsMap() {
}
func (t *tScreen) scanInput(buf *bytes.Buffer) {
// The end of the buffer isn't necessarily the end of the input, because
// large inputs are chunked. Set atEOF to false so the UTF-8 validating decoder
// returns ErrShortSrc instead of ErrInvalidUTF8 for incomplete multi-byte codepoints.
const atEOF = false
for buf.Len() > 0 {
utf := make([]byte, min(8, max(buf.Len()*2, 128)))
nOut, nIn, e := t.decoder.Transform(utf, buf.Bytes(), true)
nOut, nIn, e := t.decoder.Transform(utf, buf.Bytes(), atEOF)
_ = buf.Next(nIn)
t.input.ScanUTF8(utf[:nOut])
if e == transform.ErrShortSrc {
+23 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2025 The TCell Authors
// Copyright 2026 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -72,6 +72,7 @@ type winTty struct {
oomode uint32 // original output mode
oscreen consoleInfo
wg sync.WaitGroup
surrogate rune
sync.Mutex
}
@@ -126,7 +127,7 @@ func (w *winTty) Drain() error {
func (w *winTty) getConsoleInput() error {
// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
// in the event that both events are signalled.
// in the event that both events are signaled.
waitObjects := []syscall.Handle{w.cancelFlag, w.in}
// As arrays are contiguous in memory, a pointer to the first object is the
@@ -158,19 +159,30 @@ func (w *winTty) getConsoleInput() error {
if rv == 0 {
return er
}
loop:
for i := range nrec {
ir := rec[i]
switch ir.typ {
case keyEvent:
chr := ir.data[10] // we only see ASCII, key down events in VT mode
// because we use win32-input-mode, we will only
// see US-ASCII characters - (Q: will they be
// 16-bit values with possible surrogate pairs?)
select {
case w.buf <- chr:
case <-w.stopQ:
break
// we normally only expect to see ascii, but paste data may come in as UTF-16.
wc := rune(binary.LittleEndian.Uint16(ir.data[10:]))
if wc >= 0xD800 && wc <= 0xDBFF {
// if it was a high surrogate, which happens for pasted UTF-16,
// then save it until we get the low and can decode it.
w.surrogate = wc
continue
} else if wc >= 0xDC00 && wc <= 0xDFFF {
wc = utf16.DecodeRune(w.surrogate, wc)
}
w.surrogate = 0
for _, chr := range []byte(string(wc)) {
// We normally expect only to see ASCII (win32-input-mode),
// but apparently pasted data can arrive in UTF-16 here.
select {
case w.buf <- chr:
case <-w.stopQ:
break loop
}
}
case resizeEvent:
+11
View File
@@ -39,6 +39,7 @@ func (self noInstruction) isInstruction() {}
const (
stateNone escapeState = iota
stateEscape
stateCharacterSetDesignation
stateCSI
stateParams
stateOSC
@@ -144,9 +145,19 @@ func (ei *escapeInterpreter) parseOne(ch []byte) (isEscape bool, err error) {
case characterEquals(ch, ']'):
ei.state = stateOSC
return true, nil
case characterEquals(ch, '('),
characterEquals(ch, ')'),
characterEquals(ch, '*'),
characterEquals(ch, '+'):
ei.state = stateCharacterSetDesignation
return true, nil
default:
return false, errNotCSI
}
case stateCharacterSetDesignation:
// Not supported, so just skip it
ei.state = stateNone
return true, nil
case stateCSI:
switch {
case len(ch) == 1 && ch[0] >= '0' && ch[0] <= '9':
+2 -2
View File
@@ -1313,9 +1313,9 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
// back to '\n' fixes pasting multi-line text from Ghostty, and doesn't
// seem harmful for other terminal emulators.
//
// KeyCtrlJ (int value 10) is '\r', and KeyCtrlM (int value 13) is '\n'.
// KeyCtrlJ (int value 10) is '\r'.
if g.IsPasting && ev.Key == KeyCtrlJ {
ev.Key = KeyCtrlM
ev.Key = KeyEnter
}
err := g.execKeybindings(g.currentView, ev)
+75 -1
View File
@@ -71,6 +71,7 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) {
currentLineWidth := 0
indexOfLastWhitespace := -1
var footNoteMatcher footNoteMatcher
var trailerMatcher trailerMatcher
cells := stringToTextAreaCells(content)
y := 0
@@ -94,9 +95,10 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) {
indexOfLastWhitespace = -1
currentLineWidth = 0
footNoteMatcher.reset()
trailerMatcher.reset()
} else {
currentLineWidth += c.width
if c.char == " " && !footNoteMatcher.isFootNote() {
if c.char == " " && !footNoteMatcher.isFootNote() && !trailerMatcher.isTrailer() {
indexOfLastWhitespace = currentPos + 1
} else if autoWrapWidth > 0 && currentLineWidth > autoWrapWidth && indexOfLastWhitespace >= 0 {
wrapAt := indexOfLastWhitespace
@@ -112,9 +114,11 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) {
currentLineWidth += c1.width
}
footNoteMatcher.reset()
trailerMatcher.reset()
}
footNoteMatcher.addCharacter(c.char)
trailerMatcher.addCharacter(c.char)
}
}
@@ -167,6 +171,76 @@ func (self *footNoteMatcher) reset() {
self.didFailToMatch = false
}
var supportedTrailers = []string{
"Signed-off-by:",
"Co-authored-by:",
}
type trailerMatcher struct {
lineStr strings.Builder
didFailToMatch bool
didMatch bool
}
func (self *trailerMatcher) addCharacter(chr string) {
if self.didFailToMatch || self.didMatch {
return
}
if len(chr) != 1 {
// Trailers are all ASCII, so if we get a non-ASCII UTF-8 character (or even a multi-rune
// grapheme cluster), we can fail early.
self.didFailToMatch = true
return
}
if self.lineStr.Len() == 0 {
// If this is the first character, see if it could possibly match any supported trailer; if
// not, we can fail early and stop tracking further characters for this line.
if !anyOf(supportedTrailers, func(trailer string) bool { return trailer[0] == chr[0] }) {
self.didFailToMatch = true
return
}
}
self.lineStr.WriteString(chr)
}
func (self *trailerMatcher) isTrailer() bool {
if self.didFailToMatch {
return false
}
if self.didMatch {
return true
}
line := self.lineStr.String()
if anyOf(supportedTrailers, func(trailer string) bool { return line == trailer }) {
self.didMatch = true
return true
}
self.didFailToMatch = true
return false
}
func (self *trailerMatcher) reset() {
self.lineStr.Reset()
self.didFailToMatch = false
self.didMatch = false
}
func anyOf(strings []string, predicate func(s string) bool) bool {
for _, s := range strings {
if predicate(s) {
return true
}
}
return false
}
func (self *TextArea) updateCells() {
width := self.AutoWrapWidth
if !self.AutoWrap {
+129 -23
View File
@@ -167,9 +167,6 @@ type View struct {
// If HasLoader is true, the message will be appended with a spinning loader animation
HasLoader bool
// IgnoreCarriageReturns tells us whether to ignore '\r' characters
IgnoreCarriageReturns bool
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
ParentView *View
@@ -217,29 +214,53 @@ type searcher struct {
searchPositions []SearchPosition
modelSearchResults []SearchPosition
currentSearchIndex int
onSelectItem func(int, int, int) error
onSelectItem func(int)
renderSearchStatus func(int, int)
}
func (v *View) SetOnSelectItem(onSelectItem func(int, int, int) error) {
func (v *View) SetRenderSearchStatus(renderSearchStatus func(int, int)) {
v.searcher.renderSearchStatus = renderSearchStatus
}
func (v *View) SetOnSelectItem(onSelectItem func(int)) {
v.searcher.onSelectItem = onSelectItem
}
func (v *View) renderSearchStatus(index int, itemCount int) {
if v.searcher.renderSearchStatus != nil {
v.searcher.renderSearchStatus(index, itemCount)
}
}
func (v *View) gotoNextMatch() error {
if len(v.searcher.searchPositions) == 0 {
return nil
}
if v.Highlight && v.oy+v.cy < v.searcher.searchPositions[v.searcher.currentSearchIndex].Y {
// If the selection is before the current match, just jump to the current match and return.
// This can only happen if the user has moved the cursor to before the first match.
v.SelectSearchResult(v.searcher.currentSearchIndex)
return nil
}
if v.searcher.currentSearchIndex >= len(v.searcher.searchPositions)-1 {
v.searcher.currentSearchIndex = 0
} else {
v.searcher.currentSearchIndex++
}
return v.SelectSearchResult(v.searcher.currentSearchIndex)
v.SelectSearchResult(v.searcher.currentSearchIndex)
return nil
}
func (v *View) gotoPreviousMatch() error {
if len(v.searcher.searchPositions) == 0 {
return nil
}
if v.Highlight && v.oy+v.cy > v.searcher.searchPositions[v.searcher.currentSearchIndex].Y {
// If the selection is after the current match, just jump to the current match and return.
// This happens if the user has moved the cursor down from the current match.
v.SelectSearchResult(v.searcher.currentSearchIndex)
return nil
}
if v.searcher.currentSearchIndex == 0 {
if len(v.searcher.searchPositions) > 0 {
v.searcher.currentSearchIndex = len(v.searcher.searchPositions) - 1
@@ -247,13 +268,14 @@ func (v *View) gotoPreviousMatch() error {
} else {
v.searcher.currentSearchIndex--
}
return v.SelectSearchResult(v.searcher.currentSearchIndex)
v.SelectSearchResult(v.searcher.currentSearchIndex)
return nil
}
func (v *View) SelectSearchResult(index int) error {
func (v *View) SelectSearchResult(index int) {
itemCount := len(v.searcher.searchPositions)
if itemCount == 0 {
return nil
return
}
if index > itemCount-1 {
index = itemCount - 1
@@ -262,10 +284,10 @@ func (v *View) SelectSearchResult(index int) error {
y := v.searcher.searchPositions[index].Y
v.FocusPoint(v.ox, y, true)
v.renderSearchStatus(index, itemCount)
if v.searcher.onSelectItem != nil {
return v.searcher.onSelectItem(y, index, itemCount)
v.searcher.onSelectItem(y)
}
return nil
}
// Returns <current match index>, <total matches>
@@ -294,26 +316,29 @@ func (v *View) UpdateSearchResults(str string, modelSearchResults []SearchPositi
if len(v.searcher.searchPositions) > 0 {
// get the first result past the current cursor
currentIndex := 0
adjustedY := v.oy + v.cy
adjustedX := v.ox + v.cx
for i, pos := range v.searcher.searchPositions {
if pos.Y > adjustedY || (pos.Y == adjustedY && pos.XStart > adjustedX) {
currentIndex = i
break
if v.Highlight {
// ...but only if we're showing the highlighted line
adjustedY := v.oy + v.cy
adjustedX := v.ox + v.cx
for i, pos := range v.searcher.searchPositions {
if pos.Y > adjustedY || (pos.Y == adjustedY && pos.XStart > adjustedX) {
currentIndex = i
break
}
}
}
v.searcher.currentSearchIndex = currentIndex
}
}
func (v *View) Search(str string, modelSearchResults []SearchPosition) error {
func (v *View) Search(str string, modelSearchResults []SearchPosition) {
v.UpdateSearchResults(str, modelSearchResults)
if len(v.searcher.searchPositions) > 0 {
return v.SelectSearchResult(v.searcher.currentSearchIndex)
v.SelectSearchResult(v.searcher.currentSearchIndex)
} else {
v.renderSearchStatus(0, 0)
}
return v.searcher.onSelectItem(-1, -1, 0)
}
func (v *View) ClearSearch() {
@@ -324,8 +349,37 @@ func (v *View) IsSearching() bool {
return v.searcher.searchString != ""
}
func (v *View) nearestSearchPosition() int {
currentLineIndex := v.cy + v.oy
lastSearchPos := 0
for i, pos := range v.searcher.searchPositions {
if pos.Y == currentLineIndex {
return i
}
if pos.Y > currentLineIndex {
break
}
lastSearchPos = i
}
return lastSearchPos
}
func (v *View) SetNearestSearchPosition() {
if len(v.searcher.searchPositions) > 0 {
newPos := v.nearestSearchPosition()
if newPos != v.searcher.currentSearchIndex {
v.searcher.currentSearchIndex = newPos
v.renderSearchStatus(newPos, len(v.searcher.searchPositions))
}
}
}
func (v *View) FocusPoint(cx int, cy int, scrollIntoView bool) {
lineCount := len(v.lines)
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
v.refreshViewLinesIfNeeded()
lineCount := len(v.viewLines)
if cy < 0 || cy > lineCount {
return
}
@@ -408,6 +462,10 @@ func characterEquals(chr []byte, b byte) bool {
return len(chr) == 1 && chr[0] == b
}
func isCRLF(chr []byte) bool {
return len(chr) == 2 && chr[0] == '\r' && chr[1] == '\n'
}
// String returns a string from a given cell slice.
func (l lineType) String() string {
var str strings.Builder
@@ -783,7 +841,7 @@ func (v *View) write(p []byte) {
chr, remaining, width, state = uniseg.FirstGraphemeCluster(remaining, state)
switch {
case characterEquals(chr, '\n'):
case characterEquals(chr, '\n') || isCRLF(chr):
finishLine()
advanceToNextLine()
case characterEquals(chr, '\r'):
@@ -1757,6 +1815,52 @@ func (v *View) setContentLineCount(lineCount int) {
v.lines = v.lines[:lineCount]
}
// If the current search result is no longer visible after a scroll up, select the last search
// result that is visible in the view, if any, or the first one that is below the view if none is
// visible.
func (v *View) selectVisibleSearchResultAfterScrollUp() {
if !v.Highlight && len(v.searcher.searchPositions) != 0 {
windowBottom := v.oy + v.InnerHeight()
if v.searcher.searchPositions[v.searcher.currentSearchIndex].Y >= windowBottom {
newSearchIndex := v.searcher.currentSearchIndex
for newSearchIndex > 0 &&
v.searcher.searchPositions[newSearchIndex-1].Y >= v.oy {
newSearchIndex--
if v.searcher.searchPositions[newSearchIndex].Y < windowBottom {
break
}
}
if v.searcher.currentSearchIndex != newSearchIndex {
v.searcher.currentSearchIndex = newSearchIndex
v.renderSearchStatus(newSearchIndex, len(v.searcher.searchPositions))
}
}
}
}
// If the current search result is no longer visible after a scroll down, select the first search
// result that is visible in the view, if any, or the last one that is above the view if none is
// visible.
func (v *View) selectVisibleSearchResultAfterScrollDown() {
if !v.Highlight && len(v.searcher.searchPositions) != 0 {
if v.searcher.searchPositions[v.searcher.currentSearchIndex].Y < v.oy {
newSearchIndex := v.searcher.currentSearchIndex
windowBottom := v.oy + v.InnerHeight()
for newSearchIndex+1 < len(v.searcher.searchPositions) &&
v.searcher.searchPositions[newSearchIndex+1].Y < windowBottom {
newSearchIndex++
if v.searcher.searchPositions[newSearchIndex].Y >= v.oy {
break
}
}
if v.searcher.currentSearchIndex != newSearchIndex {
v.searcher.currentSearchIndex = newSearchIndex
v.renderSearchStatus(newSearchIndex, len(v.searcher.searchPositions))
}
}
}
}
func (v *View) ScrollUp(amount int) {
if amount > v.oy {
amount = v.oy
@@ -1767,6 +1871,7 @@ func (v *View) ScrollUp(amount int) {
v.cy += amount
v.clearHover()
v.selectVisibleSearchResultAfterScrollUp()
}
}
@@ -1778,6 +1883,7 @@ func (v *View) ScrollDown(amount int) {
v.cy -= adjustedAmount
v.clearHover()
v.selectVisibleSearchResultAfterScrollDown()
}
}
+122 -48
View File
@@ -64,6 +64,80 @@ func initOptions() {
func archInit() {
// From internal/cpu
const (
// eax bits
cpuid_AVXVNNI = 1 << 4
// ecx bits
cpuid_SSE3 = 1 << 0
cpuid_PCLMULQDQ = 1 << 1
cpuid_AVX512VBMI = 1 << 1
cpuid_AVX512VBMI2 = 1 << 6
cpuid_SSSE3 = 1 << 9
cpuid_AVX512GFNI = 1 << 8
cpuid_AVX512VAES = 1 << 9
cpuid_AVX512VNNI = 1 << 11
cpuid_AVX512BITALG = 1 << 12
cpuid_FMA = 1 << 12
cpuid_AVX512VPOPCNTDQ = 1 << 14
cpuid_SSE41 = 1 << 19
cpuid_SSE42 = 1 << 20
cpuid_POPCNT = 1 << 23
cpuid_AES = 1 << 25
cpuid_OSXSAVE = 1 << 27
cpuid_AVX = 1 << 28
// "Extended Feature Flag" bits returned in EBX for CPUID EAX=0x7 ECX=0x0
cpuid_BMI1 = 1 << 3
cpuid_AVX2 = 1 << 5
cpuid_BMI2 = 1 << 8
cpuid_ERMS = 1 << 9
cpuid_AVX512F = 1 << 16
cpuid_AVX512DQ = 1 << 17
cpuid_ADX = 1 << 19
cpuid_AVX512CD = 1 << 28
cpuid_SHA = 1 << 29
cpuid_AVX512BW = 1 << 30
cpuid_AVX512VL = 1 << 31
// "Extended Feature Flag" bits returned in ECX for CPUID EAX=0x7 ECX=0x0
cpuid_AVX512_VBMI = 1 << 1
cpuid_AVX512_VBMI2 = 1 << 6
cpuid_GFNI = 1 << 8
cpuid_AVX512VPCLMULQDQ = 1 << 10
cpuid_AVX512_BITALG = 1 << 12
// edx bits
cpuid_FSRM = 1 << 4
// edx bits for CPUID 0x80000001
cpuid_RDTSCP = 1 << 27
)
// Additional constants not in internal/cpu
const (
// eax=1: edx
cpuid_SSE2 = 1 << 26
// eax=1: ecx
cpuid_CX16 = 1 << 13
cpuid_RDRAND = 1 << 30
// eax=7,ecx=0: ebx
cpuid_RDSEED = 1 << 18
cpuid_AVX512IFMA = 1 << 21
cpuid_AVX512PF = 1 << 26
cpuid_AVX512ER = 1 << 27
// eax=7,ecx=0: edx
cpuid_AVX5124VNNIW = 1 << 2
cpuid_AVX5124FMAPS = 1 << 3
cpuid_AMXBF16 = 1 << 22
cpuid_AMXTile = 1 << 24
cpuid_AMXInt8 = 1 << 25
// eax=7,ecx=1: eax
cpuid_AVX512BF16 = 1 << 5
cpuid_AVXIFMA = 1 << 23
// eax=7,ecx=1: edx
cpuid_AVXVNNIInt8 = 1 << 4
)
Initialized = true
maxID, _, _, _ := cpuid(0, 0)
@@ -73,90 +147,90 @@ func archInit() {
}
_, _, ecx1, edx1 := cpuid(1, 0)
X86.HasSSE2 = isSet(26, edx1)
X86.HasSSE2 = isSet(edx1, cpuid_SSE2)
X86.HasSSE3 = isSet(0, ecx1)
X86.HasPCLMULQDQ = isSet(1, ecx1)
X86.HasSSSE3 = isSet(9, ecx1)
X86.HasFMA = isSet(12, ecx1)
X86.HasCX16 = isSet(13, ecx1)
X86.HasSSE41 = isSet(19, ecx1)
X86.HasSSE42 = isSet(20, ecx1)
X86.HasPOPCNT = isSet(23, ecx1)
X86.HasAES = isSet(25, ecx1)
X86.HasOSXSAVE = isSet(27, ecx1)
X86.HasRDRAND = isSet(30, ecx1)
X86.HasSSE3 = isSet(ecx1, cpuid_SSE3)
X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ)
X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3)
X86.HasFMA = isSet(ecx1, cpuid_FMA)
X86.HasCX16 = isSet(ecx1, cpuid_CX16)
X86.HasSSE41 = isSet(ecx1, cpuid_SSE41)
X86.HasSSE42 = isSet(ecx1, cpuid_SSE42)
X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT)
X86.HasAES = isSet(ecx1, cpuid_AES)
X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE)
X86.HasRDRAND = isSet(ecx1, cpuid_RDRAND)
var osSupportsAVX, osSupportsAVX512 bool
// For XGETBV, OSXSAVE bit is required and sufficient.
if X86.HasOSXSAVE {
eax, _ := xgetbv()
// Check if XMM and YMM registers have OS support.
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
osSupportsAVX = isSet(eax, 1<<1) && isSet(eax, 1<<2)
if runtime.GOOS == "darwin" {
// Darwin requires special AVX512 checks, see cpu_darwin_x86.go
osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512()
} else {
// Check if OPMASK and ZMM registers have OS support.
osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax)
osSupportsAVX512 = osSupportsAVX && isSet(eax, 1<<5) && isSet(eax, 1<<6) && isSet(eax, 1<<7)
}
}
X86.HasAVX = isSet(28, ecx1) && osSupportsAVX
X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX
if maxID < 7 {
return
}
eax7, ebx7, ecx7, edx7 := cpuid(7, 0)
X86.HasBMI1 = isSet(3, ebx7)
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
X86.HasBMI2 = isSet(8, ebx7)
X86.HasERMS = isSet(9, ebx7)
X86.HasRDSEED = isSet(18, ebx7)
X86.HasADX = isSet(19, ebx7)
X86.HasBMI1 = isSet(ebx7, cpuid_BMI1)
X86.HasAVX2 = isSet(ebx7, cpuid_AVX2) && osSupportsAVX
X86.HasBMI2 = isSet(ebx7, cpuid_BMI2)
X86.HasERMS = isSet(ebx7, cpuid_ERMS)
X86.HasRDSEED = isSet(ebx7, cpuid_RDSEED)
X86.HasADX = isSet(ebx7, cpuid_ADX)
X86.HasAVX512 = isSet(16, ebx7) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
X86.HasAVX512 = isSet(ebx7, cpuid_AVX512F) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
if X86.HasAVX512 {
X86.HasAVX512F = true
X86.HasAVX512CD = isSet(28, ebx7)
X86.HasAVX512ER = isSet(27, ebx7)
X86.HasAVX512PF = isSet(26, ebx7)
X86.HasAVX512VL = isSet(31, ebx7)
X86.HasAVX512BW = isSet(30, ebx7)
X86.HasAVX512DQ = isSet(17, ebx7)
X86.HasAVX512IFMA = isSet(21, ebx7)
X86.HasAVX512VBMI = isSet(1, ecx7)
X86.HasAVX5124VNNIW = isSet(2, edx7)
X86.HasAVX5124FMAPS = isSet(3, edx7)
X86.HasAVX512VPOPCNTDQ = isSet(14, ecx7)
X86.HasAVX512VPCLMULQDQ = isSet(10, ecx7)
X86.HasAVX512VNNI = isSet(11, ecx7)
X86.HasAVX512GFNI = isSet(8, ecx7)
X86.HasAVX512VAES = isSet(9, ecx7)
X86.HasAVX512VBMI2 = isSet(6, ecx7)
X86.HasAVX512BITALG = isSet(12, ecx7)
X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD)
X86.HasAVX512ER = isSet(ebx7, cpuid_AVX512ER)
X86.HasAVX512PF = isSet(ebx7, cpuid_AVX512PF)
X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL)
X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW)
X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ)
X86.HasAVX512IFMA = isSet(ebx7, cpuid_AVX512IFMA)
X86.HasAVX512VBMI = isSet(ecx7, cpuid_AVX512_VBMI)
X86.HasAVX5124VNNIW = isSet(edx7, cpuid_AVX5124VNNIW)
X86.HasAVX5124FMAPS = isSet(edx7, cpuid_AVX5124FMAPS)
X86.HasAVX512VPOPCNTDQ = isSet(ecx7, cpuid_AVX512VPOPCNTDQ)
X86.HasAVX512VPCLMULQDQ = isSet(ecx7, cpuid_AVX512VPCLMULQDQ)
X86.HasAVX512VNNI = isSet(ecx7, cpuid_AVX512VNNI)
X86.HasAVX512GFNI = isSet(ecx7, cpuid_AVX512GFNI)
X86.HasAVX512VAES = isSet(ecx7, cpuid_AVX512VAES)
X86.HasAVX512VBMI2 = isSet(ecx7, cpuid_AVX512VBMI2)
X86.HasAVX512BITALG = isSet(ecx7, cpuid_AVX512BITALG)
}
X86.HasAMXTile = isSet(24, edx7)
X86.HasAMXInt8 = isSet(25, edx7)
X86.HasAMXBF16 = isSet(22, edx7)
X86.HasAMXTile = isSet(edx7, cpuid_AMXTile)
X86.HasAMXInt8 = isSet(edx7, cpuid_AMXInt8)
X86.HasAMXBF16 = isSet(edx7, cpuid_AMXBF16)
// These features depend on the second level of extended features.
if eax7 >= 1 {
eax71, _, _, edx71 := cpuid(7, 1)
if X86.HasAVX512 {
X86.HasAVX512BF16 = isSet(5, eax71)
X86.HasAVX512BF16 = isSet(eax71, cpuid_AVX512BF16)
}
if X86.HasAVX {
X86.HasAVXIFMA = isSet(23, eax71)
X86.HasAVXVNNI = isSet(4, eax71)
X86.HasAVXVNNIInt8 = isSet(4, edx71)
X86.HasAVXIFMA = isSet(eax71, cpuid_AVXIFMA)
X86.HasAVXVNNI = isSet(eax71, cpuid_AVXVNNI)
X86.HasAVXVNNIInt8 = isSet(edx71, cpuid_AVXVNNIInt8)
}
}
}
func isSet(bitpos uint, value uint32) bool {
return value&(1<<bitpos) != 0
func isSet(hwc uint32, value uint32) bool {
return hwc&value != 0
}
+27 -1
View File
@@ -160,7 +160,9 @@ const (
keyEnd
keyDeleteWord
keyDeleteLine
keyDelete
keyClearScreen
keyTranspose
keyPasteStart
keyPasteEnd
)
@@ -194,6 +196,8 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
return keyDeleteLine, b[1:]
case 12: // ^L
return keyClearScreen, b[1:]
case 20: // ^T
return keyTranspose, b[1:]
case 23: // ^W
return keyDeleteWord, b[1:]
case 14: // ^N
@@ -228,6 +232,10 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
}
}
if !pasteActive && len(b) >= 4 && b[0] == keyEscape && b[1] == '[' && b[2] == '3' && b[3] == '~' {
return keyDelete, b[4:]
}
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
switch b[5] {
case 'C':
@@ -590,7 +598,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
}
t.line = t.line[:t.pos]
t.moveCursorToPos(t.pos)
case keyCtrlD:
case keyCtrlD, keyDelete:
// Erase the character under the current position.
// The EOF case when the line is empty is handled in
// readLine().
@@ -600,6 +608,24 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
}
case keyCtrlU:
t.eraseNPreviousChars(t.pos)
case keyTranspose:
// This transposes the two characters around the cursor and advances the cursor. Best-effort.
if len(t.line) < 2 || t.pos < 1 {
return
}
swap := t.pos
if swap == len(t.line) {
swap-- // special: at end of line, swap previous two chars
}
t.line[swap-1], t.line[swap] = t.line[swap], t.line[swap-1]
if t.pos < len(t.line) {
t.pos++
}
if t.echo {
t.moveCursorToPos(swap - 1)
t.writeLine(t.line[swap-1:])
t.moveCursorToPos(t.pos)
}
case keyClearScreen:
// Erases the screen and moves the cursor to the home position.
t.queue([]rune("\x1b[2J\x1b[H"))
+5 -5
View File
@@ -88,7 +88,7 @@ github.com/fatih/color
# github.com/gdamore/encoding v1.0.1
## explicit; go 1.9
github.com/gdamore/encoding
# github.com/gdamore/tcell/v2 v2.13.5
# github.com/gdamore/tcell/v2 v2.13.8
## explicit; go 1.24.0
github.com/gdamore/tcell/v2
github.com/gdamore/tcell/v2/terminfo
@@ -211,7 +211,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
github.com/jesseduffield/go-git/v5/utils/sync
github.com/jesseduffield/go-git/v5/utils/trace
# github.com/jesseduffield/gocui v0.3.1-0.20260103133810-b7e030324985
# github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18
## explicit; go 1.25
github.com/jesseduffield/gocui
# github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
@@ -345,17 +345,17 @@ golang.org/x/net/proxy
# golang.org/x/sync v0.19.0
## explicit; go 1.24.0
golang.org/x/sync/errgroup
# golang.org/x/sys v0.39.0
# golang.org/x/sys v0.40.0
## explicit; go 1.24.0
golang.org/x/sys/cpu
golang.org/x/sys/execabs
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
# golang.org/x/term v0.38.0
# golang.org/x/term v0.39.0
## explicit; go 1.24.0
golang.org/x/term
# golang.org/x/text v0.32.0
# golang.org/x/text v0.33.0
## explicit; go 1.24.0
golang.org/x/text/encoding
golang.org/x/text/encoding/internal/identifier