Compare commits

...
11 Commits
Author SHA1 Message Date
Jesse Duffield ab7b5f6d84 Improve undo action to restore files upon undoing a commit (#4167)
- **PR Description**

Right now, undoing a commit performs a hard reset, which also discards
all the changes from that commit. This PR adds new config options (and a
new `undo` section) which allow users to choose between `hard` and
`soft` reset modes when undoing commits.

Personally, I think that the default should be `soft`, because the state
before the commit had the files, so undoing a commit should put the
files where they were before. But this PR keeps `hard` as the default
and does not change current behavior.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [x] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [x] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2025-01-18 00:17:12 +11:00
Gabriel Lanata 4065175a58 Improve undo action to restore files upon undoing a commit 2025-01-18 00:07:15 +11:00
Stefan Haller 43106b6c7f Collapse/uncollapse all files in tree (#4131) 2025-01-13 21:18:38 +01:00
Mauricio TrajanoandStefan Haller 7bea41534b Collapse/expand all files in tree
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-01-13 21:13:11 +01:00
Stefan Haller 14a91d9829 Bump gocui (and tcell) (#4166)
This updates our tcell dependency to v2.8.0, adding support for ghostty
and tmux-256color.

This will hopefully fix #4133, and it might also fix #2962 and #3434
(but I don't understand enough about these to tell).
2025-01-12 13:51:33 +01:00
Stefan Haller 274e24d75e Bump gocui (and tcell)
This updates our tcell dependency to v2.8.0, adding support for ghostty and
tmux-256color.
2025-01-12 13:48:52 +01:00
Stefan Haller a1a8cd114d Add ability to configure branch color patterns using regex (#4130)
- **PR Description**

Add ability to specify color patterns in the `branchColorPatterns`
config using regex, ex. `JIRA-\d+` would match all branch names in the
form `JIRA-456`.

Example config:
```yaml
gui:
  branchColorPatterns:
    'docs/.+': 'black' # make all branches prefixed with docs/ have a black color
    'feature/collapse-all': 'red' # make a specfic branch name red
    'IDEA-\d+': 'blue' # make all branches with the prefix `IDEA-` followed by a digit, blue

```
2025-01-12 13:47:17 +01:00
Mauricio Trajano c64a7904b7 Add ability to configure branch color patterns 2025-01-12 13:44:26 +01:00
Stefan Haller 3e623cd1ce Remove the automatic coloring of certain branch names
We used to automatically color branches starting with "feature/", "bugfix/", or
"hotfix/". For those who don't want this, it's a bit non-obvious to turn off,
but it's actually pretty easy to configure manually for those who want this, so
we just remove this default coloring.
2025-01-11 22:13:33 +01:00
Jesse Duffield 6da99a49a4 Cut a new release automatically each month (#4146)
- **PR Description**

I regularly struggle to stay on top of releases, and that's because I
like to spend some time polishing the release notes and I don't always
have time for that. But that shouldn't block releases, so now releases
will happen automatically on the first Saturday of each month.

In order to block an automatic release, we simply need to add a
blocks-release label on any open PR or issue.

- **Please check if the PR fulfills these requirements**

* [ ] Cheatsheets are up-to-date (run `go generate ./...`)
* [ ] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2025-01-11 15:47:43 +11:00
Jesse Duffield 977a01172f Automatically cut release each month 2025-01-11 15:44:00 +11:00
59 changed files with 1513 additions and 364 deletions
+72
View File
@@ -0,0 +1,72 @@
name: Automated Release
on:
schedule:
# Runs at 2:00 AM UTC on the first Saturday of every month
- cron: '0 2 * * 6'
workflow_dispatch: # Allow manual triggering of the workflow
jobs:
check-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for changes since last release
run: |
if [ -z "$(git diff --name-only ${{ env.latest_tag }})" ]; then
echo "No changes detected since last release"
exit 1
fi
- name: Check for Blocking Issues/PRs
id: check_blocks
run: |
gh auth setup-git
gh auth status
echo "Checking for blocking issues and PRs..."
# Check for blocking issues
blocking_issues=$(gh issue list -l blocks-release --json number,title --jq '.[] | "- \(.title) (#\(.number))"')
# Check for blocking PRs
blocking_prs=$(gh pr list -l blocks-release --json number,title --jq '.[] | "- \(.title) (#\(.number)) (PR)"')
# Combine the results
blocking_items="$blocking_issues"$'\n'"$blocking_prs"
# Remove empty lines
blocking_items=$(echo "$blocking_items" | grep . || true)
if [ -n "$blocking_items" ]; then
echo "Blocking issues/PRs detected:"
echo "$blocking_items"
exit 1
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Calculate next version
run: |
latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "v0.0.0")
echo "Latest tag: $latest_tag"
IFS='.' read -r major minor patch <<< "$latest_tag"
new_minor=$((minor + 1))
new_tag="$major.$new_minor.0"
echo "New tag: $new_tag"
echo "new_tag=$new_tag" >> $GITHUB_ENV
# This will trigger a deploy via .github/workflows/cd.yml
- name: Push New Tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag ${{ env.new_tag }}
git push origin ${{ env.new_tag }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -3
View File
@@ -571,6 +571,8 @@ keybinding:
openMergeTool: M openMergeTool: M
openStatusFilter: <c-b> openStatusFilter: <c-b>
copyFileInfoToClipboard: "y" copyFileInfoToClipboard: "y"
collapseAll: '-'
expandAll: =
branches: branches:
createPullRequest: o createPullRequest: o
viewPullRequestOptions: O viewPullRequestOptions: O
@@ -832,14 +834,17 @@ gui:
## Custom Branch Color ## Custom Branch Color
You can customize the color of branches based on the branch prefix: You can customize the color of branches based on branch patterns (regular expressions):
```yaml ```yaml
gui: gui:
branchColors: branchColorPatterns:
'docs': '#11aaff' # use a light blue for branches beginning with 'docs/' '^docs/': '#11aaff' # use a light blue for branches beginning with 'docs/'
'ISSUE-\d+': '#ff5733' # use a bright orange for branches containing 'ISSUE-<some-number>'
``` ```
Note that the regular expressions are not implicitly anchored to the beginning/end of the branch name. If you want to do that, add leading `^` and/or trailing `$` as needed.
## Example Coloring ## Example Coloring
![border example](../../assets/colored-border-example.png) ![border example](../../assets/colored-border-example.png)
+4
View File
@@ -65,6 +65,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | | `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Search the current view by text | | | `` / `` | Search the current view by text | |
## Commit summary ## Commit summary
@@ -147,6 +149,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-t> `` | Open external diff tool (git difftool) | | | `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Open external merge tool | Run `git mergetool`. | | `` M `` | Open external merge tool | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. | | `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Search the current view by text | | | `` / `` | Search the current view by text | |
## Local branches ## Local branches
+4
View File
@@ -143,6 +143,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | ファイルツリーの表示を切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | | `` ` `` | ファイルツリーの表示を切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 検索を開始 | | | `` / `` | 検索を開始 | |
## コミットメッセージ ## コミットメッセージ
@@ -218,6 +220,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-t> `` | Open external diff tool (git difftool) | | | `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Git mergetoolを開く | Run `git mergetool`. | | `` M `` | Git mergetoolを開く | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. | | `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 検索を開始 | | | `` / `` | 検索を開始 | |
## ブランチ ## ブランチ
+4
View File
@@ -308,6 +308,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` a `` | Toggle all files included in patch | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Toggle all files included in patch | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Enter file to add selected lines to the patch (or toggle directory collapsed) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Enter file to add selected lines to the patch (or toggle directory collapsed) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | | `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 검색 시작 | | | `` / `` | 검색 시작 | |
## 커밋메시지 ## 커밋메시지
@@ -359,6 +361,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-t> `` | Open external diff tool (git difftool) | | | `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Git mergetool를 열기 | Run `git mergetool`. | | `` M `` | Git mergetool를 열기 | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. | | `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 검색 시작 | | | `` / `` | 검색 시작 | |
## 확인 패널 ## 확인 패널
+4
View File
@@ -79,6 +79,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-t> `` | Open external diff tool (git difftool) | | | `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Open external merge tool | Run `git mergetool`. | | `` M `` | Open external merge tool | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. | | `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Bevestigingspaneel ## Bevestigingspaneel
@@ -136,6 +138,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | | `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Commits ## Commits
+4
View File
@@ -229,6 +229,8 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. | | `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. |
| `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. | | `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Szukaj w bieżącym widoku po tekście | | | `` / `` | Szukaj w bieżącym widoku po tekście | |
## Pliki commita ## Pliki commita
@@ -245,6 +247,8 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
| `` a `` | Przełącz wszystkie pliki | Dodaj/usuń wszystkie pliki commita do niestandardowej łatki. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Przełącz wszystkie pliki | Dodaj/usuń wszystkie pliki commita do niestandardowej łatki. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Wejdź do pliku / Przełącz zwiń katalog | Jeśli plik jest wybrany, wejdź do pliku, aby móc dodawać/usuwać poszczególne linie do niestandardowej łatki. Jeśli wybrany jest katalog, przełącz katalog. | | `` <enter> `` | Wejdź do pliku / Przełącz zwiń katalog | Jeśli plik jest wybrany, wejdź do pliku, aby móc dodawać/usuwać poszczególne linie do niestandardowej łatki. Jeśli wybrany jest katalog, przełącz katalog. |
| `` ` `` | Przełącz widok drzewa plików | Przełącz widok plików między płaskim a drzewem. Płaski układ pokazuje wszystkie ścieżki plików na jednej liście, układ drzewa grupuje pliki według katalogów. | | `` ` `` | Przełącz widok drzewa plików | Przełącz widok plików między płaskim a drzewem. Płaski układ pokazuje wszystkie ścieżki plików na jednej liście, układ drzewa grupuje pliki według katalogów. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Szukaj w bieżącym widoku po tekście | | | `` / `` | Szukaj w bieżącym widoku po tekście | |
## Podsumowanie commita ## Podsumowanie commita
+4
View File
@@ -270,6 +270,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` a `` | Переключить все файлы, включённые в патч | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Переключить все файлы, включённые в патч | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | | `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Найти | | | `` / `` | Найти | |
## Статус ## Статус
@@ -353,6 +355,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-t> `` | Open external diff tool (git difftool) | | | `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. | | `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. |
| `` f `` | Получить изменения | Fetch changes from remote. | | `` f `` | Получить изменения | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | Найти | | | `` / `` | Найти | |
## Хранилище ## Хранилище
+4
View File
@@ -195,6 +195,8 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 | | `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 | | `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 | | `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 开始搜索 | | | `` / `` | 开始搜索 | |
## 文件 ## 文件
@@ -225,6 +227,8 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | | | `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. | | `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` f `` | 抓取 | 从远程获取变更 | | `` f `` | 抓取 | 从远程获取变更 |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 开始搜索 | | | `` / `` | 开始搜索 | |
## 构建补丁中 ## 构建补丁中
+4
View File
@@ -219,6 +219,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | | `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 收藏 (Stash) ## 收藏 (Stash)
@@ -320,6 +322,8 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | | | `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 | | `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
| `` f `` | 擷取 | 同步遠端異動 | | `` f `` | 擷取 | 同步遠端異動 |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 狀態 ## 狀態
+2 -3
View File
@@ -8,7 +8,7 @@ require (
github.com/aybabtme/humanlog v0.4.1 github.com/aybabtme/humanlog v0.4.1
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11 github.com/creack/pty v1.1.11
github.com/gdamore/tcell/v2 v2.7.4 github.com/gdamore/tcell/v2 v2.8.0
github.com/go-errors/errors v1.5.1 github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.4.2 github.com/gookit/color v1.4.2
github.com/iancoleman/orderedmap v0.3.0 github.com/iancoleman/orderedmap v0.3.0
@@ -16,7 +16,7 @@ require (
github.com/integrii/flaggy v1.4.0 github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4 github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -54,7 +54,6 @@ require (
github.com/go-git/go-billy/v5 v5.0.0 // indirect github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/invopop/jsonschema v0.10.0 // indirect github.com/invopop/jsonschema v0.10.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
+31 -8
View File
@@ -85,11 +85,10 @@ 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/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= 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/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.8.0 h1:IDclow1j6kKpU/gOhjmc+7Pj5Dxnukb74pfKN4Cxrfg=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gdamore/tcell/v2 v2.8.0/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
@@ -145,8 +144,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -188,8 +187,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4 h1:hSAimLVb4b5ktU3uJRtBsZW0P2dtXECReTcsHYfOy58= github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a h1:GLFWB8rESraTt2eIe2yssy4d4VEkCnmKbPeeZ5vCT2s=
github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g= github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g=
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q= github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -235,7 +234,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw= github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
@@ -328,6 +326,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -367,6 +368,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -402,6 +406,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -425,6 +433,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -475,13 +486,20 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -493,7 +511,10 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -548,6 +569,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+7
View File
@@ -52,7 +52,10 @@ type GuiConfig struct {
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
AuthorColors map[string]string `yaml:"authorColors"` AuthorColors map[string]string `yaml:"authorColors"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
// Deprecated: use branchColorPatterns instead
BranchColors map[string]string `yaml:"branchColors"` BranchColors map[string]string `yaml:"branchColors"`
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
BranchColorPatterns map[string]string `yaml:"branchColorPatterns"`
// The number of lines you scroll by when scrolling the main window // The number of lines you scroll by when scrolling the main window
ScrollHeight int `yaml:"scrollHeight" jsonschema:"minimum=1"` ScrollHeight int `yaml:"scrollHeight" jsonschema:"minimum=1"`
// If true, allow scrolling past the bottom of the content in the main window // If true, allow scrolling past the bottom of the content in the main window
@@ -453,6 +456,8 @@ type KeybindingFilesConfig struct {
OpenMergeTool string `yaml:"openMergeTool"` OpenMergeTool string `yaml:"openMergeTool"`
OpenStatusFilter string `yaml:"openStatusFilter"` OpenStatusFilter string `yaml:"openStatusFilter"`
CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"` CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"`
CollapseAll string `yaml:"collapseAll"`
ExpandAll string `yaml:"expandAll"`
} }
type KeybindingBranchesConfig struct { type KeybindingBranchesConfig struct {
@@ -895,6 +900,8 @@ func GetDefaultConfig() *UserConfig {
OpenStatusFilter: "<c-b>", OpenStatusFilter: "<c-b>",
ConfirmDiscard: "x", ConfirmDiscard: "x",
CopyFileInfoToClipboard: "y", CopyFileInfoToClipboard: "y",
CollapseAll: "-",
ExpandAll: "=",
}, },
Branches: KeybindingBranchesConfig{ Branches: KeybindingBranchesConfig{
CopyPullRequestURL: "<c-y>", CopyPullRequestURL: "<c-y>",
@@ -109,6 +109,20 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
Description: self.c.Tr.ToggleTreeView, Description: self.c.Tr.ToggleTreeView,
Tooltip: self.c.Tr.ToggleTreeViewTooltip, Tooltip: self.c.Tr.ToggleTreeViewTooltip,
}, },
{
Key: opts.GetKey(opts.Config.Files.CollapseAll),
Handler: self.collapseAll,
Description: self.c.Tr.CollapseAll,
Tooltip: self.c.Tr.CollapseAllTooltip,
GetDisabledReason: self.require(self.isInTreeMode),
},
{
Key: opts.GetKey(opts.Config.Files.ExpandAll),
Handler: self.expandAll,
Description: self.c.Tr.ExpandAll,
Tooltip: self.c.Tr.ExpandAllTooltip,
GetDisabledReason: self.require(self.isInTreeMode),
},
} }
return bindings return bindings
@@ -401,6 +415,22 @@ func (self *CommitFilesController) toggleTreeView() error {
return nil return nil
} }
func (self *CommitFilesController) collapseAll() error {
self.context().CommitFileTreeViewModel.CollapseAll()
self.c.PostRefreshUpdate(self.context())
return nil
}
func (self *CommitFilesController) expandAll() error {
self.context().CommitFileTreeViewModel.ExpandAll()
self.c.PostRefreshUpdate(self.context())
return nil
}
// NOTE: these functions are identical to those in files_controller.go (except for types) and // NOTE: these functions are identical to those in files_controller.go (except for types) and
// could also be cleaned up with some generics // could also be cleaned up with some generics
func normalisedSelectedCommitFileNodes(selectedNodes []*filetree.CommitFileNode) []*filetree.CommitFileNode { func normalisedSelectedCommitFileNodes(selectedNodes []*filetree.CommitFileNode) []*filetree.CommitFileNode {
@@ -420,3 +450,11 @@ func isDescendentOfSelectedCommitFileNodes(node *filetree.CommitFileNode, select
} }
return false return false
} }
func (self *CommitFilesController) isInTreeMode() *types.DisabledReason {
if !self.context().CommitFileTreeViewModel.InTreeMode() {
return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView}
}
return nil
}
+38
View File
@@ -186,6 +186,20 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Description: self.c.Tr.Fetch, Description: self.c.Tr.Fetch,
Tooltip: self.c.Tr.FetchTooltip, Tooltip: self.c.Tr.FetchTooltip,
}, },
{
Key: opts.GetKey(opts.Config.Files.CollapseAll),
Handler: self.collapseAll,
Description: self.c.Tr.CollapseAll,
Tooltip: self.c.Tr.CollapseAllTooltip,
GetDisabledReason: self.require(self.isInTreeMode),
},
{
Key: opts.GetKey(opts.Config.Files.ExpandAll),
Handler: self.expandAll,
Description: self.c.Tr.ExpandAll,
Tooltip: self.c.Tr.ExpandAllTooltip,
GetDisabledReason: self.require(self.isInTreeMode),
},
} }
} }
@@ -478,6 +492,22 @@ func (self *FilesController) enter() error {
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1}) return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
} }
func (self *FilesController) collapseAll() error {
self.context().FileTreeViewModel.CollapseAll()
self.c.PostRefreshUpdate(self.context())
return nil
}
func (self *FilesController) expandAll() error {
self.context().FileTreeViewModel.ExpandAll()
self.c.PostRefreshUpdate(self.context())
return nil
}
func (self *FilesController) EnterFile(opts types.OnFocusOpts) error { func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
node := self.context().GetSelected() node := self.context().GetSelected()
if node == nil { if node == nil {
@@ -1181,3 +1211,11 @@ func (self *FilesController) formattedPaths(nodes []*filetree.FileNode) string {
return node.GetPath() return node.GetPath()
})) }))
} }
func (self *FilesController) isInTreeMode() *types.DisabledReason {
if !self.context().FileTreeViewModel.InTreeMode() {
return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView}
}
return nil
}
+29 -24
View File
@@ -88,7 +88,20 @@ func (self *UndoController) reflogUndo() error {
} }
switch action.kind { switch action.kind {
case COMMIT, REBASE: case COMMIT:
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Undo,
Prompt: fmt.Sprintf(self.c.Tr.SoftResetPrompt, action.from),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.Undo)
return self.c.WithWaitingStatus(undoingStatus, func(gocui.Task) error {
return self.c.Helpers().Refs.ResetToRef(action.from, "soft", undoEnvVars)
})
},
})
return true, nil
case REBASE:
self.c.Confirm(types.ConfirmOpts{ self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Undo, Title: self.c.Tr.Actions.Undo,
Prompt: fmt.Sprintf(self.c.Tr.HardResetAutostashPrompt, action.from), Prompt: fmt.Sprintf(self.c.Tr.HardResetAutostashPrompt, action.from),
@@ -105,7 +118,7 @@ func (self *UndoController) reflogUndo() error {
case CHECKOUT: case CHECKOUT:
self.c.Confirm(types.ConfirmOpts{ self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Undo, Title: self.c.Tr.Actions.Undo,
Prompt: fmt.Sprintf(self.c.Tr.CheckoutPrompt, action.from), Prompt: fmt.Sprintf(self.c.Tr.CheckoutAutostashPrompt, action.from),
HandleConfirm: func() error { HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.Undo) self.c.LogAction(self.c.Tr.Actions.Undo)
return self.c.Helpers().Refs.CheckoutRef(action.from, types.CheckoutRefOptions{ return self.c.Helpers().Refs.CheckoutRef(action.from, types.CheckoutRefOptions{
@@ -159,7 +172,7 @@ func (self *UndoController) reflogRedo() error {
case CHECKOUT: case CHECKOUT:
self.c.Confirm(types.ConfirmOpts{ self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Actions.Redo, Title: self.c.Tr.Actions.Redo,
Prompt: fmt.Sprintf(self.c.Tr.CheckoutPrompt, action.to), Prompt: fmt.Sprintf(self.c.Tr.CheckoutAutostashPrompt, action.to),
HandleConfirm: func() error { HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.Redo) self.c.LogAction(self.c.Tr.Actions.Redo)
return self.c.Helpers().Refs.CheckoutRef(action.to, types.CheckoutRefOptions{ return self.c.Helpers().Refs.CheckoutRef(action.to, types.CheckoutRefOptions{
@@ -244,31 +257,23 @@ func (self *UndoController) hardResetWithAutoStash(commitHash string, options ha
return self.c.Helpers().Refs.ResetToRef(commitHash, "hard", options.EnvVars) return self.c.Helpers().Refs.ResetToRef(commitHash, "hard", options.EnvVars)
} }
// if we have any modified tracked files we need to ask the user if they want us to stash for them // if we have any modified tracked files we need to auto-stash
dirtyWorkingTree := self.c.Helpers().WorkingTree.IsWorkingTreeDirty() dirtyWorkingTree := self.c.Helpers().WorkingTree.IsWorkingTreeDirty()
if dirtyWorkingTree { if dirtyWorkingTree {
// offer to autostash changes return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
self.c.Confirm(types.ConfirmOpts{ if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + commitHash); err != nil {
Title: self.c.Tr.AutoStashTitle, return err
Prompt: self.c.Tr.AutoStashPrompt, }
HandleConfirm: func() error { if err := reset(); err != nil {
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error { return err
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + commitHash); err != nil { }
return err
}
if err := reset(); err != nil {
return err
}
err := self.c.Git().Stash.Pop(0) err := self.c.Git().Stash.Pop(0)
if err != nil { if err != nil {
return err return err
} }
return self.c.Refresh(types.RefreshOptions{}) return self.c.Refresh(types.RefreshOptions{})
})
},
}) })
return nil
} }
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error { return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
+5
View File
@@ -36,3 +36,8 @@ func (self *CollapsedPaths) ToggleCollapsed(path string) {
self.collapsedPaths.Add(path) self.collapsedPaths.Add(path)
} }
} }
func (self *CollapsedPaths) ExpandAll() {
// Could be cleaner if Set had a Clear() method...
self.collapsedPaths.RemoveSlice(self.collapsedPaths.ToSlice())
}
+14
View File
@@ -25,6 +25,20 @@ type CommitFileTree struct {
collapsedPaths *CollapsedPaths collapsedPaths *CollapsedPaths
} }
func (self *CommitFileTree) CollapseAll() {
dirPaths := lo.FilterMap(self.GetAllItems(), func(file *CommitFileNode, index int) (string, bool) {
return file.Path, !file.IsFile()
})
for _, path := range dirPaths {
self.collapsedPaths.Collapse(path)
}
}
func (self *CommitFileTree) ExpandAll() {
self.collapsedPaths.ExpandAll()
}
var _ ICommitFileTree = &CommitFileTree{} var _ ICommitFileTree = &CommitFileTree{}
func NewCommitFileTree(getFiles func() []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileTree { func NewCommitFileTree(getFiles func() []*models.CommitFile, log *logrus.Entry, showTree bool) *CommitFileTree {
@@ -1,6 +1,7 @@
package filetree package filetree
import ( import (
"strings"
"sync" "sync"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -160,3 +161,33 @@ func (self *CommitFileTreeViewModel) ToggleShowTree() {
self.SetSelection(index) self.SetSelection(index)
} }
} }
func (self *CommitFileTreeViewModel) CollapseAll() {
selectedNode := self.GetSelected()
self.ICommitFileTree.CollapseAll()
if selectedNode == nil {
return
}
topLevelPath := strings.Split(selectedNode.Path, "/")[0]
index, found := self.GetIndexForPath(topLevelPath)
if found {
self.SetSelectedLineIdx(index)
}
}
func (self *CommitFileTreeViewModel) ExpandAll() {
selectedNode := self.GetSelected()
self.ICommitFileTree.ExpandAll()
if selectedNode == nil {
return
}
index, found := self.GetIndexForPath(selectedNode.Path)
if found {
self.SetSelectedLineIdx(index)
}
}
+16
View File
@@ -31,6 +31,8 @@ type ITree[T any] interface {
IsCollapsed(path string) bool IsCollapsed(path string) bool
ToggleCollapsed(path string) ToggleCollapsed(path string)
CollapsedPaths() *CollapsedPaths CollapsedPaths() *CollapsedPaths
CollapseAll()
ExpandAll()
} }
type IFileTree interface { type IFileTree interface {
@@ -171,6 +173,20 @@ func (self *FileTree) ToggleCollapsed(path string) {
self.collapsedPaths.ToggleCollapsed(path) self.collapsedPaths.ToggleCollapsed(path)
} }
func (self *FileTree) CollapseAll() {
dirPaths := lo.FilterMap(self.GetAllItems(), func(file *FileNode, index int) (string, bool) {
return file.Path, !file.IsFile()
})
for _, path := range dirPaths {
self.collapsedPaths.Collapse(path)
}
}
func (self *FileTree) ExpandAll() {
self.collapsedPaths.ExpandAll()
}
func (self *FileTree) Tree() *FileNode { func (self *FileTree) Tree() *FileNode {
return NewFileNode(self.tree) return NewFileNode(self.tree)
} }
+31
View File
@@ -1,6 +1,7 @@
package filetree package filetree
import ( import (
"strings"
"sync" "sync"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -190,3 +191,33 @@ func (self *FileTreeViewModel) ToggleShowTree() {
self.SetSelectedLineIdx(index) self.SetSelectedLineIdx(index)
} }
} }
func (self *FileTreeViewModel) CollapseAll() {
selectedNode := self.GetSelected()
self.IFileTree.CollapseAll()
if selectedNode == nil {
return
}
topLevelPath := strings.Split(selectedNode.Path, "/")[0]
index, found := self.GetIndexForPath(topLevelPath)
if found {
self.SetSelectedLineIdx(index)
}
}
func (self *FileTreeViewModel) ExpandAll() {
selectedNode := self.GetSelected()
self.IFileTree.ExpandAll()
if selectedNode == nil {
return
}
index, found := self.GetIndexForPath(selectedNode.Path)
if found {
self.SetSelectedLineIdx(index)
}
}
+7 -1
View File
@@ -455,7 +455,13 @@ func (gui *Gui) onUserConfigLoaded() error {
} else if userConfig.Gui.ShowIcons { } else if userConfig.Gui.ShowIcons {
icons.SetNerdFontsVersion("2") icons.SetNerdFontsVersion("2")
} }
presentation.SetCustomBranches(userConfig.Gui.BranchColors)
if len(userConfig.Gui.BranchColorPatterns) > 0 {
presentation.SetCustomBranches(userConfig.Gui.BranchColorPatterns, true)
} else {
// Fall back to the deprecated branchColors config
presentation.SetCustomBranches(userConfig.Gui.BranchColors, false)
}
return nil return nil
} }
+32 -16
View File
@@ -2,6 +2,7 @@ package presentation
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"time" "time"
@@ -18,7 +19,12 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
) )
var branchPrefixColorCache = make(map[string]style.TextStyle) type colorMatcher struct {
patterns map[string]style.TextStyle
isRegex bool // NOTE: this value is needed only until the deprecated branchColors config is removed and only regex color patterns are used
}
var colorPatterns *colorMatcher
func GetBranchListDisplayStrings( func GetBranchListDisplayStrings(
branches []*models.Branch, branches []*models.Branch,
@@ -125,22 +131,29 @@ func getBranchDisplayStrings(
// GetBranchTextStyle branch color // GetBranchTextStyle branch color
func GetBranchTextStyle(name string) style.TextStyle { func GetBranchTextStyle(name string) style.TextStyle {
branchType := strings.Split(name, "/")[0] if style, ok := colorPatterns.match(name); ok {
return *style
if value, ok := branchPrefixColorCache[branchType]; ok {
return value
} }
switch branchType { return theme.DefaultTextColor
case "feature": }
return style.FgGreen
case "bugfix": func (m *colorMatcher) match(name string) (*style.TextStyle, bool) {
return style.FgYellow if m.isRegex {
case "hotfix": for pattern, style := range m.patterns {
return style.FgRed if matched, _ := regexp.MatchString(pattern, name); matched {
default: return &style, true
return theme.DefaultTextColor }
}
} else {
// old behavior using the deprecated branchColors behavior matching on branch type
branchType := strings.Split(name, "/")[0]
if value, ok := m.patterns[branchType]; ok {
return &value, true
}
} }
return nil, false
} }
func BranchStatus( func BranchStatus(
@@ -189,6 +202,9 @@ func BranchStatus(
return result return result
} }
func SetCustomBranches(customBranchColors map[string]string) { func SetCustomBranches(customBranchColors map[string]string, isRegex bool) {
branchPrefixColorCache = utils.SetCustomColors(customBranchColors) colorPatterns = &colorMatcher{
patterns: utils.SetCustomColors(customBranchColors),
isRegex: isRegex,
}
} }
+1
View File
@@ -321,6 +321,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
defer color.ForceSetColorLevel(oldColorLevel) defer color.ForceSetColorLevel(oldColorLevel)
c := utils.NewDummyCommon() c := utils.NewDummyCommon()
SetCustomBranches(c.UserConfig().Gui.BranchColorPatterns, true)
for i, s := range scenarios { for i, s := range scenarios {
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", "")) icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
+16 -2
View File
@@ -256,6 +256,11 @@ type TranslationSet struct {
NoBranchOnRemote string NoBranchOnRemote string
Fetch string Fetch string
FetchTooltip string FetchTooltip string
CollapseAll string
CollapseAllTooltip string
ExpandAll string
ExpandAllTooltip string
DisabledInFlatView string
FileEnter string FileEnter string
FileEnterTooltip string FileEnterTooltip string
FileStagingRequirements string FileStagingRequirements string
@@ -727,8 +732,9 @@ type TranslationSet struct {
ConfirmRevertCommit string ConfirmRevertCommit string
RewordInEditorTitle string RewordInEditorTitle string
RewordInEditorPrompt string RewordInEditorPrompt string
CheckoutPrompt string CheckoutAutostashPrompt string
HardResetAutostashPrompt string HardResetAutostashPrompt string
SoftResetPrompt string
UpstreamGone string UpstreamGone string
NukeDescription string NukeDescription string
DiscardStagedChangesDescription string DiscardStagedChangesDescription string
@@ -1258,6 +1264,11 @@ func EnglishTranslationSet() *TranslationSet {
NoBranchOnRemote: `This branch doesn't exist on remote. You need to push it to remote first.`, NoBranchOnRemote: `This branch doesn't exist on remote. You need to push it to remote first.`,
Fetch: `Fetch`, Fetch: `Fetch`,
FetchTooltip: "Fetch changes from remote.", FetchTooltip: "Fetch changes from remote.",
CollapseAll: "Collapse all files",
CollapseAllTooltip: "Collapse all directories in the files tree",
ExpandAll: "Expand all files",
ExpandAllTooltip: "Expand all directories in the file tree",
DisabledInFlatView: "Not available in flat view",
FileEnter: `Stage lines / Collapse directory`, FileEnter: `Stage lines / Collapse directory`,
FileEnterTooltip: "If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it.", FileEnterTooltip: "If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it.",
FileStagingRequirements: `Can only stage individual lines for tracked files`, FileStagingRequirements: `Can only stage individual lines for tracked files`,
@@ -1735,7 +1746,8 @@ func EnglishTranslationSet() *TranslationSet {
RewordInEditorTitle: "Reword in editor", RewordInEditorTitle: "Reword in editor",
RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?", RewordInEditorPrompt: "Are you sure you want to reword this commit in your editor?",
HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.", HardResetAutostashPrompt: "Are you sure you want to hard reset to '%s'? An auto-stash will be performed if necessary.",
CheckoutPrompt: "Are you sure you want to checkout '%s'?", SoftResetPrompt: "Are you sure you want to soft reset to '%s'?",
CheckoutAutostashPrompt: "Are you sure you want to checkout '%s'? An auto-stash will be performed if necessary.",
UpstreamGone: "(upstream gone)", UpstreamGone: "(upstream gone)",
NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).", NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes", DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",
@@ -2006,6 +2018,8 @@ keybinding:
gui: gui:
filterMode: 'fuzzy' filterMode: 'fuzzy'
`, `,
"0.44.0": `- The gui.branchColors config option is deprecated; it will be removed in a future version. Please use gui.branchColorPatterns instead.
- The automatic coloring of branches starting with "feature/", "bugfix/", or "hotfix/" has been removed; if you want this, it's easy to set up using the new gui.branchColorPatterns option.`,
}, },
} }
} }
@@ -0,0 +1,46 @@
package file
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CollapseExpand = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Collapsing and expanding all files in the file tree",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
shell.CreateDir("dir")
shell.CreateFile("dir/file-one", "original content\n")
shell.CreateDir("dir2")
shell.CreateFile("dir2/file-two", "original content\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Contains("dir").IsSelected(),
Contains("??").Contains("file-one"),
Contains("dir2"),
Contains("??").Contains("file-two"),
)
t.Views().Files().
Press(keys.Files.CollapseAll).
Lines(
Contains("dir"),
Contains("dir2"),
)
t.Views().Files().
Press(keys.Files.ExpandAll).
Lines(
Contains("dir").IsSelected(),
Contains("??").Contains("file-one"),
Contains("dir2"),
Contains("??").Contains("file-two"),
)
},
})
+2
View File
@@ -164,6 +164,7 @@ var tests = []*components.IntegrationTest{
diff.DiffNonStickyRange, diff.DiffNonStickyRange,
diff.IgnoreWhitespace, diff.IgnoreWhitespace,
diff.RenameSimilarityThresholdChange, diff.RenameSimilarityThresholdChange,
file.CollapseExpand,
file.CopyMenu, file.CopyMenu,
file.DirWithUntrackedFile, file.DirWithUntrackedFile,
file.DiscardAllDirChanges, file.DiscardAllDirChanges,
@@ -366,6 +367,7 @@ var tests = []*components.IntegrationTest{
ui.SwitchTabFromMenu, ui.SwitchTabFromMenu,
ui.SwitchTabWithPanelJumpKeys, ui.SwitchTabWithPanelJumpKeys,
undo.UndoCheckoutAndDrop, undo.UndoCheckoutAndDrop,
undo.UndoCommit,
undo.UndoDrop, undo.UndoDrop,
worktree.AddFromBranch, worktree.AddFromBranch,
worktree.AddFromBranchDetached, worktree.AddFromBranchDetached,
+110
View File
@@ -0,0 +1,110 @@
package undo
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var UndoCommit = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Undo/redo a commit",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("other-file", "other-file-1")
shell.Commit("one")
shell.CreateFileAndAdd("file", "file-1")
shell.Commit("two")
shell.UpdateFile("other-file", "other-file-2")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
confirmUndo := func() {
t.ExpectPopup().Confirmation().
Title(Equals("Undo")).
Content(MatchesRegexp(`Are you sure you want to soft reset to '.*'\?`)).
Confirm()
}
confirmRedo := func() {
t.ExpectPopup().Confirmation().
Title(Equals("Redo")).
Content(MatchesRegexp(`Are you sure you want to hard reset to '.*'\? An auto-stash will be performed if necessary\.`)).
Confirm()
}
confirmDiscardFile := func() {
t.ExpectPopup().Menu().
Title(Equals("Discard changes")).
Select(Contains("Discard all changes")).
Confirm()
}
t.Views().Files().
Lines(
Contains(" M other-file"),
)
t.Views().Commits().Focus().
Lines(
Contains("two").IsSelected(),
Contains("one"),
).
Press(keys.Universal.Undo).
Tap(confirmUndo).
Lines(
Contains("one").IsSelected(),
)
t.Views().Files().
Lines(
Contains("A file"),
Contains(" M other-file"),
)
t.Views().Commits().Focus().
Press(keys.Universal.Redo).
Tap(confirmRedo).
Lines(
Contains("two").IsSelected(),
Contains("one"),
)
t.Views().Files().
Lines(
Contains(" M other-file"),
)
// Undo again, this time discarding the original change before redoing again
t.Views().Commits().Focus().
Press(keys.Universal.Undo).
Tap(confirmUndo).
Lines(
Contains("one").IsSelected(),
)
t.Views().Files().Focus().
Lines(
Contains("A file"),
Contains(" M other-file").IsSelected(),
).
Press(keys.Universal.PrevItem).
Press(keys.Universal.Remove).
Tap(confirmDiscardFile).
Lines(
Contains(" M other-file"),
).
Press(keys.Universal.Redo).
Tap(confirmRedo)
t.Views().Commits().
Lines(
Contains("two"),
Contains("one"),
)
t.Views().Files().
Lines(
Contains(" M other-file"),
)
},
})
+15
View File
@@ -12,6 +12,13 @@
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color" "description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color"
}, },
"branchColors": { "branchColors": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color\nDeprecated: use branchColorPatterns instead"
},
"branchColorPatterns": {
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
}, },
@@ -1456,6 +1463,14 @@
"copyFileInfoToClipboard": { "copyFileInfoToClipboard": {
"type": "string", "type": "string",
"default": "y" "default": "y"
},
"collapseAll": {
"type": "string",
"default": "-"
},
"expandAll": {
"type": "string",
"default": "="
} }
}, },
"additionalProperties": false, "additionalProperties": false,
+2 -2
View File
@@ -20,7 +20,7 @@ In `tcell.js`, you also need to change the constant
```js ```js
const wasmFilePath = "yourfile.wasm" const wasmFilePath = "yourfile.wasm"
``` ```
to the file you outputed to when building. to the file you outputted to when building.
## Displaying your project ## Displaying your project
@@ -49,7 +49,7 @@ func main() {
To see the webpage with this example, you can type in `localhost:8080/tcell.html` into your browser while `server.go` is running. To see the webpage with this example, you can type in `localhost:8080/tcell.html` into your browser while `server.go` is running.
### Embedding ### Embedding
It is recomended to use an iframe if you want to embed the app into a webpage: It is recommended to use an iframe if you want to embed the app into a webpage:
```html ```html
<iframe src="tcell.html" title="Tcell app"></iframe> <iframe src="tcell.html" title="Tcell app"></iframe>
``` ```
+5 -3
View File
@@ -33,7 +33,7 @@ A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available.
- [godu](https://github.com/viktomas/godu) - utility to discover large files/folders - [godu](https://github.com/viktomas/godu) - utility to discover large files/folders
- [tview](https://github.com/rivo/tview/) - rich interactive widgets - [tview](https://github.com/rivo/tview/) - rich interactive widgets
- [cview](https://code.rocketnine.space/tslocum/cview) - user interface toolkit (fork of _tview_) - [cview](https://code.rocketnine.space/tslocum/cview) - user interface toolkit (fork of _tview_)
- [awsome gocui](https://github.com/awesome-gocui/gocui) - Go Console User Interface - [awesome gocui](https://github.com/awesome-gocui/gocui) - Go Console User Interface
- [gomandelbrot](https://github.com/rgm3/gomandelbrot) - Mandelbrot! - [gomandelbrot](https://github.com/rgm3/gomandelbrot) - Mandelbrot!
- [WTF](https://github.com/senorprogrammer/wtf) - personal information dashboard - [WTF](https://github.com/senorprogrammer/wtf) - personal information dashboard
- [browsh](https://github.com/browsh-org/browsh) - modern web browser ([video](https://www.youtube.com/watch?v=HZq86XfBoRo)) - [browsh](https://github.com/browsh-org/browsh) - modern web browser ([video](https://www.youtube.com/watch?v=HZq86XfBoRo))
@@ -67,6 +67,8 @@ A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available.
- [gbb](https://github.com/sdemingo/gbb) - A classical bulletin board app for tildes or public unix servers - [gbb](https://github.com/sdemingo/gbb) - A classical bulletin board app for tildes or public unix servers
- [lil](https://github.com/andrievsky/lil) - A simple and flexible interface for any service by implementing only list and get operations - [lil](https://github.com/andrievsky/lil) - A simple and flexible interface for any service by implementing only list and get operations
- [hero.go](https://github.com/barisbll/hero.go) - 2d monster shooter ([video](https://user-images.githubusercontent.com/40062673/277157369-240d7606-b471-4aa1-8c54-4379a513122b.mp4)) - [hero.go](https://github.com/barisbll/hero.go) - 2d monster shooter ([video](https://user-images.githubusercontent.com/40062673/277157369-240d7606-b471-4aa1-8c54-4379a513122b.mp4))
- [go-tetris](https://github.com/aaronriekenberg/go-tetris) - simple tetris game for native terminal and WASM using github actions+pages
- [oddshub](https://github.com/dos-2/oddshub) - A TUI designed for analyzing sports betting odds
## Pure Go Terminfo Database ## Pure Go Terminfo Database
@@ -143,7 +145,7 @@ Most _termbox-go_ programs will probably work without further modification.
Internally _Tcell_ uses UTF-8, just like Go. Internally _Tcell_ uses UTF-8, just like Go.
However, _Tcell_ understands how to However, _Tcell_ understands how to
convert to and from other character sets, using the capabilities of convert to and from other character sets, using the capabilities of
the `golang.org/x/text/encoding packages`. the `golang.org/x/text/encoding` packages.
Your application must supply Your application must supply
them, as the full set of the most common ones bloats the program by about 2 MB. them, as the full set of the most common ones bloats the program by about 2 MB.
If you're lazy, and want them all anyway, see the `encoding` sub-directory. If you're lazy, and want them all anyway, see the `encoding` sub-directory.
@@ -285,4 +287,4 @@ please let me know. PRs are especially welcome.
_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options. _Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options.
- [TideLift](https://tidelift.com/) subscriptions include support for _Tcell_, as well as many other open source packages. - [TideLift](https://tidelift.com/) subscriptions include support for _Tcell_, as well as many other open source packages.
- [Staysail Systems Inc.](mailto:info@staysail.tech) offers direct support, and custom development around _Tcell_ on an hourly basis. - [Staysail Systems Inc.](mailto:info@staysail.tech) offers direct support, and custom development around _Tcell_ on an hourly basis.
+6 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2020 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -19,15 +19,16 @@ package tcell
type AttrMask int type AttrMask int
// Attributes are not colors, but affect the display of text. They can // Attributes are not colors, but affect the display of text. They can
// be combined. // be combined, in some cases, but not others. (E.g. you can have Dim Italic,
// but only CurlyUnderline cannot be mixed with DottedUnderline.)
const ( const (
AttrBold AttrMask = 1 << iota AttrBold AttrMask = 1 << iota
AttrBlink AttrBlink
AttrReverse AttrReverse
AttrUnderline AttrUnderline // Deprecated: Use UnderlineStyle
AttrDim AttrDim
AttrItalic AttrItalic
AttrStrikeThrough AttrStrikeThrough
AttrInvalid // Mark the style or attributes invalid AttrInvalid AttrMask = 1 << 31 // Mark the style or attributes invalid
AttrNone AttrMask = 0 // Just normal text. AttrNone AttrMask = 0 // Just normal text.
) )
+1 -8
View File
@@ -58,7 +58,7 @@ func (cb *CellBuffer) SetContent(x int, y int,
// dirty as well as the base cell, to make sure we consider // dirty as well as the base cell, to make sure we consider
// both cells as dirty together. We only need to do this // both cells as dirty together. We only need to do this
// if we're changing content // if we're changing content
if (c.width > 0) && (mainc != c.currMain || !reflect.DeepEqual(combc, c.currComb)) { if (c.width > 0) && (mainc != c.currMain || len(combc) != len(c.currComb) || (len(combc) > 0 && !reflect.DeepEqual(combc, c.currComb))) {
for i := 0; i < c.width; i++ { for i := 0; i < c.width; i++ {
cb.SetDirty(x+i, y, true) cb.SetDirty(x+i, y, true)
} }
@@ -246,11 +246,4 @@ func init() {
if os.Getenv("RUNEWIDTH_EASTASIAN") == "" { if os.Getenv("RUNEWIDTH_EASTASIAN") == "" {
runewidth.DefaultCondition.EastAsianWidth = false runewidth.DefaultCondition.EastAsianWidth = false
} }
// For performance reasons, we create a lookup table. However, some users
// might be more memory conscious. If that's you, set the TCELL_MINIMIZE
// environment variable.
if os.Getenv("TCELL_MINIMIZE") == "" {
runewidth.CreateLUT()
}
} }
+123 -123
View File
@@ -314,129 +314,129 @@ const (
Color253 Color253
Color254 Color254
Color255 Color255
ColorAliceBlue ColorAliceBlue = ColorIsRGB | ColorValid | 0xF0F8FF
ColorAntiqueWhite ColorAntiqueWhite = ColorIsRGB | ColorValid | 0xFAEBD7
ColorAquaMarine ColorAquaMarine = ColorIsRGB | ColorValid | 0x7FFFD4
ColorAzure ColorAzure = ColorIsRGB | ColorValid | 0xF0FFFF
ColorBeige ColorBeige = ColorIsRGB | ColorValid | 0xF5F5DC
ColorBisque ColorBisque = ColorIsRGB | ColorValid | 0xFFE4C4
ColorBlanchedAlmond ColorBlanchedAlmond = ColorIsRGB | ColorValid | 0xFFEBCD
ColorBlueViolet ColorBlueViolet = ColorIsRGB | ColorValid | 0x8A2BE2
ColorBrown ColorBrown = ColorIsRGB | ColorValid | 0xA52A2A
ColorBurlyWood ColorBurlyWood = ColorIsRGB | ColorValid | 0xDEB887
ColorCadetBlue ColorCadetBlue = ColorIsRGB | ColorValid | 0x5F9EA0
ColorChartreuse ColorChartreuse = ColorIsRGB | ColorValid | 0x7FFF00
ColorChocolate ColorChocolate = ColorIsRGB | ColorValid | 0xD2691E
ColorCoral ColorCoral = ColorIsRGB | ColorValid | 0xFF7F50
ColorCornflowerBlue ColorCornflowerBlue = ColorIsRGB | ColorValid | 0x6495ED
ColorCornsilk ColorCornsilk = ColorIsRGB | ColorValid | 0xFFF8DC
ColorCrimson ColorCrimson = ColorIsRGB | ColorValid | 0xDC143C
ColorDarkBlue ColorDarkBlue = ColorIsRGB | ColorValid | 0x00008B
ColorDarkCyan ColorDarkCyan = ColorIsRGB | ColorValid | 0x008B8B
ColorDarkGoldenrod ColorDarkGoldenrod = ColorIsRGB | ColorValid | 0xB8860B
ColorDarkGray ColorDarkGray = ColorIsRGB | ColorValid | 0xA9A9A9
ColorDarkGreen ColorDarkGreen = ColorIsRGB | ColorValid | 0x006400
ColorDarkKhaki ColorDarkKhaki = ColorIsRGB | ColorValid | 0xBDB76B
ColorDarkMagenta ColorDarkMagenta = ColorIsRGB | ColorValid | 0x8B008B
ColorDarkOliveGreen ColorDarkOliveGreen = ColorIsRGB | ColorValid | 0x556B2F
ColorDarkOrange ColorDarkOrange = ColorIsRGB | ColorValid | 0xFF8C00
ColorDarkOrchid ColorDarkOrchid = ColorIsRGB | ColorValid | 0x9932CC
ColorDarkRed ColorDarkRed = ColorIsRGB | ColorValid | 0x8B0000
ColorDarkSalmon ColorDarkSalmon = ColorIsRGB | ColorValid | 0xE9967A
ColorDarkSeaGreen ColorDarkSeaGreen = ColorIsRGB | ColorValid | 0x8FBC8F
ColorDarkSlateBlue ColorDarkSlateBlue = ColorIsRGB | ColorValid | 0x483D8B
ColorDarkSlateGray ColorDarkSlateGray = ColorIsRGB | ColorValid | 0x2F4F4F
ColorDarkTurquoise ColorDarkTurquoise = ColorIsRGB | ColorValid | 0x00CED1
ColorDarkViolet ColorDarkViolet = ColorIsRGB | ColorValid | 0x9400D3
ColorDeepPink ColorDeepPink = ColorIsRGB | ColorValid | 0xFF1493
ColorDeepSkyBlue ColorDeepSkyBlue = ColorIsRGB | ColorValid | 0x00BFFF
ColorDimGray ColorDimGray = ColorIsRGB | ColorValid | 0x696969
ColorDodgerBlue ColorDodgerBlue = ColorIsRGB | ColorValid | 0x1E90FF
ColorFireBrick ColorFireBrick = ColorIsRGB | ColorValid | 0xB22222
ColorFloralWhite ColorFloralWhite = ColorIsRGB | ColorValid | 0xFFFAF0
ColorForestGreen ColorForestGreen = ColorIsRGB | ColorValid | 0x228B22
ColorGainsboro ColorGainsboro = ColorIsRGB | ColorValid | 0xDCDCDC
ColorGhostWhite ColorGhostWhite = ColorIsRGB | ColorValid | 0xF8F8FF
ColorGold ColorGold = ColorIsRGB | ColorValid | 0xFFD700
ColorGoldenrod ColorGoldenrod = ColorIsRGB | ColorValid | 0xDAA520
ColorGreenYellow ColorGreenYellow = ColorIsRGB | ColorValid | 0xADFF2F
ColorHoneydew ColorHoneydew = ColorIsRGB | ColorValid | 0xF0FFF0
ColorHotPink ColorHotPink = ColorIsRGB | ColorValid | 0xFF69B4
ColorIndianRed ColorIndianRed = ColorIsRGB | ColorValid | 0xCD5C5C
ColorIndigo ColorIndigo = ColorIsRGB | ColorValid | 0x4B0082
ColorIvory ColorIvory = ColorIsRGB | ColorValid | 0xFFFFF0
ColorKhaki ColorKhaki = ColorIsRGB | ColorValid | 0xF0E68C
ColorLavender ColorLavender = ColorIsRGB | ColorValid | 0xE6E6FA
ColorLavenderBlush ColorLavenderBlush = ColorIsRGB | ColorValid | 0xFFF0F5
ColorLawnGreen ColorLawnGreen = ColorIsRGB | ColorValid | 0x7CFC00
ColorLemonChiffon ColorLemonChiffon = ColorIsRGB | ColorValid | 0xFFFACD
ColorLightBlue ColorLightBlue = ColorIsRGB | ColorValid | 0xADD8E6
ColorLightCoral ColorLightCoral = ColorIsRGB | ColorValid | 0xF08080
ColorLightCyan ColorLightCyan = ColorIsRGB | ColorValid | 0xE0FFFF
ColorLightGoldenrodYellow ColorLightGoldenrodYellow = ColorIsRGB | ColorValid | 0xFAFAD2
ColorLightGray ColorLightGray = ColorIsRGB | ColorValid | 0xD3D3D3
ColorLightGreen ColorLightGreen = ColorIsRGB | ColorValid | 0x90EE90
ColorLightPink ColorLightPink = ColorIsRGB | ColorValid | 0xFFB6C1
ColorLightSalmon ColorLightSalmon = ColorIsRGB | ColorValid | 0xFFA07A
ColorLightSeaGreen ColorLightSeaGreen = ColorIsRGB | ColorValid | 0x20B2AA
ColorLightSkyBlue ColorLightSkyBlue = ColorIsRGB | ColorValid | 0x87CEFA
ColorLightSlateGray ColorLightSlateGray = ColorIsRGB | ColorValid | 0x778899
ColorLightSteelBlue ColorLightSteelBlue = ColorIsRGB | ColorValid | 0xB0C4DE
ColorLightYellow ColorLightYellow = ColorIsRGB | ColorValid | 0xFFFFE0
ColorLimeGreen ColorLimeGreen = ColorIsRGB | ColorValid | 0x32CD32
ColorLinen ColorLinen = ColorIsRGB | ColorValid | 0xFAF0E6
ColorMediumAquamarine ColorMediumAquamarine = ColorIsRGB | ColorValid | 0x66CDAA
ColorMediumBlue ColorMediumBlue = ColorIsRGB | ColorValid | 0x0000CD
ColorMediumOrchid ColorMediumOrchid = ColorIsRGB | ColorValid | 0xBA55D3
ColorMediumPurple ColorMediumPurple = ColorIsRGB | ColorValid | 0x9370DB
ColorMediumSeaGreen ColorMediumSeaGreen = ColorIsRGB | ColorValid | 0x3CB371
ColorMediumSlateBlue ColorMediumSlateBlue = ColorIsRGB | ColorValid | 0x7B68EE
ColorMediumSpringGreen ColorMediumSpringGreen = ColorIsRGB | ColorValid | 0x00FA9A
ColorMediumTurquoise ColorMediumTurquoise = ColorIsRGB | ColorValid | 0x48D1CC
ColorMediumVioletRed ColorMediumVioletRed = ColorIsRGB | ColorValid | 0xC71585
ColorMidnightBlue ColorMidnightBlue = ColorIsRGB | ColorValid | 0x191970
ColorMintCream ColorMintCream = ColorIsRGB | ColorValid | 0xF5FFFA
ColorMistyRose ColorMistyRose = ColorIsRGB | ColorValid | 0xFFE4E1
ColorMoccasin ColorMoccasin = ColorIsRGB | ColorValid | 0xFFE4B5
ColorNavajoWhite ColorNavajoWhite = ColorIsRGB | ColorValid | 0xFFDEAD
ColorOldLace ColorOldLace = ColorIsRGB | ColorValid | 0xFDF5E6
ColorOliveDrab ColorOliveDrab = ColorIsRGB | ColorValid | 0x6B8E23
ColorOrange ColorOrange = ColorIsRGB | ColorValid | 0xFFA500
ColorOrangeRed ColorOrangeRed = ColorIsRGB | ColorValid | 0xFF4500
ColorOrchid ColorOrchid = ColorIsRGB | ColorValid | 0xDA70D6
ColorPaleGoldenrod ColorPaleGoldenrod = ColorIsRGB | ColorValid | 0xEEE8AA
ColorPaleGreen ColorPaleGreen = ColorIsRGB | ColorValid | 0x98FB98
ColorPaleTurquoise ColorPaleTurquoise = ColorIsRGB | ColorValid | 0xAFEEEE
ColorPaleVioletRed ColorPaleVioletRed = ColorIsRGB | ColorValid | 0xDB7093
ColorPapayaWhip ColorPapayaWhip = ColorIsRGB | ColorValid | 0xFFEFD5
ColorPeachPuff ColorPeachPuff = ColorIsRGB | ColorValid | 0xFFDAB9
ColorPeru ColorPeru = ColorIsRGB | ColorValid | 0xCD853F
ColorPink ColorPink = ColorIsRGB | ColorValid | 0xFFC0CB
ColorPlum ColorPlum = ColorIsRGB | ColorValid | 0xDDA0DD
ColorPowderBlue ColorPowderBlue = ColorIsRGB | ColorValid | 0xB0E0E6
ColorRebeccaPurple ColorRebeccaPurple = ColorIsRGB | ColorValid | 0x663399
ColorRosyBrown ColorRosyBrown = ColorIsRGB | ColorValid | 0xBC8F8F
ColorRoyalBlue ColorRoyalBlue = ColorIsRGB | ColorValid | 0x4169E1
ColorSaddleBrown ColorSaddleBrown = ColorIsRGB | ColorValid | 0x8B4513
ColorSalmon ColorSalmon = ColorIsRGB | ColorValid | 0xFA8072
ColorSandyBrown ColorSandyBrown = ColorIsRGB | ColorValid | 0xF4A460
ColorSeaGreen ColorSeaGreen = ColorIsRGB | ColorValid | 0x2E8B57
ColorSeashell ColorSeashell = ColorIsRGB | ColorValid | 0xFFF5EE
ColorSienna ColorSienna = ColorIsRGB | ColorValid | 0xA0522D
ColorSkyblue ColorSkyblue = ColorIsRGB | ColorValid | 0x87CEEB
ColorSlateBlue ColorSlateBlue = ColorIsRGB | ColorValid | 0x6A5ACD
ColorSlateGray ColorSlateGray = ColorIsRGB | ColorValid | 0x708090
ColorSnow ColorSnow = ColorIsRGB | ColorValid | 0xFFFAFA
ColorSpringGreen ColorSpringGreen = ColorIsRGB | ColorValid | 0x00FF7F
ColorSteelBlue ColorSteelBlue = ColorIsRGB | ColorValid | 0x4682B4
ColorTan ColorTan = ColorIsRGB | ColorValid | 0xD2B48C
ColorThistle ColorThistle = ColorIsRGB | ColorValid | 0xD8BFD8
ColorTomato ColorTomato = ColorIsRGB | ColorValid | 0xFF6347
ColorTurquoise ColorTurquoise = ColorIsRGB | ColorValid | 0x40E0D0
ColorViolet ColorViolet = ColorIsRGB | ColorValid | 0xEE82EE
ColorWheat ColorWheat = ColorIsRGB | ColorValid | 0xF5DEB3
ColorWhiteSmoke ColorWhiteSmoke = ColorIsRGB | ColorValid | 0xF5F5F5
ColorYellowGreen ColorYellowGreen = ColorIsRGB | ColorValid | 0x9ACD32
) )
// These are aliases for the color gray, because some of us spell // These are aliases for the color gray, because some of us spell
+78 -6
View File
@@ -42,6 +42,7 @@ type cScreen struct {
truecolor bool truecolor bool
running bool running bool
disableAlt bool // disable the alternate screen disableAlt bool // disable the alternate screen
title string
w int w int
h int h int
@@ -49,6 +50,7 @@ type cScreen struct {
oscreen consoleInfo oscreen consoleInfo
ocursor cursorInfo ocursor cursorInfo
cursorStyle CursorStyle cursorStyle CursorStyle
cursorColor Color
oimode uint32 oimode uint32
oomode uint32 oomode uint32
cells CellBuffer cells CellBuffer
@@ -164,6 +166,20 @@ const (
vtEnableAm = "\x1b[?7h" vtEnableAm = "\x1b[?7h"
vtEnterCA = "\x1b[?1049h\x1b[22;0;0t" vtEnterCA = "\x1b[?1049h\x1b[22;0;0t"
vtExitCA = "\x1b[?1049l\x1b[23;0;0t" vtExitCA = "\x1b[?1049l\x1b[23;0;0t"
vtDoubleUnderline = "\x1b[4:2m"
vtCurlyUnderline = "\x1b[4:3m"
vtDottedUnderline = "\x1b[4:4m"
vtDashedUnderline = "\x1b[4:5m"
vtUnderColor = "\x1b[58:5:%dm"
vtUnderColorRGB = "\x1b[58:2::%d:%d:%dm"
vtUnderColorReset = "\x1b[59m"
vtEnterUrl = "\x1b]8;%s;%s\x1b\\" // NB arg 1 is id, arg 2 is url
vtExitUrl = "\x1b]8;;\x1b\\"
vtCursorColorRGB = "\x1b]12;#%02x%02x%02x\007"
vtCursorColorReset = "\x1b]112\007"
vtSaveTitle = "\x1b[22;2t"
vtRestoreTitle = "\x1b[23;2t"
vtSetTitle = "\x1b]2;%s\x1b\\"
) )
var vtCursorStyles = map[CursorStyle]string{ var vtCursorStyles = map[CursorStyle]string{
@@ -335,8 +351,10 @@ func (s *cScreen) disengage() {
if s.vten { if s.vten {
s.emitVtString(vtCursorStyles[CursorStyleDefault]) s.emitVtString(vtCursorStyles[CursorStyleDefault])
s.emitVtString(vtCursorColorReset)
s.emitVtString(vtEnableAm) s.emitVtString(vtEnableAm)
if !s.disableAlt { if !s.disableAlt {
s.emitVtString(vtRestoreTitle)
s.emitVtString(vtExitCA) s.emitVtString(vtExitCA)
} }
} else if !s.disableAlt { } else if !s.disableAlt {
@@ -374,9 +392,13 @@ func (s *cScreen) engage() error {
if s.vten { if s.vten {
s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline) s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
if !s.disableAlt { if !s.disableAlt {
s.emitVtString(vtSaveTitle)
s.emitVtString(vtEnterCA) s.emitVtString(vtEnterCA)
} }
s.emitVtString(vtDisableAm) s.emitVtString(vtDisableAm)
if s.title != "" {
s.emitVtString(fmt.Sprintf(vtSetTitle, s.title))
}
} else { } else {
s.setOutMode(0) s.setOutMode(0)
} }
@@ -426,6 +448,12 @@ func (s *cScreen) showCursor() {
if s.vten { if s.vten {
s.emitVtString(vtShowCursor) s.emitVtString(vtShowCursor)
s.emitVtString(vtCursorStyles[s.cursorStyle]) s.emitVtString(vtCursorStyles[s.cursorStyle])
if s.cursorColor == ColorReset {
s.emitVtString(vtCursorColorReset)
} else if s.cursorColor.Valid() {
r, g, b := s.cursorColor.RGB()
s.emitVtString(fmt.Sprintf(vtCursorColorRGB, r, g, b))
}
} else { } else {
s.setCursorInfo(&cursorInfo{size: 100, visible: 1}) s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
} }
@@ -449,11 +477,12 @@ func (s *cScreen) ShowCursor(x, y int) {
s.Unlock() s.Unlock()
} }
func (s *cScreen) SetCursorStyle(cs CursorStyle) { func (s *cScreen) SetCursor(cs CursorStyle, cc Color) {
s.Lock() s.Lock()
if !s.fini { if !s.fini {
if _, ok := vtCursorStyles[cs]; ok { if _, ok := vtCursorStyles[cs]; ok {
s.cursorStyle = cs s.cursorStyle = cs
s.cursorColor = cc
s.doCursor() s.doCursor()
} }
} }
@@ -875,7 +904,7 @@ func mapColor2RGB(c Color) uint16 {
// Map a tcell style to Windows attributes // Map a tcell style to Windows attributes
func (s *cScreen) mapStyle(style Style) uint16 { func (s *cScreen) mapStyle(style Style) uint16 {
f, b, a := style.Decompose() f, b, a := style.fg, style.bg, style.attrs
fa := s.oscreen.attrs & 0xf fa := s.oscreen.attrs & 0xf
ba := (s.oscreen.attrs) >> 4 & 0xf ba := (s.oscreen.attrs) >> 4 & 0xf
if f != ColorDefault && f != ColorReset { if f != ColorDefault && f != ColorReset {
@@ -912,19 +941,41 @@ func (s *cScreen) mapStyle(style Style) uint16 {
func (s *cScreen) sendVtStyle(style Style) { func (s *cScreen) sendVtStyle(style Style) {
esc := &strings.Builder{} esc := &strings.Builder{}
fg, bg, attrs := style.Decompose() fg, bg, attrs := style.fg, style.bg, style.attrs
us, uc := style.ulStyle, style.ulColor
esc.WriteString(vtSgr0) esc.WriteString(vtSgr0)
if attrs&(AttrBold|AttrDim) == AttrBold { if attrs&(AttrBold|AttrDim) == AttrBold {
esc.WriteString(vtBold) esc.WriteString(vtBold)
} }
if attrs&AttrBlink != 0 { if attrs&AttrBlink != 0 {
esc.WriteString(vtBlink) esc.WriteString(vtBlink)
} }
if attrs&AttrUnderline != 0 { if us != UnderlineStyleNone {
if uc == ColorReset {
esc.WriteString(vtUnderColorReset)
} else if uc.IsRGB() {
r, g, b := uc.RGB()
_, _ = fmt.Fprintf(esc, vtUnderColorRGB, int(r), int(g), int(b))
} else if uc.Valid() {
_, _ = fmt.Fprintf(esc, vtUnderColor, uc&0xff)
}
esc.WriteString(vtUnderline) esc.WriteString(vtUnderline)
// legacy ConHost does not understand these but Terminal does
switch us {
case UnderlineStyleSolid:
case UnderlineStyleDouble:
esc.WriteString(vtDoubleUnderline)
case UnderlineStyleCurly:
esc.WriteString(vtCurlyUnderline)
case UnderlineStyleDotted:
esc.WriteString(vtDottedUnderline)
case UnderlineStyleDashed:
esc.WriteString(vtDashedUnderline)
}
} }
if attrs&AttrReverse != 0 { if attrs&AttrReverse != 0 {
esc.WriteString(vtReverse) esc.WriteString(vtReverse)
} }
@@ -940,6 +991,13 @@ func (s *cScreen) sendVtStyle(style Style) {
} else if bg.Valid() { } else if bg.Valid() {
_, _ = fmt.Fprintf(esc, vtSetBg, bg&0xff) _, _ = fmt.Fprintf(esc, vtSetBg, bg&0xff)
} }
// URL string can be long, so don't send it unless we really need to
if style.url != "" {
_, _ = fmt.Fprintf(esc, vtEnterUrl, style.urlId, style.url)
} else {
esc.WriteString(vtExitUrl)
}
s.emitVtString(esc.String()) s.emitVtString(esc.String())
} }
@@ -1062,7 +1120,6 @@ func (s *cScreen) setCursorInfo(info *cursorInfo) {
_, _, _ = procSetConsoleCursorInfo.Call( _, _, _ = procSetConsoleCursorInfo.Call(
uintptr(s.out), uintptr(s.out),
uintptr(unsafe.Pointer(info))) uintptr(unsafe.Pointer(info)))
} }
func (s *cScreen) setCursorPos(x, y int, vtEnable bool) { func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
@@ -1227,6 +1284,15 @@ func (s *cScreen) SetStyle(style Style) {
s.Unlock() s.Unlock()
} }
func (s *cScreen) SetTitle(title string) {
s.Lock()
s.title = title
if s.vten {
s.emitVtString(fmt.Sprintf(vtSetTitle, title))
}
s.Unlock()
}
// No fallback rune support, since we have Unicode. Yay! // No fallback rune support, since we have Unicode. Yay!
func (s *cScreen) RegisterRuneFallback(_ rune, _ string) { func (s *cScreen) RegisterRuneFallback(_ rune, _ string) {
@@ -1246,6 +1312,12 @@ func (s *cScreen) HasMouse() bool {
return true return true
} }
func (s *cScreen) SetClipboard(_ []byte) {
}
func (s *cScreen) GetClipboard() {
}
func (s *cScreen) Resize(int, int, int, int) {} func (s *cScreen) Resize(int, int, int, int) {}
func (s *cScreen) HasKey(k Key) bool { func (s *cScreen) HasKey(k Key) bool {
+28 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2020 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -19,12 +19,14 @@ import (
) )
// EventPaste is used to mark the start and end of a bracketed paste. // EventPaste is used to mark the start and end of a bracketed paste.
// An event with .Start() true will be sent to mark the start. //
// Then a number of keys will be sent to indicate that the content // An event with .Start() true will be sent to mark the start of a bracketed paste,
// is pasted in. At the end, an event with .Start() false will be sent. // followed by a number of keys (string data) for the content, ending with the
// an event with .End() true.
type EventPaste struct { type EventPaste struct {
start bool start bool
t time.Time t time.Time
data []byte
} }
// When returns the time when this EventPaste was created. // When returns the time when this EventPaste was created.
@@ -46,3 +48,25 @@ func (ev *EventPaste) End() bool {
func NewEventPaste(start bool) *EventPaste { func NewEventPaste(start bool) *EventPaste {
return &EventPaste{t: time.Now(), start: start} return &EventPaste{t: time.Now(), start: start}
} }
// NewEventClipboard returns a new NewEventClipboard with a data payload
func NewEventClipboard(data []byte) *EventClipboard {
return &EventClipboard{t: time.Now(), data: data}
}
// EventClipboard represents data from the clipboard,
// in response to a GetClipboard request.
type EventClipboard struct {
t time.Time
data []byte
}
// Data returns the attached binary data.
func (ev *EventClipboard) Data() []byte {
return ev.data
}
// When returns the time when this event was created.
func (ev *EventClipboard) When() time.Time {
return ev.t
}
+33 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -79,8 +79,9 @@ type Screen interface {
// SetCursorStyle is used to set the cursor style. If the style // SetCursorStyle is used to set the cursor style. If the style
// is not supported (or cursor styles are not supported at all), // is not supported (or cursor styles are not supported at all),
// then this will have no effect. // then this will have no effect. Color will be changed if supplied,
SetCursorStyle(CursorStyle) // and the terminal supports doing so.
SetCursorStyle(CursorStyle, ...Color)
// Size returns the screen size as width, height. This changes in // Size returns the screen size as width, height. This changes in
// response to a call to Clear or Flush. // response to a call to Clear or Flush.
@@ -265,6 +266,23 @@ type Screen interface {
// Tty returns the underlying Tty. If the screen is not a terminal, the // Tty returns the underlying Tty. If the screen is not a terminal, the
// returned bool will be false // returned bool will be false
Tty() (Tty, bool) Tty() (Tty, bool)
// SetTitle sets a window title on the screen.
// Terminals may be configured to ignore this, or unable to.
// Tcell may attempt to save and restore the window title on entry and exit, but
// the results may vary. Use of unicode characters may not be supported.
SetTitle(string)
// SetClipboard is used to post arbitrary data to the system clipboard.
// This need not be UTF-8 string data. It's up to the recipient to decode the
// data meaningfully. Terminals may prevent this for security reasons.
SetClipboard([]byte)
// GetClipboard is used to request the clipboard contents. It may be ignored.
// If the terminal is willing, it will be post the clipboard contents using an
// EventPaste with the clipboard content as the Data() field. Terminals may
// prevent this for security reasons.
GetClipboard()
} }
// NewScreen returns a default Screen suitable for the user's terminal // NewScreen returns a default Screen suitable for the user's terminal
@@ -312,7 +330,7 @@ type screenImpl interface {
SetStyle(style Style) SetStyle(style Style)
ShowCursor(x int, y int) ShowCursor(x int, y int)
HideCursor() HideCursor()
SetCursorStyle(CursorStyle) SetCursor(CursorStyle, Color)
Size() (width, height int) Size() (width, height int)
EnableMouse(...MouseFlags) EnableMouse(...MouseFlags)
DisableMouse() DisableMouse()
@@ -334,7 +352,10 @@ type screenImpl interface {
Resume() error Resume() error
Beep() error Beep() error
SetSize(int, int) SetSize(int, int)
SetTitle(string)
Tty() (Tty, bool) Tty() (Tty, bool)
SetClipboard([]byte)
GetClipboard()
// Following methods are not part of the Screen api, but are used for interaction with // Following methods are not part of the Screen api, but are used for interaction with
// the common layer code. // the common layer code.
@@ -464,3 +485,11 @@ func (b *baseScreen) PostEvent(ev Event) error {
return ErrEventQFull return ErrEventQFull
} }
} }
func (b *baseScreen) SetCursorStyle(cs CursorStyle, ccs ...Color) {
if len(ccs) > 0 {
b.SetCursor(cs, ccs[0])
} else {
b.SetCursor(cs, ColorNone)
}
}
+33 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -60,6 +60,12 @@ type SimulationScreen interface {
// GetCursor returns the cursor details. // GetCursor returns the cursor details.
GetCursor() (x int, y int, visible bool) GetCursor() (x int, y int, visible bool)
// GetTitle gets the previously set title.
GetTitle() string
// GetClipboardData gets the actual data for the clipboard.
GetClipboardData() []byte
} }
// SimCell represents a simulated screen cell. The purpose of this // SimCell represents a simulated screen cell. The purpose of this
@@ -98,6 +104,8 @@ type simscreen struct {
fillchar rune fillchar rune
fillstyle Style fillstyle Style
fallback map[rune]string fallback map[rune]string
title string
clipboard []byte
Screen Screen
sync.Mutex sync.Mutex
@@ -239,7 +247,7 @@ func (s *simscreen) hideCursor() {
s.cursorvis = false s.cursorvis = false
} }
func (s *simscreen) SetCursorStyle(CursorStyle) {} func (s *simscreen) SetCursor(CursorStyle, Color) {}
func (s *simscreen) Show() { func (s *simscreen) Show() {
s.Lock() s.Lock()
@@ -495,3 +503,26 @@ func (s *simscreen) EventQ() chan Event {
func (s *simscreen) StopQ() <-chan struct{} { func (s *simscreen) StopQ() <-chan struct{} {
return s.quit return s.quit
} }
func (s *simscreen) SetTitle(title string) {
s.title = title
}
func (s *simscreen) GetTitle() string {
return s.title
}
func (s *simscreen) SetClipboard(data []byte) {
s.clipboard = data
}
func (s *simscreen) GetClipboard() {
if s.clipboard != nil {
ev := NewEventClipboard(s.clipboard)
s.postEvent(ev)
}
}
func (s *simscreen) GetClipboardData() []byte {
return s.clipboard
}
+77 -61
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -23,11 +23,13 @@ package tcell
// //
// To use Style, just declare a variable of its type. // To use Style, just declare a variable of its type.
type Style struct { type Style struct {
fg Color fg Color
bg Color bg Color
attrs AttrMask ulStyle UnderlineStyle
url string ulColor Color
urlId string attrs AttrMask
url string
urlId string
} }
// StyleDefault represents a default style, based upon the context. // StyleDefault represents a default style, based upon the context.
@@ -40,50 +42,35 @@ var styleInvalid = Style{attrs: AttrInvalid}
// Foreground returns a new style based on s, with the foreground color set // Foreground returns a new style based on s, with the foreground color set
// as requested. ColorDefault can be used to select the global default. // as requested. ColorDefault can be used to select the global default.
func (s Style) Foreground(c Color) Style { func (s Style) Foreground(c Color) Style {
return Style{ s2 := s
fg: c, s2.fg = c
bg: s.bg, return s2
attrs: s.attrs,
url: s.url,
urlId: s.urlId,
}
} }
// Background returns a new style based on s, with the background color set // Background returns a new style based on s, with the background color set
// as requested. ColorDefault can be used to select the global default. // as requested. ColorDefault can be used to select the global default.
func (s Style) Background(c Color) Style { func (s Style) Background(c Color) Style {
return Style{ s2 := s
fg: s.fg, s2.bg = c
bg: c, return s2
attrs: s.attrs,
url: s.url,
urlId: s.urlId,
}
} }
// Decompose breaks a style up, returning the foreground, background, // Decompose breaks a style up, returning the foreground, background,
// and other attributes. The URL if set is not included. // and other attributes. The URL if set is not included.
// Deprecated: Applications should not attempt to decompose style,
// as this content is not sufficient to describe the actual style.
func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) { func (s Style) Decompose() (fg Color, bg Color, attr AttrMask) {
return s.fg, s.bg, s.attrs return s.fg, s.bg, s.attrs
} }
func (s Style) setAttrs(attrs AttrMask, on bool) Style { func (s Style) setAttrs(attrs AttrMask, on bool) Style {
s2 := s
if on { if on {
return Style{ s2.attrs |= attrs
fg: s.fg, } else {
bg: s.bg, s2.attrs &^= attrs
attrs: s.attrs | attrs,
url: s.url,
urlId: s.urlId,
}
}
return Style{
fg: s.fg,
bg: s.bg,
attrs: s.attrs &^ attrs,
url: s.url,
urlId: s.urlId,
} }
return s2
} }
// Normal returns the style with all attributes disabled. // Normal returns the style with all attributes disabled.
@@ -125,40 +112,73 @@ func (s Style) Reverse(on bool) Style {
return s.setAttrs(AttrReverse, on) return s.setAttrs(AttrReverse, on)
} }
// Underline returns a new style based on s, with the underline attribute set
// as requested.
func (s Style) Underline(on bool) Style {
return s.setAttrs(AttrUnderline, on)
}
// StrikeThrough sets strikethrough mode. // StrikeThrough sets strikethrough mode.
func (s Style) StrikeThrough(on bool) Style { func (s Style) StrikeThrough(on bool) Style {
return s.setAttrs(AttrStrikeThrough, on) return s.setAttrs(AttrStrikeThrough, on)
} }
// Underline style. Modern terminals have the option of rendering the
// underline using different styles, and even different colors.
type UnderlineStyle int
const (
UnderlineStyleNone = UnderlineStyle(iota)
UnderlineStyleSolid
UnderlineStyleDouble
UnderlineStyleCurly
UnderlineStyleDotted
UnderlineStyleDashed
)
// Underline returns a new style based on s, with the underline attribute set
// as requested. The parameters can be:
//
// bool: on / off - enables just a simple underline
// UnderlineStyle: sets a specific style (should not coexist with the bool)
// Color: the color to use
func (s Style) Underline(params ...interface{}) Style {
s2 := s
for _, param := range params {
switch v := param.(type) {
case bool:
if v {
s2.ulStyle = UnderlineStyleSolid
s2.attrs |= AttrUnderline
} else {
s2.ulStyle = UnderlineStyleNone
s2.attrs &^= AttrUnderline
}
case UnderlineStyle:
if v == UnderlineStyleNone {
s2.attrs &^= AttrUnderline
} else {
s2.attrs |= AttrUnderline
}
s2.ulStyle = v
case Color:
s2.ulColor = v
default:
panic("Bad type for underline")
}
}
return s2
}
// Attributes returns a new style based on s, with its attributes set as // Attributes returns a new style based on s, with its attributes set as
// specified. // specified.
func (s Style) Attributes(attrs AttrMask) Style { func (s Style) Attributes(attrs AttrMask) Style {
return Style{ s2 := s
fg: s.fg, s2.attrs = attrs
bg: s.bg, return s2
attrs: attrs,
url: s.url,
urlId: s.urlId,
}
} }
// Url returns a style with the Url set. If the provided Url is not empty, // Url returns a style with the Url set. If the provided Url is not empty,
// and the terminal supports it, text will typically be marked up as a clickable // and the terminal supports it, text will typically be marked up as a clickable
// link to that Url. If the Url is empty, then this mode is turned off. // link to that Url. If the Url is empty, then this mode is turned off.
func (s Style) Url(url string) Style { func (s Style) Url(url string) Style {
return Style{ s2 := s
fg: s.fg, s2.url = url
bg: s.bg, return s2
attrs: s.attrs,
url: url,
urlId: s.urlId,
}
} }
// UrlId returns a style with the UrlId set. If the provided UrlId is not empty, // UrlId returns a style with the UrlId set. If the provided UrlId is not empty,
@@ -166,11 +186,7 @@ func (s Style) Url(url string) Style {
// terminal supports it, any text with the same UrlId will be grouped as if it // terminal supports it, any text with the same UrlId will be grouped as if it
// were one Url, even if it spans multiple lines. // were one Url, even if it spans multiple lines.
func (s Style) UrlId(id string) Style { func (s Style) UrlId(id string) Style {
return Style{ s2 := s
fg: s.fg, s2.urlId = "id=" + id
bg: s.bg, return s2
attrs: s.attrs,
url: s.url,
urlId: "id=" + id,
}
} }
+5
View File
@@ -67,5 +67,10 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
XTermLike: true,
}) })
} }
+1 -1
View File
@@ -23,7 +23,7 @@ package base
import ( import (
// The following imports just register themselves -- // The following imports just register themselves --
// thse are the terminal types we aggregate in this package. // these are the terminal types we aggregate in this package.
_ "github.com/gdamore/tcell/v2/terminfo/a/ansi" _ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt100" _ "github.com/gdamore/tcell/v2/terminfo/v/vt100"
_ "github.com/gdamore/tcell/v2/terminfo/v/vt102" _ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
+1
View File
@@ -52,5 +52,6 @@ import (
_ "github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi" _ "github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi"
_ "github.com/gdamore/tcell/v2/terminfo/x/xfce" _ "github.com/gdamore/tcell/v2/terminfo/x/xfce"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm" _ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty"
_ "github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty" _ "github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty"
) )
+2
View File
@@ -67,6 +67,7 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// GNOME Terminal with xterm 256-colors // GNOME Terminal with xterm 256-colors
@@ -130,5 +131,6 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
} }
+2
View File
@@ -68,6 +68,7 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// KDE console window with xterm 256-colors // KDE console window with xterm 256-colors
@@ -132,5 +133,6 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
} }
+1
View File
@@ -66,5 +66,6 @@ func init() {
KeyF19: "\x1b[33~", KeyF19: "\x1b[33~",
KeyF20: "\x1b[34~", KeyF20: "\x1b[34~",
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
} }
+2 -1
View File
@@ -14,7 +14,7 @@ pcansi
rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color rxvt,rxvt-256color,rxvt-88color,rxvt-unicode,rxvt-unicode-256color
screen,screen-256color screen,screen-256color
st,st-256color|simpleterm st,st-256color|simpleterm
tmux tmux,tmux-256color
vt52 vt52
vt100 vt100
vt102 vt102
@@ -27,4 +27,5 @@ wy60
wy99-ansi,wy99a-ansi wy99-ansi,wy99a-ansi
xfce xfce
xterm,xterm-88color,xterm-256color xterm,xterm-88color,xterm-256color
xterm-ghostty
xterm-kitty xterm-kitty
+3
View File
@@ -110,6 +110,7 @@ func init() {
KeyCtrlHome: "\x1b[7^", KeyCtrlHome: "\x1b[7^",
KeyCtrlEnd: "\x1b[8^", KeyCtrlEnd: "\x1b[8^",
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// rxvt 2.7.9 with xterm 256-colors // rxvt 2.7.9 with xterm 256-colors
@@ -215,6 +216,7 @@ func init() {
KeyCtrlHome: "\x1b[7^", KeyCtrlHome: "\x1b[7^",
KeyCtrlEnd: "\x1b[8^", KeyCtrlEnd: "\x1b[8^",
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// rxvt 2.7.9 with xterm 88-colors // rxvt 2.7.9 with xterm 88-colors
@@ -320,6 +322,7 @@ func init() {
KeyCtrlHome: "\x1b[7^", KeyCtrlHome: "\x1b[7^",
KeyCtrlEnd: "\x1b[8^", KeyCtrlEnd: "\x1b[8^",
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// rxvt-unicode terminal (X Window System) // rxvt-unicode terminal (X Window System)
+2
View File
@@ -67,6 +67,7 @@ func init() {
KeyClear: "\x1b[3;5~", KeyClear: "\x1b[3;5~",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// simpleterm with 256 colors // simpleterm with 256 colors
@@ -130,5 +131,6 @@ func init() {
KeyClear: "\x1b[3;5~", KeyClear: "\x1b[3;5~",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
} }
+130 -59
View File
@@ -8,64 +8,135 @@ func init() {
// tmux terminal multiplexer // tmux terminal multiplexer
terminfo.AddTerminfo(&terminfo.Terminfo{ terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "tmux", Name: "tmux",
Columns: 80, Columns: 80,
Lines: 24, Lines: 24,
Colors: 8, Colors: 8,
Bell: "\a", Bell: "\a",
Clear: "\x1b[H\x1b[J", Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b[?1049h", EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l", ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[34h\x1b[?25h", ShowCursor: "\x1b[34h\x1b[?25h",
HideCursor: "\x1b[?25l", HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f", AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m", Underline: "\x1b[4m",
Bold: "\x1b[1m", Bold: "\x1b[1m",
Dim: "\x1b[2m", Dim: "\x1b[2m",
Italic: "\x1b[3m", Italic: "\x1b[3m",
Blink: "\x1b[5m", Blink: "\x1b[5m",
Reverse: "\x1b[7m", Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=", EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>", ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm", SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm", SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m", ResetFgBg: "\x1b[39;49m",
PadChar: "\x00", PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e", EnterAcs: "\x0e",
ExitAcs: "\x0f", ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0", EnableAcs: "\x1b(B\x1b)0",
StrikeThrough: "\x1b[9m", StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M", Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH", SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b", CursorBack1: "\b",
CursorUp1: "\x1bM", CursorUp1: "\x1bM",
KeyUp: "\x1bOA", KeyUp: "\x1bOA",
KeyDown: "\x1bOB", KeyDown: "\x1bOB",
KeyRight: "\x1bOC", KeyRight: "\x1bOC",
KeyLeft: "\x1bOD", KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~", KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~", KeyDelete: "\x1b[3~",
KeyBackspace: "\x7f", KeyBackspace: "\x7f",
KeyHome: "\x1b[1~", KeyHome: "\x1b[1~",
KeyEnd: "\x1b[4~", KeyEnd: "\x1b[4~",
KeyPgUp: "\x1b[5~", KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~", KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP", KeyF1: "\x1bOP",
KeyF2: "\x1bOQ", KeyF2: "\x1bOQ",
KeyF3: "\x1bOR", KeyF3: "\x1bOR",
KeyF4: "\x1bOS", KeyF4: "\x1bOS",
KeyF5: "\x1b[15~", KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~", KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~", KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~", KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~", KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~", KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~", KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~", KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
})
// tmux with 256 colors
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "tmux-256color",
Columns: 80,
Lines: 24,
Colors: 256,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[34h\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1bM",
KeyUp: "\x1bOA",
KeyDown: "\x1bOB",
KeyRight: "\x1bOC",
KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~",
KeyBackspace: "\x7f",
KeyHome: "\x1b[1~",
KeyEnd: "\x1b[4~",
KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP",
KeyF2: "\x1bOQ",
KeyF3: "\x1bOR",
KeyF4: "\x1bOS",
KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
}) })
} }
+12
View File
@@ -227,13 +227,25 @@ type Terminfo struct {
CursorSteadyUnderline string CursorSteadyUnderline string
CursorBlinkingBar string CursorBlinkingBar string
CursorSteadyBar string CursorSteadyBar string
CursorColor string // nothing uses it yet
CursorColorRGB string // Cs (but not really because Cs uses X11 color string)
CursorColorReset string // Cr
EnterUrl string EnterUrl string
ExitUrl string ExitUrl string
SetWindowSize string SetWindowSize string
SetWindowTitle string // no terminfo extension
EnableFocusReporting string EnableFocusReporting string
DisableFocusReporting string DisableFocusReporting string
DisableAutoMargin string // smam DisableAutoMargin string // smam
EnableAutoMargin string // rmam EnableAutoMargin string // rmam
DoubleUnderline string // Smulx with param 2
CurlyUnderline string // Smulx with param 3
DottedUnderline string // Smulx with param 4
DashedUnderline string // Smulx with param 5
UnderlineColor string // Setuc1
UnderlineColorRGB string // Setulc
UnderlineColorReset string // ol
XTermLike bool // (XT) has XTerm extensions
} }
const ( const (
+1
View File
@@ -65,5 +65,6 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
} }
+3
View File
@@ -68,6 +68,7 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// xterm with 88 colors // xterm with 88 colors
@@ -131,6 +132,7 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
// xterm with 256 colors // xterm with 256 colors
@@ -194,5 +196,6 @@ func init() {
KeyBacktab: "\x1b[Z", KeyBacktab: "\x1b[Z",
Modifiers: 1, Modifiers: 1,
AutoMargin: true, AutoMargin: true,
XTermLike: true,
}) })
} }
+79
View File
@@ -0,0 +1,79 @@
// Generated automatically. DO NOT HAND-EDIT.
package xterm_ghostty
import "github.com/gdamore/tcell/v2/terminfo"
func init() {
// Ghostty
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "xterm-ghostty",
Aliases: []string{"ghostty"},
Columns: 80,
Lines: 24,
Colors: 256,
Bell: "\a",
Clear: "\x1b[H\x1b[2J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[?12l\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b(B\x1b[m",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
ResetFgBg: "\x1b[39;49m",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x1b(0",
ExitAcs: "\x1b(B",
EnableAutoMargin: "\x1b[?7h",
DisableAutoMargin: "\x1b[?7l",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[<",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1b[A",
KeyUp: "\x1bOA",
KeyDown: "\x1bOB",
KeyRight: "\x1bOC",
KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~",
KeyBackspace: "\x7f",
KeyHome: "\x1bOH",
KeyEnd: "\x1bOF",
KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP",
KeyF2: "\x1bOQ",
KeyF3: "\x1bOR",
KeyF4: "\x1bOS",
KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z",
Modifiers: 1,
TrueColor: true,
AutoMargin: true,
InsertChar: "\x1b[@",
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
XTermLike: true,
})
}
+4
View File
@@ -67,5 +67,9 @@ func init() {
Modifiers: 1, Modifiers: 1,
TrueColor: true, TrueColor: true,
AutoMargin: true, AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
}) })
} }
+5
View File
@@ -27,9 +27,14 @@ import (
// will be automatically included anyway. // will be automatically included anyway.
"github.com/gdamore/tcell/v2/terminfo" "github.com/gdamore/tcell/v2/terminfo"
"github.com/gdamore/tcell/v2/terminfo/dynamic" "github.com/gdamore/tcell/v2/terminfo/dynamic"
"fmt"
) )
func loadDynamicTerminfo(term string) (*terminfo.Terminfo, error) { func loadDynamicTerminfo(term string) (*terminfo.Terminfo, error) {
if term == "" {
return nil, fmt.Errorf("%w: term not set", ErrTermNotFound)
}
ti, _, e := dynamic.LoadTerminfo(term) ti, _, e := dynamic.LoadTerminfo(term)
if e != nil { if e != nil {
return nil, e return nil, e
+253 -15
View File
@@ -19,6 +19,7 @@ package tcell
import ( import (
"bytes" "bytes"
"encoding/base64"
"errors" "errors"
"io" "io"
"os" "os"
@@ -32,9 +33,6 @@ import (
"golang.org/x/text/transform" "golang.org/x/text/transform"
"github.com/gdamore/tcell/v2/terminfo" "github.com/gdamore/tcell/v2/terminfo"
// import the stock terminals
_ "github.com/gdamore/tcell/v2/terminfo/base"
) )
// NewTerminfoScreen returns a Screen that uses the stock TTY interface // NewTerminfoScreen returns a Screen that uses the stock TTY interface
@@ -154,8 +152,18 @@ type tScreen struct {
setWinSize string setWinSize string
enableFocus string enableFocus string
disableFocus string disableFocus string
doubleUnder string
curlyUnder string
dottedUnder string
dashedUnder string
underColor string
underRGB string
underFg string
cursorStyles map[CursorStyle]string cursorStyles map[CursorStyle]string
cursorStyle CursorStyle cursorStyle CursorStyle
cursorColor Color
cursorRGB string
cursorFg string
saved *term.State saved *term.State
stopQ chan struct{} stopQ chan struct{}
eventQ chan Event eventQ chan Event
@@ -164,6 +172,11 @@ type tScreen struct {
mouseFlags MouseFlags mouseFlags MouseFlags
pasteEnabled bool pasteEnabled bool
focusEnabled bool focusEnabled bool
setTitle string
saveTitle string
restoreTitle string
title string
setClipboard string
sync.Mutex sync.Mutex
} }
@@ -338,7 +351,7 @@ func (t *tScreen) prepareBracketedPaste() {
t.disablePaste = t.ti.DisablePaste t.disablePaste = t.ti.DisablePaste
t.prepareKey(keyPasteStart, t.ti.PasteStart) t.prepareKey(keyPasteStart, t.ti.PasteStart)
t.prepareKey(keyPasteEnd, t.ti.PasteEnd) t.prepareKey(keyPasteEnd, t.ti.PasteEnd)
} else if t.ti.Mouse != "" { } else if t.ti.Mouse != "" || t.ti.XTermLike {
t.enablePaste = "\x1b[?2004h" t.enablePaste = "\x1b[?2004h"
t.disablePaste = "\x1b[?2004l" t.disablePaste = "\x1b[?2004l"
t.prepareKey(keyPasteStart, "\x1b[200~") t.prepareKey(keyPasteStart, "\x1b[200~")
@@ -346,6 +359,54 @@ func (t *tScreen) prepareBracketedPaste() {
} }
} }
func (t *tScreen) prepareUnderlines() {
if t.ti.DoubleUnderline != "" {
t.doubleUnder = t.ti.DoubleUnderline
} else if t.ti.XTermLike {
t.doubleUnder = "\x1b[4:2m"
}
if t.ti.CurlyUnderline != "" {
t.curlyUnder = t.ti.CurlyUnderline
} else if t.ti.XTermLike {
t.curlyUnder = "\x1b[4:3m"
}
if t.ti.DottedUnderline != "" {
t.dottedUnder = t.ti.DottedUnderline
} else if t.ti.XTermLike {
t.dottedUnder = "\x1b[4:4m"
}
if t.ti.DashedUnderline != "" {
t.dashedUnder = t.ti.DashedUnderline
} else if t.ti.XTermLike {
t.dashedUnder = "\x1b[4:5m"
}
// Underline colors. We're not going to rely upon terminfo for this
// Essentially all terminals that support the curly underlines are
// expected to also support coloring them too - which reflects actual
// practice since these were introduced at about the same time.
if t.ti.UnderlineColor != "" {
t.underColor = t.ti.UnderlineColor
} else if t.ti.CurlyUnderline != "" {
t.underColor = "\x1b[58:5:%p1%dm"
}
if t.ti.UnderlineColorRGB != "" {
// An interesting wart here is that in order to facilitate
// using just a single parameter, the Setulc parameter takes
// the 24-bit color as an integer rather than separate bytes.
// This matches the "new" style direct color approach that
// ncurses took, even though everyone else when another way.
t.underRGB = t.ti.UnderlineColorRGB
} else if t.ti.CurlyUnderline != "" {
t.underRGB = "\x1b[58:2::%p1%d:%p2%d:%p3%dm"
}
if t.ti.UnderlineColorReset != "" {
t.underFg = t.ti.UnderlineColorReset
} else if t.ti.CurlyUnderline != "" {
t.underFg = "\x1b[59m"
}
}
func (t *tScreen) prepareExtendedOSC() { func (t *tScreen) prepareExtendedOSC() {
// Linux is a special beast - because it has a mouse entry, but does // Linux is a special beast - because it has a mouse entry, but does
// not swallow these OSC commands properly. // not swallow these OSC commands properly.
@@ -359,27 +420,43 @@ func (t *tScreen) prepareExtendedOSC() {
if t.ti.EnterUrl != "" { if t.ti.EnterUrl != "" {
t.enterUrl = t.ti.EnterUrl t.enterUrl = t.ti.EnterUrl
t.exitUrl = t.ti.ExitUrl t.exitUrl = t.ti.ExitUrl
} else if t.ti.Mouse != "" { } else if t.ti.Mouse != "" || t.ti.XTermLike {
t.enterUrl = "\x1b]8;%p2%s;%p1%s\x1b\\" t.enterUrl = "\x1b]8;%p2%s;%p1%s\x1b\\"
t.exitUrl = "\x1b]8;;\x1b\\" t.exitUrl = "\x1b]8;;\x1b\\"
} }
if t.ti.SetWindowSize != "" { if t.ti.SetWindowSize != "" {
t.setWinSize = t.ti.SetWindowSize t.setWinSize = t.ti.SetWindowSize
} else if t.ti.Mouse != "" { } else if t.ti.Mouse != "" || t.ti.XTermLike {
t.setWinSize = "\x1b[8;%p1%p2%d;%dt" t.setWinSize = "\x1b[8;%p1%p2%d;%dt"
} }
if t.ti.EnableFocusReporting != "" { if t.ti.EnableFocusReporting != "" {
t.enableFocus = t.ti.EnableFocusReporting t.enableFocus = t.ti.EnableFocusReporting
} else if t.ti.Mouse != "" { } else if t.ti.Mouse != "" || t.ti.XTermLike {
t.enableFocus = "\x1b[?1004h" t.enableFocus = "\x1b[?1004h"
} }
if t.ti.DisableFocusReporting != "" { if t.ti.DisableFocusReporting != "" {
t.disableFocus = t.ti.DisableFocusReporting t.disableFocus = t.ti.DisableFocusReporting
} else if t.ti.Mouse != "" { } else if t.ti.Mouse != "" || t.ti.XTermLike {
t.disableFocus = "\x1b[?1004l" t.disableFocus = "\x1b[?1004l"
} }
if t.ti.SetWindowTitle != "" {
t.setTitle = t.ti.SetWindowTitle
} else if t.ti.XTermLike {
t.saveTitle = "\x1b[22;2t"
t.restoreTitle = "\x1b[23;2t"
// this also tries to request that UTF-8 is allowed in the title
t.setTitle = "\x1b[>2t\x1b]2;%p1%s\x1b\\"
}
if t.setClipboard == "" && t.ti.XTermLike {
// this string takes a base64 string and sends it to the clipboard.
// it will also be able to retrieve the clipboard using "?" as the
// sent string, when we support that.
t.setClipboard = "\x1b]52;c;%p1%s\x1b\\"
}
} }
func (t *tScreen) prepareCursorStyles() { func (t *tScreen) prepareCursorStyles() {
@@ -397,7 +474,7 @@ func (t *tScreen) prepareCursorStyles() {
CursorStyleBlinkingBar: t.ti.CursorBlinkingBar, CursorStyleBlinkingBar: t.ti.CursorBlinkingBar,
CursorStyleSteadyBar: t.ti.CursorSteadyBar, CursorStyleSteadyBar: t.ti.CursorSteadyBar,
} }
} else if t.ti.Mouse != "" { } else if t.ti.Mouse != "" || t.ti.XTermLike {
t.cursorStyles = map[CursorStyle]string{ t.cursorStyles = map[CursorStyle]string{
CursorStyleDefault: "\x1b[0 q", CursorStyleDefault: "\x1b[0 q",
CursorStyleBlinkingBlock: "\x1b[1 q", CursorStyleBlinkingBlock: "\x1b[1 q",
@@ -408,6 +485,20 @@ func (t *tScreen) prepareCursorStyles() {
CursorStyleSteadyBar: "\x1b[6 q", CursorStyleSteadyBar: "\x1b[6 q",
} }
} }
if t.ti.CursorColorRGB != "" {
// if it was X11 style with just a single %p1%s, then convert
t.cursorRGB = t.ti.CursorColorRGB
}
if t.ti.CursorColorReset != "" {
t.cursorFg = t.ti.CursorColorReset
}
if t.cursorRGB == "" {
t.cursorRGB = "\x1b]12;%p1%s\007"
t.cursorFg = "\x1b]112\007"
}
// convert XTERM style color names to RGB color code. We have no way to do palette colors
t.cursorRGB = strings.Replace(t.cursorRGB, "%p1%s", "#%p1%02x%p2%02x%p3%02x", 1)
} }
func (t *tScreen) prepareKey(key Key, val string) { func (t *tScreen) prepareKey(key Key, val string) {
@@ -416,6 +507,11 @@ func (t *tScreen) prepareKey(key Key, val string) {
func (t *tScreen) prepareKeys() { func (t *tScreen) prepareKeys() {
ti := t.ti ti := t.ti
if strings.HasPrefix(ti.Name, "xterm") {
// assume its some form of XTerm clone
t.ti.XTermLike = true
ti.XTermLike = true
}
t.prepareKey(KeyBackspace, ti.KeyBackspace) t.prepareKey(KeyBackspace, ti.KeyBackspace)
t.prepareKey(KeyF1, ti.KeyF1) t.prepareKey(KeyF1, ti.KeyF1)
t.prepareKey(KeyF2, ti.KeyF2) t.prepareKey(KeyF2, ti.KeyF2)
@@ -550,6 +646,7 @@ func (t *tScreen) prepareKeys() {
t.prepareXtermModifiers() t.prepareXtermModifiers()
t.prepareBracketedPaste() t.prepareBracketedPaste()
t.prepareCursorStyles() t.prepareCursorStyles()
t.prepareUnderlines()
t.prepareExtendedOSC() t.prepareExtendedOSC()
outer: outer:
@@ -742,7 +839,7 @@ func (t *tScreen) drawCell(x, y int) int {
style = t.style style = t.style
} }
if style != t.curstyle { if style != t.curstyle {
fg, bg, attrs := style.Decompose() fg, bg, attrs := style.fg, style.bg, style.attrs
t.TPuts(ti.AttrOff) t.TPuts(ti.AttrOff)
@@ -750,8 +847,39 @@ func (t *tScreen) drawCell(x, y int) int {
if attrs&AttrBold != 0 { if attrs&AttrBold != 0 {
t.TPuts(ti.Bold) t.TPuts(ti.Bold)
} }
if attrs&AttrUnderline != 0 { if us, uc := style.ulStyle, style.ulColor; us != UnderlineStyleNone {
t.TPuts(ti.Underline) if t.underColor != "" || t.underRGB != "" {
if uc == ColorReset {
t.TPuts(t.underFg)
} else if uc.IsRGB() {
if t.underRGB != "" {
r, g, b := uc.RGB()
t.TPuts(ti.TParm(t.underRGB, int(r), int(g), int(b)))
} else {
if v, ok := t.colors[uc]; ok {
uc = v
} else {
v = FindColor(uc, t.palette)
t.colors[uc] = v
uc = v
}
t.TPuts(ti.TParm(t.underColor, int(uc&0xff)))
}
} else if uc.Valid() {
t.TPuts(ti.TParm(t.underColor, int(uc&0xff)))
}
}
t.TPuts(ti.Underline) // to ensure everyone gets at least a basic underline
switch us {
case UnderlineStyleDouble:
t.TPuts(t.doubleUnder)
case UnderlineStyleCurly:
t.TPuts(t.curlyUnder)
case UnderlineStyleDotted:
t.TPuts(t.dottedUnder)
case UnderlineStyleDashed:
t.TPuts(t.dashedUnder)
}
} }
if attrs&AttrReverse != 0 { if attrs&AttrReverse != 0 {
t.TPuts(ti.Reverse) t.TPuts(ti.Reverse)
@@ -827,9 +955,10 @@ func (t *tScreen) ShowCursor(x, y int) {
t.Unlock() t.Unlock()
} }
func (t *tScreen) SetCursorStyle(cs CursorStyle) { func (t *tScreen) SetCursor(cs CursorStyle, cc Color) {
t.Lock() t.Lock()
t.cursorStyle = cs t.cursorStyle = cs
t.cursorColor = cc
t.Unlock() t.Unlock()
} }
@@ -852,6 +981,14 @@ func (t *tScreen) showCursor() {
t.TPuts(esc) t.TPuts(esc)
} }
} }
if t.cursorRGB != "" {
if t.cursorColor == ColorReset {
t.TPuts(t.cursorFg)
} else if t.cursorColor.Valid() {
r, g, b := t.cursorColor.RGB()
t.TPuts(t.ti.TParm(t.cursorRGB, int(r), int(g), int(b)))
}
}
t.cx = x t.cx = x
t.cy = y t.cy = y
} }
@@ -890,8 +1027,7 @@ func (t *tScreen) Show() {
func (t *tScreen) clearScreen() { func (t *tScreen) clearScreen() {
t.TPuts(t.ti.AttrOff) t.TPuts(t.ti.AttrOff)
t.TPuts(t.exitUrl) t.TPuts(t.exitUrl)
fg, bg, _ := t.style.Decompose() _ = t.sendFgBg(t.style.fg, t.style.bg, AttrNone)
_ = t.sendFgBg(fg, bg, AttrNone)
t.TPuts(t.ti.Clear) t.TPuts(t.ti.Clear)
t.clear = false t.clear = false
} }
@@ -1376,6 +1512,61 @@ func (t *tScreen) parseFocus(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
return true, false return true, false
} }
func (t *tScreen) parseClipboard(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
b := buf.Bytes()
state := 0
prefix := []byte("\x1b]52;c;")
if len(prefix) >= len(b) {
if bytes.HasPrefix(prefix, b) {
// inconclusive so far
return true, false
}
// definitely not a match
return false, false
}
b = b[len(prefix):]
for _, c := range b {
// valid base64 digits
if (state == 0) {
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/') || (c == '=') {
continue
}
if (c == '\x1b') {
state = 1
continue
}
if (c == '\a') {
// matched with BEL instead of ST
b = b[:len(b)-1] // drop the trailing BEL
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(b)))
if num, err := base64.StdEncoding.Decode(decoded, b); err == nil {
*evs = append(*evs, NewEventClipboard(decoded[:num]))
}
_, _ = buf.ReadBytes('\a')
return true, true
}
return false, false
}
if (state == 1) {
if (c == '\\') {
b = b[:len(b)-2] // drop the trailing ST (\x1b\\)
// now decode the data
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(b)))
if num, err := base64.StdEncoding.Decode(decoded, b); err == nil {
*evs = append(*evs, NewEventClipboard(decoded[:num]))
}
_, _ = buf.ReadBytes('\\')
return true, true
}
return false, false
}
}
// not enough data yet (not terminated)
return true, false
}
// parseXtermMouse is like parseSgrMouse, but it parses a legacy // parseXtermMouse is like parseSgrMouse, but it parses a legacy
// X11 mouse record. // X11 mouse record.
func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
@@ -1579,6 +1770,14 @@ func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event
} }
} }
if t.setClipboard != "" {
if part, comp := t.parseClipboard(buf, &res); comp {
continue
} else if part {
partials++
}
}
if partials == 0 || expire { if partials == 0 || expire {
if b[0] == '\x1b' { if b[0] == '\x1b' {
if len(b) == 1 { if len(b) == 1 {
@@ -1829,12 +2028,18 @@ func (t *tScreen) engage() error {
// (In theory there could be terminals that don't support X,Y cursor // (In theory there could be terminals that don't support X,Y cursor
// positions without a setup command, but we don't support them.) // positions without a setup command, but we don't support them.)
t.TPuts(ti.EnterCA) t.TPuts(ti.EnterCA)
if t.saveTitle != "" {
t.TPuts(t.saveTitle)
}
} }
t.TPuts(ti.EnterKeypad) t.TPuts(ti.EnterKeypad)
t.TPuts(ti.HideCursor) t.TPuts(ti.HideCursor)
t.TPuts(ti.EnableAcs) t.TPuts(ti.EnableAcs)
t.TPuts(ti.DisableAutoMargin) t.TPuts(ti.DisableAutoMargin)
t.TPuts(ti.Clear) t.TPuts(ti.Clear)
if t.title != "" && t.setTitle != "" {
t.TPuts(t.ti.TParm(t.setTitle, t.title))
}
t.wg.Add(2) t.wg.Add(2)
go t.inputLoop(stopQ) go t.inputLoop(stopQ)
@@ -1870,11 +2075,17 @@ func (t *tScreen) disengage() {
if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault { if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault {
t.TPuts(t.cursorStyles[CursorStyleDefault]) t.TPuts(t.cursorStyles[CursorStyleDefault])
} }
if t.cursorFg != "" && t.cursorColor.Valid() {
t.TPuts(t.cursorFg)
}
t.TPuts(ti.ResetFgBg) t.TPuts(ti.ResetFgBg)
t.TPuts(ti.AttrOff) t.TPuts(ti.AttrOff)
t.TPuts(ti.ExitKeypad) t.TPuts(ti.ExitKeypad)
t.TPuts(ti.EnableAutoMargin) t.TPuts(ti.EnableAutoMargin)
if os.Getenv("TCELL_ALTSCREEN") != "disable" { if os.Getenv("TCELL_ALTSCREEN") != "disable" {
if t.restoreTitle != "" {
t.TPuts(t.restoreTitle)
}
t.TPuts(ti.Clear) // only needed if ExitCA is empty t.TPuts(ti.Clear) // only needed if ExitCA is empty
t.TPuts(ti.ExitCA) t.TPuts(ti.ExitCA)
} }
@@ -1909,3 +2120,30 @@ func (t *tScreen) EventQ() chan Event {
func (t *tScreen) GetCells() *CellBuffer { func (t *tScreen) GetCells() *CellBuffer {
return &t.cells return &t.cells
} }
func (t *tScreen) SetTitle(title string) {
t.Lock()
t.title = title
if t.setTitle != "" && t.running {
t.TPuts(t.ti.TParm(t.setTitle, title))
}
t.Unlock()
}
func (t *tScreen) SetClipboard(data []byte) {
// Post binary data to the system clipboard. It might be UTF-8, it might not be.
t.Lock()
if t.setClipboard != "" {
encoded := base64.StdEncoding.EncodeToString(data)
t.TPuts(t.ti.TParm(t.setClipboard, encoded))
}
t.Unlock()
}
func (t *tScreen) GetClipboard() {
t.Lock()
if t.setClipboard != "" {
t.TPuts(t.ti.TParm(t.setClipboard, "?"))
}
t.Unlock()
}
+6 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -17,6 +17,11 @@
package tcell package tcell
import (
// import the stock terminals
_ "github.com/gdamore/tcell/v2/terminfo/base"
)
// initialize is used at application startup, and sets up the initial values // initialize is used at application startup, and sets up the initial values
// including file descriptors used for terminals and saving the initial state // including file descriptors used for terminals and saving the initial state
// so that it can be restored when the application terminates. // so that it can be restored when the application terminates.
+29 -8
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The TCell Authors // Copyright 2024 The TCell Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License. // you may not use file except in compliance with the License.
@@ -19,11 +19,13 @@ package tcell
import ( import (
"errors" "errors"
"github.com/gdamore/tcell/v2/terminfo" "fmt"
"strings" "strings"
"sync" "sync"
"syscall/js" "syscall/js"
"unicode/utf8" "unicode/utf8"
"github.com/gdamore/tcell/v2/terminfo"
) )
func NewTerminfoScreen() (Screen, error) { func NewTerminfoScreen() (Screen, error) {
@@ -66,6 +68,9 @@ func (t *wScreen) Init() error {
t.Unlock() t.Unlock()
js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent)) js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent))
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
js.Global().Set("onFocus", js.FuncOf(t.unset))
return nil return nil
} }
@@ -133,14 +138,23 @@ func (t *wScreen) drawCell(x, y int) int {
if bg == -1 { if bg == -1 {
bg = 0x000000 bg = 0x000000
} }
us, uc := style.ulStyle, paletteColor(style.ulColor)
if uc == -1 {
uc = 0x000000
}
var combcarr []interface{} = make([]interface{}, len(combc)) s := ""
for i, c := range combc { if len(combc) > 0 {
combcarr[i] = c b := make([]rune, 0, 1 + len(combc))
b = append(b, mainc)
b = append(b, combc...)
s = string(b)
} else {
s = string(mainc)
} }
t.cells.SetDirty(x, y, false) t.cells.SetDirty(x, y, false)
js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs)) js.Global().Call("drawCell", x, y, s, fg, bg, int(style.attrs), int(us), int(uc))
return width return width
} }
@@ -151,9 +165,12 @@ func (t *wScreen) ShowCursor(x, y int) {
t.Unlock() t.Unlock()
} }
func (t *wScreen) SetCursorStyle(cs CursorStyle) { func (t *wScreen) SetCursor(cs CursorStyle, cc Color) {
if !cc.Valid() {
cc = ColorLightGray
}
t.Lock() t.Lock()
js.Global().Call("setCursorStyle", curStyleClasses[cs]) js.Global().Call("setCursorStyle", curStyleClasses[cs], fmt.Sprintf("#%06x", cc.Hex()))
t.Unlock() t.Unlock()
} }
@@ -511,6 +528,10 @@ func (t *wScreen) StopQ() <-chan struct{} {
return t.quit return t.quit
} }
func (t *wScreen) SetTitle(title string) {
js.Global().Call("setTitle", title)
}
// WebKeyNames maps string names reported from HTML // WebKeyNames maps string names reported from HTML
// (KeyboardEvent.key) to tcell accepted keys. // (KeyboardEvent.key) to tcell accepted keys.
var WebKeyNames = map[string]Key{ var WebKeyNames = map[string]Key{
+3 -4
View File
@@ -37,7 +37,7 @@ github.com/fatih/color
# github.com/gdamore/encoding v1.0.1 # github.com/gdamore/encoding v1.0.1
## explicit; go 1.9 ## explicit; go 1.9
github.com/gdamore/encoding github.com/gdamore/encoding
# github.com/gdamore/tcell/v2 v2.7.4 # github.com/gdamore/tcell/v2 v2.8.0
## explicit; go 1.12 ## explicit; go 1.12
github.com/gdamore/tcell/v2 github.com/gdamore/tcell/v2
github.com/gdamore/tcell/v2/terminfo github.com/gdamore/tcell/v2/terminfo
@@ -75,6 +75,7 @@ github.com/gdamore/tcell/v2/terminfo/w/wy60
github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi
github.com/gdamore/tcell/v2/terminfo/x/xfce github.com/gdamore/tcell/v2/terminfo/x/xfce
github.com/gdamore/tcell/v2/terminfo/x/xterm github.com/gdamore/tcell/v2/terminfo/x/xterm
github.com/gdamore/tcell/v2/terminfo/x/xterm_ghostty
github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
# github.com/go-errors/errors v1.5.1 # github.com/go-errors/errors v1.5.1
## explicit; go 1.14 ## explicit; go 1.14
@@ -105,8 +106,6 @@ github.com/gobwas/glob/syntax/ast
github.com/gobwas/glob/syntax/lexer github.com/gobwas/glob/syntax/lexer
github.com/gobwas/glob/util/runes github.com/gobwas/glob/util/runes
github.com/gobwas/glob/util/strings github.com/gobwas/glob/util/strings
# github.com/google/go-cmp v0.5.6
## explicit; go 1.8
# github.com/gookit/color v1.4.2 # github.com/gookit/color v1.4.2
## explicit; go 1.12 ## explicit; go 1.12
github.com/gookit/color github.com/gookit/color
@@ -172,7 +171,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/index
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame 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/merkletrie/noder
# github.com/jesseduffield/gocui v0.3.1-0.20250107151125-716b1eb82fb4 # github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a
## explicit; go 1.12 ## explicit; go 1.12
github.com/jesseduffield/gocui github.com/jesseduffield/gocui
# github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a # github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a