mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-11-09 20:44:03 -08:00
feat: add sapling segment
This commit is contained in:
parent
275723d913
commit
dd32018836
|
@ -200,6 +200,8 @@ const (
|
|||
RUBY SegmentType = "ruby"
|
||||
// RUST writes the cargo version information if cargo.toml is present
|
||||
RUST SegmentType = "rust"
|
||||
// SAPLING represents the sapling segment
|
||||
SAPLING SegmentType = "sapling"
|
||||
// SESSION represents the user info segment
|
||||
SESSION SegmentType = "session"
|
||||
// SHELL writes which shell we're currently in
|
||||
|
@ -291,6 +293,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
|
|||
ROOT: func() SegmentWriter { return &segments.Root{} },
|
||||
RUBY: func() SegmentWriter { return &segments.Ruby{} },
|
||||
RUST: func() SegmentWriter { return &segments.Rust{} },
|
||||
SAPLING: func() SegmentWriter { return &segments.Sapling{} },
|
||||
SESSION: func() SegmentWriter { return &segments.Session{} },
|
||||
SHELL: func() SegmentWriter { return &segments.Shell{} },
|
||||
SPOTIFY: func() SegmentWriter { return &segments.Spotify{} },
|
||||
|
|
|
@ -128,7 +128,6 @@ type Git struct {
|
|||
RawUpstreamURL string
|
||||
UpstreamGone bool
|
||||
IsWorkTree bool
|
||||
RepoName string
|
||||
IsBare bool
|
||||
|
||||
// needed for posh-git support
|
||||
|
|
161
src/segments/sapling.go
Normal file
161
src/segments/sapling.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||
)
|
||||
|
||||
// SaplingStatus represents part of the status of a Sapling repository
|
||||
type SaplingStatus struct {
|
||||
ScmStatus
|
||||
}
|
||||
|
||||
func (s *SaplingStatus) add(code string) {
|
||||
// M = modified
|
||||
// A = added
|
||||
// R = removed/deleted
|
||||
// C = clean
|
||||
// ! = missing (deleted by a non-sl command, but still tracked)
|
||||
// ? = not tracked
|
||||
// I = ignored
|
||||
// = origin of the previous file (with --copies)
|
||||
switch code {
|
||||
case "M":
|
||||
s.Modified++
|
||||
case "A":
|
||||
s.Added++
|
||||
case "R":
|
||||
s.Deleted++
|
||||
case "C":
|
||||
s.Clean++
|
||||
case "!":
|
||||
s.Missing++
|
||||
case "?":
|
||||
s.Untracked++
|
||||
case "I":
|
||||
s.Ignored++
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
SAPLINGCOMMAND = "sl"
|
||||
SLCOMMITTEMPLATE = "no:{node}\nns:{sl_node}\nnd:{sl_date}\nun:{sl_user}\nbm:{activebookmark}"
|
||||
)
|
||||
|
||||
type Sapling struct {
|
||||
scm
|
||||
|
||||
ShortHash string
|
||||
Hash string
|
||||
When string
|
||||
Author string
|
||||
Bookmark string
|
||||
|
||||
Working *SaplingStatus
|
||||
}
|
||||
|
||||
func (sl *Sapling) Template() string {
|
||||
return " {{ if .Bookmark }}\uf097 {{ .Bookmark }}*{{ else }}\ue729 {{ .ShortHash }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }} "
|
||||
}
|
||||
|
||||
func (sl *Sapling) Enabled() bool {
|
||||
if !sl.shouldDisplay() {
|
||||
return false
|
||||
}
|
||||
|
||||
sl.setHeadContext()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (sl *Sapling) shouldDisplay() bool {
|
||||
if !sl.hasCommand(SAPLINGCOMMAND) {
|
||||
return false
|
||||
}
|
||||
|
||||
slDir, err := sl.env.HasParentFilePath(".sl")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if sl.shouldIgnoreRootRepository(slDir.ParentFolder) {
|
||||
return false
|
||||
}
|
||||
|
||||
sl.workingDir = slDir.Path
|
||||
sl.rootDir = slDir.Path
|
||||
// convert the worktree file path to a windows one when in a WSL shared folder
|
||||
sl.realDir = strings.TrimSuffix(sl.convertToWindowsPath(slDir.Path), "/.sl")
|
||||
sl.RepoName = platform.Base(sl.env, sl.convertToLinuxPath(sl.realDir))
|
||||
sl.setDir(slDir.Path)
|
||||
return true
|
||||
}
|
||||
|
||||
func (sl *Sapling) setDir(dir string) {
|
||||
dir = platform.ReplaceHomeDirPrefixWithTilde(sl.env, dir) // align with template PWD
|
||||
if sl.env.GOOS() == platform.WINDOWS {
|
||||
sl.Dir = strings.TrimSuffix(dir, `\.sl`)
|
||||
return
|
||||
}
|
||||
sl.Dir = strings.TrimSuffix(dir, "/.sl")
|
||||
}
|
||||
|
||||
func (sl *Sapling) setHeadContext() {
|
||||
sl.setCommitContext()
|
||||
|
||||
sl.Working = &SaplingStatus{}
|
||||
|
||||
displayStatus := sl.props.GetBool(FetchStatus, true)
|
||||
if !displayStatus {
|
||||
return
|
||||
}
|
||||
|
||||
changes := sl.getSaplingCommandOutput("status")
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(changes, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
// element is the element from someSlice for where we are
|
||||
sl.Working.add(line[0:1])
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *Sapling) setCommitContext() {
|
||||
body := sl.getSaplingCommandOutput("log", "--limit", "1", "--template", SLCOMMITTEMPLATE)
|
||||
splitted := strings.Split(strings.TrimSpace(body), "\n")
|
||||
for _, line := range splitted {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) <= 3 {
|
||||
continue
|
||||
}
|
||||
anchor := line[:3]
|
||||
line = line[3:]
|
||||
switch anchor {
|
||||
case "no:":
|
||||
sl.Hash = line
|
||||
case "ns:":
|
||||
sl.ShortHash = line
|
||||
case "nd:":
|
||||
sl.When = line
|
||||
case "un:":
|
||||
sl.Author = line
|
||||
case "bm:":
|
||||
sl.Bookmark = line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *Sapling) getSaplingCommandOutput(command string, args ...string) string {
|
||||
args = append([]string{command}, args...)
|
||||
val, err := sl.env.RunCommand(sl.command, args...)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(val)
|
||||
}
|
248
src/segments/sapling_test.go
Normal file
248
src/segments/sapling_test.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/mock"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
)
|
||||
|
||||
func TestSetDir(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Expected string
|
||||
Path string
|
||||
GOOS string
|
||||
}{
|
||||
{
|
||||
Case: "In home folder",
|
||||
Expected: "~/sapling",
|
||||
Path: "/usr/home/sapling/.sl",
|
||||
GOOS: platform.LINUX,
|
||||
},
|
||||
{
|
||||
Case: "Outside home folder",
|
||||
Expected: "/usr/sapling/repo",
|
||||
Path: "/usr/sapling/repo/.sl",
|
||||
GOOS: platform.LINUX,
|
||||
},
|
||||
{
|
||||
Case: "Windows home folder",
|
||||
Expected: "~\\sapling",
|
||||
Path: "\\usr\\home\\sapling\\.sl",
|
||||
GOOS: platform.WINDOWS,
|
||||
},
|
||||
{
|
||||
Case: "Windows outside home folder",
|
||||
Expected: "\\usr\\sapling\\repo",
|
||||
Path: "\\usr\\sapling\\repo\\.sl",
|
||||
GOOS: platform.WINDOWS,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("GOOS").Return(tc.GOOS)
|
||||
home := "/usr/home"
|
||||
if tc.GOOS == platform.WINDOWS {
|
||||
home = "\\usr\\home"
|
||||
}
|
||||
env.On("Home").Return(home)
|
||||
sl := &Sapling{
|
||||
scm: scm{
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
sl.setDir(tc.Path)
|
||||
assert.Equal(t, tc.Expected, sl.Dir, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCommitContext(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Output string
|
||||
Error error
|
||||
|
||||
ExpectedHash string
|
||||
ExpectedShortHash string
|
||||
ExpectedWhen string
|
||||
ExpectedAuthor string
|
||||
ExpectedBookmark string
|
||||
}{
|
||||
{
|
||||
Case: "Error",
|
||||
Error: errors.New("error"),
|
||||
},
|
||||
{
|
||||
Case: "No output",
|
||||
},
|
||||
{
|
||||
Case: "All output",
|
||||
Output: `
|
||||
no:734349e9f1abd229ec6e9bbebed35aed56b26a9e
|
||||
ns:734349e9f
|
||||
nd:23 minutes ago
|
||||
un:jan
|
||||
bm:sapling-segment
|
||||
`,
|
||||
ExpectedHash: "734349e9f1abd229ec6e9bbebed35aed56b26a9e",
|
||||
ExpectedShortHash: "734349e9f",
|
||||
ExpectedWhen: "23 minutes ago",
|
||||
ExpectedAuthor: "jan",
|
||||
ExpectedBookmark: "sapling-segment",
|
||||
},
|
||||
{
|
||||
Case: "Short line",
|
||||
Output: "er",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("RunCommand", "sl", []string{"log", "--limit", "1", "--template", SLCOMMITTEMPLATE}).Return(tc.Output, tc.Error)
|
||||
sl := &Sapling{
|
||||
scm: scm{
|
||||
env: env,
|
||||
command: SAPLINGCOMMAND,
|
||||
},
|
||||
}
|
||||
sl.setCommitContext()
|
||||
assert.Equal(t, tc.ExpectedHash, sl.Hash, tc.Case)
|
||||
assert.Equal(t, tc.ExpectedShortHash, sl.ShortHash, tc.Case)
|
||||
assert.Equal(t, tc.ExpectedWhen, sl.When, tc.Case)
|
||||
assert.Equal(t, tc.ExpectedAuthor, sl.Author, tc.Case)
|
||||
assert.Equal(t, tc.ExpectedBookmark, sl.Bookmark, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldDisplay(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
HasSapling bool
|
||||
InRepo bool
|
||||
Expected bool
|
||||
Excluded bool
|
||||
}{
|
||||
{
|
||||
Case: "Sapling not installed",
|
||||
},
|
||||
{
|
||||
Case: "Sapling installed, not in repo",
|
||||
HasSapling: true,
|
||||
},
|
||||
{
|
||||
Case: "Sapling installed, in repo but ignored",
|
||||
HasSapling: true,
|
||||
InRepo: true,
|
||||
Excluded: true,
|
||||
},
|
||||
{
|
||||
Case: "Sapling installed, in repo",
|
||||
HasSapling: true,
|
||||
InRepo: true,
|
||||
Expected: true,
|
||||
},
|
||||
}
|
||||
fileInfo := &platform.FileInfo{
|
||||
Path: "/sapling/repo/.sl",
|
||||
ParentFolder: "/sapling/repo",
|
||||
IsDir: true,
|
||||
}
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("HasCommand", "sl").Return(tc.HasSapling)
|
||||
env.On("InWSLSharedDrive").Return(false)
|
||||
env.On("GOOS").Return(platform.LINUX)
|
||||
env.On("Home").Return("/usr/home/sapling")
|
||||
env.On("DirMatchesOneOf", fileInfo.ParentFolder, []string{"/sapling/repo"}).Return(tc.Excluded)
|
||||
if tc.InRepo {
|
||||
env.On("HasParentFilePath", ".sl").Return(fileInfo, nil)
|
||||
} else {
|
||||
env.On("HasParentFilePath", ".sl").Return(&platform.FileInfo{}, errors.New("error"))
|
||||
}
|
||||
sl := &Sapling{
|
||||
scm: scm{
|
||||
env: env,
|
||||
props: &properties.Map{
|
||||
properties.ExcludeFolders: []string{"/sapling/repo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
got := sl.shouldDisplay()
|
||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||
if tc.Expected {
|
||||
assert.Equal(t, "/sapling/repo/.sl", sl.workingDir, tc.Case)
|
||||
assert.Equal(t, "/sapling/repo/.sl", sl.rootDir, tc.Case)
|
||||
assert.Equal(t, "/sapling/repo", sl.realDir, tc.Case)
|
||||
assert.Equal(t, "repo", sl.RepoName, tc.Case)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHeadContext(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
FetchStatus bool
|
||||
Output string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Case: "Do not fetch status",
|
||||
},
|
||||
{
|
||||
Case: "Fetch status, no output",
|
||||
FetchStatus: true,
|
||||
},
|
||||
{
|
||||
Case: "Fetch status, changed files",
|
||||
FetchStatus: true,
|
||||
Output: `
|
||||
M file.go
|
||||
M file2.go
|
||||
`,
|
||||
Expected: "~2",
|
||||
},
|
||||
{
|
||||
Case: "Fetch status, all cases",
|
||||
FetchStatus: true,
|
||||
Output: `
|
||||
M file.go
|
||||
R file2.go
|
||||
A file3.go
|
||||
C file4.go
|
||||
! missing.go
|
||||
? untracked.go
|
||||
? untracked.go
|
||||
I ignored.go
|
||||
I ignored.go
|
||||
`,
|
||||
Expected: "?2 +1 ~1 -1 !1 =1 Ø2",
|
||||
},
|
||||
}
|
||||
output := `
|
||||
no:734349e9f1abd229ec6e9bbebed35aed56b26a9e
|
||||
ns:734349e9f
|
||||
nd:23 minutes ago
|
||||
un:jan
|
||||
bm:sapling-segment
|
||||
`
|
||||
for _, tc := range cases {
|
||||
env := new(mock.MockedEnvironment)
|
||||
env.On("RunCommand", "sl", []string{"log", "--limit", "1", "--template", SLCOMMITTEMPLATE}).Return(output, nil)
|
||||
env.On("RunCommand", "sl", []string{"status"}).Return(tc.Output, nil)
|
||||
sl := &Sapling{
|
||||
scm: scm{
|
||||
env: env,
|
||||
props: &properties.Map{
|
||||
FetchStatus: tc.FetchStatus,
|
||||
},
|
||||
command: SAPLINGCOMMAND,
|
||||
},
|
||||
}
|
||||
sl.setHeadContext()
|
||||
got := sl.Working.String()
|
||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||
}
|
||||
}
|
|
@ -22,10 +22,22 @@ type ScmStatus struct {
|
|||
Moved int
|
||||
Conflicted int
|
||||
Untracked int
|
||||
Clean int
|
||||
Missing int
|
||||
Ignored int
|
||||
}
|
||||
|
||||
func (s *ScmStatus) Changed() bool {
|
||||
return s.Added > 0 || s.Deleted > 0 || s.Modified > 0 || s.Unmerged > 0 || s.Moved > 0 || s.Conflicted > 0 || s.Untracked > 0
|
||||
return s.Unmerged > 0 ||
|
||||
s.Added > 0 ||
|
||||
s.Deleted > 0 ||
|
||||
s.Modified > 0 ||
|
||||
s.Moved > 0 ||
|
||||
s.Conflicted > 0 ||
|
||||
s.Untracked > 0 ||
|
||||
s.Clean > 0 ||
|
||||
s.Missing > 0 ||
|
||||
s.Ignored > 0
|
||||
}
|
||||
|
||||
func (s *ScmStatus) String() string {
|
||||
|
@ -43,6 +55,9 @@ func (s *ScmStatus) String() string {
|
|||
status += stringIfValue(s.Moved, ">")
|
||||
status += stringIfValue(s.Unmerged, "x")
|
||||
status += stringIfValue(s.Conflicted, "!")
|
||||
status += stringIfValue(s.Missing, "!")
|
||||
status += stringIfValue(s.Clean, "=")
|
||||
status += stringIfValue(s.Ignored, "Ø")
|
||||
return strings.TrimSpace(status)
|
||||
}
|
||||
|
||||
|
@ -53,6 +68,7 @@ type scm struct {
|
|||
IsWslSharedPath bool
|
||||
CommandMissing bool
|
||||
Dir string // actual repo root directory
|
||||
RepoName string
|
||||
|
||||
workingDir string
|
||||
rootDir string
|
||||
|
|
|
@ -289,6 +289,7 @@
|
|||
"ruby",
|
||||
"rust",
|
||||
"r",
|
||||
"sapling",
|
||||
"session",
|
||||
"spotify",
|
||||
"shell",
|
||||
|
@ -1822,6 +1823,31 @@
|
|||
"description": "https://ohmyposh.dev/docs/segments/root"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "sapling"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"title": "Sapling Segment",
|
||||
"description": "https://ohmyposh.dev/docs/segments/sapling",
|
||||
"properties": {
|
||||
"properties": {
|
||||
"properties": {
|
||||
"fetch_status": {
|
||||
"type": "boolean",
|
||||
"title": "Display Status",
|
||||
"description": "Display the local changes or not",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
|
|
|
@ -164,8 +164,8 @@ You can set the following properties to `true` to enable fetching additional inf
|
|||
|
||||
| Name | Type | Description |
|
||||
| ------------ | ----------- | -------------------------------------- |
|
||||
| `.Author` | `User` | the author or the commit (see below) |
|
||||
| `.Committer` | `User` | the comitter or the commit (see below) |
|
||||
| `.Author` | `User` | the author of the commit (see below) |
|
||||
| `.Committer` | `User` | the comitter of the commit (see below) |
|
||||
| `.Subject` | `string` | the commit subject |
|
||||
| `.Timestamp` | `time.Time` | the commit timestamp |
|
||||
|
||||
|
|
85
website/docs/segments/sapling.mdx
Normal file
85
website/docs/segments/sapling.mdx
Normal file
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
id: sapling
|
||||
title: Sapling
|
||||
sidebar_label: Sapling
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Display [sapling][sapling] information when in a sapling repository.
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "sapling",
|
||||
"style": "powerline",
|
||||
"powerline_symbol": "\uE0B0",
|
||||
"foreground": "#193549",
|
||||
"background": "#4C9642",
|
||||
"background_templates": ["{{ if .Bookmark }}#4C9642{{ end }}"],
|
||||
"properties": {
|
||||
"fetch_status": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
### Fetching information
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fetch_status` | `boolean` | fetch the local changes - defaults to `true` |
|
||||
| `native_fallback` | `boolean` | when set to `true` and `sl.exe` is not available when inside a WSL2 shared Windows drive, we will fallback to the native sapling executable to fetch data. Not all information can be displayed in this case. Defaults to `false` |
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
:::note default template
|
||||
|
||||
```template
|
||||
{{ if .Bookmark }}\uf097 {{ .Bookmark }}*{{ else }}\ue729 {{ .ShortHash }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------ | --------------- | ------------------------------------- |
|
||||
| `.RepoName` | `string` | the repo folder name |
|
||||
| `.Working` | `SaplingStatus` | changes in the worktree (see below) |
|
||||
| `.Author` | `string` | the author of the commit |
|
||||
| `.Hash` | `string` | the full hash of the commit |
|
||||
| `.ShortHash` | `string` | the short hash of the commit |
|
||||
| `.When` | `string` | the commit's relative time indication |
|
||||
| `.Bookmark` | `string` | the commit's bookmark (if any) |
|
||||
| `.Dir` | `string` | the repository's root directory |
|
||||
|
||||
### SaplingStatus
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------ | --------- | -------------------------------------------- |
|
||||
| `.Modified` | `int` | number of modified changes |
|
||||
| `.Added` | `int` | number of added changes |
|
||||
| `.Deleted` | `int` | number of removed changes |
|
||||
| `.Untracked` | `boolean` | number of untracked changes |
|
||||
| `.Clean` | `int` | number of clean changes |
|
||||
| `.Missing` | `int` | number of missing changes |
|
||||
| `.Ignored` | `boolean` | number of ignored changes |
|
||||
| `.String` | `string` | a string representation of the changes above |
|
||||
|
||||
Local changes use the following syntax:
|
||||
|
||||
| Icon | Description |
|
||||
| ---- | ----------- |
|
||||
| `+` | added |
|
||||
| `~` | modified |
|
||||
| `-` | deleted |
|
||||
| `?` | untracked |
|
||||
| `=` | clean |
|
||||
| `!` | missing |
|
||||
| `Ø` | ignored |
|
||||
|
||||
[sapling]: https://sapling-scm.com/
|
||||
[templates]: /docs/configuration/templates
|
|
@ -103,6 +103,7 @@ module.exports = {
|
|||
"segments/root",
|
||||
"segments/ruby",
|
||||
"segments/rust",
|
||||
"segments/sapling",
|
||||
"segments/session",
|
||||
"segments/shell",
|
||||
"segments/spotify",
|
||||
|
|
Loading…
Reference in a new issue