Merge branch 'main' into main

This commit is contained in:
Luis 2024-08-10 07:29:27 -04:00 committed by GitHub
commit 846a9febc1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
503 changed files with 13655 additions and 9495 deletions

View file

@ -759,7 +759,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/598065?v=4",
"profile": "http://www.terrasoft.gr/",
"contributions": [
"code"
"code",
"design"
]
},
{
@ -1484,7 +1485,9 @@
"avatar_url": "https://avatars.githubusercontent.com/u/10334184?v=4",
"profile": "https://github.com/LNKLEO",
"contributions": [
"code"
"code",
"design",
"doc"
]
},
{
@ -2426,6 +2429,261 @@
"code",
"doc"
]
},
{
"login": "herbygillot",
"name": "Herby Gillot",
"avatar_url": "https://avatars.githubusercontent.com/u/618376?v=4",
"profile": "https://github.com/herbygillot",
"contributions": [
"doc"
]
},
{
"login": "arjan-s",
"name": "arjan-s",
"avatar_url": "https://avatars.githubusercontent.com/u/10400299?v=4",
"profile": "https://github.com/arjan-s",
"contributions": [
"code",
"design",
"doc"
]
},
{
"login": "0323pin",
"name": "pin",
"avatar_url": "https://avatars.githubusercontent.com/u/90570748?v=4",
"profile": "https://github.com/0323pin",
"contributions": [
"code"
]
},
{
"login": "FireIsGood",
"name": "FireIsGood",
"avatar_url": "https://avatars.githubusercontent.com/u/109556932?v=4",
"profile": "http://fireis.dev",
"contributions": [
"doc",
"code"
]
},
{
"login": "Joxtacy",
"name": "Jesper Hasselquist",
"avatar_url": "https://avatars.githubusercontent.com/u/10127673?v=4",
"profile": "https://github.com/Joxtacy",
"contributions": [
"code",
"design",
"doc"
]
},
{
"login": "aaronpowell",
"name": "Aaron Powell",
"avatar_url": "https://avatars.githubusercontent.com/u/434140?v=4",
"profile": "https://www.aaron-powell.com",
"contributions": [
"code",
"design",
"doc"
]
},
{
"login": "Dartypier",
"name": "Jacopo Zecchi",
"avatar_url": "https://avatars.githubusercontent.com/u/22201626?v=4",
"profile": "https://github.com/Dartypier",
"contributions": [
"doc"
]
},
{
"login": "rose-m",
"name": "Michael Rose",
"avatar_url": "https://avatars.githubusercontent.com/u/4354632?v=4",
"profile": "https://github.com/rose-m",
"contributions": [
"code",
"doc"
]
},
{
"login": "denehoffman",
"name": "Nathaniel D. Hoffman",
"avatar_url": "https://avatars.githubusercontent.com/u/36977879?v=4",
"profile": "http://denehoffman.com",
"contributions": [
"code",
"doc"
]
},
{
"login": "michaelschwobe",
"name": "Michael Schwobe",
"avatar_url": "https://avatars.githubusercontent.com/u/926242?v=4",
"profile": "https://schwobe.dev",
"contributions": [
"code",
"design",
"doc"
]
},
{
"login": "Nibodhika",
"name": "Nibodhika",
"avatar_url": "https://avatars.githubusercontent.com/u/729967?v=4",
"profile": "https://github.com/Nibodhika",
"contributions": [
"code",
"doc"
]
},
{
"login": "sassdawe",
"name": "David Sass",
"avatar_url": "https://avatars.githubusercontent.com/u/10754765?v=4",
"profile": "http://davidsass.io",
"contributions": [
"doc"
]
},
{
"login": "carehart",
"name": "Charlie Arehart",
"avatar_url": "https://avatars.githubusercontent.com/u/389746?v=4",
"profile": "http://www.carehart.org",
"contributions": [
"doc"
]
},
{
"login": "aramikuto",
"name": "Aleksandr Kondrashov",
"avatar_url": "https://avatars.githubusercontent.com/u/116561995?v=4",
"profile": "https://github.com/aramikuto",
"contributions": [
"doc"
]
},
{
"login": "kimsey0",
"name": "Jacob Bundgaard",
"avatar_url": "https://avatars.githubusercontent.com/u/984760?v=4",
"profile": "https://jacobbundgaard.dk",
"contributions": [
"doc"
]
},
{
"login": "ThisaruGuruge",
"name": "Thisaru Guruge",
"avatar_url": "https://avatars.githubusercontent.com/u/40016057?v=4",
"profile": "https://thisaru.me",
"contributions": [
"doc"
]
},
{
"login": "edwin-shdw",
"name": "Edwin",
"avatar_url": "https://avatars.githubusercontent.com/u/62764562?v=4",
"profile": "https://github.com/edwin-shdw",
"contributions": [
"doc"
]
},
{
"login": "jcdickinson",
"name": "Jonathan Dickinson",
"avatar_url": "https://avatars.githubusercontent.com/u/522465?v=4",
"profile": "https://dickinson.id",
"contributions": [
"doc"
]
},
{
"login": "po1o",
"name": "Polo-François Poli",
"avatar_url": "https://avatars.githubusercontent.com/u/5702825?v=4",
"profile": "https://github.com/po1o",
"contributions": [
"code"
]
},
{
"login": "EDIflyer",
"name": "EDIflyer",
"avatar_url": "https://avatars.githubusercontent.com/u/13610277?v=4",
"profile": "https://github.com/EDIflyer",
"contributions": [
"code",
"doc"
]
},
{
"login": "felipebz",
"name": "Felipe Zorzo",
"avatar_url": "https://avatars.githubusercontent.com/u/13829?v=4",
"profile": "https://felipezorzo.com.br",
"contributions": [
"code",
"design",
"doc"
]
},
{
"login": "DeepSpace2",
"name": "Adi Vaknin",
"avatar_url": "https://avatars.githubusercontent.com/u/6841988?v=4",
"profile": "https://github.com/DeepSpace2",
"contributions": [
"code",
"doc"
]
},
{
"login": "EladLeev",
"name": "Elad Leev",
"avatar_url": "https://avatars.githubusercontent.com/u/835319?v=4",
"profile": "https://leevs.dev/",
"contributions": [
"code",
"doc"
]
},
{
"login": "Soyvolon",
"name": "Bounds",
"avatar_url": "https://avatars.githubusercontent.com/u/16871668?v=4",
"profile": "https://github.com/Soyvolon",
"contributions": [
"code",
"doc"
]
},
{
"login": "Yash-Garg",
"name": "Yash Garg",
"avatar_url": "https://avatars.githubusercontent.com/u/33605526?v=4",
"profile": "http://yashgarg.dev",
"contributions": [
"code",
"design",
"doc"
]
},
{
"login": "sarpuser",
"name": "Sarp User",
"avatar_url": "https://avatars.githubusercontent.com/u/23362324?v=4",
"profile": "https://github.com/sarpuser",
"contributions": [
"code",
"doc"
]
}
],
"contributorsPerLine": 7,

View file

@ -52,9 +52,6 @@ RUN pwsh -Command Install-Module posh-git -Scope AllUsers -Force; \
# add the oh-my-posh path to the PATH variable
ENV PATH "$PATH:/home/${USERNAME}/bin"
# Can be used to override the devcontainer prompt default theme:
ENV POSH_THEME="/workspaces/oh-my-posh/themes/jandedobbeleer.omp.json"
# Deploy oh-my-posh prompt to Powershell:
COPY Microsoft.PowerShell_profile.ps1 /home/${USERNAME}/.config/powershell/Microsoft.PowerShell_profile.ps1
@ -67,10 +64,10 @@ RUN chmod 777 -R /home/${USERNAME}/.config
# Override vscode's own Bash prompt with oh-my-posh:
RUN sed -i 's/^__bash_prompt$/#&/' /home/${USERNAME}/.bashrc && \
echo "eval \"\$(oh-my-posh init bash --config $POSH_THEME)\"" >> /home/${USERNAME}/.bashrc
echo "eval \"\$(oh-my-posh init bash)\"" >> /home/${USERNAME}/.bashrc
# Override vscode's own ZSH prompt with oh-my-posh:
RUN echo "eval \"\$(oh-my-posh init zsh --config $POSH_THEME)\"" >> /home/${USERNAME}/.zshrc
RUN echo "eval \"\$(oh-my-posh init zsh)\"" >> /home/${USERNAME}/.zshrc
# Set container timezone:
ARG TZ="UTC"

View file

@ -6,6 +6,4 @@ Import-Module Terminal-Icons
Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete
$env:POSH_GIT_ENABLED=$true
oh-my-posh init pwsh --config $env:POSH_THEME | Invoke-Expression
# NOTE: You can override the above env var from the devcontainer.json "args" under the "build" key.
oh-my-posh init pwsh | Invoke-Expression

View file

@ -1,4 +1,2 @@
# Activate oh-my-posh prompt:
oh-my-posh init fish --config $POSH_THEME | source
# NOTE: You can override the above env vars from the devcontainer.json "args" under the "build" key.
oh-my-posh init fish | source

View file

@ -8,10 +8,7 @@
// Update the VARIANT arg to pick a version of Go: 1, 1.16, 1.17
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"VARIANT": "1-1.22.3-bullseye",
// Options:
"POSH_THEME": "/workspaces/oh-my-posh/themes/jandedobbeleer.omp.json",
"VARIANT": "1-1.22-bullseye",
// Override me with your own timezone:
"TZ": "UTC",
@ -23,7 +20,16 @@
"PS_VERSION": "7.2.7"
}
},
"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
"runArgs": [
"--cap-add=SYS_PTRACE",
"--security-opt",
"seccomp=unconfined",
"--security-opt",
"label=disable"
],
"containerEnv": {
"HOME": "/home/vscode"
},
"customizations": {
"vscode": {
@ -54,6 +60,7 @@
"terminal.integrated.defaultProfile.linux": "pwsh",
"terminal.integrated.defaultProfile.windows": "pwsh",
"terminal.integrated.defaultProfile.osx": "pwsh",
"terminal.integrated.shellIntegration.enabled": false,
"tasks.statusbar.default.hide": true
},
"extensions": [

View file

@ -1,4 +1,4 @@
name: Bug Report
name: 🐛 Bug Report
description: File a bug report
labels: ["🐛 bug"]
assignees:

27
.github/ISSUE_TEMPLATE/docs.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: 📖 Documentation
description: Suggest a change to the documentation
labels: ["📖 docs"]
assignees:
- jandedobbeleer
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to request this improvement!
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true
- type: textarea
id: enhancement-request
attributes:
label: What would you like to see changed/added?
description: Try to give some examples or text to make it really clear!
placeholder: Tell us what you would like to see!
value: "This could change in the documentation!"
validations:
required: true

27
.github/ISSUE_TEMPLATE/enhancement.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: 🤩 Enhancement
description: Suggest a change to an existing feature
labels: ["🤩 enhancement"]
assignees:
- jandedobbeleer
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to request this improvement!
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true
- type: textarea
id: enhancement-request
attributes:
label: What would you like to see changed?
description: Try to give some examples to make it really clear!
placeholder: Tell us what you would like to see!
value: "This feature would benefit from this!"
validations:
required: true

View file

@ -1,4 +1,4 @@
name: Feature Request
name: 🚀 Feature Request
description: Request a new feature
labels: ["🚀 feat"]
assignees:
@ -19,8 +19,8 @@ body:
- type: textarea
id: feature-request
attributes:
label: What would you like to see changed/added?
description: Try to give some examples to make it really clear!
label: What would you like to see added?
description: Try to give some examples to make it really clear.
placeholder: Tell us what you would like to see!
value: "Something new and amazing!"
validations:

View file

@ -12,7 +12,7 @@
Tips:
If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
we're working a guide (https://ohmyposh.dev/docs/contributing/git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.
-->

View file

@ -9,7 +9,7 @@ jobs:
container: ghcr.io/jandedobbeleer/golang-android-container:latest
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Build
run: |
VERSION=$(echo "${{ github.event.release.name }}" | cut -c2-)

View file

@ -17,18 +17,18 @@ jobs:
shell: pwsh
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Install Go 🗳
uses: ./.github/workflows/composite/bootstrap-go
- name: Run GoReleaser 🚀
uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200
with:
distribution: goreleaser
version: latest
args: build --clean --snapshot --skip=post-hooks --skip=before
workdir: src
- name: Archive production artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a
with:
name: builds
retention-days: 1

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Check and close 🔐
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:

View file

@ -20,14 +20,14 @@ jobs:
working-directory: ${{ github.workspace }}/src
steps:
- name: Install Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32
with:
go-version: 1.21
cache-dependency-path: src/go.sum
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Golang CI
uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86
with:
working-directory: src
- name: Unit Tests

View file

@ -20,17 +20,17 @@ jobs:
working-directory: ${{ github.workspace }}/src
steps:
- name: Install Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32
with:
go-version: 1.21
cache-dependency-path: src/go.sum
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Initialize CodeQL
uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276
uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@9fdb3e49720b44c48891d036bb502feb25684276
uses: github/codeql-action/autobuild@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276
uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa

View file

@ -10,3 +10,4 @@ runs:
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
with:
go-version: '1.22.3'
cache-dependency-path: src/go.sum

View file

@ -18,16 +18,16 @@ jobs:
runs-on: ubuntu-latest
name: Build and Deploy
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
with:
submodules: true
persist-credentials: false
- name: Install Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32
with:
go-version: 1.21
cache-dependency-path: src/go.sum
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b
with:
node-version: 20.9.0
# Create Kind cluster to have a Kubernetes context for cloud-native-azure theme
@ -44,7 +44,7 @@ jobs:
- name: Set default Kubernetes namespace
run: |
kubectl config set-context posh --namespace demo
- uses: azure/login@6b2456866fc08b011acb422a92a4aa20e2c4de32
- uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build oh-my-posh 🔧

View file

@ -10,12 +10,12 @@ jobs:
working-directory: ${{ github.workspace }}/src
steps:
- name: Install Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32
with:
go-version: 1.21
cache-dependency-path: src/go.sum
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Check for unused dependencies
run: |
go mod tidy

View file

@ -15,7 +15,7 @@ jobs:
working-directory: ${{ github.workspace }}/packages/inno
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Build installer 📦
id: build
env:

View file

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Lint files
uses: articulate/actions-markdownlint@17b8abe7407cd17590c006ecc837c35e1ac3ed83
with:

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Check and merge ⛙
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:

View file

@ -19,7 +19,7 @@ jobs:
skipped: ${{ steps.changelog.outputs.skipped }}
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Create changelog ✍️
id: changelog
uses: TriPSs/conventional-changelog-action@3a392e9aa44a72686b0fc13259a90d287dd0877c
@ -32,16 +32,14 @@ jobs:
artifacts:
needs: changelog
if: ${{ needs.changelog.outputs.skipped == 'false' }}
runs-on: ubuntu-latest
env:
COSIGN_KEY_LOCATION: "/tmp/cosign.key"
runs-on: windows-latest
defaults:
run:
shell: pwsh
working-directory: ${{ github.workspace }}/src
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Install Go 🗳
uses: ./.github/workflows/composite/bootstrap-go
- name: Tag HEAD 😸
@ -49,25 +47,40 @@ jobs:
git config --global user.name "GitHub Actions"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag ${{ needs.changelog.outputs.tag }}
- name: Install cosign 🔑
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20
with:
cosign-release: 'v1.4.0'
- name: Private Key 🔐
- name: Prerequisites 🔐
run: |
$PSDefaultParameterValues['Out-File:Encoding']='UTF8'
$env:COSIGN_KEY > $env:COSIGN_KEY_LOCATION
$shaSigningKeyLocation = Join-Path -Path $env:RUNNER_TEMP -ChildPath sha_signing_key.pem
$env:SIGNING_KEY > $shaSigningKeyLocation
Write-Output "SHA_SIGNING_KEY_LOCATION=$shaSigningKeyLocation" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
# create a base64 encoded value of your certificate using
# [convert]::ToBase64String((Get-Content -path "certificate.pfx" -AsByteStream))
$pfxPath = Join-Path -Path $env:RUNNER_TEMP -ChildPath "code_signing_cert.pfx"
$encodedBytes = [System.Convert]::FromBase64String($env:SIGNING_CERTIFICATE)
Set-Content -Path $pfxPath -Value $encodedBytes -AsByteStream
Write-Output "SIGNING_CERTIFICATE_LOCATION=$pfxPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
# requires Windows Dev Kit 10.0.22621.0
$signtool = 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x86/signtool.exe'
Write-Output "SIGNTOOL=$signtool" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
# openssl
$openssl = 'C:/Program Files/Git/usr/bin/openssl.exe'
Write-Output "OPENSSL=$openssl" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
env:
COSIGN_KEY: ${{secrets.COSIGN_KEY}}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_CERTIFICATE: ${{ secrets.CERTIFICATE }}
- name: Run GoReleaser 🚀
uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200
with:
distribution: goreleaser
version: latest
args: build --clean
args: release --clean --skip publish
workdir: src
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
- name: Zip theme files 🤐
run: |
$compress = @{
@ -83,9 +96,8 @@ jobs:
$zipHash = Get-FileHash $_.FullName -Algorithm SHA256
$zipHash.Hash | Out-File -Encoding 'UTF8' "./dist/$($_.Name).sha256"
}
shell: pwsh
- name: Release 🎓
uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
with:
tag_name: ${{ needs.changelog.outputs.tag }}
body: ${{ needs.changelog.outputs.body }}
@ -94,3 +106,4 @@ jobs:
files: |
src/dist/posh-*
src/dist/themes.*
src/dist/checksums.*

View file

@ -18,7 +18,7 @@ jobs:
working-directory: ${{ github.workspace }}/packages/scoop
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Update Template ✍️
run: |
./build.ps1 -Version ${{ github.event.inputs.version }}

View file

@ -17,7 +17,7 @@ jobs:
WINGETCREATE_TOKEN: ${{ secrets.WINGETCREATE_TOKEN }}
steps:
- name: Checkout code 👋
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Create manifest and submit PR 📦
run: |
./build.ps1 -Version "${{ github.event.inputs.version }}" -Token $env:WINGETCREATE_TOKEN

1
.gitignore vendored
View file

@ -2,6 +2,7 @@
.fleet/
src/test/umbraco/obj/
src/keys
# Created by https://www.toptal.com/developers/gitignore/api/node,go,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=node,go,visualstudiocode

26
.vscode/launch.json vendored
View file

@ -145,6 +145,27 @@
"git"
]
},
{
"name": "Upgrade",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/src",
"args": [
"upgrade"
]
},
{
"name": "Font install",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/src",
"args": [
"font",
"install"
]
},
{
"type": "node",
"request": "launch",
@ -172,13 +193,14 @@
"envFile": "${workspaceFolder}/website/.env"
},
{
"name": "Cache",
"name": "Cache clear",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/src",
"args": [
"cache"
"cache",
"clear"
]
}
]

View file

@ -32,7 +32,6 @@ Fish and PowerShell, the latter of which is the default.
1. Open the `.devcontainer/devcontainer.json` file and in the "*build*" section modify:
- `TZ`: with [your own timezone][timezones]
- `DEFAULT_POSH_THEME`: with [your preferred theme][themes]
2. Summon the Command Panel (Ctrl+Shift+P) and select `Codespaces: Rebuild Container`
to rebuild your devcontainer. (This should take just a few seconds.)
@ -59,4 +58,3 @@ if you do `Codespaces: Rebuild Container` again, you'll be back to the latest st
[codespaces-link]: https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=175405157
[devcontainer-ext]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
[timezones]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
[themes]: https://ohmyposh.dev/docs/themes

View file

@ -0,0 +1,34 @@
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
properties:
resources:
- resource: Microsoft.WinGet.DSC/WinGetPackage
directives:
description: Install Visual Studio Code
allowPrerelease: false
settings:
id: Microsoft.VisualStudioCode
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: golang
directives:
description: Install Golang
allowPrerelease: false
settings:
id: GoLang.Go
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
dependsOn: [golang]
directives:
description: Install golangci-lint
allowPrerelease: false
settings:
id: GolangCI.golangci-lint
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
directives:
description: Install NodeJS
allowPrerelease: false
settings:
id: OpenJS.NodeJS
source: winget
configurationVersion: 0.2.0

View file

@ -1,4 +0,0 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnLQ9sj71/ignxPXEa072vezEZf5D
X1fO3KuoFCtzYTLc/c3rwVGtIdzc02qUoXABysJ0Ok8lsmWvwKvC7yWblg==
-----END PUBLIC KEY-----

View file

@ -10,10 +10,10 @@ Param
# Get signing certificate
$pfxPath = Join-Path -Path $env:RUNNER_TEMP -ChildPath "cert.pfx"
$signtool = 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe'
$signtool = 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x86/signtool.exe'
# create a base64 encoded value of your certificate using
# [convert]::ToBase64String((Get-Content -path "certificate.pfx" -AsByteStream))
# requires Windows Dev Kit 10.0.22000.0
# requires Windows Dev Kit 10.0.22621.0
$encodedBytes = [System.Convert]::FromBase64String($env:CERTIFICATE)
Set-Content -Path $pfxPath -Value $encodedBytes -AsByteStream

View file

@ -17,7 +17,7 @@ SignedUninstaller=yes
CloseApplications=no
[Files]
Source: "bin\oh-my-posh.exe"; DestDir: "{app}\bin"; Flags: sign
Source: "bin\oh-my-posh.exe"; DestDir: "{app}\bin"
Source: "bin\themes\*"; DestDir: "{app}\themes"
[Registry]

View file

@ -1,5 +1,6 @@
# Make sure to check the documentation at https://goreleaser.com
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
before:
hooks:
- go mod tidy
@ -38,12 +39,18 @@ builds:
goarch: arm
hooks:
post:
- sh -c "cosign sign-blob --key $COSIGN_KEY_LOCATION {{ .Path }} > dist/{{ .Name }}.sig"
- pwsh -c "if ('{{ .Path }}'.EndsWith('.exe')) { & '{{ .Env.SIGNTOOL }}' sign /f '{{ .Env.SIGNING_CERTIFICATE_LOCATION }}' /p '{{ .Env.SIGNING_CERTIFICATE_PASSWORD }}' /fd SHA256 /t http://timestamp.digicert.com '{{ .Path }}' }"
archives:
- id: oh-my-posh
format: binary
name_template: "posh-{{ .Os }}-{{ .Arch }}"
checksum:
name_template: 'checksums.txt'
signs:
- cmd: pwsh
args:
- "-c"
- "& '{{ .Env.OPENSSL }}' pkeyutl -sign -inkey '{{ .Env.SHA_SIGNING_KEY_LOCATION }}' -out '${artifact}.sig' -rawin -in '${artifact}'"
artifacts: checksum
changelog:
skip: true
disable: true

View file

@ -1,673 +0,0 @@
package ansi
import (
"fmt"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/mattn/go-runewidth"
)
func init() {
runewidth.DefaultCondition.EastAsianWidth = false
}
var (
knownStyles = []*style{
{AnchorStart: `<b>`, AnchorEnd: `</b>`, Start: "\x1b[1m", End: "\x1b[22m"},
{AnchorStart: `<u>`, AnchorEnd: `</u>`, Start: "\x1b[4m", End: "\x1b[24m"},
{AnchorStart: `<o>`, AnchorEnd: `</o>`, Start: "\x1b[53m", End: "\x1b[55m"},
{AnchorStart: `<i>`, AnchorEnd: `</i>`, Start: "\x1b[3m", End: "\x1b[23m"},
{AnchorStart: `<s>`, AnchorEnd: `</s>`, Start: "\x1b[9m", End: "\x1b[29m"},
{AnchorStart: `<d>`, AnchorEnd: `</d>`, Start: "\x1b[2m", End: "\x1b[22m"},
{AnchorStart: `<f>`, AnchorEnd: `</f>`, Start: "\x1b[5m", End: "\x1b[25m"},
{AnchorStart: `<r>`, AnchorEnd: `</r>`, Start: "\x1b[7m", End: "\x1b[27m"},
}
resetStyle = &style{AnchorStart: "RESET", AnchorEnd: `</>`, End: "\x1b[0m"}
backgroundStyle = &style{AnchorStart: "BACKGROUND", AnchorEnd: `</>`, End: "\x1b[49m"}
)
type style struct {
AnchorStart string
AnchorEnd string
Start string
End string
}
type Colors struct {
Background string `json:"background" toml:"background"`
Foreground string `json:"foreground" toml:"foreground"`
}
const (
// Transparent implies a transparent color
Transparent = "transparent"
// Accent is the OS accent color
Accent = "accent"
// ParentBackground takes the previous segment's background color
ParentBackground = "parentBackground"
// ParentForeground takes the previous segment's color
ParentForeground = "parentForeground"
// Background takes the current segment's background color
Background = "background"
// Foreground takes the current segment's foreground color
Foreground = "foreground"
AnchorRegex = `^(?P<ANCHOR><(?P<FG>[^,<>]+)?,?(?P<BG>[^<>]+)?>)`
colorise = "\x1b[%sm"
transparent = "\x1b[0m\x1b[%s;49m\x1b[7m"
transparentEnd = "\x1b[27m"
backgroundEnd = "\x1b[49m"
AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
OSC99 = "osc99"
OSC7 = "osc7"
OSC51 = "osc51"
ANCHOR = "ANCHOR"
BG = "BG"
FG = "FG"
hyperLinkStart = "<LINK>"
hyperLinkEnd = "</LINK>"
hyperLinkText = "<TEXT>"
hyperLinkTextEnd = "</TEXT>"
)
// Writer writes colorized ANSI strings
type Writer struct {
TerminalBackground string
Colors *Colors
ParentColors []*Colors
AnsiColors ColorString
Plain bool
TrueColor bool
builder strings.Builder
length int
foreground Color
background Color
current ColorHistory
runes []rune
transparent bool
invisible bool
hyperlink bool
shell string
format string
left string
title string
linechange string
clearBelow string
clearLine string
saveCursorPosition string
restoreCursorPosition string
escapeLeft string
escapeRight string
osc99 string
osc7 string
osc51 string
hyperlinkStart string
hyperlinkCenter string
hyperlinkEnd string
iTermPromptMark string
iTermCurrentDir string
iTermRemoteHost string
}
func (w *Writer) Init(shellName string) {
w.shell = shellName
w.format = "%s"
switch w.shell {
case shell.BASH:
w.format = "\\[%s\\]"
w.linechange = "\\[\x1b[%d%s\\]"
w.left = "\\[\x1b[%dD\\]"
w.clearBelow = "\\[\x1b[0J\\]"
w.clearLine = "\\[\x1b[K\\]"
w.saveCursorPosition = "\\[\x1b7\\]"
w.restoreCursorPosition = "\\[\x1b8\\]"
w.title = "\\[\x1b]0;%s\007\\]"
w.escapeLeft = "\\["
w.escapeRight = "\\]"
w.hyperlinkStart = "\\[\x1b]8;;"
w.hyperlinkCenter = "\x1b\\\\\\]"
w.hyperlinkEnd = "\\[\x1b]8;;\x1b\\\\\\]"
w.osc99 = "\\[\x1b]9;9;%s\x1b\\\\\\]"
w.osc7 = "\\[\x1b]7;file://%s/%s\x1b\\\\\\]"
w.osc51 = "\\[\x1b]51;A;%s@%s:%s\x1b\\\\\\]"
w.iTermPromptMark = "\\[$(iterm2_prompt_mark)\\]"
w.iTermCurrentDir = "\\[\x1b]1337;CurrentDir=%s\x07\\]"
w.iTermRemoteHost = "\\[\x1b]1337;RemoteHost=%s@%s\x07\\]"
case shell.ZSH, shell.TCSH:
w.format = "%%{%s%%}"
w.linechange = "%%{\x1b[%d%s%%}"
w.left = "%%{\x1b[%dD%%}"
w.clearBelow = "%{\x1b[0J%}"
w.clearLine = "%{\x1b[K%}"
w.saveCursorPosition = "%{\x1b7%}"
w.restoreCursorPosition = "%{\x1b8%}"
w.title = "%%{\x1b]0;%s\007%%}"
w.escapeLeft = "%{"
w.escapeRight = "%}"
w.hyperlinkStart = "%{\x1b]8;;"
w.hyperlinkCenter = "\x1b\\%}"
w.hyperlinkEnd = "%{\x1b]8;;\x1b\\%}"
w.osc99 = "%%{\x1b]9;9;%s\x1b\\%%}"
w.osc7 = "%%{\x1b]7;file://%s/%s\x1b\\%%}"
w.osc51 = "%%{\x1b]51;A%s@%s:%s\x1b\\%%}"
w.iTermPromptMark = "%{$(iterm2_prompt_mark)%}"
w.iTermCurrentDir = "%%{\x1b]1337;CurrentDir=%s\x07%%}"
w.iTermRemoteHost = "%%{\x1b]1337;RemoteHost=%s@%s\x07%%}"
default:
w.linechange = "\x1b[%d%s"
w.left = "\x1b[%dD"
w.clearBelow = "\x1b[0J"
w.clearLine = "\x1b[K"
w.saveCursorPosition = "\x1b7"
w.restoreCursorPosition = "\x1b8"
w.title = "\x1b]0;%s\007"
// when in fish on Linux, it seems hyperlinks ending with \\ print a \
// unlike on macOS. However, this is a fish bug, so do not try to fix it here:
// https://github.com/JanDeDobbeleer/oh-my-posh/pull/3288#issuecomment-1369137068
w.hyperlinkStart = "\x1b]8;;"
w.hyperlinkCenter = "\x1b\\"
w.hyperlinkEnd = "\x1b]8;;\x1b\\"
w.osc99 = "\x1b]9;9;%s\x1b\\"
w.osc7 = "\x1b]7;file://%s/%s\x1b\\"
w.osc51 = "\x1b]51;A%s@%s:%s\x1b\\"
w.iTermCurrentDir = "\x1b]1337;CurrentDir=%s\x07"
w.iTermRemoteHost = "\x1b]1337;RemoteHost=%s@%s\x07"
}
}
func (w *Writer) SetColors(background, foreground string) {
w.Colors = &Colors{
Background: background,
Foreground: foreground,
}
}
func (w *Writer) SetParentColors(background, foreground string) {
if w.ParentColors == nil {
w.ParentColors = make([]*Colors, 0)
}
w.ParentColors = append([]*Colors{{
Background: background,
Foreground: foreground,
}}, w.ParentColors...)
}
func (w *Writer) ChangeLine(numberOfLines int) string {
if w.Plain {
return ""
}
position := "B"
if numberOfLines < 0 {
position = "F"
numberOfLines = -numberOfLines
}
return fmt.Sprintf(w.linechange, numberOfLines, position)
}
func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string {
if w.Plain {
return ""
}
if strings.HasSuffix(pwd, ":") {
pwd += "\\"
}
switch pwdType {
case OSC7:
return fmt.Sprintf(w.osc7, hostName, pwd)
case OSC51:
return fmt.Sprintf(w.osc51, userName, hostName, pwd)
case OSC99:
fallthrough
default:
return fmt.Sprintf(w.osc99, pwd)
}
}
func (w *Writer) ClearAfter() string {
if w.Plain {
return ""
}
return w.clearLine + w.clearBelow
}
func (w *Writer) FormatTitle(title string) string {
title = w.trimAnsi(title)
if w.Plain {
return title
}
// we have to do this to prevent bash/zsh from misidentifying escape sequences
switch w.shell {
case shell.BASH:
title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title)
case shell.ZSH:
title = strings.NewReplacer("`", "\\`", `%`, `%%`).Replace(title)
case shell.ELVISH, shell.XONSH:
// these shells don't support setting the title
return ""
}
return fmt.Sprintf(w.title, title)
}
func (w *Writer) FormatText(text string) string {
return fmt.Sprintf(w.format, text)
}
func (w *Writer) SaveCursorPosition() string {
return w.saveCursorPosition
}
func (w *Writer) RestoreCursorPosition() string {
return w.restoreCursorPosition
}
func (w *Writer) PromptStart() string {
return fmt.Sprintf(w.format, "\x1b]133;A\007")
}
func (w *Writer) CommandStart() string {
return fmt.Sprintf(w.format, "\x1b]133;B\007")
}
func (w *Writer) CommandFinished(code int, ignore bool) string {
if ignore {
return fmt.Sprintf(w.format, "\x1b]133;D\007")
}
mark := fmt.Sprintf("\x1b]133;D;%d\007", code)
return fmt.Sprintf(w.format, mark)
}
func (w *Writer) LineBreak() string {
cr := fmt.Sprintf(w.left, 1000)
lf := fmt.Sprintf(w.linechange, 1, "B")
return cr + lf
}
func (w *Writer) Write(background, foreground, text string) {
if len(text) == 0 {
return
}
w.background, w.foreground = w.asAnsiColors(background, foreground)
// default to white foreground
if w.foreground.IsEmpty() {
w.foreground = w.AnsiColors.ToColor("white", false, w.TrueColor)
}
// validate if we start with a color override
match := regex.FindNamedRegexMatch(AnchorRegex, text)
if len(match) != 0 && match[ANCHOR] != hyperLinkStart {
colorOverride := true
for _, style := range knownStyles {
if match[ANCHOR] != style.AnchorStart {
continue
}
w.writeEscapedAnsiString(style.Start)
colorOverride = false
}
if colorOverride {
w.current.Add(w.asAnsiColors(match[BG], match[FG]))
}
}
w.writeSegmentColors()
// print the hyperlink part AFTER the coloring
if match[ANCHOR] == hyperLinkStart {
w.hyperlink = true
w.builder.WriteString(w.hyperlinkStart)
}
text = text[len(match[ANCHOR]):]
w.runes = []rune(text)
hyperlinkTextPosition := 0
for i := 0; i < len(w.runes); i++ {
s := w.runes[i]
// ignore everything which isn't overriding
if s != '<' {
w.write(s)
continue
}
// color/end overrides first
text = string(w.runes[i:])
match = regex.FindNamedRegexMatch(AnchorRegex, text)
if len(match) > 0 {
// check for hyperlinks first
switch match[ANCHOR] {
case hyperLinkStart:
w.hyperlink = true
i += len([]rune(match[ANCHOR])) - 1
w.builder.WriteString(w.hyperlinkStart)
continue
case hyperLinkText:
w.hyperlink = false
i += len([]rune(match[ANCHOR])) - 1
hyperlinkTextPosition = i
w.builder.WriteString(w.hyperlinkCenter)
continue
case hyperLinkTextEnd:
// this implies there's no text in the hyperlink
if hyperlinkTextPosition+1 == i {
w.builder.WriteString("link")
w.length += 4
}
i += len([]rune(match[ANCHOR])) - 1
continue
case hyperLinkEnd:
i += len([]rune(match[ANCHOR])) - 1
w.builder.WriteString(w.hyperlinkEnd)
continue
}
i = w.writeArchorOverride(match, background, i)
continue
}
w.length += runewidth.RuneWidth(s)
w.write(s)
}
// reset colors
w.writeEscapedAnsiString(resetStyle.End)
// pop last color from the stack
w.current.Pop()
}
func (w *Writer) String() (string, int) {
defer func() {
w.length = 0
w.builder.Reset()
}()
return w.builder.String(), w.length
}
func (w *Writer) writeEscapedAnsiString(text string) {
if w.Plain {
return
}
if len(w.format) != 0 {
text = fmt.Sprintf(w.format, text)
}
w.builder.WriteString(text)
}
func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) Color {
return w.AnsiColors.ToColor(colorString, isBackground, w.TrueColor)
}
func (w *Writer) write(s rune) {
if w.invisible {
return
}
if !w.hyperlink {
w.length += runewidth.RuneWidth(s)
}
w.builder.WriteRune(s)
}
func (w *Writer) writeSegmentColors() {
// use correct starting colors
bg := w.background
fg := w.foreground
if !w.current.Background().IsEmpty() {
bg = w.current.Background()
}
if !w.current.Foreground().IsEmpty() {
fg = w.current.Foreground()
}
// ignore processing fully tranparent colors
w.invisible = fg.IsTransparent() && bg.IsTransparent()
if w.invisible {
return
}
if fg.IsTransparent() && len(w.TerminalBackground) != 0 { //nolint: gocritic
background := w.getAnsiFromColorString(w.TerminalBackground, false)
w.writeEscapedAnsiString(fmt.Sprintf(colorise, background))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground()))
} else if fg.IsTransparent() && !bg.IsEmpty() {
w.transparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparent, bg))
} else {
if !bg.IsEmpty() && !bg.IsTransparent() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg))
}
if !fg.IsEmpty() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, fg))
}
}
// set current colors
w.current.Add(bg, fg)
}
func (w *Writer) writeArchorOverride(match map[string]string, background string, i int) int {
position := i
// check color reset first
if match[ANCHOR] == resetStyle.AnchorEnd {
return w.endColorOverride(position)
}
position += len([]rune(match[ANCHOR])) - 1
for _, style := range knownStyles {
if style.AnchorEnd == match[ANCHOR] {
w.writeEscapedAnsiString(style.End)
return position
}
if style.AnchorStart == match[ANCHOR] {
w.writeEscapedAnsiString(style.Start)
return position
}
}
if match[FG] == Transparent && len(match[BG]) == 0 {
match[BG] = background
}
bg, fg := w.asAnsiColors(match[BG], match[FG])
// ignore processing fully tranparent colors
w.invisible = fg.IsTransparent() && bg.IsTransparent()
if w.invisible {
return position
}
// make sure we have colors
if fg.IsEmpty() {
fg = w.foreground
}
if bg.IsEmpty() {
bg = w.background
}
w.current.Add(bg, fg)
if w.current.Foreground().IsTransparent() && len(w.TerminalBackground) != 0 {
background := w.getAnsiFromColorString(w.TerminalBackground, false)
w.writeEscapedAnsiString(fmt.Sprintf(colorise, background))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.current.Background().ToForeground()))
return position
}
if w.current.Foreground().IsTransparent() && !w.current.Background().IsTransparent() {
w.transparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparent, w.current.Background()))
return position
}
if w.current.Background() != w.background {
// end the colors in case we have a transparent background
if w.current.Background().IsTransparent() {
w.writeEscapedAnsiString(backgroundEnd)
} else {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.current.Background()))
}
}
if w.current.Foreground() != w.foreground {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.current.Foreground()))
}
return position
}
func (w *Writer) endColorOverride(position int) int {
// make sure to reset the colors if needed
position += len([]rune(resetStyle.AnchorEnd)) - 1
// do not restore colors at the end of the string, we print it anyways
if position == len(w.runes)-1 {
w.current.Pop()
return position
}
// reset colors to previous when we have more than 1 in stack
// as soon as we have more than 1, we can pop the last one
// and print the previous override as it wasn't ended yet
if w.current.Len() > 1 {
fg := w.current.Foreground()
bg := w.current.Background()
w.current.Pop()
previousBg := w.current.Background()
previousFg := w.current.Foreground()
if w.transparent {
w.writeEscapedAnsiString(transparentEnd)
}
if previousBg != bg {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, previousBg))
}
if previousFg != fg {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, previousFg))
}
return position
}
// pop the last colors from the stack
defer w.current.Pop()
// do not reset when colors are identical
if w.current.Background() == w.background && w.current.Foreground() == w.foreground {
return position
}
if w.transparent {
w.writeEscapedAnsiString(transparentEnd)
}
if w.background.IsClear() {
w.writeEscapedAnsiString(backgroundStyle.End)
}
if w.current.Background() != w.background && !w.background.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.background))
}
if (w.current.Foreground() != w.foreground || w.transparent) && !w.foreground.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.foreground))
}
w.transparent = false
return position
}
func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) {
if len(background) == 0 {
background = Background
}
if len(foreground) == 0 {
foreground = Foreground
}
background = w.expandKeyword(background)
foreground = w.expandKeyword(foreground)
inverted := foreground == Transparent && len(background) != 0
backgroundAnsi := w.getAnsiFromColorString(background, !inverted)
foregroundAnsi := w.getAnsiFromColorString(foreground, false)
return backgroundAnsi, foregroundAnsi
}
func (w *Writer) isKeyword(color string) bool {
switch color {
case Transparent, ParentBackground, ParentForeground, Background, Foreground:
return true
default:
return false
}
}
func (w *Writer) expandKeyword(keyword string) string {
resolveParentColor := func(keyword string) string {
for _, color := range w.ParentColors {
if color == nil {
return Transparent
}
switch keyword {
case ParentBackground:
keyword = color.Background
case ParentForeground:
keyword = color.Foreground
default:
if len(keyword) == 0 {
return Transparent
}
return keyword
}
}
if len(keyword) == 0 {
return Transparent
}
return keyword
}
resolveKeyword := func(keyword string) string {
switch {
case keyword == Background && w.Colors != nil:
return w.Colors.Background
case keyword == Foreground && w.Colors != nil:
return w.Colors.Foreground
case (keyword == ParentBackground || keyword == ParentForeground) && w.ParentColors != nil:
return resolveParentColor(keyword)
default:
return Transparent
}
}
for ok := w.isKeyword(keyword); ok; ok = w.isKeyword(keyword) {
resolved := resolveKeyword(keyword)
if resolved == keyword {
break
}
keyword = resolved
}
return keyword
}
func (w *Writer) trimAnsi(text string) string {
if len(text) == 0 || !strings.Contains(text, "\x1b") {
return text
}
return regex.ReplaceAllString(AnsiRegex, text, "")
}

View file

@ -1,60 +0,0 @@
package ansi
import (
"errors"
"testing"
"github.com/alecthomas/assert"
"github.com/jandedobbeleer/oh-my-posh/src/mock"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
)
func TestGetAnsiFromColorString(t *testing.T) {
cases := []struct {
Case string
Expected Color
Color string
Background bool
Color256 bool
}{
{Case: "256 color", Expected: Color("38;5;99"), Color: "99", Background: false},
{Case: "256 color", Expected: Color("38;5;122"), Color: "122", Background: false},
{Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: true},
{Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: false},
{Case: "Hex foreground", Expected: Color("38;2;170;187;204"), Color: "#AABBCC", Background: false},
{Case: "Hex backgrond", Expected: Color("48;2;170;187;204"), Color: "#AABBCC", Background: true},
{Case: "Base 8 foreground", Expected: Color("31"), Color: "red", Background: false},
{Case: "Base 8 background", Expected: Color("41"), Color: "red", Background: true},
{Case: "Base 16 foreground", Expected: Color("91"), Color: "lightRed", Background: false},
{Case: "Base 16 backround", Expected: Color("101"), Color: "lightRed", Background: true},
{Case: "Non true color TERM", Expected: Color("38;5;146"), Color: "#AABBCC", Color256: true},
}
for _, tc := range cases {
ansiColors := &DefaultColors{}
ansiColor := ansiColors.ToColor(tc.Color, tc.Background, !tc.Color256)
assert.Equal(t, tc.Expected, ansiColor, tc.Case)
}
}
func TestMakeColors(t *testing.T) {
env := &mock.MockedEnvironment{}
env.On("Flags").Return(&platform.Flags{
TrueColor: true,
})
env.On("WindowsRegistryKeyValue", `HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM\ColorizationColor`).Return(&platform.WindowsRegistryValue{}, errors.New("err"))
colors := MakeColors(nil, false, "", env)
assert.IsType(t, &DefaultColors{}, colors)
colors = MakeColors(nil, true, "", env)
assert.IsType(t, &CachedColors{}, colors)
assert.IsType(t, &DefaultColors{}, colors.(*CachedColors).ansiColors)
colors = MakeColors(testPalette, false, "", env)
assert.IsType(t, &PaletteColors{}, colors)
assert.IsType(t, &DefaultColors{}, colors.(*PaletteColors).ansiColors)
colors = MakeColors(testPalette, true, "", env)
assert.IsType(t, &CachedColors{}, colors)
assert.IsType(t, &PaletteColors{}, colors.(*CachedColors).ansiColors)
assert.IsType(t, &DefaultColors{}, colors.(*CachedColors).ansiColors.(*PaletteColors).ansiColors)
}

View file

@ -1,19 +0,0 @@
//go:build !windows
package ansi
import "github.com/jandedobbeleer/oh-my-posh/src/platform"
func GetAccentColor(_ platform.Environment) (*RGB, error) {
return nil, &platform.NotImplemented{}
}
func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) {
if len(defaultColor) == 0 {
return
}
d.accent = &Colors{
Foreground: string(d.ToColor(defaultColor, false, env.Flags().TrueColor)),
Background: string(d.ToColor(defaultColor, true, env.Flags().TrueColor)),
}
}

65
src/cache/cache.go vendored Normal file
View file

@ -0,0 +1,65 @@
package cache
import (
"fmt"
"os"
"strconv"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
)
type Cache interface {
Init(home string)
Close()
// Gets the value for a given key.
// Returns the value and a boolean indicating if the key was found.
// In case the ttl expired, the function returns false.
Get(key string) (string, bool)
// Sets a value for a given key.
// The ttl indicates how many minutes to cache the value.
Set(key, value string, ttl int)
// Deletes a key from the cache.
Delete(key string)
}
const (
FileName = "omp.cache"
)
var SessionFileName = fmt.Sprintf("%s.%s", FileName, pid())
func pid() string {
pid := os.Getenv("POSH_PID")
if len(pid) == 0 {
log.Debug("POSH_PID not set, using process pid")
pid = strconv.Itoa(os.Getppid())
}
return pid
}
const (
TEMPLATECACHE = "template_cache"
TOGGLECACHE = "toggle_cache"
PROMPTCOUNTCACHE = "prompt_count_cache"
ENGINECACHE = "engine_cache"
FONTLISTCACHE = "font_list_cache"
ONEDAY = 1440
ONEWEEK = 10080
ONEMONTH = 43200
)
type Entry struct {
Value string `json:"value"`
Timestamp int64 `json:"timestamp"`
TTL int `json:"ttl"`
}
func (c *Entry) Expired() bool {
if c.TTL < 0 {
return false
}
return time.Now().Unix() >= (c.Timestamp + int64(c.TTL)*60)
}

22
src/cache/command.go vendored Normal file
View file

@ -0,0 +1,22 @@
package cache
import (
"github.com/jandedobbeleer/oh-my-posh/src/maps"
)
type Command struct {
Commands *maps.Concurrent
}
func (c *Command) Set(command, path string) {
c.Commands.Set(command, path)
}
func (c *Command) Get(command string) (string, bool) {
cacheCommand, found := c.Commands.Get(command)
if !found {
return "", false
}
command, ok := cacheCommand.(string)
return command, ok
}

88
src/cache/file.go vendored Normal file
View file

@ -0,0 +1,88 @@
package cache
import (
"encoding/json"
"os"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/maps"
)
type File struct {
cache *maps.Concurrent
cacheFilePath string
dirty bool
}
func (fc *File) Init(cacheFilePath string) {
defer log.Trace(time.Now(), cacheFilePath)
fc.cache = maps.NewConcurrent()
fc.cacheFilePath = cacheFilePath
log.Debug("loading cache file:", fc.cacheFilePath)
content, err := os.ReadFile(fc.cacheFilePath)
if err != nil {
// set to dirty so we create it on close
fc.dirty = true
log.Error(err)
return
}
var list map[string]*Entry
if err := json.Unmarshal(content, &list); err != nil {
return
}
for key, co := range list {
if co.Expired() {
continue
}
log.Debug("loading cache key:", key)
fc.cache.Set(key, co)
}
}
func (fc *File) Close() {
if !fc.dirty {
return
}
cache := fc.cache.ToSimple()
if dump, err := json.MarshalIndent(cache, "", " "); err == nil {
_ = os.WriteFile(fc.cacheFilePath, dump, 0644)
}
}
// returns the value for the given key as long as
// the TTL (minutes) is not expired
func (fc *File) Get(key string) (string, bool) {
val, found := fc.cache.Get(key)
if !found {
return "", false
}
if co, ok := val.(*Entry); ok {
return co.Value, true
}
return "", false
}
// sets the value for the given key with a TTL (minutes)
func (fc *File) Set(key, value string, ttl int) {
fc.cache.Set(key, &Entry{
Value: value,
Timestamp: time.Now().Unix(),
TTL: ttl,
})
fc.dirty = true
}
// delete the key from the cache
func (fc *File) Delete(key string) {
fc.cache.Delete(key)
fc.dirty = true
}

View file

@ -2,18 +2,18 @@ package mock
import mock "github.com/stretchr/testify/mock"
// MockedCache is an autogenerated mock type for the cache type
type MockedCache struct {
// Cache is an autogenerated mock type for the cache type
type Cache struct {
mock.Mock
}
// close provides a mock function with given fields:
func (_m *MockedCache) Close() {
func (_m *Cache) Close() {
_m.Called()
}
// get provides a mock function with given fields: key
func (_m *MockedCache) Get(key string) (string, bool) {
func (_m *Cache) Get(key string) (string, bool) {
ret := _m.Called(key)
var r0 string
@ -34,16 +34,16 @@ func (_m *MockedCache) Get(key string) (string, bool) {
}
// init provides a mock function with given fields: home
func (_m *MockedCache) Init(home string) {
func (_m *Cache) Init(home string) {
_m.Called(home)
}
// set provides a mock function with given fields: key, value, ttl
func (_m *MockedCache) Set(key, value string, ttl int) {
func (_m *Cache) Set(key, value string, ttl int) {
_m.Called(key, value, ttl)
}
// delete provides a mock function with given fields: key
func (_m *MockedCache) Delete(key string) {
func (_m *Cache) Delete(key string) {
_m.Called(key)
}

40
src/cache/template.go vendored Normal file
View file

@ -0,0 +1,40 @@
package cache
import (
"sync"
"github.com/jandedobbeleer/oh-my-posh/src/maps"
)
type Template struct {
Root bool
PWD string
AbsolutePWD string
PSWD string
Folder string
Shell string
ShellVersion string
UserName string
HostName string
Code int
Env map[string]string
Var maps.Simple
OS string
WSL bool
PromptCount int
SHLVL int
Jobs int
Segments *maps.Concurrent
SegmentsCache maps.Simple
Initialized bool
sync.RWMutex
}
func (t *Template) AddSegmentData(key string, value any) {
t.Segments.Set(key, value)
}
func (t *Template) RemoveSegmentData(key string) {
t.Segments.Delete(key)
}

View file

@ -7,7 +7,8 @@ import (
"path/filepath"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
@ -34,24 +35,21 @@ You can do the following:
_ = cmd.Help()
return
}
env := &platform.Shell{
CmdFlags: &platform.Flags{},
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{},
}
env.Init()
defer env.Close()
switch args[0] {
case "path":
fmt.Print(env.CachePath())
fmt.Println(env.CachePath())
case "clear":
cacheFilePath := filepath.Join(env.CachePath(), platform.CacheFile)
err := os.Remove(cacheFilePath)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("removed cache file at %s\n", cacheFilePath)
clear(env.CachePath())
case "edit":
cacheFilePath := filepath.Join(env.CachePath(), platform.CacheFile)
cacheFilePath := filepath.Join(env.CachePath(), cache.FileName)
editFileWithEditor(cacheFilePath)
}
},
@ -63,16 +61,42 @@ func init() {
func editFileWithEditor(file string) {
editor := os.Getenv("EDITOR")
var args []string
if strings.Contains(editor, " ") {
splitted := strings.Split(editor, " ")
editor = splitted[0]
args = splitted[1:]
}
args = append(args, file)
cmd := exec.Command(editor, args...)
err := cmd.Run()
if err != nil {
fmt.Println(err.Error())
}
}
func clear(cachePath string) {
// get all files in the cache directory that start with omp.cache and delete them
files, err := os.ReadDir(cachePath)
if err != nil {
return
}
for _, file := range files {
if file.IsDir() {
continue
}
if !strings.HasPrefix(file.Name(), cache.FileName) {
continue
}
path := filepath.Join(cachePath, file.Name())
if err := os.Remove(path); err == nil {
fmt.Println("removed cache file:", path)
}
}
}

View file

@ -7,8 +7,8 @@ import (
"slices"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
@ -36,14 +36,14 @@ Exports the ~/myconfig.omp.json config file and prints the result to stdout.
Exports the ~/myconfig.omp.json config file to toml and prints the result to stdout.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &platform.Shell{
CmdFlags: &platform.Flags{
Config: config,
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
},
}
env.Init()
defer env.Close()
cfg := engine.LoadConfig(env)
cfg := config.Load(env)
if len(output) == 0 && len(format) == 0 {
// usage error
@ -69,11 +69,11 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std
switch format {
case "json", "jsonc":
format = engine.JSON
format = config.JSON
case "toml", "tml":
format = engine.TOML
format = config.TOML
case "yaml", "yml":
format = engine.YAML
format = config.YAML
default:
// data error
os.Exit(65)
@ -83,16 +83,18 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std
},
}
func cleanOutputPath(path string, env platform.Environment) string {
func cleanOutputPath(path string, env runtime.Environment) string {
if strings.HasPrefix(path, "~") {
path = strings.TrimPrefix(path, "~")
path = filepath.Join(env.Home(), path)
}
if !filepath.IsAbs(path) {
if absPath, err := filepath.Abs(path); err == nil {
path = absPath
}
}
return filepath.Clean(path)
}

View file

@ -3,10 +3,12 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/image"
"github.com/jandedobbeleer/oh-my-posh/src/prompt"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
)
@ -47,9 +49,9 @@ Exports the config to an image file ~/mytheme.png.
Exports the config to an image file using customized output options.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &platform.Shell{
CmdFlags: &platform.Flags{
Config: config,
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
Shell: shell.GENERIC,
TerminalWidth: 150,
},
@ -57,7 +59,7 @@ Exports the config to an image file using customized output options.`,
env.Init()
defer env.Close()
cfg := engine.LoadConfig(env)
cfg := config.Load(env)
// set sane defaults for things we don't print
cfg.ConsoleTitleTemplate = ""
@ -67,26 +69,21 @@ Exports the config to an image file using customized output options.`,
// add variables to the environment
env.Var = cfg.Var
writerColors := cfg.MakeColors()
writer := &ansi.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)
eng := &engine.Engine{
terminal.Init(shell.GENERIC)
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate(env)
terminal.Colors = cfg.MakeColors()
eng := &prompt.Engine{
Config: cfg,
Env: env,
Writer: writer,
}
prompt := eng.Primary()
primaryPrompt := eng.Primary()
imageCreator := &engine.ImageRenderer{
AnsiString: prompt,
imageCreator := &image.Renderer{
AnsiString: primaryPrompt,
Author: author,
BgColor: bgColor,
Ansi: writer,
}
if outputImage != "" {

View file

@ -3,8 +3,8 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
@ -39,15 +39,15 @@ Migrates the ~/myconfig.omp.json config file to TOML and writes the result to yo
A backup of the current config can be found at ~/myconfig.omp.json.bak.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &platform.Shell{
CmdFlags: &platform.Flags{
Config: config,
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
Migrate: true,
},
}
env.Init()
defer env.Close()
cfg := engine.LoadConfig(env)
cfg := config.Load(env)
if write {
cfg.BackupAndMigrate()
return

View file

@ -3,8 +3,8 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
@ -34,15 +34,15 @@ Migrates the ~/myconfig.omp.json config file's glyphs and writes the result to y
A backup of the current config can be found at ~/myconfig.omp.json.bak.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &platform.Shell{
CmdFlags: &platform.Flags{
Config: config,
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
},
}
env.Init()
defer env.Close()
cfg := engine.LoadConfig(env)
cfg := config.Load(env)
cfg.MigrateGlyphs = true
if len(format) == 0 {

View file

@ -4,30 +4,37 @@ import (
"fmt"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/prompt"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
)
// debugCmd represents the prompt command
var debugCmd = &cobra.Command{
Use: "debug",
Short: "Print the prompt in debug mode",
Long: "Print the prompt in debug mode.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Use: "debug [bash|zsh|fish|powershell|pwsh|cmd|nu|tcsh|elvish|xonsh]",
Short: "Print the prompt in debug mode",
Long: "Print the prompt in debug mode.",
ValidArgs: supportedShells,
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
startTime := time.Now()
env := &platform.Shell{
CmdFlags: &platform.Flags{
Config: config,
if len(args) == 0 {
_ = cmd.Help()
return
}
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
Debug: true,
PWD: pwd,
Shell: shellName,
Shell: args[0],
Plain: plain,
},
}
@ -35,24 +42,19 @@ var debugCmd = &cobra.Command{
env.Init()
defer env.Close()
cfg := engine.LoadConfig(env)
cfg := config.Load(env)
// add variables to the environment
env.Var = cfg.Var
writerColors := cfg.MakeColors()
writer := &ansi.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
Plain: plain,
TrueColor: env.CmdFlags.TrueColor,
}
terminal.Init(shell.GENERIC)
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate(env)
terminal.Colors = cfg.MakeColors()
terminal.Plain = plain
writer.Init(shell.GENERIC)
eng := &engine.Engine{
eng := &prompt.Engine{
Config: cfg,
Env: env,
Writer: writer,
Plain: plain,
}

View file

@ -3,7 +3,7 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
"github.com/spf13/cobra"
@ -42,8 +42,8 @@ func init() {
}
func toggleFeature(cmd *cobra.Command, feature string, enable bool) {
env := &platform.Shell{
CmdFlags: &platform.Flags{
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Shell: shellName,
},
}

View file

@ -4,14 +4,13 @@ import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/font"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
)
var (
user bool
// fontCmd can work with fonts
fontCmd = &cobra.Command{
Use: "font [install|configure]",
@ -36,25 +35,15 @@ This command is used to install fonts and configure the font in your terminal.
if len(args) > 1 {
fontName = args[1]
}
env := &platform.Shell{}
env := &runtime.Terminal{}
env.Init()
defer env.Close()
// Windows users need to specify the --user flag if they want to install the font as user
// If the user does not specify the --user flag, the font will be installed as a system font
// and therefore we need to be administrator
system := env.Root()
if env.GOOS() == platform.WINDOWS && !user && !system {
fmt.Println(`
You need to be administrator to install a font as system font.
You can either run this command as administrator or specify the --user flag to install the font for your user only:
terminal.Init(env.Shell())
oh-my-posh font install --user
`)
return
}
font.Run(fontName, env)
font.Run(fontName, system)
return
case "configure":
fmt.Println("not implemented")
@ -67,5 +56,4 @@ This command is used to install fonts and configure the font in your terminal.
func init() {
RootCmd.AddCommand(fontCmd)
fontCmd.Flags().BoolVar(&user, "user", false, "install font as user")
}

View file

@ -5,8 +5,9 @@ import (
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
color2 "github.com/gookit/color"
"github.com/spf13/cobra"
@ -44,8 +45,8 @@ This command is used to get the value of the following variables:
return
}
env := &platform.Shell{
CmdFlags: &platform.Flags{
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Shell: shellName,
},
}
@ -56,7 +57,7 @@ This command is used to get the value of the following variables:
case "shell":
fmt.Println(env.Shell())
case "accent":
rgb, err := ansi.GetAccentColor(env)
rgb, err := color.GetAccentColor(env)
if err != nil {
fmt.Println("error getting accent color:", err.Error())
return
@ -64,8 +65,7 @@ This command is used to get the value of the following variables:
accent := color2.RGB(rgb.R, rgb.G, rgb.B)
fmt.Println("#" + accent.Hex())
case "toggles":
cache := env.Cache()
togglesCache, _ := cache.Get(platform.TOGGLECACHE)
togglesCache, _ := env.Session().Get(cache.TOGGLECACHE)
var toggles []string
if len(togglesCache) != 0 {
toggles = strings.Split(togglesCache, ",")

View file

@ -2,12 +2,11 @@ package cli
import (
"fmt"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
"github.com/spf13/cobra"
)
@ -16,6 +15,20 @@ var (
printOutput bool
strict bool
manual bool
debug bool
supportedShells = []string{
"bash",
"zsh",
"fish",
"powershell",
"pwsh",
"cmd",
"nu",
"tcsh",
"elvish",
"xonsh",
}
initCmd = &cobra.Command{
Use: "init [bash|zsh|fish|powershell|pwsh|cmd|nu|tcsh|elvish|xonsh]",
@ -23,19 +36,8 @@ var (
Long: `Initialize your shell and config.
See the documentation to initialize your shell: https://ohmyposh.dev/docs/installation/prompt.`,
ValidArgs: []string{
"bash",
"zsh",
"fish",
"powershell",
"pwsh",
"cmd",
"nu",
"tcsh",
"elvish",
"xonsh",
},
Args: NoArgsOrOneValidArg,
ValidArgs: supportedShells,
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
@ -50,51 +52,40 @@ func init() {
initCmd.Flags().BoolVarP(&printOutput, "print", "p", false, "print the init script")
initCmd.Flags().BoolVarP(&strict, "strict", "s", false, "run in strict mode")
initCmd.Flags().BoolVarP(&manual, "manual", "m", false, "enable/disable manual mode")
initCmd.Flags().BoolVar(&debug, "debug", false, "enable/disable debug mode")
_ = initCmd.MarkPersistentFlagRequired("config")
RootCmd.AddCommand(initCmd)
}
func runInit(shellName string) {
env := &platform.Shell{
CmdFlags: &platform.Flags{
var startTime time.Time
if debug {
startTime = time.Now()
}
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Shell: shellName,
Config: config,
Config: configFlag,
Strict: strict,
Manual: manual,
Debug: debug,
},
}
env.Init()
defer env.Close()
cfg := engine.LoadConfig(env)
cfg := config.Load(env)
shell.Transient = cfg.TransientPrompt != nil
shell.ErrorLine = cfg.ErrorLine != nil || cfg.ValidLine != nil
shell.Tooltips = len(cfg.Tooltips) > 0
shell.ShellIntegration = cfg.ShellIntegration
shell.PromptMark = shellName == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(ansi.PromptMark)
feats := cfg.Features()
for i, block := range cfg.Blocks {
// only fetch cursor position when relevant
if !cfg.DisableCursorPositioning && (i == 0 && block.Newline) {
shell.CursorPositioning = true
}
if block.Type == engine.RPrompt {
shell.RPrompt = true
}
}
// allow overriding the upgrade notice from the config
if cfg.DisableNotice {
env.Cache().Set(upgrade.CACHEKEY, "disabled", -1)
}
if printOutput {
init := shell.PrintInit(env)
if printOutput || debug {
init := shell.PrintInit(env, feats, &startTime)
fmt.Print(init)
return
}
init := shell.Init(env)
init := shell.Init(env, feats)
fmt.Print(init)
}

View file

@ -3,7 +3,7 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
"github.com/spf13/cobra"
)
@ -15,13 +15,13 @@ var noticeCmd = &cobra.Command{
Long: "Print the upgrade notice when a new version is available.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &platform.Shell{
CmdFlags: &platform.Flags{},
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{},
}
env.Init()
defer env.Close()
if notice, hasNotice := upgrade.Notice(env); hasNotice {
if notice, hasNotice := upgrade.Notice(env, false); hasNotice {
fmt.Println(notice)
}
},

View file

@ -3,8 +3,8 @@ package cli
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/engine"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/prompt"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
@ -19,6 +19,8 @@ var (
terminalWidth int
eval bool
cleared bool
cached bool
jobCount int
command string
shellVersion string
@ -49,8 +51,8 @@ var printCmd = &cobra.Command{
return
}
flags := &platform.Flags{
Config: config,
flags := &runtime.Flags{
Config: configFlag,
PWD: pwd,
PSWD: pswd,
ErrorCode: status,
@ -64,30 +66,32 @@ var printCmd = &cobra.Command{
Plain: plain,
Primary: args[0] == "primary",
Cleared: cleared,
Cached: cached,
NoExitCode: noStatus,
Column: column,
JobCount: jobCount,
}
eng := engine.New(flags)
eng := prompt.New(flags)
defer eng.Env.Close()
switch args[0] {
case "debug":
fmt.Print(eng.ExtraPrompt(engine.Debug))
fmt.Print(eng.ExtraPrompt(prompt.Debug))
case "primary":
fmt.Print(eng.Primary())
case "secondary":
fmt.Print(eng.ExtraPrompt(engine.Secondary))
fmt.Print(eng.ExtraPrompt(prompt.Secondary))
case "transient":
fmt.Print(eng.ExtraPrompt(engine.Transient))
fmt.Print(eng.ExtraPrompt(prompt.Transient))
case "right":
fmt.Print(eng.RPrompt())
case "tooltip":
fmt.Print(eng.Tooltip(command))
case "valid":
fmt.Print(eng.ExtraPrompt(engine.Valid))
fmt.Print(eng.ExtraPrompt(prompt.Valid))
case "error":
fmt.Print(eng.ExtraPrompt(engine.Error))
fmt.Print(eng.ExtraPrompt(prompt.Error))
default:
_ = cmd.Help()
}
@ -110,8 +114,12 @@ func init() {
printCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not")
printCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval")
printCmd.Flags().IntVar(&column, "column", 0, "the column position of the cursor")
// Deprecated flags
printCmd.Flags().IntVar(&jobCount, "job-count", 0, "number of background jobs")
// Deprecated flags, keep to not break CLI integration
printCmd.Flags().IntVarP(&status, "error", "e", 0, "last exit code")
printCmd.Flags().BoolVar(&noStatus, "no-exit-code", false, "no valid exit code (cancelled or no command yet)")
printCmd.Flags().BoolVar(&cached, "cached", false, "use a cached prompt")
RootCmd.AddCommand(printCmd)
}

View file

@ -10,7 +10,7 @@ import (
)
var (
config string
configFlag string
displayVersion bool
)
@ -48,7 +48,7 @@ var (
)
func init() {
RootCmd.PersistentFlags().StringVarP(&config, "config", "c", "", "config file path")
RootCmd.PersistentFlags().StringVarP(&configFlag, "config", "c", "", "config file path")
RootCmd.Flags().BoolVarP(&initialize, "init", "i", false, "init (deprecated)")
RootCmd.Flags().BoolVar(&displayVersion, "version", false, "version")
RootCmd.Flags().StringVarP(&shellName, "shell", "s", "", "shell (deprecated)")

View file

@ -3,8 +3,8 @@ package cli
import (
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
@ -19,12 +19,11 @@ var toggleCmd = &cobra.Command{
_ = cmd.Help()
return
}
env := &platform.Shell{}
env := &runtime.Terminal{}
env.Init()
defer env.Close()
cache := env.Cache()
togglesCache, _ := cache.Get(platform.TOGGLECACHE)
togglesCache, _ := env.Session().Get(cache.TOGGLECACHE)
var toggles []string
if len(togglesCache) != 0 {
toggles = strings.Split(togglesCache, ",")
@ -45,7 +44,7 @@ var toggleCmd = &cobra.Command{
newToggles = append(newToggles, segment)
}
cache.Set(platform.TOGGLECACHE, strings.Join(newToggles, ","), 1440)
env.Session().Set(cache.TOGGLECACHE, strings.Join(newToggles, ","), 1440)
},
}

95
src/cli/upgrade.go Normal file
View file

@ -0,0 +1,95 @@
package cli
import (
"fmt"
"os"
stdruntime "runtime"
"slices"
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
"github.com/spf13/cobra"
)
var force bool
// noticeCmd represents the get command
var upgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Upgrade when a new version is available.",
Long: "Upgrade when a new version is available.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
supportedPlatforms := []string{
runtime.WINDOWS,
runtime.DARWIN,
runtime.LINUX,
}
if !slices.Contains(supportedPlatforms, stdruntime.GOOS) {
fmt.Print("\n⚠ upgrade is not supported on this platform\n\n")
return
}
env := &runtime.Terminal{}
env.Init()
defer env.Close()
terminal.Init(env.Shell())
fmt.Print(terminal.StartProgress())
latest, err := upgrade.Latest(env)
if err != nil {
fmt.Printf("\n❌ %s\n\n%s", err, terminal.StopProgress())
os.Exit(1)
return
}
if force {
executeUpgrade(latest)
return
}
cfg := config.Load(env)
version := fmt.Sprintf("v%s", build.Version)
if upgrade.IsMajorUpgrade(version, latest) {
message := terminal.StopProgress()
message += fmt.Sprintf("\n🚨 major upgrade available: %s -> %s, use oh-my-posh upgrade --force to upgrade\n\n", version, latest)
fmt.Print(message)
return
}
if version != latest {
executeUpgrade(latest)
return
}
message := terminal.StopProgress()
if !cfg.DisableNotice {
message += "\n✅ no new version available\n\n"
}
fmt.Print(message)
},
}
func executeUpgrade(latest string) {
err := upgrade.Run(latest)
fmt.Print(terminal.StopProgress())
if err == nil {
return
}
os.Exit(1)
}
func init() {
upgradeCmd.Flags().BoolVarP(&force, "force", "f", false, "force the upgrade even if the version is up to date")
RootCmd.AddCommand(upgradeCmd)
}

View file

@ -1,37 +1,39 @@
package ansi
package color
import (
"fmt"
"strconv"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/gookit/color"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/template"
)
// ColorString is the interface that wraps ToColor method.
var TrueColor = true
// String is the interface that wraps ToColor method.
//
// ToColor gets the ANSI color code for a given color string.
// This can include a valid hex color in the format `#FFFFFF`,
// but also a name of one of the first 16 ANSI colors like `lightBlue`.
type ColorString interface {
ToColor(colorString string, isBackground bool, trueColor bool) Color
type String interface {
ToAnsi(colorString Ansi, isBackground bool) Ansi
}
type ColorSet struct {
Foreground Color
Background Color
type Set struct {
Background Ansi `json:"background" toml:"background"`
Foreground Ansi `json:"foreground" toml:"foreground"`
}
type ColorHistory []*ColorSet
type History []*Set
func (c *ColorHistory) Len() int {
func (c *History) Len() int {
return len(*c)
}
func (c *ColorHistory) Add(background, foreground Color) {
colors := &ColorSet{
func (c *History) Add(background, foreground Ansi) {
colors := &Set{
Foreground: foreground,
Background: background,
}
@ -50,7 +52,7 @@ func (c *ColorHistory) Add(background, foreground Color) {
*c = append(*c, colors)
}
func (c *ColorHistory) Pop() {
func (c *History) Pop() {
if c.Len() == 0 {
return
}
@ -58,7 +60,7 @@ func (c *ColorHistory) Pop() {
*c = (*c)[:c.Len()-1]
}
func (c *ColorHistory) Background() Color {
func (c *History) Background() Ansi {
if c.Len() == 0 {
return emptyColor
}
@ -66,7 +68,7 @@ func (c *ColorHistory) Background() Color {
return (*c)[c.Len()-1].Background
}
func (c *ColorHistory) Foreground() Color {
func (c *History) Foreground() Ansi {
if c.Len() == 0 {
return emptyColor
}
@ -74,44 +76,70 @@ func (c *ColorHistory) Foreground() Color {
return (*c)[c.Len()-1].Foreground
}
// Color is an ANSI color code ready to be printed to the console.
// Ansi is an ANSI color code ready to be printed to the console.
// Example: "38;2;255;255;255", "48;2;255;255;255", "31", "95".
type Color string
type Ansi string
const (
emptyColor = Color("")
transparentColor = Color(Transparent)
emptyColor = Ansi("")
)
func (c Color) IsEmpty() bool {
func (c Ansi) IsEmpty() bool {
return c == emptyColor
}
func (c Color) IsTransparent() bool {
return c == transparentColor
func (c Ansi) IsTransparent() bool {
return c == Transparent
}
func (c Color) IsClear() bool {
return c == transparentColor || c == emptyColor
func (c Ansi) IsClear() bool {
return c == Transparent || c == emptyColor
}
func (c Color) ToForeground() Color {
colorString := string(c)
func (c Ansi) ToForeground() Ansi {
colorString := c.String()
if strings.HasPrefix(colorString, "38;") {
return Color(strings.Replace(colorString, "38;", "48;", 1))
return Ansi(strings.Replace(colorString, "38;", "48;", 1))
}
return c
}
func MakeColors(palette Palette, cacheEnabled bool, accentColor string, env platform.Environment) (colors ColorString) {
defaultColors := &DefaultColors{}
func (c Ansi) ResolveTemplate(env runtime.Environment) Ansi {
if c.IsEmpty() {
return c
}
if c.IsTransparent() {
return emptyColor
}
tmpl := &template.Text{
Template: string(c),
Context: nil,
Env: env,
}
text, err := tmpl.Render()
if err != nil {
return Transparent
}
return Ansi(text)
}
func (c Ansi) String() string {
return string(c)
}
func MakeColors(palette Palette, cacheEnabled bool, accentColor Ansi, env runtime.Environment) (colors String) {
defaultColors := &Defaults{}
defaultColors.SetAccentColor(env, accentColor)
colors = defaultColors
if palette != nil {
colors = &PaletteColors{ansiColors: colors, palette: palette}
}
if cacheEnabled {
colors = &CachedColors{ansiColors: colors}
colors = &Cached{ansiColors: colors}
}
return
}
@ -120,14 +148,14 @@ type RGB struct {
R, G, B uint8
}
// DefaultColors is the default AnsiColors implementation.
type DefaultColors struct {
accent *Colors
// Defaults is the default AnsiColors implementation.
type Defaults struct {
accent *Set
}
var (
// Map for color names and their respective foreground [0] or background [1] color codes
ansiColorCodes = map[string][2]Color{
ansiColorCodes = map[Ansi][2]Ansi{
"black": {"30", "40"},
"red": {"31", "41"},
"green": {"32", "42"},
@ -153,103 +181,121 @@ const (
backgroundIndex = 1
)
func (d *DefaultColors) ToColor(colorString string, isBackground, trueColor bool) Color {
if len(colorString) == 0 {
func (d *Defaults) ToAnsi(ansiColor Ansi, isBackground bool) Ansi {
if len(ansiColor) == 0 {
return emptyColor
}
if colorString == Transparent {
return transparentColor
if ansiColor.IsTransparent() {
return ansiColor
}
if colorString == Accent {
if ansiColor == Accent {
if d.accent == nil {
return emptyColor
}
if isBackground {
return Color(d.accent.Background)
return d.accent.Background
}
return Color(d.accent.Foreground)
return d.accent.Foreground
}
colorFromName, err := getAnsiColorFromName(colorString, isBackground)
colorFromName, err := getAnsiColorFromName(ansiColor, isBackground)
if err == nil {
return colorFromName
}
colorString := ansiColor.String()
if !strings.HasPrefix(colorString, "#") {
val, err := strconv.ParseUint(colorString, 10, 64)
if err != nil || val > 255 {
return emptyColor
}
c256 := color.C256(uint8(val), isBackground)
return Color(c256.String())
return Ansi(c256.String())
}
style := color.HEX(colorString, isBackground)
if !style.IsEmpty() {
if trueColor {
return Color(style.String())
if TrueColor {
return Ansi(style.String())
}
return Color(style.C256().String())
return Ansi(style.C256().String())
}
if colorInt, err := strconv.ParseInt(colorString, 10, 8); err == nil {
c := color.C256(uint8(colorInt), isBackground)
return Color(c.String())
return Ansi(c.String())
}
return emptyColor
}
// getAnsiColorFromName returns the color code for a given color name if the name is
// known ANSI color name.
func getAnsiColorFromName(colorName string, isBackground bool) (Color, error) {
if colorCodes, found := ansiColorCodes[colorName]; found {
func getAnsiColorFromName(colorValue Ansi, isBackground bool) (Ansi, error) {
if colorCodes, found := ansiColorCodes[colorValue]; found {
if isBackground {
return colorCodes[backgroundIndex], nil
}
return colorCodes[foregroundIndex], nil
}
return "", fmt.Errorf("color name %s does not exist", colorName)
return "", fmt.Errorf("color name %s does not exist", colorValue)
}
func IsAnsiColorName(colorString string) bool {
_, ok := ansiColorCodes[colorString]
func IsAnsiColorName(colorValue Ansi) bool {
_, ok := ansiColorCodes[colorValue]
return ok
}
// PaletteColors is the AnsiColors Decorator that uses the Palette to do named color
// lookups before ANSI color code generation.
type PaletteColors struct {
ansiColors ColorString
ansiColors String
palette Palette
}
func (p *PaletteColors) ToColor(colorString string, isBackground, trueColor bool) Color {
func (p *PaletteColors) ToAnsi(colorString Ansi, isBackground bool) Ansi {
paletteColor, err := p.palette.ResolveColor(colorString)
if err != nil {
return emptyColor
}
ansiColor := p.ansiColors.ToColor(paletteColor, isBackground, trueColor)
ansiColor := p.ansiColors.ToAnsi(paletteColor, isBackground)
return ansiColor
}
// CachedColors is the AnsiColors Decorator that does simple color lookup caching.
// Cached is the AnsiColors Decorator that does simple color lookup caching.
// ToColor calls are cheap, but not free, and having a simple cache in
// has measurable positive effect on performance.
type CachedColors struct {
ansiColors ColorString
colorCache map[cachedColorKey]Color
type Cached struct {
ansiColors String
colorCache map[cachedColorKey]Ansi
}
type cachedColorKey struct {
colorString string
colorString Ansi
isBackground bool
}
func (c *CachedColors) ToColor(colorString string, isBackground, trueColor bool) Color {
func (c *Cached) ToAnsi(colorString Ansi, isBackground bool) Ansi {
if c.colorCache == nil {
c.colorCache = make(map[cachedColorKey]Color)
c.colorCache = make(map[cachedColorKey]Ansi)
}
key := cachedColorKey{colorString, isBackground}
if ansiColor, hit := c.colorCache[key]; hit {
return ansiColor
}
ansiColor := c.ansiColors.ToColor(colorString, isBackground, trueColor)
ansiColor := c.ansiColors.ToAnsi(colorString, isBackground)
c.colorCache[key] = ansiColor
return ansiColor
}

89
src/color/colors_test.go Normal file
View file

@ -0,0 +1,89 @@
package color
import (
"errors"
"testing"
"github.com/alecthomas/assert"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
testify_ "github.com/stretchr/testify/mock"
)
func TestGetAnsiFromColorString(t *testing.T) {
cases := []struct {
Case string
Expected Ansi
Color Ansi
Background bool
Color256 bool
}{
{Case: "256 color", Expected: Ansi("38;5;99"), Color: "99", Background: false},
{Case: "256 color", Expected: Ansi("38;5;122"), Color: "122", Background: false},
{Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: true},
{Case: "Invalid background", Expected: emptyColor, Color: "invalid", Background: false},
{Case: "Hex foreground", Expected: Ansi("38;2;170;187;204"), Color: "#AABBCC", Background: false},
{Case: "Hex backgrond", Expected: Ansi("48;2;170;187;204"), Color: "#AABBCC", Background: true},
{Case: "Base 8 foreground", Expected: Ansi("31"), Color: "red", Background: false},
{Case: "Base 8 background", Expected: Ansi("41"), Color: "red", Background: true},
{Case: "Base 16 foreground", Expected: Ansi("91"), Color: "lightRed", Background: false},
{Case: "Base 16 backround", Expected: Ansi("101"), Color: "lightRed", Background: true},
{Case: "Non true color TERM", Expected: Ansi("38;5;146"), Color: "#AABBCC", Color256: true},
}
for _, tc := range cases {
ansiColors := &Defaults{}
TrueColor = !tc.Color256
ansiColor := ansiColors.ToAnsi(tc.Color, tc.Background)
assert.Equal(t, tc.Expected, ansiColor, tc.Case)
}
}
func TestMakeColors(t *testing.T) {
env := &mock.Environment{}
env.On("WindowsRegistryKeyValue", `HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM\ColorizationColor`).Return(&runtime.WindowsRegistryValue{}, errors.New("err"))
colors := MakeColors(nil, false, "", env)
assert.IsType(t, &Defaults{}, colors)
colors = MakeColors(nil, true, "", env)
assert.IsType(t, &Cached{}, colors)
assert.IsType(t, &Defaults{}, colors.(*Cached).ansiColors)
colors = MakeColors(testPalette, false, "", env)
assert.IsType(t, &PaletteColors{}, colors)
assert.IsType(t, &Defaults{}, colors.(*PaletteColors).ansiColors)
colors = MakeColors(testPalette, true, "", env)
assert.IsType(t, &Cached{}, colors)
assert.IsType(t, &PaletteColors{}, colors.(*Cached).ansiColors)
assert.IsType(t, &Defaults{}, colors.(*Cached).ansiColors.(*PaletteColors).ansiColors)
}
func TestAnsiRender(t *testing.T) {
cases := []struct {
Case string
Expected Ansi
Term string
}{
{Case: "Inside vscode", Expected: "#123456", Term: "vscode"},
{Case: "Outside vscode", Expected: "", Term: "windowsterminal"},
}
for _, tc := range cases {
env := new(mock.Environment)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{
Env: map[string]string{
"TERM_PROGRAM": tc.Term,
},
})
env.On("Flags").Return(&runtime.Flags{})
ansi := Ansi("{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
got := ansi.ResolveTemplate(env)
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

20
src/color/colors_unix.go Normal file
View file

@ -0,0 +1,20 @@
//go:build !windows
package color
import "github.com/jandedobbeleer/oh-my-posh/src/runtime"
func GetAccentColor(_ runtime.Environment) (*RGB, error) {
return nil, &runtime.NotImplemented{}
}
func (d *Defaults) SetAccentColor(_ runtime.Environment, defaultColor Ansi) {
if len(defaultColor) == 0 {
return
}
d.accent = &Set{
Foreground: d.ToAnsi(defaultColor, false),
Background: d.ToAnsi(defaultColor, true),
}
}

View file

@ -1,22 +1,23 @@
package ansi
package color
import (
"errors"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/gookit/color"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
)
func GetAccentColor(env platform.Environment) (*RGB, error) {
func GetAccentColor(env runtime.Environment) (*RGB, error) {
if env == nil {
return nil, errors.New("unable to get color without environment")
}
// see https://stackoverflow.com/questions/3560890/vista-7-how-to-get-glass-color
value, err := env.WindowsRegistryKeyValue(`HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM\ColorizationColor`)
if err != nil || value.ValueType != platform.DWORD {
if err != nil || value.ValueType != runtime.DWORD {
return nil, err
}
return &RGB{
R: byte(value.DWord >> 16),
G: byte(value.DWord >> 8),
@ -24,19 +25,22 @@ func GetAccentColor(env platform.Environment) (*RGB, error) {
}, nil
}
func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) {
func (d *Defaults) SetAccentColor(env runtime.Environment, defaultColor Ansi) {
rgb, err := GetAccentColor(env)
if err != nil {
d.accent = &Colors{
Foreground: string(d.ToColor(defaultColor, false, env.Flags().TrueColor)),
Background: string(d.ToColor(defaultColor, true, env.Flags().TrueColor)),
d.accent = &Set{
Foreground: d.ToAnsi(defaultColor, false),
Background: d.ToAnsi(defaultColor, true),
}
return
}
foreground := color.RGB(rgb.R, rgb.G, rgb.B, false)
background := color.RGB(rgb.R, rgb.G, rgb.B, true)
d.accent = &Colors{
Foreground: foreground.String(),
Background: background.String(),
d.accent = &Set{
Foreground: Ansi(foreground.String()),
Background: Ansi(background.String()),
}
}

View file

@ -1,8 +1,8 @@
package ansi
package color
type Cycle []*Colors
type Cycle []*Set
func (c Cycle) Loop() (*Colors, Cycle) {
func (c Cycle) Loop() (*Set, Cycle) {
if len(c) == 0 {
return nil, c
}

77
src/color/keywords.go Normal file
View file

@ -0,0 +1,77 @@
package color
const (
// Transparent implies a transparent color
Transparent Ansi = "transparent"
// Accent is the OS accent color
Accent Ansi = "accent"
// ParentBackground takes the previous segment's background color
ParentBackground Ansi = "parentBackground"
// ParentForeground takes the previous segment's color
ParentForeground Ansi = "parentForeground"
// Background takes the current segment's background color
Background Ansi = "background"
// Foreground takes the current segment's foreground color
Foreground Ansi = "foreground"
)
func (color Ansi) isKeyword() bool {
switch color { //nolint: exhaustive
case Transparent, ParentBackground, ParentForeground, Background, Foreground:
return true
default:
return false
}
}
func (color Ansi) Resolve(current *Set, parents []*Set) Ansi {
resolveParentColor := func(keyword Ansi) Ansi {
for _, parentColor := range parents {
if parentColor == nil {
return Transparent
}
switch keyword { //nolint: exhaustive
case ParentBackground:
keyword = parentColor.Background
case ParentForeground:
keyword = parentColor.Foreground
default:
if len(keyword) == 0 {
return Transparent
}
return keyword
}
}
if len(keyword) == 0 {
return Transparent
}
return keyword
}
resolveKeyword := func(keyword Ansi) Ansi {
switch {
case keyword == Background && current != nil:
return current.Background
case keyword == Foreground && current != nil:
return current.Foreground
case (keyword == ParentBackground || keyword == ParentForeground) && parents != nil:
return resolveParentColor(keyword)
default:
return Transparent
}
}
for color.isKeyword() {
resolved := resolveKeyword(color)
if resolved == color {
break
}
color = resolved
}
return color
}

View file

@ -1,4 +1,4 @@
package ansi
package color
import (
"fmt"
@ -6,7 +6,7 @@ import (
"strings"
)
type Palette map[string]string
type Palette map[Ansi]Ansi
const (
paletteKeyPrefix = "p:"
@ -17,12 +17,12 @@ const (
// ResolveColor gets a color value from the palette using given colorName.
// If colorName is not a palette reference, it is returned as is.
func (p Palette) ResolveColor(colorName string) (string, error) {
func (p Palette) ResolveColor(colorName Ansi) (Ansi, error) {
return p.resolveColor(colorName, 1, &colorName)
}
// originalColorName is a pointer to save allocations
func (p Palette) resolveColor(colorName string, depth int, originalColorName *string) (string, error) {
func (p Palette) resolveColor(colorName Ansi, depth int, originalColorName *Ansi) (Ansi, error) {
key, ok := asPaletteKey(colorName)
// colorName is not a palette key, return it as is
if !ok {
@ -45,31 +45,31 @@ func (p Palette) resolveColor(colorName string, depth int, originalColorName *st
return color, nil
}
func asPaletteKey(colorName string) (string, bool) {
func asPaletteKey(colorName Ansi) (Ansi, bool) {
prefix, isKey := isPaletteKey(colorName)
if !isKey {
return "", false
}
key := strings.TrimPrefix(colorName, prefix)
key := strings.TrimPrefix(colorName.String(), prefix.String())
return key, true
return Ansi(key), true
}
func isPaletteKey(colorName string) (string, bool) {
return paletteKeyPrefix, strings.HasPrefix(colorName, paletteKeyPrefix)
func isPaletteKey(colorName Ansi) (Ansi, bool) {
return paletteKeyPrefix, strings.HasPrefix(colorName.String(), paletteKeyPrefix)
}
// PaletteKeyError records the missing Palette key.
type PaletteKeyError struct {
Key string
Key Ansi
palette Palette
}
func (p *PaletteKeyError) Error() string {
keys := make([]string, 0, len(p.palette))
for key := range p.palette {
keys = append(keys, key)
keys = append(keys, key.String())
}
sort.Strings(keys)
allColors := strings.Join(keys, ",")
@ -80,8 +80,8 @@ func (p *PaletteKeyError) Error() string {
// PaletteRecursiveKeyError records the Palette key and resolved color value (which
// is also a Palette key)
type PaletteRecursiveKeyError struct {
Key string
Value string
Key Ansi
Value Ansi
depth int
}
@ -92,7 +92,7 @@ func (p *PaletteRecursiveKeyError) Error() string {
// maybeResolveColor wraps resolveColor and silences possible errors, returning
// Transparent color by default, as a Block does not know how to handle color errors.
func (p Palette) MaybeResolveColor(colorName string) string {
func (p Palette) MaybeResolveColor(colorName Ansi) Ansi {
color, err := p.ResolveColor(colorName)
if err != nil {
return ""

View file

@ -1,4 +1,4 @@
package ansi
package color
import (
"testing"
@ -18,9 +18,9 @@ var (
type TestPaletteRequest struct {
Case string
Request string
Request Ansi
ExpectedError bool
Expected string
Expected Ansi
}
func TestPaletteShouldResolveColorFromTestPalette(t *testing.T) {
@ -45,7 +45,7 @@ func testPaletteRequest(t *testing.T, tc TestPaletteRequest) {
assert.Equal(t, tc.Expected, actual, "expected different color value")
} else {
assert.NotNil(t, err, tc.Case)
assert.Equal(t, tc.Expected, err.Error())
assert.Equal(t, string(tc.Expected), err.Error())
}
}
@ -186,7 +186,7 @@ func TestPaletteShouldResolveRecursiveReference(t *testing.T) {
assert.Equal(t, tc.Expected, actual, "expected different color value")
} else {
assert.NotNil(t, err, "expected error")
assert.Equal(t, tc.Expected, err.Error())
assert.Equal(t, string(tc.Expected), err.Error())
}
}
}
@ -199,7 +199,7 @@ func TestPaletteShouldHandleEmptyKey(t *testing.T) {
actual, err := tp.ResolveColor("p:")
assert.Nil(t, err, "expected no error")
assert.Equal(t, "#000000", actual, "expected different color value")
assert.Equal(t, Ansi("#000000"), actual, "expected different color value")
}
func BenchmarkPaletteMixedCaseResolution(b *testing.B) {

View file

@ -1,4 +1,4 @@
package ansi
package color
type Palettes struct {
Template string `json:"template,omitempty" toml:"template,omitempty"`

106
src/config/backup.go Normal file
View file

@ -0,0 +1,106 @@
package config
import (
"bytes"
"io"
"os"
"strings"
json "github.com/goccy/go-json"
yaml "github.com/goccy/go-yaml"
toml "github.com/pelletier/go-toml/v2"
)
func (cfg *Config) Backup() {
dst := cfg.origin + ".bak"
source, err := os.Open(cfg.origin)
if err != nil {
return
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return
}
}
func (cfg *Config) Export(format string) string {
if len(format) != 0 {
cfg.Format = format
}
var result bytes.Buffer
switch cfg.Format {
case YAML:
prefix := "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n"
yamlEncoder := yaml.NewEncoder(&result)
err := yamlEncoder.Encode(cfg)
if err != nil {
return ""
}
return prefix + result.String()
case JSON:
jsonEncoder := json.NewEncoder(&result)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", " ")
_ = jsonEncoder.Encode(cfg)
prefix := "{\n \"$schema\": \"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\","
data := strings.Replace(result.String(), "{", prefix, 1)
return escapeGlyphs(data, cfg.MigrateGlyphs)
case TOML:
tomlEncoder := toml.NewEncoder(&result)
tomlEncoder.SetIndentTables(true)
err := tomlEncoder.Encode(cfg)
if err != nil {
return ""
}
return result.String()
}
// unsupported format
return ""
}
func (cfg *Config) BackupAndMigrate() {
cfg.Backup()
cfg.Migrate()
cfg.Write(cfg.Format)
}
func (cfg *Config) Write(format string) {
content := cfg.Export(format)
if len(content) == 0 {
// we are unable to perform the export
os.Exit(65)
return
}
destination := cfg.Output
if len(destination) == 0 {
destination = cfg.origin
}
f, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return
}
defer func() {
_ = f.Close()
}()
_, err = f.WriteString(content)
if err != nil {
return
}
}

108
src/config/block.go Normal file
View file

@ -0,0 +1,108 @@
package config
import (
"sync"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
)
// BlockType type of block
type BlockType string
// BlockAlignment aligment of a Block
type BlockAlignment string
// Overflow defines how to handle a right block that overflows with the previous block
type Overflow string
const (
// Prompt writes one or more Segments
Prompt BlockType = "prompt"
// LineBreak creates a line break in the prompt
LineBreak BlockType = "newline"
// RPrompt is a right aligned prompt
RPrompt BlockType = "rprompt"
// Left aligns left
Left BlockAlignment = "left"
// Right aligns right
Right BlockAlignment = "right"
// Break adds a line break
Break Overflow = "break"
// Hide hides the block
Hide Overflow = "hide"
)
// Block defines a part of the prompt with optional segments
type Block struct {
Type BlockType `json:"type,omitempty" toml:"type,omitempty"`
Alignment BlockAlignment `json:"alignment,omitempty" toml:"alignment,omitempty"`
Segments []*Segment `json:"segments,omitempty" toml:"segments,omitempty"`
Newline bool `json:"newline,omitempty" toml:"newline,omitempty"`
Filler string `json:"filler,omitempty" toml:"filler,omitempty"`
Overflow Overflow `json:"overflow,omitempty" toml:"overflow,omitempty"`
LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"`
TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"`
// Deprecated: keep the logic for legacy purposes
HorizontalOffset int `json:"horizontal_offset,omitempty" toml:"horizontal_offset,omitempty"`
VerticalOffset int `json:"vertical_offset,omitempty" toml:"vertical_offset,omitempty"`
MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"`
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"`
env runtime.Environment
}
func (b *Block) Init(env runtime.Environment) {
b.env = env
b.executeSegmentLogic()
}
func (b *Block) Enabled() bool {
if b.Type == LineBreak {
return true
}
for _, segment := range b.Segments {
if segment.Enabled {
return true
}
}
return false
}
func (b *Block) executeSegmentLogic() {
if shouldHideForWidth(b.env, b.MinWidth, b.MaxWidth) {
return
}
b.setEnabledSegments()
b.setSegmentsText()
}
func (b *Block) setEnabledSegments() {
wg := sync.WaitGroup{}
wg.Add(len(b.Segments))
defer wg.Wait()
for _, segment := range b.Segments {
go func(s *Segment) {
defer wg.Done()
s.SetEnabled(b.env)
}(segment)
}
}
func (b *Block) setSegmentsText() {
wg := sync.WaitGroup{}
wg.Add(len(b.Segments))
defer wg.Wait()
for _, segment := range b.Segments {
go func(s *Segment) {
defer wg.Done()
s.setText()
}(segment)
}
}

View file

@ -1,4 +1,4 @@
package engine
package config
import (
"testing"

138
src/config/config.go Normal file
View file

@ -0,0 +1,138 @@
package config
import (
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
)
const (
JSON string = "json"
YAML string = "yaml"
TOML string = "toml"
Version = 2
)
// Config holds all the theme for rendering the prompt
type Config struct {
Version int `json:"version" toml:"version"`
FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"`
ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty"`
TerminalBackground color.Ansi `json:"terminal_background,omitempty" toml:"terminal_background,omitempty"`
AccentColor color.Ansi `json:"accent_color,omitempty" toml:"accent_color,omitempty"`
Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty"`
Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty"`
TransientPrompt *Segment `json:"transient_prompt,omitempty" toml:"transient_prompt,omitempty"`
ValidLine *Segment `json:"valid_line,omitempty" toml:"valid_line,omitempty"`
ErrorLine *Segment `json:"error_line,omitempty" toml:"error_line,omitempty"`
SecondaryPrompt *Segment `json:"secondary_prompt,omitempty" toml:"secondary_prompt,omitempty"`
DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty"`
Palette color.Palette `json:"palette,omitempty" toml:"palette,omitempty"`
Palettes *color.Palettes `json:"palettes,omitempty" toml:"palettes,omitempty"`
Cycle color.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty"`
ShellIntegration bool `json:"shell_integration,omitempty" toml:"shell_integration,omitempty"`
PWD string `json:"pwd,omitempty" toml:"pwd,omitempty"`
Var map[string]any `json:"var,omitempty" toml:"var,omitempty"`
EnableCursorPositioning bool `json:"enable_cursor_positioning,omitempty" toml:"enable_cursor_positioning,omitempty"`
PatchPwshBleed bool `json:"patch_pwsh_bleed,omitempty" toml:"patch_pwsh_bleed,omitempty"`
DisableNotice bool `json:"disable_notice,omitempty" toml:"disable_notice,omitempty"`
AutoUpgrade bool `json:"auto_upgrade,omitempty" toml:"auto_upgrade,omitempty"`
ITermFeatures terminal.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty"`
// Deprecated
OSC99 bool `json:"osc99,omitempty" toml:"osc99,omitempty"`
Output string `json:"-" toml:"-"`
MigrateGlyphs bool `json:"-" toml:"-"`
Format string `json:"-" toml:"-"`
origin string
// eval bool
updated bool
env runtime.Environment
}
func (cfg *Config) MakeColors() color.String {
cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1"
return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env)
}
func (cfg *Config) getPalette() color.Palette {
if cfg.Palettes == nil {
return cfg.Palette
}
tmpl := &template.Text{
Template: cfg.Palettes.Template,
Env: cfg.env,
}
if palette, err := tmpl.Render(); err == nil {
if p, ok := cfg.Palettes.List[palette]; ok {
return p
}
}
return cfg.Palette
}
func (cfg *Config) Features() shell.Features {
var feats shell.Features
if cfg.TransientPrompt != nil {
feats = append(feats, shell.Transient)
}
if cfg.ShellIntegration {
feats = append(feats, shell.FTCSMarks)
}
if !cfg.AutoUpgrade && !cfg.DisableNotice {
feats = append(feats, shell.Notice)
}
if cfg.AutoUpgrade {
feats = append(feats, shell.Upgrade)
}
if cfg.ErrorLine != nil || cfg.ValidLine != nil {
feats = append(feats, shell.LineError)
}
if len(cfg.Tooltips) > 0 {
feats = append(feats, shell.Tooltips)
}
if cfg.env.Shell() == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark) {
feats = append(feats, shell.PromptMark)
}
for i, block := range cfg.Blocks {
if (i == 0 && block.Newline) && cfg.EnableCursorPositioning {
feats = append(feats, shell.CursorPositioning)
}
if block.Type == RPrompt {
feats = append(feats, shell.RPrompt)
}
for _, segment := range block.Segments {
if segment.Type == AZ {
source := segment.Properties.GetString(segments.Source, segments.FirstMatch)
if source == segments.Pwsh || source == segments.FirstMatch {
feats = append(feats, shell.Azure)
}
}
if segment.Type == GIT {
source := segment.Properties.GetString(segments.Source, segments.Cli)
if source == segments.Pwsh {
feats = append(feats, shell.PoshGit)
}
}
}
}
return feats
}

91
src/config/config_test.go Normal file
View file

@ -0,0 +1,91 @@
package config
import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
)
func TestGetPalette(t *testing.T) {
palette := color.Palette{
"red": "#ff0000",
"blue": "#0000ff",
}
cases := []struct {
Case string
Palettes *color.Palettes
Palette color.Palette
ExpectedPalette color.Palette
}{
{
Case: "match",
Palettes: &color.Palettes{
Template: "{{ .Shell }}",
List: map[string]color.Palette{
"bash": palette,
"zsh": {
"red": "#ff0001",
"blue": "#0000fb",
},
},
},
ExpectedPalette: palette,
},
{
Case: "no match, no fallback",
Palettes: &color.Palettes{
Template: "{{ .Shell }}",
List: map[string]color.Palette{
"fish": palette,
"zsh": {
"red": "#ff0001",
"blue": "#0000fb",
},
},
},
ExpectedPalette: nil,
},
{
Case: "no match, default",
Palettes: &color.Palettes{
Template: "{{ .Shell }}",
List: map[string]color.Palette{
"zsh": {
"red": "#ff0001",
"blue": "#0000fb",
},
},
},
Palette: palette,
ExpectedPalette: palette,
},
{
Case: "no palettes",
ExpectedPalette: nil,
},
}
for _, tc := range cases {
env := &mock.Environment{}
env.On("TemplateCache").Return(&cache.Template{
Env: map[string]string{},
Shell: "bash",
})
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Flags").Return(&runtime.Flags{})
cfg := &Config{
env: env,
Palette: tc.Palette,
Palettes: tc.Palettes,
}
got := cfg.getPalette()
assert.Equal(t, tc.ExpectedPalette, got, tc.Case)
}
}

199
src/config/default.go Normal file
View file

@ -0,0 +1,199 @@
package config
import (
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
)
func Default(env runtime.Environment, warning bool) *Config {
exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}"
exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} "
if warning {
exitBackgroundTemplate = "p:red"
exitTemplate = " CONFIG ERROR "
}
cfg := &Config{
Version: 2,
FinalSpace: true,
Blocks: []*Block{
{
Type: Prompt,
Alignment: Left,
Segments: []*Segment{
{
Type: SESSION,
Style: Diamond,
LeadingDiamond: "\ue0b6",
TrailingDiamond: "\ue0b0",
Foreground: "p:black",
Background: "p:yellow",
Template: " {{ if .SSHSession }}\ueba9 {{ end }}{{ .UserName }} ",
},
{
Type: PATH,
Style: Powerline,
PowerlineSymbol: "\ue0b0",
Foreground: "p:white",
Background: "p:orange",
Properties: properties.Map{
properties.Style: "folder",
},
Template: " \uea83 {{ path .Path .Location }} ",
},
{
Type: GIT,
Style: Powerline,
PowerlineSymbol: "\ue0b0",
Foreground: "p:black",
Background: "p:green",
BackgroundTemplates: []string{
"{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}",
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}",
"{{ if gt .Ahead 0 }}#49416D{{ end }}",
"{{ if gt .Behind 0 }}#7A306C{{ end }}",
},
ForegroundTemplates: []string{
"{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}",
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}",
"{{ if gt .Ahead 0 }}p:white{{ end }}",
},
Properties: properties.Map{
segments.BranchMaxLength: 25,
segments.FetchStatus: true,
segments.FetchUpstreamIcon: true,
},
Template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} ", //nolint:lll
},
{
Type: ROOT,
Style: Powerline,
PowerlineSymbol: "\ue0b0",
Foreground: "p:white",
Background: "p:yellow",
Template: " \uf0e7 ",
},
{
Type: STATUS,
Style: Diamond,
LeadingDiamond: "<transparent,background>\ue0b0</>",
TrailingDiamond: "\ue0b4",
Foreground: "p:white",
Background: "p:blue",
BackgroundTemplates: []string{
exitBackgroundTemplate,
},
Properties: properties.Map{
properties.AlwaysEnabled: true,
},
Template: exitTemplate,
},
},
},
{
Type: RPrompt,
Segments: []*Segment{
{
Type: NODE,
Style: Plain,
Foreground: "p:green",
Background: "transparent",
Template: "\ue718 ",
Properties: properties.Map{
segments.HomeEnabled: false,
segments.FetchPackageManager: false,
segments.DisplayMode: "files",
},
},
{
Type: GOLANG,
Style: Plain,
Foreground: "p:blue",
Background: "transparent",
Template: "\ue626 ",
Properties: properties.Map{
properties.FetchVersion: false,
},
},
{
Type: PYTHON,
Style: Plain,
Foreground: "p:yellow",
Background: "transparent",
Template: "\ue235 ",
Properties: properties.Map{
properties.FetchVersion: false,
segments.DisplayMode: "files",
segments.FetchVirtualEnv: false,
},
},
{
Type: SHELL,
Style: Plain,
Foreground: "p:white",
Background: "transparent",
Template: "in <p:blue><b>{{ .Name }}</b></> ",
},
{
Type: TIME,
Style: Plain,
Foreground: "p:white",
Background: "transparent",
Template: "at <p:blue><b>{{ .CurrentDate | date \"15:04:05\" }}</b></>",
},
},
},
},
ConsoleTitleTemplate: "{{ .Shell }} in {{ .Folder }}",
Palette: color.Palette{
"black": "#262B44",
"blue": "#4B95E9",
"green": "#59C9A5",
"orange": "#F07623",
"red": "#D81E5B",
"white": "#E0DEF4",
"yellow": "#F3AE35",
},
SecondaryPrompt: &Segment{
Foreground: "p:black",
Background: "transparent",
Template: "<p:yellow,transparent>\ue0b6</><,p:yellow> > </><p:yellow,transparent>\ue0b0</> ",
},
TransientPrompt: &Segment{
Foreground: "p:black",
Background: "transparent",
Template: "<p:yellow,transparent>\ue0b6</><,p:yellow> {{ .Folder }} </><p:yellow,transparent>\ue0b0</> ",
},
Tooltips: []*Segment{
{
Type: AWS,
Style: Diamond,
LeadingDiamond: "\ue0b0",
TrailingDiamond: "\ue0b4",
Foreground: "p:white",
Background: "p:orange",
Template: " \ue7ad {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} ",
Properties: properties.Map{
properties.DisplayDefault: true,
},
Tips: []string{"aws"},
},
{
Type: AZ,
Style: Diamond,
LeadingDiamond: "\ue0b0",
TrailingDiamond: "\ue0b4",
Foreground: "p:white",
Background: "p:blue",
Template: " \uebd8 {{ .Name }} ",
Properties: properties.Map{
properties.DisplayDefault: true,
},
Tips: []string{"az"},
},
},
}
cfg.env = env
return cfg
}

97
src/config/load.go Normal file
View file

@ -0,0 +1,97 @@
package config
import (
"bytes"
"fmt"
stdOS "os"
"path/filepath"
"strings"
"time"
"github.com/gookit/goutil/jsonutil"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
json "github.com/goccy/go-json"
yaml "github.com/goccy/go-yaml"
toml "github.com/pelletier/go-toml/v2"
)
// LoadConfig returns the default configuration including possible user overrides
func Load(env runtime.Environment) *Config {
cfg := loadConfig(env)
// only migrate automatically when the switch isn't set
if !env.Flags().Migrate && cfg.Version < Version {
cfg.BackupAndMigrate()
}
if !cfg.ShellIntegration {
return cfg
}
// bash - ok
// fish - ok
// pwsh - ok
// zsh - ok
// cmd - ok, as of v1.4.25 (chrisant996/clink#457, fixed in chrisant996/clink@8a5d7ea)
// nu - built-in (and bugged) feature - nushell/nushell#5585, https://www.nushell.sh/blog/2022-08-16-nushell-0_67.html#shell-integration-fdncred-and-tyriar
// elv - broken OSC sequences
// xonsh - broken OSC sequences
// tcsh - overall broken, FTCS_COMMAND_EXECUTED could be added to POSH_POSTCMD in the future
switch env.Shell() {
case shell.ELVISH, shell.XONSH, shell.TCSH, shell.NU:
cfg.ShellIntegration = false
}
return cfg
}
func loadConfig(env runtime.Environment) *Config {
defer env.Trace(time.Now())
configFile := env.Flags().Config
if len(configFile) == 0 {
env.Debug("no config file specified, using default")
return Default(env, false)
}
var cfg Config
cfg.origin = configFile
cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".")
cfg.env = env
data, err := stdOS.ReadFile(configFile)
if err != nil {
env.Error(err)
return Default(env, true)
}
switch cfg.Format {
case "yml", "yaml":
cfg.Format = YAML
err = yaml.Unmarshal(data, &cfg)
case "jsonc", "json":
cfg.Format = JSON
str := jsonutil.StripComments(string(data))
data = []byte(str)
decoder := json.NewDecoder(bytes.NewReader(data))
err = decoder.Decode(&cfg)
case "toml", "tml":
cfg.Format = TOML
err = toml.Unmarshal(data, &cfg)
default:
err = fmt.Errorf("unsupported config file format: %s", cfg.Format)
}
if err != nil {
env.Error(err)
return Default(env, true)
}
setDisableNotice(data, &cfg)
return &cfg
}

View file

@ -1,11 +1,11 @@
package engine
package config
import (
"fmt"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
)
@ -30,10 +30,10 @@ func (cfg *Config) Migrate() {
cfg.ConsoleTitleTemplate = strings.ReplaceAll(cfg.ConsoleTitleTemplate, ".Path", ".PWD")
}
cfg.updated = true
cfg.Version = configVersion
cfg.Version = Version
}
func (segment *Segment) migrate(env platform.Environment, version int) {
func (segment *Segment) migrate(env runtime.Environment, version int) {
if version < 1 {
segment.migrationOne(env)
}
@ -42,8 +42,8 @@ func (segment *Segment) migrate(env platform.Environment, version int) {
}
}
func (segment *Segment) migrationOne(env platform.Environment) {
if err := segment.mapSegmentWithWriter(env); err != nil {
func (segment *Segment) migrationOne(env runtime.Environment) {
if err := segment.MapSegmentWithWriter(env); err != nil {
return
}
// General properties that need replacement
@ -162,8 +162,8 @@ func (segment *Segment) migrationOne(env platform.Environment) {
delete(segment.Properties, colorBackground)
}
func (segment *Segment) migrationTwo(env platform.Environment) {
if err := segment.mapSegmentWithWriter(env); err != nil {
func (segment *Segment) migrationTwo(env runtime.Environment) {
if err := segment.MapSegmentWithWriter(env); err != nil {
return
}
if !segment.hasProperty(segmentTemplate) {

View file

@ -0,0 +1,140 @@
package config
import (
"context"
"fmt"
"io"
httplib "net/http"
"strconv"
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/http"
)
type ConnectionError struct {
reason string
}
func (f *ConnectionError) Error() string {
return f.reason
}
type codePoints map[uint64]uint64
func getGlyphCodePoints() (codePoints, error) {
var codePoints = make(codePoints)
ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cncl()
request, err := httplib.NewRequestWithContext(ctx, httplib.MethodGet, "https://ohmyposh.dev/codepoints.csv", nil)
if err != nil {
return codePoints, &ConnectionError{reason: err.Error()}
}
response, err := http.HTTPClient.Do(request)
if err != nil {
return codePoints, err
}
defer response.Body.Close()
bytes, err := io.ReadAll(response.Body)
if err != nil {
return codePoints, err
}
lines := strings.Split(string(bytes), "\n")
for _, line := range lines {
fields := strings.Split(line, ",")
if len(fields) < 2 {
continue
}
oldGlyph, err := strconv.ParseUint(fields[0], 16, 32)
if err != nil {
continue
}
newGlyph, err := strconv.ParseUint(fields[1], 16, 32)
if err != nil {
continue
}
codePoints[oldGlyph] = newGlyph
}
return codePoints, nil
}
func escapeGlyphs(s string, migrate bool) string {
shouldExclude := func(r rune) bool {
if r < 0x1000 { // Basic Multilingual Plane
return true
}
if r > 0x1F600 && r < 0x1F64F { // Emoticons
return true
}
if r > 0x1F300 && r < 0x1F5FF { // Misc Symbols and Pictographs
return true
}
if r > 0x1F680 && r < 0x1F6FF { // Transport and Map
return true
}
if r > 0x2600 && r < 0x26FF { // Misc symbols
return true
}
if r > 0x2700 && r < 0x27BF { // Dingbats
return true
}
if r > 0xFE00 && r < 0xFE0F { // Variation Selectors
return true
}
if r > 0x1F900 && r < 0x1F9FF { // Supplemental Symbols and Pictographs
return true
}
if r > 0x1F1E6 && r < 0x1F1FF { // Flags
return true
}
return false
}
var cp codePoints
var err error
if migrate {
cp, err = getGlyphCodePoints()
if err != nil {
migrate = false
}
}
var builder strings.Builder
for _, r := range s {
// exclude regular characters and emojis
if shouldExclude(r) {
builder.WriteRune(r)
continue
}
if migrate {
if val, OK := cp[uint64(r)]; OK {
r = rune(val)
}
}
if r > 0x10000 {
// calculate surrogate pairs
one := 0xd800 + (((r - 0x10000) >> 10) & 0x3ff)
two := 0xdc00 + ((r - 0x10000) & 0x3ff)
quoted := fmt.Sprintf("\\u%04x\\u%04x", one, two)
builder.WriteString(quoted)
continue
}
quoted := fmt.Sprintf("\\u%04x", r)
builder.WriteString(quoted)
}
return builder.String()
}

View file

@ -0,0 +1,35 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetCodePoints(t *testing.T) {
codepoints, err := getGlyphCodePoints()
if connectionError, ok := err.(*ConnectionError); ok {
t.Log(connectionError.Error())
return
}
assert.Equal(t, 1939, len(codepoints))
}
func TestEscapeGlyphs(t *testing.T) {
cases := []struct {
Input string
Expected string
}{
{Input: "󰉋", Expected: "\\udb80\\ude4b"},
{Input: "a", Expected: "a"},
{Input: "\ue0b4", Expected: "\\ue0b4"},
{Input: "\ufd03", Expected: "\\ufd03"},
{Input: "}", Expected: "}"},
{Input: "🏚", Expected: "🏚"},
{Input: "\U000F011B", Expected: "\\udb80\\udd1b"},
{Input: "󰄛", Expected: "\\udb80\\udd1b"},
}
for _, tc := range cases {
assert.Equal(t, tc.Expected, escapeGlyphs(tc.Input, false), tc.Input)
}
}

View file

@ -1,15 +1,15 @@
package engine
package config
import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/mock"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/stretchr/testify/assert"
mock2 "github.com/stretchr/testify/mock"
testify_ "github.com/stretchr/testify/mock"
)
const (
@ -91,7 +91,7 @@ func (m *MockedWriter) Template() string {
return m.template
}
func (m *MockedWriter) Init(_ properties.Properties, _ platform.Environment) {}
func (m *MockedWriter) Init(_ properties.Properties, _ runtime.Environment) {}
func TestIconOverride(t *testing.T) {
cases := []struct {
@ -327,8 +327,8 @@ func TestSegmentTemplateMigration(t *testing.T) {
Type: tc.Type,
Properties: tc.Props,
}
env := &mock.MockedEnvironment{}
env.On("Debug", mock2.Anything).Return(nil)
env := &mock.Environment{}
env.On("Debug", testify_.Anything).Return(nil)
segment.migrationOne(env)
assert.Equal(t, tc.Expected, segment.Properties[segmentTemplate], tc.Case)
}
@ -431,7 +431,7 @@ func TestMigrateConfig(t *testing.T) {
for _, tc := range cases {
cfg := &Config{
ConsoleTitleTemplate: tc.Template,
env: &mock.MockedEnvironment{},
env: &mock.Environment{},
}
cfg.Migrate()
assert.Equal(t, tc.Expected, cfg.ConsoleTitleTemplate, tc.Case)
@ -465,7 +465,7 @@ func TestMigrationTwo(t *testing.T) {
if tc.Template != "" {
segment.Properties[segmentTemplate] = tc.Template
}
segment.migrationTwo(&mock.MockedEnvironment{})
segment.migrationTwo(&mock.Environment{})
assert.Equal(t, tc.Expected, segment.Template, tc.Case)
assert.NotContains(t, segment.Properties, segmentTemplate, tc.Case)
}

View file

@ -0,0 +1,5 @@
//go:build !disablenotice
package config
func setDisableNotice(_ []byte, _ *Config) {}

View file

@ -0,0 +1,15 @@
//go:build disablenotice
package config
import "bytes"
func setDisableNotice(stream []byte, cfg *Config) {
if !hasKeyInByteStream(stream, "disable_notice") {
cfg.DisableNotice = true
}
}
func hasKeyInByteStream(data []byte, key string) bool {
return bytes.Contains(data, []byte(key))
}

View file

@ -1,8 +1,8 @@
package engine
package config
import "github.com/jandedobbeleer/oh-my-posh/src/platform"
import "github.com/jandedobbeleer/oh-my-posh/src/runtime"
func shouldHideForWidth(env platform.Environment, minWidth, maxWidth int) bool {
func shouldHideForWidth(env runtime.Environment, minWidth, maxWidth int) bool {
if maxWidth == 0 && minWidth == 0 {
return false
}

View file

@ -1,9 +1,9 @@
package engine
package config
import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/mock"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert"
)
@ -27,7 +27,7 @@ func TestShouldHideForWidth(t *testing.T) {
{Case: "Min & Max cols - show", MinWidth: 10, MaxWidth: 20, Width: 11, Expected: false},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env := new(mock.Environment)
env.On("TerminalWidth").Return(tc.Width, tc.Error)
got := shouldHideForWidth(env, tc.MinWidth, tc.MaxWidth)
assert.Equal(t, tc.Expected, got, tc.Case)

255
src/config/segment.go Normal file
View file

@ -0,0 +1,255 @@
package config
import (
"fmt"
"runtime/debug"
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/template"
c "golang.org/x/text/cases"
"golang.org/x/text/language"
)
// SegmentStyle the style of segment, for more information, see the constants
type SegmentStyle string
func (s *SegmentStyle) resolve(env runtime.Environment, context any) SegmentStyle {
txtTemplate := &template.Text{
Context: context,
Env: env,
}
txtTemplate.Template = string(*s)
value, err := txtTemplate.Render()
// default to Plain
if err != nil || len(value) == 0 {
return Plain
}
return SegmentStyle(value)
}
type Segment struct {
Type SegmentType `json:"type,omitempty" toml:"type,omitempty"`
Tips []string `json:"tips,omitempty" toml:"tips,omitempty"`
Style SegmentStyle `json:"style,omitempty" toml:"style,omitempty"`
PowerlineSymbol string `json:"powerline_symbol,omitempty" toml:"powerline_symbol,omitempty"`
LeadingPowerlineSymbol string `json:"leading_powerline_symbol,omitempty" toml:"leading_powerline_symbol,omitempty"`
InvertPowerline bool `json:"invert_powerline,omitempty" toml:"invert_powerline,omitempty"`
ForegroundTemplates template.List `json:"foreground_templates,omitempty" toml:"foreground_templates,omitempty"`
BackgroundTemplates template.List `json:"background_templates,omitempty" toml:"background_templates,omitempty"`
LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"`
TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"`
Template string `json:"template,omitempty" toml:"template,omitempty"`
Templates template.List `json:"templates,omitempty" toml:"templates,omitempty"`
TemplatesLogic template.Logic `json:"templates_logic,omitempty" toml:"templates_logic,omitempty"`
Properties properties.Map `json:"properties,omitempty" toml:"properties,omitempty"`
Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty"`
Alias string `json:"alias,omitempty" toml:"alias,omitempty"`
MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"`
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"`
Filler string `json:"filler,omitempty" toml:"filler,omitempty"`
Background color.Ansi `json:"background" toml:"background"`
Foreground color.Ansi `json:"foreground" toml:"foreground"`
Newline bool `json:"newline,omitempty" toml:"newline,omitempty"`
Enabled bool `json:"-" toml:"-"`
Text string
env runtime.Environment
writer SegmentWriter
styleCache SegmentStyle
name string
// debug info
Duration time.Duration
NameLength int
}
func (segment *Segment) Name() string {
if len(segment.name) != 0 {
return segment.name
}
name := segment.Alias
if len(name) == 0 {
name = c.Title(language.English).String(string(segment.Type))
}
segment.name = name
return name
}
func (segment *Segment) SetEnabled(env runtime.Environment) {
defer func() {
err := recover()
if err == nil {
return
}
// display a message explaining omp failed(with the err)
message := fmt.Sprintf("\noh-my-posh fatal error rendering %s segment:%s\n\n%s\n", segment.Type, err, debug.Stack())
fmt.Println(message)
segment.Enabled = true
}()
// segment timings for debug purposes
var start time.Time
if env.Flags().Debug {
start = time.Now()
segment.NameLength = len(segment.Name())
defer func() {
segment.Duration = time.Since(start)
}()
}
err := segment.MapSegmentWithWriter(env)
if err != nil || !segment.shouldIncludeFolder() {
return
}
segment.env.DebugF("segment: %s", segment.Name())
// validate toggles
if toggles, OK := segment.env.Session().Get(cache.TOGGLECACHE); OK && len(toggles) > 0 {
list := strings.Split(toggles, ",")
for _, toggle := range list {
if SegmentType(toggle) == segment.Type || toggle == segment.Alias {
return
}
}
}
if shouldHideForWidth(segment.env, segment.MinWidth, segment.MaxWidth) {
return
}
if segment.writer.Enabled() {
segment.Enabled = true
env.TemplateCache().AddSegmentData(segment.Name(), segment.writer)
}
}
func (segment *Segment) setText() {
if !segment.Enabled {
return
}
segment.Text = segment.string()
segment.Enabled = len(strings.ReplaceAll(segment.Text, " ", "")) > 0
if !segment.Enabled {
segment.env.TemplateCache().RemoveSegmentData(segment.Name())
}
}
func (segment *Segment) string() string {
var templatesResult string
if !segment.Templates.Empty() {
templatesResult = segment.Templates.Resolve(segment.writer, segment.env, "", segment.TemplatesLogic)
if len(segment.Template) == 0 {
return templatesResult
}
}
if len(segment.Template) == 0 {
segment.Template = segment.writer.Template()
}
tmpl := &template.Text{
Template: segment.Template,
Context: segment.writer,
Env: segment.env,
TemplatesResult: templatesResult,
}
text, err := tmpl.Render()
if err != nil {
return err.Error()
}
return text
}
func (segment *Segment) shouldIncludeFolder() bool {
if segment.env == nil {
return true
}
cwdIncluded := segment.cwdIncluded()
cwdExcluded := segment.cwdExcluded()
return cwdIncluded && !cwdExcluded
}
func (segment *Segment) cwdIncluded() bool {
value, ok := segment.Properties[properties.IncludeFolders]
if !ok {
// IncludeFolders isn't specified, everything is included
return true
}
list := properties.ParseStringArray(value)
if len(list) == 0 {
// IncludeFolders is an empty array, everything is included
return true
}
return segment.env.DirMatchesOneOf(segment.env.Pwd(), list)
}
func (segment *Segment) cwdExcluded() bool {
value, ok := segment.Properties[properties.ExcludeFolders]
if !ok {
value = segment.Properties[properties.IgnoreFolders]
}
list := properties.ParseStringArray(value)
return segment.env.DirMatchesOneOf(segment.env.Pwd(), list)
}
func (segment *Segment) ResolveForeground() color.Ansi {
if len(segment.ForegroundTemplates) != 0 {
match := segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground.String())
segment.Foreground = color.Ansi(match)
}
return segment.Foreground
}
func (segment *Segment) ResolveBackground() color.Ansi {
if len(segment.BackgroundTemplates) != 0 {
match := segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background.String())
segment.Background = color.Ansi(match)
}
return segment.Background
}
func (segment *Segment) ResolveStyle() SegmentStyle {
if len(segment.styleCache) != 0 {
return segment.styleCache
}
segment.styleCache = segment.Style.resolve(segment.env, segment.writer)
return segment.styleCache
}
func (segment *Segment) IsPowerline() bool {
style := segment.ResolveStyle()
return style == Powerline || style == Accordion
}
func (segment *Segment) HasEmptyDiamondAtEnd() bool {
if segment.ResolveStyle() != Diamond {
return false
}
return len(segment.TrailingDiamond) == 0
}

View file

@ -1,16 +1,18 @@
package engine
package config
import (
"encoding/json"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/mock"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/stretchr/testify/assert"
mock2 "github.com/stretchr/testify/mock"
testify_ "github.com/stretchr/testify/mock"
)
const (
@ -21,8 +23,8 @@ func TestMapSegmentWriterCanMap(t *testing.T) {
sc := &Segment{
Type: SESSION,
}
env := new(mock.MockedEnvironment)
err := sc.mapSegmentWithWriter(env)
env := new(mock.Environment)
err := sc.MapSegmentWithWriter(env)
assert.NoError(t, err)
assert.NotNil(t, sc.writer)
}
@ -31,8 +33,8 @@ func TestMapSegmentWriterCannotMap(t *testing.T) {
sc := &Segment{
Type: "nilwriter",
}
env := new(mock.MockedEnvironment)
err := sc.mapSegmentWithWriter(env)
env := new(mock.Environment)
err := sc.MapSegmentWithWriter(env)
assert.Error(t, err)
}
@ -71,8 +73,8 @@ func TestShouldIncludeFolder(t *testing.T) {
{Case: "!Include & !Exclude", Included: false, Excluded: false, Expected: false},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("GOOS").Return(platform.LINUX)
env := new(mock.Environment)
env.On("GOOS").Return(runtime.LINUX)
env.On("Home").Return("")
env.On("Pwd").Return(cwd)
env.On("DirMatchesOneOf", cwd, []string{"Projects/oh-my-posh"}).Return(tc.Included)
@ -91,39 +93,39 @@ func TestShouldIncludeFolder(t *testing.T) {
func TestGetColors(t *testing.T) {
cases := []struct {
Case string
Background bool
ExpectedColor string
Templates []string
DefaultColor string
Region string
Profile string
Case string
Background bool
Expected color.Ansi
Templates []string
Default color.Ansi
Region string
Profile string
}{
{Case: "No template - foreground", ExpectedColor: "color", Background: false, DefaultColor: "color"},
{Case: "No template - background", ExpectedColor: "color", Background: true, DefaultColor: "color"},
{Case: "Nil template", ExpectedColor: "color", DefaultColor: "color", Templates: nil},
{Case: "No template - foreground", Expected: "color", Background: false, Default: "color"},
{Case: "No template - background", Expected: "color", Background: true, Default: "color"},
{Case: "Nil template", Expected: "color", Default: "color", Templates: nil},
{
Case: "Template - default",
ExpectedColor: "color",
DefaultColor: "color",
Case: "Template - default",
Expected: "color",
Default: "color",
Templates: []string{
"{{if contains \"john\" .Profile}}color2{{end}}",
},
Profile: "doe",
},
{
Case: "Template - override",
ExpectedColor: "color2",
DefaultColor: "color",
Case: "Template - override",
Expected: "color2",
Default: "color",
Templates: []string{
"{{if contains \"john\" .Profile}}color2{{end}}",
},
Profile: "john",
},
{
Case: "Template - override multiple",
ExpectedColor: "color3",
DefaultColor: "color",
Case: "Template - override multiple",
Expected: "color3",
Default: "color",
Templates: []string{
"{{if contains \"doe\" .Profile}}color2{{end}}",
"{{if contains \"john\" .Profile}}color3{{end}}",
@ -131,9 +133,9 @@ func TestGetColors(t *testing.T) {
Profile: "john",
},
{
Case: "Template - override multiple no match",
ExpectedColor: "color",
DefaultColor: "color",
Case: "Template - override multiple no match",
Expected: "color",
Default: "color",
Templates: []string{
"{{if contains \"doe\" .Profile}}color2{{end}}",
"{{if contains \"philip\" .Profile}}color3{{end}}",
@ -142,11 +144,13 @@ func TestGetColors(t *testing.T) {
},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
env.On("TemplateCache").Return(&platform.TemplateCache{
env := new(mock.Environment)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{
Env: make(map[string]string),
})
env.On("Flags").Return(&runtime.Flags{})
segment := &Segment{
writer: &segments.Aws{
Profile: tc.Profile,
@ -154,16 +158,18 @@ func TestGetColors(t *testing.T) {
},
env: env,
}
if tc.Background {
segment.Background = tc.DefaultColor
segment.Background = tc.Default
segment.BackgroundTemplates = tc.Templates
color := segment.background()
assert.Equal(t, tc.ExpectedColor, color, tc.Case)
bgColor := segment.ResolveBackground()
assert.Equal(t, tc.Expected, bgColor, tc.Case)
continue
}
segment.Foreground = tc.DefaultColor
segment.Foreground = tc.Default
segment.ForegroundTemplates = tc.Templates
color := segment.foreground()
assert.Equal(t, tc.ExpectedColor, color, tc.Case)
fgColor := segment.ResolveForeground()
assert.Equal(t, tc.Expected, fgColor, tc.Case)
}
}

View file

@ -1,88 +1,23 @@
package engine
package config
import (
"errors"
"fmt"
"runtime/debug"
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
c "golang.org/x/text/cases"
"golang.org/x/text/language"
)
// Segment represent a single segment and it's configuration
type Segment struct {
Type SegmentType `json:"type,omitempty" toml:"type,omitempty"`
Tips []string `json:"tips,omitempty" toml:"tips,omitempty"`
Style SegmentStyle `json:"style,omitempty" toml:"style,omitempty"`
PowerlineSymbol string `json:"powerline_symbol,omitempty" toml:"powerline_symbol,omitempty"`
LeadingPowerlineSymbol string `json:"leading_powerline_symbol,omitempty" toml:"leading_powerline_symbol,omitempty"`
InvertPowerline bool `json:"invert_powerline,omitempty" toml:"invert_powerline,omitempty"`
Foreground string `json:"foreground,omitempty" toml:"foreground,omitempty"`
ForegroundTemplates template.List `json:"foreground_templates,omitempty" toml:"foreground_templates,omitempty"`
Background string `json:"background,omitempty" toml:"background,omitempty"`
BackgroundTemplates template.List `json:"background_templates,omitempty" toml:"background_templates,omitempty"`
LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"`
TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"`
Template string `json:"template,omitempty" toml:"template,omitempty"`
Templates template.List `json:"templates,omitempty" toml:"templates,omitempty"`
TemplatesLogic template.Logic `json:"templates_logic,omitempty" toml:"templates_logic,omitempty"`
Properties properties.Map `json:"properties,omitempty" toml:"properties,omitempty"`
Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty"`
Alias string `json:"alias,omitempty" toml:"alias,omitempty"`
MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"`
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"`
Filler string `json:"filler,omitempty" toml:"filler,omitempty"`
Enabled bool `json:"-" toml:"-"`
colors *ansi.Colors
env platform.Environment
writer SegmentWriter
text string
styleCache SegmentStyle
name string
// debug info
duration time.Duration
nameLength int
}
// SegmentType the type of segment, for more information, see the constants
type SegmentType string
// SegmentWriter is the interface used to define what and if to write to the prompt
type SegmentWriter interface {
Enabled() bool
Template() string
Init(props properties.Properties, env platform.Environment)
Init(props properties.Properties, env runtime.Environment)
}
// SegmentStyle the style of segment, for more information, see the constants
type SegmentStyle string
func (s *SegmentStyle) Resolve(env platform.Environment, context any) SegmentStyle {
txtTemplate := &template.Text{
Context: context,
Env: env,
}
txtTemplate.Template = string(*s)
value, err := txtTemplate.Render()
// default to Plain
if err != nil || len(value) == 0 {
return Plain
}
return SegmentStyle(value)
}
// SegmentType the type of segment, for more information, see the constants
type SegmentType string
const (
// Plain writes it without ornaments
Plain SegmentStyle = "plain"
@ -92,7 +27,6 @@ const (
Accordion SegmentStyle = "accordion"
// Diamond writes the prompt shaped with a leading and trailing symbol
Diamond SegmentStyle = "diamond"
// ANGULAR writes which angular cli version us currently active
ANGULAR SegmentType = "angular"
// ARGOCD writes the current argocd context
@ -101,6 +35,8 @@ const (
AWS SegmentType = "aws"
// AZ writes the Azure subscription info we're currently in
AZ SegmentType = "az"
// AZD writes the Azure Developer CLI environment info we're current in
AZD SegmentType = "azd"
// AZFUNC writes current AZ func version
AZFUNC SegmentType = "azfunc"
// BATTERY writes the battery percentage
@ -111,6 +47,8 @@ const (
BREWFATHER SegmentType = "brewfather"
// Buf segment writes the active buf version
BUF SegmentType = "buf"
// BUN writes the active bun version
BUN SegmentType = "bun"
// CARBONINTENSITY writes the actual and forecast carbon intensity in gCO2/kWh
CARBONINTENSITY SegmentType = "carbonintensity"
// cds (SAP CAP) version
@ -175,6 +113,8 @@ const (
LUA SegmentType = "lua"
// MERCURIAL writes the Mercurial source control information
MERCURIAL SegmentType = "mercurial"
// MVN writes the active maven version
MVN SegmentType = "mvn"
// NBA writes NBA game data
NBA SegmentType = "nba"
// NBGV writes the nbgv version information
@ -187,6 +127,8 @@ const (
NPM SegmentType = "npm"
// NX writes which Nx version us currently active
NX SegmentType = "nx"
// NIXSHELL writes the active nix shell details
NIXSHELL SegmentType = "nix-shell"
// OCAML writes the active Ocaml version
OCAML SegmentType = "ocaml"
// OS write os specific icon
@ -201,6 +143,8 @@ const (
PHP SegmentType = "php"
// PLASTIC represents the plastic scm status and information
PLASTIC SegmentType = "plastic"
// pnpm version
PNPM SegmentType = "pnpm"
// Project version
PROJECT SegmentType = "project"
// PULUMI writes the pulumi user, store and stack
@ -265,6 +209,8 @@ const (
WITHINGS SegmentType = "withings"
// XMAKE write the xmake version if xmake.lua is present
XMAKE SegmentType = "xmake"
// yarn version
YARN SegmentType = "yarn"
// YTM writes YouTube Music information and status
YTM SegmentType = "ytm"
)
@ -276,11 +222,13 @@ var Segments = map[SegmentType]func() SegmentWriter{
ARGOCD: func() SegmentWriter { return &segments.Argocd{} },
AWS: func() SegmentWriter { return &segments.Aws{} },
AZ: func() SegmentWriter { return &segments.Az{} },
AZD: func() SegmentWriter { return &segments.Azd{} },
AZFUNC: func() SegmentWriter { return &segments.AzFunc{} },
BATTERY: func() SegmentWriter { return &segments.Battery{} },
BAZEL: func() SegmentWriter { return &segments.Bazel{} },
BREWFATHER: func() SegmentWriter { return &segments.Brewfather{} },
BUF: func() SegmentWriter { return &segments.Buf{} },
BUN: func() SegmentWriter { return &segments.Bun{} },
CARBONINTENSITY: func() SegmentWriter { return &segments.CarbonIntensity{} },
CDS: func() SegmentWriter { return &segments.Cds{} },
CF: func() SegmentWriter { return &segments.Cf{} },
@ -313,11 +261,13 @@ var Segments = map[SegmentType]func() SegmentWriter{
LASTFM: func() SegmentWriter { return &segments.LastFM{} },
LUA: func() SegmentWriter { return &segments.Lua{} },
MERCURIAL: func() SegmentWriter { return &segments.Mercurial{} },
MVN: func() SegmentWriter { return &segments.Mvn{} },
NBA: func() SegmentWriter { return &segments.Nba{} },
NBGV: func() SegmentWriter { return &segments.Nbgv{} },
NIGHTSCOUT: func() SegmentWriter { return &segments.Nightscout{} },
NODE: func() SegmentWriter { return &segments.Node{} },
NPM: func() SegmentWriter { return &segments.Npm{} },
NIXSHELL: func() SegmentWriter { return &segments.NixShell{} },
NX: func() SegmentWriter { return &segments.Nx{} },
OCAML: func() SegmentWriter { return &segments.OCaml{} },
OS: func() SegmentWriter { return &segments.Os{} },
@ -326,6 +276,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
PERL: func() SegmentWriter { return &segments.Perl{} },
PHP: func() SegmentWriter { return &segments.Php{} },
PLASTIC: func() SegmentWriter { return &segments.Plastic{} },
PNPM: func() SegmentWriter { return &segments.Pnpm{} },
PROJECT: func() SegmentWriter { return &segments.Project{} },
PULUMI: func() SegmentWriter { return &segments.Pulumi{} },
PYTHON: func() SegmentWriter { return &segments.Python{} },
@ -358,95 +309,11 @@ var Segments = map[SegmentType]func() SegmentWriter{
WINREG: func() SegmentWriter { return &segments.WindowsRegistry{} },
WITHINGS: func() SegmentWriter { return &segments.Withings{} },
XMAKE: func() SegmentWriter { return &segments.XMake{} },
YARN: func() SegmentWriter { return &segments.Yarn{} },
YTM: func() SegmentWriter { return &segments.Ytm{} },
}
func (segment *Segment) style() SegmentStyle {
if len(segment.styleCache) != 0 {
return segment.styleCache
}
segment.styleCache = segment.Style.Resolve(segment.env, segment.writer)
return segment.styleCache
}
func (segment *Segment) shouldIncludeFolder() bool {
if segment.env == nil {
return true
}
cwdIncluded := segment.cwdIncluded()
cwdExcluded := segment.cwdExcluded()
return cwdIncluded && !cwdExcluded
}
func (segment *Segment) isPowerline() bool {
style := segment.style()
return style == Powerline || style == Accordion
}
func (segment *Segment) hasEmptyDiamondAtEnd() bool {
if segment.style() != Diamond {
return false
}
return len(segment.TrailingDiamond) == 0
}
func (segment *Segment) cwdIncluded() bool {
value, ok := segment.Properties[properties.IncludeFolders]
if !ok {
// IncludeFolders isn't specified, everything is included
return true
}
list := properties.ParseStringArray(value)
if len(list) == 0 {
// IncludeFolders is an empty array, everything is included
return true
}
return segment.env.DirMatchesOneOf(segment.env.Pwd(), list)
}
func (segment *Segment) cwdExcluded() bool {
value, ok := segment.Properties[properties.ExcludeFolders]
if !ok {
value = segment.Properties[properties.IgnoreFolders]
}
list := properties.ParseStringArray(value)
return segment.env.DirMatchesOneOf(segment.env.Pwd(), list)
}
func (segment *Segment) shouldInvokeWithTip(tip string) bool {
for _, t := range segment.Tips {
if t == tip {
return true
}
}
return false
}
func (segment *Segment) foreground() string {
if segment.colors == nil {
segment.colors = &ansi.Colors{}
}
if len(segment.colors.Foreground) == 0 {
segment.colors.Foreground = segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground)
}
return segment.colors.Foreground
}
func (segment *Segment) background() string {
if segment.colors == nil {
segment.colors = &ansi.Colors{}
}
if len(segment.colors.Background) == 0 {
segment.colors.Background = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background)
}
return segment.colors.Background
}
func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error {
func (segment *Segment) MapSegmentWithWriter(env runtime.Environment) error {
segment.env = env
if segment.Properties == nil {
@ -457,7 +324,6 @@ func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error {
writer := f()
wrapper := &properties.Wrapper{
Properties: segment.Properties,
Env: env,
}
writer.Init(wrapper, env)
segment.writer = writer
@ -466,110 +332,3 @@ func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error {
return errors.New("unable to map writer")
}
func (segment *Segment) string() string {
var templatesResult string
if !segment.Templates.Empty() {
templatesResult = segment.Templates.Resolve(segment.writer, segment.env, "", segment.TemplatesLogic)
if len(segment.Template) == 0 {
return templatesResult
}
}
if len(segment.Template) == 0 {
segment.Template = segment.writer.Template()
}
tmpl := &template.Text{
Template: segment.Template,
Context: segment.writer,
Env: segment.env,
TemplatesResult: templatesResult,
}
text, err := tmpl.Render()
if err != nil {
return err.Error()
}
return text
}
func (segment *Segment) Name() string {
if len(segment.name) != 0 {
return segment.name
}
name := segment.Alias
if len(name) == 0 {
name = c.Title(language.English).String(string(segment.Type))
}
segment.name = name
return name
}
func (segment *Segment) SetEnabled(env platform.Environment) {
defer func() {
err := recover()
if err == nil {
return
}
// display a message explaining omp failed(with the err)
message := fmt.Sprintf("\noh-my-posh fatal error rendering %s segment:%s\n\n%s\n", segment.Type, err, debug.Stack())
fmt.Println(message)
segment.Enabled = true
}()
// segment timings for debug purposes
var start time.Time
if env.Flags().Debug {
start = time.Now()
segment.nameLength = len(segment.Name())
defer func() {
segment.duration = time.Since(start)
}()
}
err := segment.mapSegmentWithWriter(env)
if err != nil || !segment.shouldIncludeFolder() {
return
}
segment.env.DebugF("Segment: %s", segment.Name())
// validate toggles
if toggles, OK := segment.env.Cache().Get(platform.TOGGLECACHE); OK && len(toggles) > 0 {
list := strings.Split(toggles, ",")
for _, toggle := range list {
if SegmentType(toggle) == segment.Type || toggle == segment.Alias {
return
}
}
}
if shouldHideForWidth(segment.env, segment.MinWidth, segment.MaxWidth) {
return
}
if segment.writer.Enabled() {
segment.Enabled = true
env.TemplateCache().AddSegmentData(segment.Name(), segment.writer)
}
}
func (segment *Segment) SetText() {
if !segment.Enabled {
return
}
segment.text = segment.string()
segment.Enabled = len(strings.ReplaceAll(segment.text, " ", "")) > 0
if !segment.Enabled {
segment.env.TemplateCache().RemoveSegmentData(segment.Name())
}
if segment.Interactive {
return
}
// we have to do this to prevent bash/zsh from misidentifying escape sequences
switch segment.env.Shell() {
case shell.BASH:
segment.text = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(segment.text)
case shell.ZSH:
segment.text = strings.NewReplacer("`", "\\`", `%`, `%%`).Replace(segment.text)
}
}

View file

@ -1,304 +0,0 @@
package engine
import (
"strings"
"sync"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
)
// BlockType type of block
type BlockType string
// BlockAlignment aligment of a Block
type BlockAlignment string
// Overflow defines how to handle a right block that overflows with the previous block
type Overflow string
const (
// Prompt writes one or more Segments
Prompt BlockType = "prompt"
// LineBreak creates a line break in the prompt
LineBreak BlockType = "newline"
// RPrompt is a right aligned prompt
RPrompt BlockType = "rprompt"
// Left aligns left
Left BlockAlignment = "left"
// Right aligns right
Right BlockAlignment = "right"
// Break adds a line break
Break Overflow = "break"
// Hide hides the block
Hide Overflow = "hide"
)
// Block defines a part of the prompt with optional segments
type Block struct {
Type BlockType `json:"type,omitempty" toml:"type,omitempty"`
Alignment BlockAlignment `json:"alignment,omitempty" toml:"alignment,omitempty"`
Segments []*Segment `json:"segments,omitempty" toml:"segments,omitempty"`
Newline bool `json:"newline,omitempty" toml:"newline,omitempty"`
Filler string `json:"filler,omitempty" toml:"filler,omitempty"`
Overflow Overflow `json:"overflow,omitempty" toml:"overflow,omitempty"`
// Deprecated: keep the logic for legacy purposes
HorizontalOffset int `json:"horizontal_offset,omitempty" toml:"horizontal_offset,omitempty"`
VerticalOffset int `json:"vertical_offset,omitempty" toml:"vertical_offset,omitempty"`
MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"`
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"`
env platform.Environment
writer *ansi.Writer
activeSegment *Segment
previousActiveSegment *Segment
}
func (b *Block) Init(env platform.Environment, writer *ansi.Writer) {
b.env = env
b.writer = writer
b.executeSegmentLogic()
}
func (b *Block) InitPlain(env platform.Environment, config *Config) {
b.writer = &ansi.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, config.TerminalBackground),
AnsiColors: config.MakeColors(),
TrueColor: env.Flags().TrueColor,
}
b.writer.Init(shell.GENERIC)
b.env = env
b.executeSegmentLogic()
}
func (b *Block) executeSegmentLogic() {
if shouldHideForWidth(b.env, b.MinWidth, b.MaxWidth) {
return
}
b.setEnabledSegments()
b.setSegmentsText()
}
func (b *Block) setActiveSegment(segment *Segment) {
b.activeSegment = segment
b.writer.SetColors(segment.background(), segment.foreground())
}
func (b *Block) Enabled() bool {
if b.Type == LineBreak {
return true
}
for _, segment := range b.Segments {
if segment.Enabled {
return true
}
}
return false
}
func (b *Block) setEnabledSegments() {
wg := sync.WaitGroup{}
wg.Add(len(b.Segments))
defer wg.Wait()
for _, segment := range b.Segments {
go func(s *Segment) {
defer wg.Done()
s.SetEnabled(b.env)
}(segment)
}
}
func (b *Block) setSegmentsText() {
wg := sync.WaitGroup{}
wg.Add(len(b.Segments))
defer wg.Wait()
for _, segment := range b.Segments {
go func(s *Segment) {
defer wg.Done()
s.SetText()
}(segment)
}
}
func (b *Block) RenderSegments() (string, int) {
for _, segment := range b.Segments {
if !segment.Enabled && segment.style() != Accordion {
continue
}
if colors, newCycle := cycle.Loop(); colors != nil {
cycle = &newCycle
segment.colors = colors
}
b.setActiveSegment(segment)
b.renderActiveSegment()
}
b.writeSeparator(true)
return b.writer.String()
}
func (b *Block) renderActiveSegment() {
b.writeSeparator(false)
switch b.activeSegment.style() {
case Plain, Powerline:
b.writer.Write(ansi.Background, ansi.Foreground, b.activeSegment.text)
case Diamond:
background := ansi.Transparent
if b.previousActiveSegment != nil && b.previousActiveSegment.hasEmptyDiamondAtEnd() {
background = b.previousActiveSegment.background()
}
b.writer.Write(background, ansi.Background, b.activeSegment.LeadingDiamond)
b.writer.Write(ansi.Background, ansi.Foreground, b.activeSegment.text)
case Accordion:
if b.activeSegment.Enabled {
b.writer.Write(ansi.Background, ansi.Foreground, b.activeSegment.text)
}
}
b.previousActiveSegment = b.activeSegment
b.writer.SetParentColors(b.previousActiveSegment.background(), b.previousActiveSegment.foreground())
}
func (b *Block) writeSeparator(final bool) {
isCurrentDiamond := b.activeSegment.style() == Diamond
if final && isCurrentDiamond {
b.writer.Write(ansi.Transparent, ansi.Background, b.activeSegment.TrailingDiamond)
return
}
isPreviousDiamond := b.previousActiveSegment != nil && b.previousActiveSegment.style() == Diamond
if isPreviousDiamond {
b.adjustTrailingDiamondColorOverrides()
}
if isPreviousDiamond && isCurrentDiamond && len(b.activeSegment.LeadingDiamond) == 0 {
b.writer.Write(ansi.Background, ansi.ParentBackground, b.previousActiveSegment.TrailingDiamond)
return
}
if isPreviousDiamond && len(b.previousActiveSegment.TrailingDiamond) > 0 {
b.writer.Write(ansi.Transparent, ansi.ParentBackground, b.previousActiveSegment.TrailingDiamond)
}
isPowerline := b.activeSegment.isPowerline()
shouldOverridePowerlineLeadingSymbol := func() bool {
if !isPowerline {
return false
}
if isPowerline && len(b.activeSegment.LeadingPowerlineSymbol) == 0 {
return false
}
if b.previousActiveSegment != nil && b.previousActiveSegment.isPowerline() {
return false
}
return true
}
if shouldOverridePowerlineLeadingSymbol() {
b.writer.Write(ansi.Transparent, ansi.Background, b.activeSegment.LeadingPowerlineSymbol)
return
}
resolvePowerlineSymbol := func() string {
if isPowerline {
return b.activeSegment.PowerlineSymbol
}
if b.previousActiveSegment != nil && b.previousActiveSegment.isPowerline() {
return b.previousActiveSegment.PowerlineSymbol
}
return ""
}
symbol := resolvePowerlineSymbol()
if len(symbol) == 0 {
return
}
bgColor := ansi.Background
if final || !isPowerline {
bgColor = ansi.Transparent
}
if b.activeSegment.style() == Diamond && len(b.activeSegment.LeadingDiamond) == 0 {
bgColor = ansi.Background
}
if b.activeSegment.InvertPowerline {
b.writer.Write(b.getPowerlineColor(), bgColor, symbol)
return
}
b.writer.Write(bgColor, b.getPowerlineColor(), symbol)
}
func (b *Block) adjustTrailingDiamondColorOverrides() {
// as we now already adjusted the activeSegment, we need to change the value
// of background and foreground to parentBackground and parentForeground
// this will still break when using parentBackground and parentForeground as keywords
// in a trailing diamond, but let's fix that when it happens as it requires either a rewrite
// of the logic for diamonds or storing grandparents as well like one happy family.
if b.previousActiveSegment == nil || len(b.previousActiveSegment.TrailingDiamond) == 0 {
return
}
if !strings.Contains(b.previousActiveSegment.TrailingDiamond, ansi.Background) && !strings.Contains(b.previousActiveSegment.TrailingDiamond, ansi.Foreground) {
return
}
match := regex.FindNamedRegexMatch(ansi.AnchorRegex, b.previousActiveSegment.TrailingDiamond)
if len(match) == 0 {
return
}
adjustOverride := func(anchor, override string) {
newOverride := override
switch override {
case ansi.Foreground:
newOverride = ansi.ParentForeground
case ansi.Background:
newOverride = ansi.ParentBackground
}
if override == newOverride {
return
}
newAnchor := strings.Replace(match[ansi.ANCHOR], override, newOverride, 1)
b.previousActiveSegment.TrailingDiamond = strings.Replace(b.previousActiveSegment.TrailingDiamond, anchor, newAnchor, 1)
}
if len(match[ansi.BG]) > 0 {
adjustOverride(match[ansi.ANCHOR], match[ansi.BG])
}
if len(match[ansi.FG]) > 0 {
adjustOverride(match[ansi.ANCHOR], match[ansi.FG])
}
}
func (b *Block) getPowerlineColor() string {
if b.previousActiveSegment == nil {
return ansi.Transparent
}
if b.previousActiveSegment.style() == Diamond && len(b.previousActiveSegment.TrailingDiamond) == 0 {
return b.previousActiveSegment.background()
}
if b.activeSegment.style() == Diamond && len(b.activeSegment.LeadingDiamond) == 0 {
return b.previousActiveSegment.background()
}
if !b.previousActiveSegment.isPowerline() {
return ansi.Transparent
}
return b.previousActiveSegment.background()
}

View file

@ -1,524 +0,0 @@
package engine
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
json "github.com/goccy/go-json"
yaml "github.com/goccy/go-yaml"
"github.com/gookit/goutil/jsonutil"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
toml "github.com/pelletier/go-toml/v2"
)
const (
JSON string = "json"
YAML string = "yaml"
TOML string = "toml"
configVersion = 2
)
// Config holds all the theme for rendering the prompt
type Config struct {
Version int `json:"version" toml:"version"`
FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"`
ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty"`
TerminalBackground string `json:"terminal_background,omitempty" toml:"terminal_background,omitempty"`
AccentColor string `json:"accent_color,omitempty" toml:"accent_color,omitempty"`
Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty"`
Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty"`
TransientPrompt *Segment `json:"transient_prompt,omitempty" toml:"transient_prompt,omitempty"`
ValidLine *Segment `json:"valid_line,omitempty" toml:"valid_line,omitempty"`
ErrorLine *Segment `json:"error_line,omitempty" toml:"error_line,omitempty"`
SecondaryPrompt *Segment `json:"secondary_prompt,omitempty" toml:"secondary_prompt,omitempty"`
DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty"`
Palette ansi.Palette `json:"palette,omitempty" toml:"palette,omitempty"`
Palettes *ansi.Palettes `json:"palettes,omitempty" toml:"palettes,omitempty"`
Cycle ansi.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty"`
ShellIntegration bool `json:"shell_integration,omitempty" toml:"shell_integration,omitempty"`
PWD string `json:"pwd,omitempty" toml:"pwd,omitempty"`
Var map[string]any `json:"var,omitempty" toml:"var,omitempty"`
DisableCursorPositioning bool `json:"disable_cursor_positioning,omitempty" toml:"disable_cursor_positioning,omitempty"`
PatchPwshBleed bool `json:"patch_pwsh_bleed,omitempty" toml:"patch_pwsh_bleed,omitempty"`
DisableNotice bool `json:"disable_notice,omitempty" toml:"disable_notice,omitempty"`
ITermFeatures ansi.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty"`
// Deprecated
OSC99 bool `json:"osc99,omitempty" toml:"osc99,omitempty"`
Output string `json:"-" toml:"-"`
MigrateGlyphs bool `json:"-" toml:"-"`
Format string `json:"-" toml:"-"`
origin string
// eval bool
updated bool
env platform.Environment
}
// MakeColors creates instance of AnsiColors to use in AnsiWriter according to
// environment and configuration.
func (cfg *Config) MakeColors() ansi.ColorString {
cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1"
return ansi.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env)
}
func (cfg *Config) getPalette() ansi.Palette {
if cfg.Palettes == nil {
return cfg.Palette
}
tmpl := &template.Text{
Template: cfg.Palettes.Template,
Env: cfg.env,
}
if palette, err := tmpl.Render(); err == nil {
if p, ok := cfg.Palettes.List[palette]; ok {
return p
}
}
return cfg.Palette
}
// LoadConfig returns the default configuration including possible user overrides
func LoadConfig(env platform.Environment) *Config {
cfg := loadConfig(env)
// only migrate automatically when the switch isn't set
if !env.Flags().Migrate && cfg.Version < configVersion {
cfg.BackupAndMigrate()
}
if !cfg.ShellIntegration {
return cfg
}
// bash - ok
// fish - ok
// pwsh - ok
// zsh - ok
// cmd - ok, as of v1.4.25 (chrisant996/clink#457, fixed in chrisant996/clink@8a5d7ea)
// nu - built-in (and bugged) feature - nushell/nushell#5585, https://www.nushell.sh/blog/2022-08-16-nushell-0_67.html#shell-integration-fdncred-and-tyriar
// elv - broken OSC sequences
// xonsh - broken OSC sequences
// tcsh - overall broken, FTCS_COMMAND_EXECUTED could be added to POSH_POSTCMD in the future
switch env.Shell() {
case shell.ELVISH, shell.XONSH, shell.TCSH, shell.NU:
cfg.ShellIntegration = false
}
return cfg
}
func loadConfig(env platform.Environment) *Config {
defer env.Trace(time.Now())
configFile := env.Flags().Config
if len(configFile) == 0 {
env.Debug("no config file specified, using default")
return defaultConfig(env, false)
}
var cfg Config
cfg.origin = configFile
cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".")
cfg.env = env
data, err := os.ReadFile(configFile)
if err != nil {
env.DebugF("error reading config file: %s", err)
return defaultConfig(env, true)
}
switch cfg.Format {
case "yml", "yaml":
cfg.Format = YAML
err = yaml.Unmarshal(data, &cfg)
case "jsonc", "json":
cfg.Format = JSON
str := jsonutil.StripComments(string(data))
data = []byte(str)
decoder := json.NewDecoder(bytes.NewReader(data))
err = decoder.Decode(&cfg)
case "toml", "tml":
cfg.Format = TOML
err = toml.Unmarshal(data, &cfg)
default:
err = fmt.Errorf("unsupported config file format: %s", cfg.Format)
}
if err != nil {
env.DebugF("error decoding config file: %s", err)
return defaultConfig(env, true)
}
return &cfg
}
func (cfg *Config) Export(format string) string {
if len(format) != 0 {
cfg.Format = format
}
var result bytes.Buffer
switch cfg.Format {
case YAML:
prefix := "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n"
yamlEncoder := yaml.NewEncoder(&result)
err := yamlEncoder.Encode(cfg)
if err != nil {
return ""
}
return prefix + result.String()
case JSON:
jsonEncoder := json.NewEncoder(&result)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", " ")
_ = jsonEncoder.Encode(cfg)
prefix := "{\n \"$schema\": \"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\","
data := strings.Replace(result.String(), "{", prefix, 1)
return escapeGlyphs(data, cfg.MigrateGlyphs)
case TOML:
prefix := "#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n"
tomlEncoder := toml.NewEncoder(&result)
tomlEncoder.SetIndentTables(true)
err := tomlEncoder.Encode(cfg)
if err != nil {
return ""
}
return prefix + result.String()
}
// unsupported format
return ""
}
func (cfg *Config) BackupAndMigrate() {
cfg.Backup()
cfg.Migrate()
cfg.Write(cfg.Format)
}
func (cfg *Config) Write(format string) {
content := cfg.Export(format)
if len(content) == 0 {
// we are unable to perform the export
os.Exit(65)
return
}
destination := cfg.Output
if len(destination) == 0 {
destination = cfg.origin
}
f, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return
}
defer func() {
_ = f.Close()
}()
_, err = f.WriteString(content)
if err != nil {
return
}
}
func (cfg *Config) Backup() {
dst := cfg.origin + ".bak"
source, err := os.Open(cfg.origin)
if err != nil {
return
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return
}
}
func escapeGlyphs(s string, migrate bool) string {
shouldExclude := func(r rune) bool {
if r < 0x1000 { // Basic Multilingual Plane
return true
}
if r > 0x1F600 && r < 0x1F64F { // Emoticons
return true
}
if r > 0x1F300 && r < 0x1F5FF { // Misc Symbols and Pictographs
return true
}
if r > 0x1F680 && r < 0x1F6FF { // Transport and Map
return true
}
if r > 0x2600 && r < 0x26FF { // Misc symbols
return true
}
if r > 0x2700 && r < 0x27BF { // Dingbats
return true
}
if r > 0xFE00 && r < 0xFE0F { // Variation Selectors
return true
}
if r > 0x1F900 && r < 0x1F9FF { // Supplemental Symbols and Pictographs
return true
}
if r > 0x1F1E6 && r < 0x1F1FF { // Flags
return true
}
return false
}
var cp codePoints
var err error
if migrate {
cp, err = getGlyphCodePoints()
if err != nil {
migrate = false
}
}
var builder strings.Builder
for _, r := range s {
// exclude regular characters and emojis
if shouldExclude(r) {
builder.WriteRune(r)
continue
}
if migrate {
if val, OK := cp[uint64(r)]; OK {
r = rune(val)
}
}
if r > 0x10000 {
// calculate surrogate pairs
one := 0xd800 + (((r - 0x10000) >> 10) & 0x3ff)
two := 0xdc00 + ((r - 0x10000) & 0x3ff)
quoted := fmt.Sprintf("\\u%04x\\u%04x", one, two)
builder.WriteString(quoted)
continue
}
quoted := fmt.Sprintf("\\u%04x", r)
builder.WriteString(quoted)
}
return builder.String()
}
func defaultConfig(env platform.Environment, warning bool) *Config {
exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}"
exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} "
if warning {
exitBackgroundTemplate = "p:red"
exitTemplate = " CONFIG ERROR "
}
cfg := &Config{
Version: 2,
FinalSpace: true,
Blocks: []*Block{
{
Type: Prompt,
Alignment: Left,
Segments: []*Segment{
{
Type: SESSION,
Style: Diamond,
LeadingDiamond: "\ue0b6",
TrailingDiamond: "\ue0b0",
Background: "p:yellow",
Foreground: "p:black",
Template: " {{ if .SSHSession }}\ueba9 {{ end }}{{ .UserName }} ",
},
{
Type: PATH,
Style: Powerline,
PowerlineSymbol: "\ue0b0",
Background: "p:orange",
Foreground: "p:white",
Properties: properties.Map{
properties.Style: "folder",
},
Template: " \uea83 {{ path .Path .Location }} ",
},
{
Type: GIT,
Style: Powerline,
PowerlineSymbol: "\ue0b0",
Background: "p:green",
BackgroundTemplates: []string{
"{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}",
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}",
"{{ if gt .Ahead 0 }}#49416D{{ end }}",
"{{ if gt .Behind 0 }}#7A306C{{ end }}",
},
Foreground: "p:black",
ForegroundTemplates: []string{
"{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}",
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}",
"{{ if gt .Ahead 0 }}p:white{{ end }}",
},
Properties: properties.Map{
segments.BranchMaxLength: 25,
segments.FetchStatus: true,
segments.FetchUpstreamIcon: true,
},
Template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} ", //nolint:lll
},
{
Type: ROOT,
Style: Powerline,
PowerlineSymbol: "\ue0b0",
Background: "p:yellow",
Foreground: "p:white",
Template: " \uf0e7 ",
},
{
Type: STATUS,
Style: Diamond,
LeadingDiamond: "<transparent,background>\ue0b0</>",
TrailingDiamond: "\ue0b4",
Background: "p:blue",
BackgroundTemplates: []string{
exitBackgroundTemplate,
},
Foreground: "p:white",
Properties: properties.Map{
properties.AlwaysEnabled: true,
},
Template: exitTemplate,
},
},
},
{
Type: RPrompt,
Segments: []*Segment{
{
Type: NODE,
Style: Plain,
Background: "transparent",
Foreground: "p:green",
Template: "\ue718 ",
Properties: properties.Map{
segments.HomeEnabled: false,
segments.FetchPackageManager: false,
segments.DisplayMode: "files",
},
},
{
Type: GOLANG,
Style: Plain,
Background: "transparent",
Foreground: "p:blue",
Template: "\ue626 ",
Properties: properties.Map{
properties.FetchVersion: false,
},
},
{
Type: PYTHON,
Style: Plain,
Background: "transparent",
Foreground: "p:yellow",
Template: "\ue235 ",
Properties: properties.Map{
properties.FetchVersion: false,
segments.DisplayMode: "files",
segments.FetchVirtualEnv: false,
},
},
{
Type: SHELL,
Style: Plain,
Background: "transparent",
Foreground: "p:white",
Template: "in <p:blue><b>{{ .Name }}</b></> ",
},
{
Type: TIME,
Style: Plain,
Background: "transparent",
Foreground: "p:white",
Template: "at <p:blue><b>{{ .CurrentDate | date \"15:04:05\" }}</b></>",
},
},
},
},
ConsoleTitleTemplate: "{{ .Shell }} in {{ .Folder }}",
Palette: ansi.Palette{
"black": "#262B44",
"blue": "#4B95E9",
"green": "#59C9A5",
"orange": "#F07623",
"red": "#D81E5B",
"white": "#E0DEF4",
"yellow": "#F3AE35",
},
SecondaryPrompt: &Segment{
Background: "transparent",
Foreground: "p:black",
Template: "<p:yellow,transparent>\ue0b6</><,p:yellow> > </><p:yellow,transparent>\ue0b0</> ",
},
TransientPrompt: &Segment{
Background: "transparent",
Foreground: "p:black",
Template: "<p:yellow,transparent>\ue0b6</><,p:yellow> {{ .Folder }} </><p:yellow,transparent>\ue0b0</> ",
},
Tooltips: []*Segment{
{
Type: AWS,
Style: Diamond,
LeadingDiamond: "\ue0b0",
TrailingDiamond: "\ue0b4",
Background: "p:orange",
Foreground: "p:white",
Template: " \ue7ad {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} ",
Properties: properties.Map{
properties.DisplayDefault: true,
},
Tips: []string{"aws"},
},
{
Type: AZ,
Style: Diamond,
LeadingDiamond: "\ue0b0",
TrailingDiamond: "\ue0b4",
Background: "p:blue",
Foreground: "p:white",
Template: " \uebd8 {{ .Name }} ",
Properties: properties.Map{
properties.DisplayDefault: true,
},
Tips: []string{"az"},
},
},
}
cfg.env = env
return cfg
}

View file

@ -1,106 +0,0 @@
package engine
import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/mock"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/stretchr/testify/assert"
mock2 "github.com/stretchr/testify/mock"
)
func TestEscapeGlyphs(t *testing.T) {
cases := []struct {
Input string
Expected string
}{
{Input: "󰉋", Expected: "\\udb80\\ude4b"},
{Input: "a", Expected: "a"},
{Input: "\ue0b4", Expected: "\\ue0b4"},
{Input: "\ufd03", Expected: "\\ufd03"},
{Input: "}", Expected: "}"},
{Input: "🏚", Expected: "🏚"},
{Input: "\U000F011B", Expected: "\\udb80\\udd1b"},
{Input: "󰄛", Expected: "\\udb80\\udd1b"},
}
for _, tc := range cases {
assert.Equal(t, tc.Expected, escapeGlyphs(tc.Input, false), tc.Input)
}
}
func TestGetPalette(t *testing.T) {
palette := ansi.Palette{
"red": "#ff0000",
"blue": "#0000ff",
}
cases := []struct {
Case string
Palettes *ansi.Palettes
Palette ansi.Palette
ExpectedPalette ansi.Palette
}{
{
Case: "match",
Palettes: &ansi.Palettes{
Template: "{{ .Shell }}",
List: map[string]ansi.Palette{
"bash": palette,
"zsh": {
"red": "#ff0001",
"blue": "#0000fb",
},
},
},
ExpectedPalette: palette,
},
{
Case: "no match, no fallback",
Palettes: &ansi.Palettes{
Template: "{{ .Shell }}",
List: map[string]ansi.Palette{
"fish": palette,
"zsh": {
"red": "#ff0001",
"blue": "#0000fb",
},
},
},
ExpectedPalette: nil,
},
{
Case: "no match, default",
Palettes: &ansi.Palettes{
Template: "{{ .Shell }}",
List: map[string]ansi.Palette{
"zsh": {
"red": "#ff0001",
"blue": "#0000fb",
},
},
},
Palette: palette,
ExpectedPalette: palette,
},
{
Case: "no palettes",
ExpectedPalette: nil,
},
}
for _, tc := range cases {
env := &mock.MockedEnvironment{}
env.On("TemplateCache").Return(&platform.TemplateCache{
Env: map[string]string{},
Shell: "bash",
})
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
cfg := &Config{
env: env,
Palette: tc.Palette,
Palettes: tc.Palettes,
}
got := cfg.getPalette()
assert.Equal(t, tc.ExpectedPalette, got, tc.Case)
}
}

View file

@ -1,288 +0,0 @@
package engine
import (
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
)
var (
cycle *ansi.Cycle = &ansi.Cycle{}
)
type Engine struct {
Config *Config
Env platform.Environment
Writer *ansi.Writer
Plain bool
console strings.Builder
currentLineLength int
rprompt string
rpromptLength int
}
func (e *Engine) write(text string) {
e.console.WriteString(text)
}
func (e *Engine) string() string {
text := e.console.String()
e.console.Reset()
return text
}
func (e *Engine) canWriteRightBlock(rprompt bool) (int, bool) {
if rprompt && (len(e.rprompt) == 0) {
return 0, false
}
consoleWidth, err := e.Env.TerminalWidth()
if err != nil || consoleWidth == 0 {
return 0, false
}
promptWidth := e.currentLineLength
availableSpace := consoleWidth - promptWidth
// spanning multiple lines
if availableSpace < 0 {
overflow := promptWidth % consoleWidth
availableSpace = consoleWidth - overflow
}
if rprompt {
availableSpace -= e.rpromptLength
}
promptBreathingRoom := 5
if rprompt {
promptBreathingRoom = 30
}
canWrite := availableSpace >= promptBreathingRoom
return availableSpace, canWrite
}
func (e *Engine) writeRPrompt() {
space, OK := e.canWriteRightBlock(true)
if !OK {
return
}
e.write(e.Writer.SaveCursorPosition())
e.write(strings.Repeat(" ", space))
e.write(e.rprompt)
e.write(e.Writer.RestoreCursorPosition())
}
func (e *Engine) pwd() {
// only print when supported
sh := e.Env.Shell()
if sh == shell.ELVISH || sh == shell.XONSH {
return
}
// only print when relevant
if len(e.Config.PWD) == 0 && !e.Config.OSC99 {
return
}
cwd := e.Env.Pwd()
// in BASH, we need to escape the path
if e.Env.Shell() == shell.BASH {
cwd = strings.ReplaceAll(cwd, `\`, `\\`)
}
// Backwards compatibility for deprecated OSC99
if e.Config.OSC99 {
e.write(e.Writer.ConsolePwd(ansi.OSC99, "", "", cwd))
return
}
// Allow template logic to define when to enable the PWD (when supported)
tmpl := &template.Text{
Template: e.Config.PWD,
Env: e.Env,
}
pwdType, err := tmpl.Render()
if err != nil || len(pwdType) == 0 {
return
}
user := e.Env.User()
host, _ := e.Env.Host()
e.write(e.Writer.ConsolePwd(pwdType, user, host, cwd))
}
func (e *Engine) newline() {
// WARP terminal will remove \n from the prompt, so we hack a newline in
if e.isWarp() {
e.write(e.Writer.LineBreak())
} else {
e.write("\n")
}
e.currentLineLength = 0
}
func (e *Engine) isWarp() bool {
return e.Env.Getenv("TERM_PROGRAM") == "WarpTerminal"
}
func (e *Engine) isIterm() bool {
return e.Env.Getenv("TERM_PROGRAM") == "iTerm.app"
}
func (e *Engine) shouldFill(filler string, remaining, blockLength int) (string, bool) {
if len(filler) == 0 {
return "", false
}
padLength := remaining - blockLength
if padLength <= 0 {
return "", false
}
// allow for easy color overrides and templates
e.Writer.Write("", "", filler)
filler, lenFiller := e.Writer.String()
if lenFiller == 0 {
return "", false
}
repeat := padLength / lenFiller
return strings.Repeat(filler, repeat), true
}
func (e *Engine) getTitleTemplateText() string {
tmpl := &template.Text{
Template: e.Config.ConsoleTitleTemplate,
Env: e.Env,
}
if text, err := tmpl.Render(); err == nil {
return text
}
return ""
}
func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool {
defer e.patchPowerShellBleed()
// This is deprecated but we leave it in to not break configs
// It is encouraged to used "newline": true on block level
// rather than the standalone the linebreak block
if block.Type == LineBreak {
// do not print a newline to avoid a leading space
// when we're printin the first primary prompt in
// the shell
if !cancelNewline {
e.newline()
}
return false
}
// when in bash, for rprompt blocks we need to write plain
// and wrap in escaped mode or the prompt will not render correctly
if e.Env.Shell() == shell.BASH && block.Type == RPrompt {
block.InitPlain(e.Env, e.Config)
} else {
block.Init(e.Env, e.Writer)
}
if !block.Enabled() {
return false
}
// do not print a newline to avoid a leading space
// when we're printin the first primary prompt in
// the shell
if block.Newline && !cancelNewline {
e.newline()
}
text, length := block.RenderSegments()
// do not print anything when we don't have any text
if length == 0 {
return false
}
switch block.Type { //nolint:exhaustive
case Prompt:
if block.VerticalOffset != 0 {
e.write(e.Writer.ChangeLine(block.VerticalOffset))
}
if block.Alignment == Left {
e.currentLineLength += length
e.write(text)
return true
}
if block.Alignment != Right {
return false
}
space, OK := e.canWriteRightBlock(false)
// we can't print the right block as there's not enough room available
if !OK {
switch block.Overflow {
case Break:
e.newline()
case Hide:
// make sure to fill if needed
if padText, OK := e.shouldFill(block.Filler, space, 0); OK {
e.write(padText)
}
e.currentLineLength = 0
return true
}
}
defer func() {
e.currentLineLength = 0
}()
// validate if we have a filler and fill if needed
if padText, OK := e.shouldFill(block.Filler, space, length); OK {
e.write(padText)
e.write(text)
return true
}
var prompt string
space -= length
if space > 0 {
prompt += strings.Repeat(" ", space)
}
prompt += text
e.write(prompt)
case RPrompt:
e.rprompt = text
e.rpromptLength = length
}
return true
}
func (e *Engine) patchPowerShellBleed() {
// when in PowerShell, we need to clear the line after the prompt
// to avoid the background being printed on the next line
// when at the end of the buffer.
// See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65
if e.Env.Shell() != shell.PWSH && e.Env.Shell() != shell.PWSH5 {
return
}
// only do this when enabled
if !e.Config.PatchPwshBleed {
return
}
e.write(e.Writer.ClearAfter())
}

View file

@ -1,86 +0,0 @@
package engine
import (
"os"
"path/filepath"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/stretchr/testify/assert"
)
var cases = []struct {
Case string
Config string
}{
{Case: ".omp.json suffix", Config: "~/jandedobbeleer.omp.json"},
{Case: ".omp.yaml suffix", Config: "~/jandedobbeleer.omp.yaml"},
{Case: ".omp.yml suffix", Config: "~/jandedobbeleer.omp.yml"},
{Case: ".omp.toml suffix", Config: "~/jandedobbeleer.omp.toml"},
{Case: ".json suffix", Config: "~/jandedobbeleer.json"},
{Case: ".yaml suffix", Config: "~/jandedobbeleer.yaml"},
{Case: ".yml suffix", Config: "~/jandedobbeleer.yml"},
{Case: ".toml suffix", Config: "~/jandedobbeleer.toml"},
}
func runImageTest(config, content string) (string, error) {
poshImagePath := "jandedobbeleer.png"
file, err := os.CreateTemp("", poshImagePath)
if err != nil {
return "", err
}
defer os.Remove(file.Name())
writer := &ansi.Writer{}
writer.Init(shell.GENERIC)
image := &ImageRenderer{
AnsiString: content,
Ansi: writer,
}
env := &platform.Shell{
CmdFlags: &platform.Flags{
Config: config,
},
}
err = image.Init(env)
if err != nil {
return "", err
}
err = image.SavePNG()
if err == nil {
os.Remove(image.Path)
}
return filepath.Base(image.Path), err
}
func TestStringImageFileWithText(t *testing.T) {
for _, tc := range cases {
filename, err := runImageTest(tc.Config, "foobar")
if connectionError, ok := err.(*ConnectionError); ok {
t.Log(connectionError.Error())
continue
}
assert.Equal(t, "jandedobbeleer.png", filename, tc.Case)
assert.NoError(t, err)
}
}
func TestStringImageFileWithANSI(t *testing.T) {
prompt := ` jan  `
for _, tc := range cases {
filename, err := runImageTest(tc.Config, prompt)
if connectionError, ok := err.(*ConnectionError); ok {
t.Log(connectionError.Error())
continue
}
assert.Equal(t, "jandedobbeleer.png", filename, tc.Case)
assert.NoError(t, err)
}
}

View file

@ -1,61 +0,0 @@
package engine
import (
"context"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
)
type codePoints map[uint64]uint64
func getGlyphCodePoints() (codePoints, error) {
var codePoints = make(codePoints)
ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cncl()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://ohmyposh.dev/codepoints.csv", nil)
if err != nil {
return codePoints, &ConnectionError{reason: err.Error()}
}
response, err := platform.Client.Do(request)
if err != nil {
return codePoints, err
}
defer response.Body.Close()
bytes, err := io.ReadAll(response.Body)
if err != nil {
return codePoints, err
}
lines := strings.Split(string(bytes), "\n")
for _, line := range lines {
fields := strings.Split(line, ",")
if len(fields) < 2 {
continue
}
oldGlyph, err := strconv.ParseUint(fields[0], 16, 32)
if err != nil {
continue
}
newGlyph, err := strconv.ParseUint(fields[1], 16, 32)
if err != nil {
continue
}
codePoints[oldGlyph] = newGlyph
}
return codePoints, nil
}

View file

@ -1,16 +0,0 @@
package engine
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetCodePoints(t *testing.T) {
codepoints, err := getGlyphCodePoints()
if connectionError, ok := err.(*ConnectionError); ok {
t.Log(connectionError.Error())
return
}
assert.Equal(t, 1939, len(codepoints))
}

View file

@ -1,60 +0,0 @@
package engine
import (
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
)
// New returns a prompt engine initialized with the
// given configuration options, and is ready to print any
// of the prompt components.
func New(flags *platform.Flags) *Engine {
env := &platform.Shell{
CmdFlags: flags,
}
env.Init()
cfg := LoadConfig(env)
if cfg.PatchPwshBleed {
patchPowerShellBleed(env.Shell(), flags)
}
env.Var = cfg.Var
flags.HasTransient = cfg.TransientPrompt != nil
ansiWriter := &ansi.Writer{
TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: cfg.MakeColors(),
Plain: flags.Plain,
TrueColor: env.CmdFlags.TrueColor,
}
ansiWriter.Init(env.Shell())
eng := &Engine{
Config: cfg,
Env: env,
Writer: ansiWriter,
Plain: flags.Plain,
}
return eng
}
func patchPowerShellBleed(sh string, flags *platform.Flags) {
// when in PowerShell, and force patching the bleed bug
// we need to reduce the terminal width by 1 so the last
// character isn't cut off by the ANSI escape sequences
// See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65
if sh != shell.PWSH && sh != shell.PWSH5 {
return
}
// only do this when relevant
if flags.TerminalWidth <= 0 {
return
}
flags.TerminalWidth--
}

View file

@ -1,239 +0,0 @@
package engine
import (
"fmt"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
)
type ExtraPromptType int
const (
Transient ExtraPromptType = iota
Valid
Error
Secondary
Debug
)
func (e *Engine) Primary() string {
if e.Config.ShellIntegration {
exitCode, _ := e.Env.StatusCodes()
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
e.write(e.Writer.PromptStart())
}
// cache a pointer to the color cycle
cycle = &e.Config.Cycle
var cancelNewline, didRender bool
for i, block := range e.Config.Blocks {
// do not print a leading newline when we're at the first row and the prompt is cleared
if i == 0 {
row, _ := e.Env.CursorPosition()
cancelNewline = e.Env.Flags().Cleared || e.Env.Flags().PromptCount == 1 || row == 1
}
// skip setting a newline when we didn't print anything yet
if i != 0 {
cancelNewline = !didRender
}
// only render rprompt for shells where we need it from the primary prompt
renderRPrompt := true
switch e.Env.Shell() {
case shell.ELVISH, shell.FISH, shell.NU, shell.XONSH, shell.CMD:
renderRPrompt = false
}
if block.Type == RPrompt && !renderRPrompt {
continue
}
if e.renderBlock(block, cancelNewline) {
didRender = true
}
}
if len(e.Config.ConsoleTitleTemplate) > 0 && !e.Env.Flags().Plain {
title := e.getTitleTemplateText()
e.write(e.Writer.FormatTitle(title))
}
if e.Config.FinalSpace {
e.write(" ")
e.currentLineLength++
}
if e.Config.ITermFeatures != nil && e.isIterm() {
host, _ := e.Env.Host()
e.write(e.Writer.RenderItermFeatures(e.Config.ITermFeatures, e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host))
}
if e.Config.ShellIntegration && e.Config.TransientPrompt == nil {
e.write(e.Writer.CommandStart())
}
e.pwd()
switch e.Env.Shell() {
case shell.ZSH:
if !e.Env.Flags().Eval {
break
}
// Warp doesn't support RPROMPT so we need to write it manually
if e.isWarp() {
e.writeRPrompt()
// escape double quotes contained in the prompt
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`))
return prompt
}
// escape double quotes contained in the prompt
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), `"`, `\"`))
prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt)
return prompt
case shell.PWSH, shell.PWSH5, shell.GENERIC:
e.writeRPrompt()
case shell.BASH:
space, OK := e.canWriteRightBlock(true)
if !OK {
break
}
// in bash, the entire rprompt needs to be escaped for the prompt to be interpreted correctly
// see https://github.com/jandedobbeleer/oh-my-posh/pull/2398
writer := &ansi.Writer{
TrueColor: e.Env.Flags().TrueColor,
}
writer.Init(shell.GENERIC)
prompt := writer.SaveCursorPosition()
prompt += strings.Repeat(" ", space)
prompt += e.rprompt
prompt += writer.RestoreCursorPosition()
prompt = e.Writer.FormatText(prompt)
e.write(prompt)
}
return e.string()
}
func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
// populate env with latest context
e.Env.LoadTemplateCache()
var prompt *Segment
switch promptType {
case Debug:
prompt = e.Config.DebugPrompt
case Transient:
prompt = e.Config.TransientPrompt
case Valid:
prompt = e.Config.ValidLine
case Error:
prompt = e.Config.ErrorLine
case Secondary:
prompt = e.Config.SecondaryPrompt
}
if prompt == nil {
prompt = &Segment{}
}
getTemplate := func(template string) string {
if len(template) != 0 {
return template
}
switch promptType { //nolint: exhaustive
case Debug:
return "[DBG]: "
case Transient:
return "{{ .Shell }}> "
case Secondary:
return "> "
default:
return ""
}
}
tmpl := &template.Text{
Template: getTemplate(prompt.Template),
Env: e.Env,
}
promptText, err := tmpl.Render()
if err != nil {
promptText = err.Error()
}
if promptType == Transient && e.Config.ShellIntegration {
exitCode, _ := e.Env.StatusCodes()
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
e.write(e.Writer.PromptStart())
}
foreground := prompt.ForegroundTemplates.FirstMatch(nil, e.Env, prompt.Foreground)
background := prompt.BackgroundTemplates.FirstMatch(nil, e.Env, prompt.Background)
e.Writer.SetColors(background, foreground)
e.Writer.Write(background, foreground, promptText)
str, length := e.Writer.String()
if promptType == Transient {
consoleWidth, err := e.Env.TerminalWidth()
if err == nil || consoleWidth != 0 {
if padText, OK := e.shouldFill(prompt.Filler, consoleWidth, length); OK {
str += padText
}
}
}
if promptType == Transient && e.Config.ShellIntegration {
str += e.Writer.CommandStart()
}
switch e.Env.Shell() {
case shell.ZSH:
// escape double quotes contained in the prompt
if promptType == Transient {
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(str, "\"", "\"\""))
// empty RPROMPT
prompt += "\nRPROMPT=\"\""
return prompt
}
return str
case shell.PWSH, shell.PWSH5:
// Return the string and empty our buffer
// clear the line afterwards to prevent text from being written on the same line
// see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3628
return str + e.Writer.ClearAfter()
case shell.CMD, shell.BASH, shell.FISH, shell.NU, shell.GENERIC:
// Return the string and empty our buffer
return str
}
return ""
}
func (e *Engine) RPrompt() string {
filterRPromptBlock := func(blocks []*Block) *Block {
for _, block := range blocks {
if block.Type == RPrompt {
return block
}
}
return nil
}
block := filterRPromptBlock(e.Config.Blocks)
if block == nil {
return ""
}
block.Init(e.Env, e.Writer)
if !block.Enabled() {
return ""
}
text, length := block.RenderSegments()
e.rpromptLength = length
return text
}

View file

@ -1,73 +0,0 @@
package engine
import (
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
)
func (e *Engine) Tooltip(tip string) string {
tip = strings.Trim(tip, " ")
var tooltip *Segment
for _, tp := range e.Config.Tooltips {
if !tp.shouldInvokeWithTip(tip) {
continue
}
tooltip = tp
}
if tooltip == nil {
return ""
}
if err := tooltip.mapSegmentWithWriter(e.Env); err != nil {
return ""
}
if !tooltip.writer.Enabled() {
return ""
}
tooltip.Enabled = true
// little hack to reuse the current logic
block := &Block{
Alignment: Right,
Segments: []*Segment{tooltip},
}
switch e.Env.Shell() {
case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC:
block.Init(e.Env, e.Writer)
if !block.Enabled() {
return ""
}
text, _ := block.RenderSegments()
return text
case shell.PWSH, shell.PWSH5:
block.InitPlain(e.Env, e.Config)
if !block.Enabled() {
return ""
}
consoleWidth, err := e.Env.TerminalWidth()
if err != nil || consoleWidth == 0 {
return ""
}
text, length := block.RenderSegments()
space := consoleWidth - e.Env.Flags().Column - length
if space <= 0 {
return ""
}
// clear from cursor to the end of the line in case a previous tooltip
// is cut off and partially preserved, if the new one is shorter
e.write(e.Writer.ClearAfter())
e.write(strings.Repeat(" ", space))
e.write(text)
return e.string()
}
return ""
}

View file

@ -10,9 +10,14 @@ import (
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
)
var program *tea.Program
var (
program *tea.Program
environment runtime.Environment
)
const listHeight = 14
@ -113,6 +118,7 @@ func downloadFontZip(location string) {
program.Send(errMsg(err))
return
}
program.Send(zipMsg(zipFile))
}
@ -122,6 +128,7 @@ func installLocalFontZIP(zipFile string, user bool) {
program.Send(errMsg(err))
return
}
installFontZIP(data, user)
}
@ -131,6 +138,7 @@ func installFontZIP(zipFile []byte, user bool) {
program.Send(errMsg(err))
return
}
program.Send(successMsg(families))
}
@ -158,6 +166,7 @@ func (m *main) Init() tea.Cmd {
}
go getFontsList()
}()
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170"))
@ -166,6 +175,7 @@ func (m *main) Init() tea.Cmd {
if isLocalZipFile() {
m.state = unzipFont
}
return m.spinner.Tick
}
@ -231,6 +241,10 @@ func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}
if m.list == nil {
return m, nil
}
lst, cmd := m.list.Update(msg)
m.list = &lst
return m, cmd
@ -240,36 +254,48 @@ func (m *main) View() string {
if m.err != nil {
return textStyle.Render(m.err.Error())
}
switch m.state {
case getFonts:
return textStyle.Render(fmt.Sprintf("%s Downloading font list", m.spinner.View()))
return textStyle.Render(fmt.Sprintf("%s Downloading font list%s", m.spinner.View(), terminal.StartProgress()))
case selectFont:
return "\n" + m.list.View()
return fmt.Sprintf("\n%s%s", m.list.View(), terminal.StopProgress())
case downloadFont:
return textStyle.Render(fmt.Sprintf("%s Downloading %s", m.spinner.View(), m.font))
return textStyle.Render(fmt.Sprintf("%s Downloading %s%s", m.spinner.View(), m.font, terminal.StartProgress()))
case unzipFont:
return textStyle.Render(fmt.Sprintf("%s Extracting %s", m.spinner.View(), m.font))
case installFont:
return textStyle.Render(fmt.Sprintf("%s Installing %s", m.spinner.View(), m.font))
case quit:
return textStyle.Render("No need to install a new font? That's cool.")
return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress()))
case done:
var builder strings.Builder
builder.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n", m.font))
builder.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.font, terminal.StopProgress()))
builder.WriteString("The following font families are now available for configuration:\n")
for _, family := range m.families {
builder.WriteString(fmt.Sprintf(" • %s\n", family))
for i, family := range m.families {
builder.WriteString(fmt.Sprintf(" • %s", family))
if i < len(m.families)-1 {
builder.WriteString("\n")
}
}
return textStyle.Render(builder.String())
}
return ""
}
func Run(font string, system bool) {
func Run(font string, env runtime.Environment) {
main := &main{
font: font,
system: system,
system: env.Root(),
}
environment = env
program = tea.NewProgram(main)
if _, err := program.Run(); err != nil {
print("Error running program: %v", err)

View file

@ -8,10 +8,10 @@ import (
"errors"
"fmt"
"io"
"net/http"
httplib "net/http"
"net/url"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/http"
)
func Download(fontPath string) ([]byte, error) {
@ -33,22 +33,22 @@ func Download(fontPath string) ([]byte, error) {
}
func isZipFile(data []byte) bool {
contentType := http.DetectContentType(data)
contentType := httplib.DetectContentType(data)
return contentType == "application/zip"
}
func getRemoteFile(location string) (data []byte, err error) {
req, err := http.NewRequestWithContext(context.Background(), "GET", location, nil)
req, err := httplib.NewRequestWithContext(context.Background(), "GET", location, nil)
if err != nil {
return nil, err
}
resp, err := platform.Client.Do(req)
resp, err := http.HTTPClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp.StatusCode != httplib.StatusOK {
return data, fmt.Errorf("Failed to download zip file: %s\n→ %s", resp.Status, location)
}

View file

@ -5,12 +5,13 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
httplib "net/http"
"sort"
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/http"
)
type release struct {
@ -26,6 +27,10 @@ type Asset struct {
func (a Asset) FilterValue() string { return a.Name }
func Fonts() ([]*Asset, error) {
if assets, err := getCachedFontData(); err == nil {
return assets, nil
}
assets, err := fetchFontAssets("ryanoasis/nerd-fonts")
if err != nil {
return nil, err
@ -39,9 +44,43 @@ func Fonts() ([]*Asset, error) {
assets = append(assets, cascadiaCode...)
sort.Slice(assets, func(i, j int) bool { return assets[i].Name < assets[j].Name })
setCachedFontData(assets)
return assets, nil
}
func getCachedFontData() ([]*Asset, error) {
if environment == nil {
return nil, errors.New("environment not set")
}
list, OK := environment.Cache().Get(cache.FONTLISTCACHE)
if !OK {
return nil, errors.New("cache not found")
}
assets := make([]*Asset, 0)
err := json.Unmarshal([]byte(list), &assets)
if err != nil {
return nil, err
}
return assets, nil
}
func setCachedFontData(assets []*Asset) {
if environment == nil {
return
}
data, err := json.Marshal(assets)
if err != nil {
return
}
environment.Cache().Set(cache.FONTLISTCACHE, string(data), cache.ONEDAY)
}
func CascadiaCode() ([]*Asset, error) {
return fetchFontAssets("microsoft/cascadia-code")
}
@ -51,14 +90,14 @@ func fetchFontAssets(repo string) ([]*Asset, error) {
defer cancelF()
repoURL := "https://api.github.com/repos/" + repo + "/releases/latest"
req, err := http.NewRequestWithContext(ctx, "GET", repoURL, nil)
req, err := httplib.NewRequestWithContext(ctx, "GET", repoURL, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/vnd.github.v3+json")
response, err := platform.Client.Do(req)
if err != nil || response.StatusCode != http.StatusOK {
response, err := http.HTTPClient.Do(req)
if err != nil || response.StatusCode != httplib.StatusOK {
return nil, fmt.Errorf("failed to get %s release", repo)
}

View file

@ -7,7 +7,11 @@ import (
"bytes"
"io"
"path"
stdruntime "runtime"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd"
)
func contains[S ~[]E, E comparable](s S, e E) bool {
@ -31,6 +35,12 @@ func InstallZIP(data []byte, user bool) ([]string, error) {
fonts := make(map[string]*Font)
for _, zf := range zipReader.File {
// prevent zipslip attacks
// https://security.snyk.io/research/zip-slip-vulnerability
if strings.Contains(zf.Name, "..") {
continue
}
rc, err := zf.Open()
if err != nil {
return families, err
@ -42,7 +52,7 @@ func InstallZIP(data []byte, user bool) ([]string, error) {
return families, err
}
fontData, err := newFont(zf.Name, data)
fontData, err := newFont(path.Base(zf.Name), data)
if err != nil {
continue
}
@ -69,5 +79,10 @@ func InstallZIP(data []byte, user bool) ([]string, error) {
}
}
// Update the font cache when installing fonts on Linux
if stdruntime.GOOS == runtime.LINUX || stdruntime.GOOS == runtime.DARWIN {
_, _ = cmd.Run("fc-cache", "-f")
}
return families, nil
}

View file

@ -10,15 +10,23 @@ import (
"strings"
)
// FontsDir denotes the path to the user's fonts directory on Unix-like systems.
var FontsDir = path.Join(os.Getenv("HOME"), "/.local/share/fonts")
var (
fontsDir = path.Join(os.Getenv("HOME"), "/.local/share/fonts")
systemFontsDir = "/usr/share/fonts"
)
func install(font *Font, _ bool) error {
// If we're running as root, install the font system-wide.
targetDir := fontsDir
if os.Geteuid() == 0 {
targetDir = systemFontsDir
}
// On Linux, fontconfig can understand subdirectories. So, to keep the
// font directory clean, install all font files for a particular font
// family into a subdirectory named after the family (with hyphens instead
// of spaces).
fullPath := path.Join(FontsDir,
fullPath := path.Join(targetDir,
strings.ToLower(strings.ReplaceAll(font.Family, " ", "-")),
path.Base(font.FileName))

View file

@ -43,6 +43,7 @@ func install(font *Font, admin bool) error {
return fmt.Errorf("Unable to remove existing font file: %s", err.Error())
}
}
err := os.WriteFile(fullPath, font.Data, 0644)
if err != nil {
return fmt.Errorf("Unable to write font file: %s", err.Error())

View file

@ -12,35 +12,35 @@ require (
github.com/fogleman/gg v1.3.0
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4
github.com/huandu/xstrings v1.4.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.4
github.com/shirou/gopsutil/v3 v3.24.5
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0
github.com/wayneashleyberry/terminal-dimensions v1.1.0
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/image v0.16.0
golang.org/x/sys v0.20.0
golang.org/x/text v0.15.0
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/image v0.19.0
golang.org/x/sys v0.23.0
golang.org/x/text v0.17.0
gopkg.in/ini.v1 v1.67.0
)
require (
github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.26.2
github.com/charmbracelet/lipgloss v0.10.0
github.com/charmbracelet/bubbletea v0.26.6
github.com/charmbracelet/lipgloss v0.12.1
github.com/goccy/go-json v0.10.3
github.com/goccy/go-yaml v1.11.3
github.com/gookit/goutil v0.6.15
github.com/hashicorp/hcl/v2 v2.20.1
github.com/mattn/go-runewidth v0.0.15
github.com/gookit/goutil v0.6.16
github.com/hashicorp/hcl/v2 v2.21.0
github.com/mattn/go-runewidth v0.0.16
github.com/pelletier/go-toml/v2 v2.2.2
github.com/spf13/cobra v1.8.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/mod v0.17.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/mod v0.20.0
gopkg.in/yaml.v3 v3.0.1
)
@ -50,13 +50,13 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
)
@ -66,9 +66,13 @@ require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/charmbracelet/x/input v0.1.2 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.2 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@ -84,12 +88,11 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zclconf/go-cty v1.14.3 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/tools v0.19.0 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
)
@ -97,4 +100,4 @@ replace github.com/atotto/clipboard v0.1.4 => github.com/jandedobbeleer/clipboar
replace github.com/shirou/gopsutil/v3 v3.23.9 => github.com/jandedobbeleer/gopsutil/v3 v3.23.9-1
replace github.com/goccy/go-yaml v1.11.3 => github.com/jandedobbeleer/go-yaml v1.11.3-2
replace github.com/goccy/go-yaml v1.11.3 => github.com/jandedobbeleer/go-yaml v1.11.3-4

View file

@ -25,11 +25,19 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.2 h1:Eeb+n75Om9gQ+I6YpbCXQRKHt5Pn4vMwusQpwLiEgJQ=
github.com/charmbracelet/bubbletea v0.26.2/go.mod h1:6I0nZ3YHUrQj7YHIHlM8RySX4ZIthTliMY+W8X8b+Gs=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/input v0.1.2 h1:QJAZr33eOhDowkkEQ24rsJy4Llxlm+fRDf/cQrmqJa0=
github.com/charmbracelet/x/input v0.1.2/go.mod h1:LGBim0maUY4Pitjn/4fHnuXb4KirU3DODsyuHuXdOyA=
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg=
github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -40,8 +48,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/esimov/stackblur-go v1.1.0 h1:fwnZJC/7sHFzu4CDMgdJ1QxMN/q3k5MGILuoU4hH6oQ=
github.com/esimov/stackblur-go v1.1.0/go.mod h1:7PcTPCHHKStxbZvBkUlQJjRclqjnXtQ0NoORZt1AlHE=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@ -61,8 +69,6 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -70,13 +76,13 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo=
github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY=
github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc=
github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4=
github.com/gookit/goutil v0.6.16 h1:9fRMCF4X9abdRD5+2HhBS/GwafjBlTUBjRtA5dgkvuw=
github.com/gookit/goutil v0.6.16/go.mod h1:op2q8AoPDFSiY2+qkHxcBWQMYxOLQ1GbLXqe7vrwscI=
github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
@ -84,8 +90,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jandedobbeleer/clipboard v0.1.4-1 h1:rJehm5W0a3hvjcxyB3snqLBV4yvMBBc12JyMP7ngNQw=
github.com/jandedobbeleer/clipboard v0.1.4-1/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/jandedobbeleer/go-yaml v1.11.3-2 h1:mz6E/lRl29RqwYkb4HC03C2YwF5tVDNdfiPWa1hYRdo=
github.com/jandedobbeleer/go-yaml v1.11.3-2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/jandedobbeleer/go-yaml v1.11.3-4 h1:+S8q91z8LmfSxwhon2ndSk5pZdFpD3aYMyo2tUQb6P0=
github.com/jandedobbeleer/go-yaml v1.11.3-4/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -101,9 +107,8 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -112,8 +117,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
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/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -134,7 +139,6 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -148,22 +152,22 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/gofontwoff v0.0.0-20181114050219-180f79e6909d h1:lvCTyBbr36+tqMccdGMwuEU+hjux/zL6xSmf5S9ITaA=
github.com/shurcooL/gofontwoff v0.0.0-20181114050219-180f79e6909d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -179,12 +183,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g=
github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg=
@ -193,30 +195,30 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zclconf/go-cty v1.14.3 h1:1JXy1XroaGrzZuG6X9dt7HL6s9AwbY+l4UNL8o5B6ho=
github.com/zclconf/go-cty v1.14.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -230,30 +232,24 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

Some files were not shown because too many files have changed in this diff Show more