Compare commits

...

746 commits

Author SHA1 Message Date
Drew Smirnoff
e5cb757278
chore: govulncheck bumps (#1004)
Signed-off-by: drew <me@andrinoff.com>
2026-08-04 18:41:27 +04:00
Andrey Nering
53788271b3 chore: remove CODEOWNERS 2026-04-22 11:14:50 -03:00
Andrey Nering
f57087499b
v2.1.2 2026-04-09 17:25:27 -03:00
Andrey Nering
1130f2d89f
ci: fix govulncheck (#927) 2026-04-09 16:18:13 -03:00
Matt Van Horn
fac0fb456d
fix: use shell.Fields to parse $PAGER command (#922)
Replace cmd/sh shell invocation with shell.Fields from mvdan.cc/sh.
This properly handles paths with spaces on all platforms without
shelling out.

Fixes #385
2026-04-09 16:12:10 -03:00
dependabot[bot]
752de97c5a
chore(deps): bump the all group across 1 directory with 4 updates (#857)
Bumps the all group with 4 updates in the / directory: [github.com/spf13/cobra](https://github.com/spf13/cobra), [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text).


Updates `github.com/spf13/cobra` from 1.10.1 to 1.10.2
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2)

Updates `golang.org/x/sys` from 0.38.0 to 0.39.0
- [Commits](https://github.com/golang/sys/compare/v0.38.0...v0.39.0)

Updates `golang.org/x/term` from 0.37.0 to 0.38.0
- [Commits](https://github.com/golang/term/compare/v0.37.0...v0.38.0)

Updates `golang.org/x/text` from 0.31.0 to 0.32.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.31.0...v0.32.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: golang.org/x/sys
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/term
  dependency-version: 0.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/text
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 10:08:33 +00:00
dependabot[bot]
7903cdbc5f
chore(deps): bump golangci/golangci-lint-action in the all group (#854)
Bumps the all group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `golangci/golangci-lint-action` from 9.1.0 to 9.2.0
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v9.1.0...v9.2.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-08 10:05:33 +00:00
dependabot[bot]
a861530349
chore(deps): bump the all group with 2 updates (#848)
Bumps the all group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

Updates `golangci/golangci-lint-action` from 9.0.0 to 9.1.0
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v9.0.0...v9.1.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 11:33:59 +00:00
dependabot[bot]
ba37804fd5
chore(deps): bump the all group with 2 updates (#847)
Bumps the all group with 2 updates: [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text).


Updates `golang.org/x/term` from 0.36.0 to 0.37.0
- [Commits](https://github.com/golang/term/compare/v0.36.0...v0.37.0)

Updates `golang.org/x/text` from 0.30.0 to 0.31.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/text
  dependency-version: 0.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 10:43:05 +00:00
dependabot[bot]
dbf59aafa9
chore(deps): bump golang.org/x/sys in the all group (#844)
Bumps the all group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.37.0 to 0.38.0
- [Commits](https://github.com/golang/sys/compare/v0.37.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 11:35:26 +00:00
dependabot[bot]
07d4e1de3f
chore(deps): bump golangci/golangci-lint-action in the all group (#843)
Bumps the all group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `golangci/golangci-lint-action` from 8.0.0 to 9.0.0
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 11:33:09 +00:00
dependabot[bot]
e9840f0735
chore(deps): bump the all group with 3 updates (#833)
Bumps the all group with 3 updates: [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text).


Updates `golang.org/x/sys` from 0.36.0 to 0.37.0
- [Commits](https://github.com/golang/sys/compare/v0.36.0...v0.37.0)

Updates `golang.org/x/term` from 0.35.0 to 0.36.0
- [Commits](https://github.com/golang/term/compare/v0.35.0...v0.36.0)

Updates `golang.org/x/text` from 0.29.0 to 0.30.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/term
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/text
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-13 09:24:50 +00:00
dependabot[bot]
283d20b83c
chore(deps): bump github.com/mattn/go-runewidth in the all group (#832)
Bumps the all group with 1 update: [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth).


Updates `github.com/mattn/go-runewidth` from 0.0.17 to 0.0.19
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.17...v0.0.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-runewidth
  dependency-version: 0.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-05 15:39:22 +00:00
Charm
36bf1711bf
ci: sync dependabot config (#831) 2025-10-05 17:30:25 +02:00
dependabot[bot]
f9ec6882c1
chore(deps): bump github.com/mattn/go-runewidth in the all group (#828)
Bumps the all group with 1 update: [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth).


Updates `github.com/mattn/go-runewidth` from 0.0.16 to 0.0.17
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.16...v0.0.17)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-runewidth
  dependency-version: 0.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 10:41:25 +00:00
dependabot[bot]
1afeed4c56
chore(deps): bump github.com/charmbracelet/bubbletea in the all group (#827)
Bumps the all group with 1 update: [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea).


Updates `github.com/charmbracelet/bubbletea` from 1.3.9 to 1.3.10
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.9...v1.3.10)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-version: 1.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 09:14:29 +00:00
Nathan Cain
677fcb3eee bump install with go path to v2 2025-09-18 22:11:28 -04:00
dependabot[bot]
a440b3319b
chore(deps): bump the all group with 3 updates (#824)
Bumps the all group with 3 updates: [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea), [github.com/muesli/mango-cobra](https://github.com/muesli/mango-cobra) and [github.com/spf13/viper](https://github.com/spf13/viper).


Updates `github.com/charmbracelet/bubbletea` from 1.3.7 to 1.3.9
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.7...v1.3.9)

Updates `github.com/muesli/mango-cobra` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/muesli/mango-cobra/releases)
- [Commits](https://github.com/muesli/mango-cobra/compare/v1.2.0...v1.3.0)

Updates `github.com/spf13/viper` from 1.20.1 to 1.21.0
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.20.1...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-version: 1.3.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: github.com/muesli/mango-cobra
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: github.com/spf13/viper
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 09:24:52 +00:00
dependabot[bot]
1c32d1377c
chore(deps): bump actions/setup-go from 5 to 6 in the all group (#821)
Bumps the all group with 1 update: [actions/setup-go](https://github.com/actions/setup-go).


Updates `actions/setup-go` from 5 to 6
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 10:14:37 -04:00
Andrey Nering
963219b96e
security: bump mapstructure module with security fixes (#822) 2025-09-08 09:35:05 -03:00
dependabot[bot]
901b99931e
chore(deps): bump the all group with 5 updates (#820)
Bumps the all group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) | `1.3.6` | `1.3.7` |
| [github.com/spf13/cobra](https://github.com/spf13/cobra) | `1.9.1` | `1.10.1` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.35.0` | `0.36.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.34.0` | `0.35.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.28.0` | `0.29.0` |


Updates `github.com/charmbracelet/bubbletea` from 1.3.6 to 1.3.7
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.6...v1.3.7)

Updates `github.com/spf13/cobra` from 1.9.1 to 1.10.1
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.1)

Updates `golang.org/x/sys` from 0.35.0 to 0.36.0
- [Commits](https://github.com/golang/sys/compare/v0.35.0...v0.36.0)

Updates `golang.org/x/term` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/term/compare/v0.34.0...v0.35.0)

Updates `golang.org/x/text` from 0.28.0 to 0.29.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-version: 1.3.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/sys
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/term
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/text
  dependency-version: 0.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 09:30:47 -03:00
dependabot[bot]
9432fd920c
chore(deps): bump the all group with 3 updates (#818)
Bumps the all group with 3 updates: [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text).


Updates `golang.org/x/sys` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/sys/compare/v0.34.0...v0.35.0)

Updates `golang.org/x/term` from 0.33.0 to 0.34.0
- [Commits](https://github.com/golang/term/compare/v0.33.0...v0.34.0)

Updates `golang.org/x/text` from 0.27.0 to 0.28.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/term
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: golang.org/x/text
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 13:33:59 -03:00
Charm
deaa57fe00
ci: sync dependabot config (#817) 2025-08-26 13:04:08 -03:00
dependabot[bot]
e61f329428
chore(deps): bump actions/checkout from 4 to 5 (#813)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 10:55:11 -03:00
github-actions[bot]
bf0a9b4d3d
ci: sync golangci-lint config (#808)
Co-authored-by: andreynering <7011819+andreynering@users.noreply.github.com>
2025-08-11 08:23:51 -04:00
dependabot[bot]
e010ed866e
chore(deps): bump golang.org/x/term from 0.32.0 to 0.33.0 (#798)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.32.0 to 0.33.0.
- [Commits](https://github.com/golang/term/compare/v0.32.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 12:23:28 +00:00
dependabot[bot]
0e76788a6a
chore(deps): bump github.com/charmbracelet/bubbletea from 1.3.5 to 1.3.6 (#797)
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 1.3.5 to 1.3.6.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.5...v1.3.6)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-version: 1.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 12:20:32 +00:00
dependabot[bot]
66b389ef17
chore(deps): bump golang.org/x/sys from 0.33.0 to 0.34.0 (#796)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/sys/compare/v0.33.0...v0.34.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 11:50:28 +00:00
dependabot[bot]
18a739c091
chore(deps): bump golang.org/x/text from 0.26.0 to 0.27.0 (#795)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.26.0 to 0.27.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 11:24:38 +00:00
one230six
9240ab4625
docs: remove redundant word in comment (#782) 2025-06-12 10:25:33 -03:00
bashbunni
a6ed0f2430
docs: add contribution guidelines (#781) 2025-06-11 08:16:46 -03:00
dependabot[bot]
3079ce20b9
chore(deps): bump golang.org/x/text from 0.25.0 to 0.26.0 (#779)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.25.0 to 0.26.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 09:49:18 +00:00
Prithvi Jethwa
bcd1296ce5
fix: allow config file to load showLineNumbers (#653) 2025-06-04 14:41:27 -03:00
github-actions[bot]
e54618ef13
ci: sync golangci-lint config (#777)
Co-authored-by: andreynering <7011819+andreynering@users.noreply.github.com>
2025-06-02 08:52:12 -03:00
Carlos Alexandro Becker
d37e988787
chore(deps): update x/net
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2025-05-30 09:58:41 -03:00
dependabot[bot]
e83c6edf93
chore(deps): bump github.com/charmbracelet/log from 0.4.1 to 0.4.2 (#769)
Bumps [github.com/charmbracelet/log](https://github.com/charmbracelet/log) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/charmbracelet/log/releases)
- [Commits](https://github.com/charmbracelet/log/compare/v0.4.1...v0.4.2)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/log
  dependency-version: 0.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-19 09:08:18 +00:00
Markus Billharz
e23f237fb7 fix: only show edit if documents are available 2025-05-14 21:32:15 -04:00
Markus Billharz
ee27edd3b1 fix: handle case when selected md is nil 2025-05-14 21:32:15 -04:00
dependabot[bot]
5b2dfddcda
chore(deps): bump golang.org/x/term from 0.31.0 to 0.32.0 (#762)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.31.0 to 0.32.0.
- [Commits](https://github.com/golang/term/compare/v0.31.0...v0.32.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 09:25:09 +00:00
dependabot[bot]
0880dea1be
chore(deps): bump golang.org/x/text from 0.24.0 to 0.25.0 (#763)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 09:21:41 +00:00
dependabot[bot]
ef85822a00
chore(deps): bump golang.org/x/sys from 0.32.0 to 0.33.0 (#764)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.32.0 to 0.33.0.
- [Commits](https://github.com/golang/sys/compare/v0.32.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 09:21:02 +00:00
dependabot[bot]
cdeceb9a7f
chore(deps): bump golangci/golangci-lint-action from 7.0.0 to 8.0.0 (#759)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v7.0.0...v8.0.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 10:22:45 +00:00
dependabot[bot]
366e9a0d65
chore(deps): bump github.com/charmbracelet/bubbletea from 1.3.4 to 1.3.5 (#758)
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 1.3.4 to 1.3.5.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.4...v1.3.5)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-version: 1.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 09:45:24 +00:00
Ktrod
76e08e7dce
fix: sync viewport on half page up/down to prevent duplicate strings in render (#756) 2025-04-28 10:29:49 -03:00
Charm
e60e83c6aa
ci: sync dependabot config (#755) 2025-04-28 08:46:31 -03:00
dependabot[bot]
bb9fcb439b
chore(deps): bump github.com/charmbracelet/glamour from 0.9.1 to 0.10.0 (#753)
Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.9.1 to 0.10.0.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Changelog](https://github.com/charmbracelet/glamour/blob/master/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/glamour/compare/v0.9.1...v0.10.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/glamour
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 10:47:08 +00:00
Andrey Nering
6303d0b784
chore(deps): update golang.org/x/net with security fixes (#749) 2025-04-17 10:38:19 -03:00
Andrey Nering
d6b9ddf345
docs: mention snapcraft release on readme (#748)
Closes #747
2025-04-17 10:08:27 -03:00
dependabot[bot]
470fa3d718
chore(deps): bump golang.org/x/text from 0.23.0 to 0.24.0 (#742)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.23.0 to 0.24.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 09:51:09 +00:00
dependabot[bot]
2c0e4d878a
chore(deps): bump github.com/charmbracelet/bubbles from 0.20.0 to 0.21.0 (#746)
Bumps [github.com/charmbracelet/bubbles](https://github.com/charmbracelet/bubbles) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/charmbracelet/bubbles/releases)
- [Changelog](https://github.com/charmbracelet/bubbles/blob/master/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbles/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbles
  dependency-version: 0.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 09:48:13 +00:00
Glauber Santana
d2e04adbfe
fix: tui mode handling of remote urls (#744) 2025-04-09 10:18:13 -03:00
dependabot[bot]
c84a03bbfd
chore(deps): bump github.com/fsnotify/fsnotify from 1.8.0 to 1.9.0 (#741)
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-07 09:48:44 +00:00
dependabot[bot]
e8049d79f2
chore(deps): bump golang.org/x/term from 0.30.0 to 0.31.0 (#739)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/term/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-07 09:48:19 +00:00
Andrey Nering
41d70ee83b fix(lint): fix all linting issues 2025-04-03 15:23:59 -03:00
Andrey Nering
0a6732f089 fix: update .golangci.yml for v2 2025-04-03 15:23:59 -03:00
Andrey Nering
230dbc79b2 ci: add lint-sync workflow 2025-04-03 15:23:59 -03:00
Andrey Nering
81c64fdd2d chore: delete makefile 2025-04-03 15:23:59 -03:00
Andrey Nering
c48ee3d98c chore: add a taskfile 2025-04-03 15:23:59 -03:00
Andrey Nering
23f9873f6a chore: add .editorconfig 2025-04-03 15:23:59 -03:00
Ktrod
05872a506a
fix(watch): watch for dir instead of file to work on all scenarios (#734)
Co-authored-by: Andrey Nering <andreynering@users.noreply.github.com>
2025-04-02 09:47:04 -03:00
dependabot[bot]
50a763ef94
chore(deps): bump github.com/spf13/viper from 1.20.0 to 1.20.1 (#731)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.20.0 to 1.20.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.20.0...v1.20.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 09:49:02 +00:00
dependabot[bot]
4d59a973a7
chore(deps): bump golangci/golangci-lint-action from 6.5.2 to 7.0.0 (#730)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.5.2 to 7.0.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.5.2...v7.0.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 09:21:02 +00:00
dependabot[bot]
d54666ddf7
chore(deps): bump github.com/charmbracelet/glamour from 0.9.0 to 0.9.1 (#727)
Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Changelog](https://github.com/charmbracelet/glamour/blob/master/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/glamour/compare/v0.9.0...v0.9.1)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/glamour
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 10:14:31 +00:00
dependabot[bot]
0b668e1593
chore(deps): bump golangci/golangci-lint-action from 6.5.1 to 6.5.2 (#726)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.5.1 to 6.5.2.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.5.1...v6.5.2)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 09:44:16 +00:00
dependabot[bot]
6558aaab69
chore(deps): bump github.com/charmbracelet/log from 0.4.0 to 0.4.1 (#724)
Bumps [github.com/charmbracelet/log](https://github.com/charmbracelet/log) from 0.4.0 to 0.4.1.
- [Release notes](https://github.com/charmbracelet/log/releases)
- [Commits](https://github.com/charmbracelet/log/compare/v0.4.0...v0.4.1)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 10:15:16 +00:00
dependabot[bot]
85668a6a21
chore(deps): bump github.com/spf13/viper from 1.19.0 to 1.20.0 (#722)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.19.0 to 1.20.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.19.0...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 10:08:56 +00:00
dependabot[bot]
411cdb142f
chore(deps): bump github.com/charmbracelet/glamour from 0.8.0 to 0.9.0 (#721)
Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Changelog](https://github.com/charmbracelet/glamour/blob/master/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/glamour/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/glamour
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 10:08:02 +00:00
dependabot[bot]
6cd480d68d
chore(deps): bump golangci/golangci-lint-action from 6.5.0 to 6.5.1 (#720)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.5.0 to 6.5.1.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.5.0...v6.5.1)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 09:31:38 +00:00
dependabot[bot]
652010599b
chore(deps): bump golang.org/x/text from 0.22.0 to 0.23.0 (#715)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 10:14:22 +00:00
dependabot[bot]
eeb0fdd06f
chore(deps): bump golang.org/x/term from 0.29.0 to 0.30.0 (#714)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.29.0 to 0.30.0.
- [Commits](https://github.com/golang/term/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-10 10:10:35 +00:00
Carlos Alexandro Becker
f88a901caa
ci: fix goreleaser config 2025-03-07 15:10:54 -03:00
dependabot[bot]
7dc4b9c87f
chore(deps): bump github.com/charmbracelet/bubbletea from 1.3.3 to 1.3.4 (#711)
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 1.3.3 to 1.3.4.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.3...v1.3.4)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 10:36:24 +00:00
dependabot[bot]
2da054e802
chore(deps): bump golang.org/x/net from 0.27.0 to 0.33.0 (#707)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.27.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.27.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 20:39:11 +00:00
dependabot[bot]
7154552fa0
chore(deps): bump github.com/muesli/termenv (#704)
Bumps [github.com/muesli/termenv](https://github.com/muesli/termenv) from 0.15.3-0.20240618155329-98d742f6907a to 0.16.0.
- [Release notes](https://github.com/muesli/termenv/releases)
- [Commits](https://github.com/muesli/termenv/commits/v0.16.0)

---
updated-dependencies:
- dependency-name: github.com/muesli/termenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 11:06:44 +00:00
dependabot[bot]
0401d55125
chore(deps): bump github.com/spf13/viper from 1.15.0 to 1.19.0 (#701)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.15.0 to 1.19.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.15.0...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 11:28:14 +00:00
dependabot[bot]
6175a7989e
chore(deps): bump github.com/caarlos0/env/v11 from 11.0.1 to 11.3.1 (#700)
Bumps [github.com/caarlos0/env/v11](https://github.com/caarlos0/env) from 11.0.1 to 11.3.1.
- [Release notes](https://github.com/caarlos0/env/releases)
- [Changelog](https://github.com/caarlos0/env/blob/main/.goreleaser.yml)
- [Commits](https://github.com/caarlos0/env/compare/v11.0.1...v11.3.1)

---
updated-dependencies:
- dependency-name: github.com/caarlos0/env/v11
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 11:25:33 +00:00
dependabot[bot]
5526167854
chore(deps): bump github.com/spf13/cobra from 1.7.0 to 1.9.1 (#699)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.7.0 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.7.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 11:25:26 +00:00
dependabot[bot]
f0f1201ba5
chore(deps): bump github.com/fsnotify/fsnotify from 1.6.0 to 1.8.0 (#698)
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.6.0 to 1.8.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.6.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 11:24:53 +00:00
dependabot[bot]
490cfcafd0
chore(deps): bump golangci/golangci-lint-action from 6.3.3 to 6.5.0 (#697)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.3.3 to 6.5.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.3.3...v6.5.0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 11:13:28 +00:00
Andrey Nering
7fbc040cc8 refactor: rename s to content 2025-02-13 09:10:00 -08:00
shane.xb.qian
9dd8a6f3fd fix: make pipeline for tui work
* 'cat foo.md | glow -t'

Signed-off-by: shane.xb.qian <shane.qian@foxmail.com>
2025-02-13 09:10:00 -08:00
Charm
1b840ba227
ci: sync dependabot config (#695) 2025-02-13 11:17:14 -03:00
dependabot[bot]
436aa682be
chore(deps): bump github.com/charmbracelet/bubbles from 0.18.0 to 0.20.0 (#690)
Bumps [github.com/charmbracelet/bubbles](https://github.com/charmbracelet/bubbles) from 0.18.0 to 0.20.0.
- [Release notes](https://github.com/charmbracelet/bubbles/releases)
- [Changelog](https://github.com/charmbracelet/bubbles/blob/master/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbles/compare/v0.18.0...v0.20.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbles
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 13:51:18 +00:00
dependabot[bot]
305ea9d447
chore(deps): bump golang.org/x/term from 0.22.0 to 0.29.0 (#694)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.22.0 to 0.29.0.
- [Commits](https://github.com/golang/term/compare/v0.22.0...v0.29.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 13:49:30 +00:00
dependabot[bot]
929a9e326f
chore(deps): bump github.com/charmbracelet/x/editor (#692)
Bumps [github.com/charmbracelet/x/editor](https://github.com/charmbracelet/x) from 0.0.0-20240625164403-2627ec16405d to 0.1.0.
- [Release notes](https://github.com/charmbracelet/x/releases)
- [Commits](https://github.com/charmbracelet/x/commits/v0.1.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/x/editor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 13:46:44 +00:00
dependabot[bot]
8850757e80
chore(deps): bump github.com/charmbracelet/bubbletea (#693)
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 0.26.7-0.20240711210516-ea13ffb9a18d to 1.3.3.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/commits/v1.3.3)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 13:45:29 +00:00
dependabot[bot]
4db0304573
chore(deps): bump golangci/golangci-lint-action from 6.3.2 to 6.3.3 (#689)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.3.2 to 6.3.3.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.3.2...v6.3.3)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 13:43:46 +00:00
dependabot[bot]
19c6af747f
chore(deps): bump golang.org/x/text from 0.16.0 to 0.22.0 (#691)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.16.0 to 0.22.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.16.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 13:43:38 +00:00
Charm
b17f04c0ef
ci: sync dependabot config (#688) 2025-02-13 10:40:53 -03:00
Shane-XB-Qian
546b6287d8
ci: upgrade go and golangci-lint ver (#685) 2025-02-12 11:11:44 -03:00
Shane-XB-Qian
f689c90084
fix: correct abs to rel path conversion (#683) 2025-02-11 10:53:34 -03:00
Andrey Nering
51fa19b8b4 ci: fix semgrep 2025-02-11 05:35:31 -08:00
Andrey Nering
05e5b2e279 feat: watch for file changes and reload automatically 2025-02-11 05:35:31 -08:00
Andrey Nering
e44e4684e1 fix: fallback to the current directory if no argument was given 2025-02-11 05:35:31 -08:00
Andrey Nering
2671509003 fix: make all setting false by default 2025-02-07 08:16:33 -08:00
Andrey Nering
8e363644f6 chore: add missing line end to .gitignore 2025-02-07 08:16:33 -08:00
Andrey Nering
e03817b2cb
feat: add new --tui / -t flag (#679) 2025-02-05 16:45:36 -03:00
Andrey Nering
009c37602a
fix: restore hint for e / edit on the footer (#678) 2025-02-03 13:20:54 -08:00
NijeboerFrank
3989fe0d5e
fix: ensure r key works when filtering files (#648) 2025-02-03 17:03:38 -03:00
bashbunni
7b3bfac306
test: skip networked tests if no connection (#661)
* test: skip networked tests if no connection

* fix: handle unexpected http responses

* fix: return if a valid URL is generated

* test: delete networked test
2024-12-18 11:10:03 -08:00
bashbunni
f255f3a603
feat: support GLAMOUR_STYLE for custom pager styles (#587)
* feat: support GLAMOUR_STYLE for custom pager styles

* fix: resolve errors
2024-12-16 07:55:09 -08:00
Carlos Alexandro Becker
ae57cced06
fix: remove more stash-related stuff (#656)
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-10-31 09:53:59 -03:00
Stanislav (Stas) Katkov
f1b565125f
feat: make --all configurable globally (#651)
* Make --all configurable globally

* docs: add all option to docs and default config

* refactor: tidy fields, set showAllFiles to true if unset

---------

Co-authored-by: Stanislav (Stas) Katkov <github@skatkov.com>
Co-authored-by: bashbunni <bunni@bashbunni.dev>
2024-10-01 10:23:20 -07:00
Christian Rocha
67243bb6fb
docs(license): update copyright date 2024-08-23 15:00:20 -04:00
Christian Rocha
f02c9a8fdc
docs(readme): update GIF 2024-08-23 14:49:31 -04:00
Christian Rocha
b26341ec7b docs(readme): pull network stuff from config example 2024-08-23 13:25:12 -04:00
Carlos Alexandro Becker
736e77dbc6
feat!: rename module to /v2 (#640)
Next version of Glow drops stash support, which is a breaking change.

This makes it accordingly.

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-08-23 12:45:30 -03:00
Carlos Alexandro Becker
cd99146e9e
fix: update codeowners (#637)
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-08-23 12:45:18 -03:00
Carlos Alexandro Becker
3f3b2c4586
test: github:// proto 2024-08-21 15:07:42 -03:00
Carlos Alexandro Becker
c89c8bd6e7
feat: update glamour (#634)
* feat: updating glamour

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: trim space

* chore: bump

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore(deps): bump

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: perms

* fix: improve logs

* feat: update glamour

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-08-21 14:43:05 -03:00
Carlos Alexandro Becker
4578bc149a
feat: updating glamour (#631)
* feat: updating glamour

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: trim space

* chore: bump

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore(deps): bump

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-25 10:32:08 -04:00
Carlos Alexandro Becker
0dcf1271dd
feat: suspend
closes #333
2024-07-11 18:07:35 -03:00
Carlos Alexandro Becker
94835d99ee
test: skip flukish test 2024-07-11 12:20:59 -03:00
kalle (jag)
96d2a38e67
feat: allow --width=0 to disable wordwrap (#366)
Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-11 12:16:12 -03:00
Carlos Alexandro Becker
2d5bd3b799
feat: show line numbers configuration option
closes #311
2024-07-11 12:08:57 -03:00
Carlos Alexandro Becker
247c707b25
fix: glow URL
closes #464
2024-07-11 12:02:03 -03:00
Carlos Alexandro Becker
c356273a76
chore: update glamour to master
fixes  #485

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-10 17:02:37 -03:00
Carlos Alexandro Becker
3f9102d116
fix: yet another log handling fix 2024-07-10 12:02:50 -03:00
Carlos Alexandro Becker
a1a85636ea
fix: handle not being able to create log file 2024-07-10 10:42:21 -03:00
Carlos Alexandro Becker
b80e8312f2
fix: handle permission denied 2024-07-10 10:39:05 -03:00
Carlos Alexandro Becker
39c336f823
ci: improve coverage workflow 2024-07-10 10:37:00 -03:00
Carlos Alexandro Becker
e4cacf7319
fix: mkdir cache dir 2024-07-10 10:35:52 -03:00
Carlos Alexandro Becker
ad01f380bd
fix: improve line no 2024-07-09 16:46:49 -03:00
Carlos Alexandro Becker
597e56bad4
feat: open editor in current line
closes #547
2024-07-09 16:38:32 -03:00
Carlos Alexandro Becker
400228cff6
docs: update install 2024-07-09 16:38:10 -03:00
Carlos Alexandro Becker
1407793c5d
fix: pass color profile down to glamour (#626)
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 16:13:32 -03:00
Carlos Alexandro Becker
401610afa4
ci: run govulncheck, semgrep, etc (#627)
* ci: run govulncheck, semgrep, etc

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: error check

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 16:13:05 -03:00
Carlos Alexandro Becker
9ebe39cd09
feat: improve gitlab/github readme url (#456)
* Use GitHub API to find readme filename

* Fix lint errors and typos

* Bring back "tries to find" instead of "finds"

* Rename `readmeURL` to `apiURL`

* Don't close body

* Use GitLab API to find readme filename

* feat: improve gitlab/github readme url

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: danielwerg <35052399+danielwerg@users.noreply.github.com>
2024-07-09 16:07:39 -03:00
Carlos Alexandro Becker
ab94744c39
chore: fix codeowners
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 16:04:11 -03:00
Carlos Alexandro Becker
2288311e82
fix: do not hard fail if no config
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 10:58:52 -03:00
Carlos Alexandro Becker
dbf6e3e799
ci: fix goreleaser config
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 10:48:54 -03:00
Carlos Alexandro Becker
1133e5047b
fix: config failing in tests (#625)
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 10:47:44 -03:00
Carlos Alexandro Becker
f44e5b5f5c
fix: improve message
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-09 10:17:18 -03:00
Carlos Alexandro Becker
d89d79a00c
feat: --preserve-new-lines (#623)
closes #502
2024-07-09 09:50:10 -03:00
Carlos Alexandro Becker
fe066f2140
feat: allow config from CHARM_CONFIG_HOME and XDG_CONFIG_HOME (#621)
* feat: allow config from CHARM_CONFIG_HOME

closes #594
closes #495
closes #328
closes #475

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: improvements on config handling

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore: log

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-08 22:33:49 -03:00
Carlos Alexandro Becker
b1d377237d
feat: add 'r' to refresh list and document (#624)
* feat: add 'r' to refresh list

closes #416

Co-authored-by: Dieter Eickstaedt <eickstaedt@deicon.de>

* feat: refresh document

Closes #501

Co-authored-by: fedeztk <federicoserranexus@gmail.com>

---------

Co-authored-by: Dieter Eickstaedt <eickstaedt@deicon.de>
Co-authored-by: fedeztk <federicoserranexus@gmail.com>
2024-07-08 16:15:08 -03:00
haennes
0a7db86725
docs: use nix-shell as nix-env -iA is bad practice (#622) 2024-07-08 11:19:02 -03:00
Carlos Alexandro Becker
f1cf0ba785
feat: man pages
closes #269
2024-07-03 13:59:11 -03:00
Carlos Alexandro Becker
d7032fe57f
feat: shell completions
closes #533
closes #546
closes #512
2024-07-03 13:41:11 -03:00
Carlos Alexandro Becker
821d51da13
fix: do not show absolute path as note 2024-07-03 13:36:17 -03:00
Carlos Alexandro Becker
dbfcc27dad
chore: go mod tidy
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-03 13:27:07 -03:00
Carlos Alexandro Becker
380f71086c
Merge remote-tracking branch 'origin/master' 2024-07-03 13:26:53 -03:00
Aitva
08c564c858
fix: --all bypass .gitignore rules (#285) (#504)
* feat(deps): bump github.com/muesli/gitcha from 0.2.0 to 0.3.0

* fix: --all bypass .gitignore rules (#285)

* Update ui/ui.go

---------

Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-03 13:19:19 -03:00
Carlos Alexandro Becker
e31dc2c890
Merge remote-tracking branch 'origin/master' 2024-07-03 13:16:12 -03:00
Carlos Alexandro Becker
8e51396575
fix: check other possible readme paths/branches (#450)
* fix: check other possible readme paths/branches

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

* fix: url

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

* fix: Readme.md

---------

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2024-07-03 13:09:20 -03:00
rustfix
f3d0cb89da
chore: fix typo in comment (#607)
Signed-off-by: rustfix <771054535@qq.com>
2024-07-03 13:06:57 -03:00
Carlos Alexandro Becker
7e471f1787
fix: lint issue
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-03 13:05:46 -03:00
dependabot[bot]
3ee20895b1
chore(deps): bump github.com/charmbracelet/glamour from 0.6.0 to 0.7.0 (#600)
Bumps [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Commits](https://github.com/charmbracelet/glamour/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/glamour
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 12:26:38 -03:00
dependabot[bot]
c47e2acd30
chore(deps): bump golangci/golangci-lint-action from 4 to 6 (#612)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 6.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v6)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 12:26:01 -03:00
Carlos Alexandro Becker
fce3edf7db
feat!: cleanup and updated (#619)
* feat!: cleanup

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* more cleanup

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: more cleanup

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: more cleanup

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-07-03 12:11:29 -03:00
Ayman Bagabas
d2e7742a6a
chore(ci): enable gofumpt 2024-05-22 11:48:31 -04:00
Maas Lalani
2430b0af3f Create CODEOWNERS 2024-02-28 21:01:19 -05:00
dependabot[bot]
cece1377cd
chore(deps): bump github.com/mattn/go-runewidth from 0.0.14 to 0.0.15 (#576)
Bumps [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) from 0.0.14 to 0.0.15.
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.14...v0.0.15)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-runewidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:59:38 -05:00
dependabot[bot]
feefb88e70
chore(deps): bump github.com/spf13/cobra from 1.6.1 to 1.8.0 (#577)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.1 to 1.8.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.6.1...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:59:24 -05:00
dependabot[bot]
ec12c2717d
feat(deps): bump golang.org/x/sys from 0.15.0 to 0.17.0 (#570)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/sys/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:59:13 -05:00
dependabot[bot]
f1437fce69
chore(deps): bump github.com/dustin/go-humanize (#575)
Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.1-0.20200219035652-afde56e7acac to 1.0.1.
- [Commits](https://github.com/dustin/go-humanize/commits/v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/dustin/go-humanize
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:54:52 -05:00
dependabot[bot]
79c39932fb
chore(deps): bump golangci/golangci-lint-action from 3 to 4 (#572)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:54:32 -05:00
dependabot[bot]
64cc6a6baa
chore(deps): bump actions/checkout from 3 to 4 (#574)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:54:24 -05:00
dependabot[bot]
8221279fa3
chore(deps): bump actions/setup-go from 4 to 5 (#573)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:54:12 -05:00
Ayman Bagabas
dd035fb441
chore(ci): change dependabot pr's title 2024-02-13 12:43:41 -05:00
dependabot[bot]
d9bb637f5e
feat(deps): bump github.com/charmbracelet/bubbletea (#567) 2024-02-13 12:37:42 -05:00
dependabot[bot]
6d86484b5e
feat(deps): bump golang.org/x/net from 0.12.0 to 0.17.0 (#568)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.12.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.12.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:08:10 -05:00
dependabot[bot]
2b91a93645
feat(deps): bump golang.org/x/crypto from 0.11.0 to 0.17.0 (#569)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.11.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.11.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 12:00:18 -05:00
Ayman Bagabas
bccf3d0cb7
fix(ci): update coverage workflow 2024-02-13 11:51:30 -05:00
K.B.Dharun Krishna
1eacfb4ff1
docs: add Winget to Windows Install Options (#500)
* README: add Winget to Windows Install Options

* Update README.md

Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2023-12-07 09:19:20 -03:00
Carlos Alexandro Becker
c991ec475d
feat: use x/editor (#543)
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2023-11-17 09:02:52 -03:00
Carlos Alexandro Becker
54dd62a2a4
fix: handle running inside a snap (#527)
* fix: handle running inside a snap

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: fixes

* docs: improve error message

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: bashbunni <bunni@bashbunni.dev>
2023-08-28 09:05:12 -03:00
Lars Müller
9a3f7a19cb
fix: replace string concatenation in loop with string builder (#505) 2023-08-24 13:52:15 -03:00
Carlos Alexandro Becker
f0734709f0 feat(deps): update some dependencies
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2023-07-13 14:47:06 +02:00
bashbunni
58ec351692
docs: add issue template (#486)
* docs: add issue template

* chore(template): add locale
2023-05-23 11:57:58 -07:00
Ayman Bagabas
9c79fa1b48
fix(ci): remove soft-serve workflow 2023-05-12 14:08:04 -04:00
Carlos Alexandro Becker
8aed0d9092
docs: update license
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2023-05-11 12:38:59 +00:00
Christian Muehlhaeuser
ad21129703 chore: bump charm dep 2023-05-09 11:16:02 +02:00
Christian Muehlhaeuser
8c80ea5f67 fix: lazily init UI 2023-05-09 11:16:02 +02:00
dependabot[bot]
4a3d98697b chore(deps): bump actions/setup-go from 3 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-16 11:04:01 +01:00
Kian-Meng Ang
442bc281c3 docs: fix typos
Found via `typos --format brief`
2023-03-02 22:22:16 -05:00
Carlos Alexandro Becker
c1483664d5
fix: do not use deprecated func (#451)
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2023-01-24 15:02:10 -03:00
Carlos Alexandro Becker
8b5468468a
fix: improve editor handling (#449)
* fix: improve editor handling

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

* test: add tests

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2023-01-24 14:51:11 -03:00
Carlos Alexandro Becker
0325be3670
fix: rm emoji from package description (#453) 2023-01-24 14:17:47 -03:00
WieeRd
19732a6303 fix: support hard line breaks (charmbracelet#212) 2023-01-23 17:41:06 +01:00
Christian Rocha
6543979ed1 fix: performance scrolling
This update fixes performance scrolling by way of the latest Bubbles
release: v0.15.0.
2023-01-20 11:55:40 -05:00
Maas Lalani
071783d907
feat: copy contents of document (#439)
* feat: copy contents of document

Co-authored-by: bashbunni <bash@charm.sh>

* fix: use atotto/clipboard for copying

Co-authored-by: bashbunni <bash@charm.sh>
2023-01-20 11:06:02 -05:00
Maas Lalani
6ba2683628
Show edit help when editable (#442)
* fix: only show edit help on pager when editable

* fix: only allow `LocalDoc`s to be edited

* feat: show e • edit help when document is editable
2023-01-19 14:13:41 -05:00
Justin Moser
dd1593b10d Keybindings for editing the document from the pager 2023-01-06 13:37:21 -05:00
Nils Leif Fischer
ff59ead353 Extend example how to read from stdin 2023-01-06 13:29:12 -05:00
dependabot[bot]
b9f519e1ea feat(deps): bump github.com/charmbracelet/bubbletea
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 0.23.0 to 0.23.1.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v0.23.0...v0.23.1)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-06 13:19:12 -05:00
dependabot[bot]
5b43697cb3 feat(deps): bump golang.org/x/text from 0.4.0 to 0.6.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.4.0 to 0.6.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.4.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-06 13:18:06 -05:00
Maas Lalani
d9bf3b8ee8 fix: don't close HTTP body 2022-12-19 18:09:17 -05:00
Carlos A Becker
9f110c304b
docs: update license year
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-12-05 14:49:35 -03:00
Maas Lalani
a07fdb73f0 feat(ui): e to open edit local markdown files in editor
Introduce key binding to allow users to open their `EDITOR` to edit local markdown files in stash and pager view.
2022-11-23 09:40:07 -05:00
Christian Muehlhaeuser
400f3f8e1f chore: disable dependabot timer 2022-11-18 02:23:00 +01:00
Carlos A Becker
4dd3ba1d3c chore: fmt & lint issues 2022-11-11 02:14:26 +01:00
Carlos A Becker
60d98a01e5 feat: bump deps 2022-11-11 02:14:26 +01:00
Christian Rocha
db7f49b445 Bump Bubbles to v0.14.0, update accordingly, and simplify spinners
* Re-implement spinner min/max lifetimes in pager
* Remove generalized spin commands in favor of model-level commands
* Update textinput, viewport, and spinner constructors
2022-11-11 02:14:26 +01:00
Christian Rocha
9c9b3e6087 Bump Bubble Tea, Bubbles, Glamour, Lip Gloss, and Termenv and do related updates
This update also removes any usage of the charm/common package

Co-authored-by: Christian Rocha <christian@rocha.is>
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-11-11 02:14:26 +01:00
Carlos A Becker
d2870afb3d chore: go 1.16
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-11-11 02:14:26 +01:00
Ayman Bagabas
dec59a2199
feat(ci): use config files from meta repo (#389)
* chore: bump cobra & viper

* feat(ci): use config files from meta repo

* publish docker images
* generate shell completions
* generate man pages

* feat(ci): use shared workflows from meta repo
2022-10-26 16:05:52 -03:00
Carlos Alexandro Becker
5f14914b7e
fix: editor with args (#364)
* fix: editor with args

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>

* Update config_cmd.go

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-10-25 15:35:12 -03:00
Christian Muehlhaeuser
44b078e44a
docs: new readme footer (#396) 2022-10-25 15:26:53 -03:00
dependabot[bot]
74b70644ba
chore(deps): bump actions/setup-go from 2 to 3 (#405)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 15:01:50 -03:00
dependabot[bot]
8eede63988
chore(deps): bump goreleaser/goreleaser-action from 2 to 3 (#402)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 15:01:26 -03:00
dependabot[bot]
06f85dda9a
chore(deps): bump golangci/golangci-lint-action from 2 to 3 (#401)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 2 to 3.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 15:01:12 -03:00
dependabot[bot]
7486c604d7
chore(deps): bump actions/checkout from 2 to 3 (#400)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-25 15:00:48 -03:00
Carlos A Becker
5e24b49e3e
chore: dependabot
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-10-25 14:59:44 -03:00
Carlos A Becker
1e8666011b
docs: fix missing commas
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-10-25 11:07:35 -03:00
Ayman Bagabas
580642af8b
docs: apt-key deprecation
Use the recommended keyrings path /etc/apt/keyrings

Per: https://manpages.debian.org/unstable/apt/apt-key.8.en.html#DEPRECATION
2022-10-17 16:48:40 -04:00
Ayman Bagabas
1237ee6bf0
docs: update apt/yum installation instructions 2022-10-06 17:20:45 -04:00
Jon Austin
3607b402a6
docs: streamline install instructions (#308)
* update to mimic the melt readme

Add a note to just use `go install` for installation.
2022-09-24 17:47:35 -07:00
Christopher Murphy
e0d667a48c fix: use filepath.Dir instead of path.Dir
Use the `filepath` implementation of `Dir` instead of `path`. Resolve
path resolution with Windows paths containing `\\` separators.
2022-09-16 11:42:22 +02:00
Christian Muehlhaeuser
7f810bfc9f docs: add Chocolatey install instructions to README 2022-09-10 04:26:52 +02:00
tploss
e078953667 Remove exit message
As already discussed in https://github.com/charmbracelet/glow/issues/329
the exit message can be a bit irritating, especially when integrating
glow into scripts/etc.
PR https://github.com/charmbracelet/glow/pull/331 did more than just
remove the exit message but was since closed by the requestor.
2022-07-12 12:16:45 +02:00
Carlos Alexandro Becker
01d9858f7c
fix: goreleaser workflow (#358)
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-07-08 15:30:10 -03:00
Ayman Bagabas
84b2e5bc42 docs: add apt/yum repos to readme 2022-07-08 14:25:58 -04:00
Carlos Alexandro Becker
c75609322f
feat(deps): update cobra and viper (#346)
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-05-26 16:35:24 -03:00
Ayman Bagabas
b36e5ad810
feat: sync glow with git.charm.sh 2022-01-24 17:11:42 -05:00
Christian Muehlhaeuser
a8a1b98c73
glow is now in the community repo and can directly be installed with pacman 2022-01-24 01:53:57 +01:00
Christian Rocha
8138788b0a Automatically read from stdin if stdin is a pipe 2021-10-22 13:13:09 -04:00
Sebastian Cohnen
8fc9a300a4 Update README.md
adds macOS to the list of releases that are available for ARM
2021-08-14 09:29:35 +02:00
Christian Muehlhaeuser
adfc2b1c2a Update linter config 2021-07-12 03:00:48 +02:00
Christian Rocha
d0737b41af Update README footer per the newer Charm repos 2021-05-18 17:10:24 -04:00
Christian Rocha
24fc70e56b Update date in LICENSE 2021-05-18 17:10:24 -04:00
Christian Muehlhaeuser
442333d77c Fix goreportcard badge 2021-05-18 20:54:45 +02:00
Tobias Klauser
1f2b3671c7 Use golang.org/x/term
The golang.org/x/crypto/ssh/terminal package is deprecated and merely a
wrapper around golang.org/x/term. Use the latter directly and avoid a
direct dependency on the former.
2021-04-29 15:34:10 +02:00
Christian Muehlhaeuser
4863f57cea
Bump bubbletea, glamour, and go-runewidth deps 2021-04-08 16:31:00 +02:00
Christian Muehlhaeuser
92b86072a4
Bump termenv to v0.8.1 2021-04-03 01:47:38 +02:00
Christian Muehlhaeuser
ca833c733c Only login to snapcraft when secret is available in goreleaser workflow 2021-03-28 08:24:25 +02:00
Christian Muehlhaeuser
3ff9a5dfd6 Always use latest version of golangci-lint 2021-03-27 13:53:52 +01:00
Christian Muehlhaeuser
4f9ad6721c
Bump bubbletea dependency to v0.13.1 2021-03-13 04:38:54 +01:00
Christian Muehlhaeuser
e5ec06b3b9
Simplify code by removing else block 2021-03-12 13:30:30 +01:00
Christian Muehlhaeuser
2d8f8c2d64
Fix typo 2021-03-12 13:28:38 +01:00
Christian Muehlhaeuser
0333e88252
Bump bubbletea dependency 2021-03-12 12:57:45 +01:00
Christian Muehlhaeuser
8b4cae02da
Enable golint as linter 2021-03-12 02:20:07 +01:00
Christian Rocha
e9aa607400
Fix tab contrast in light mode, particularly in macOS terminal 2021-03-11 10:48:27 -05:00
Christian Muehlhaeuser
b6ec7ae14c
Bump glamour dependency 2021-03-11 16:39:18 +01:00
Christian Muehlhaeuser
5b29a023bf
Bump charm, babyenv, termenv, cobra, and viper dependencies 2021-03-11 16:33:53 +01:00
Christian Muehlhaeuser
02ad6ad364 Simplify key event handling 2021-03-10 17:11:34 +01:00
Christian Muehlhaeuser
cfb746e66a Pass through all keys when editing filter 2021-03-09 08:02:54 -05:00
Christian Muehlhaeuser
a5d4a512b7
Add goreleaser workflow (#257) 2021-03-09 13:27:02 +01:00
Carlos Alexandro Becker
1f6e2ff187 feat: snapcraft packages
Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
2021-03-09 03:31:38 +01:00
Christian Rocha
4a94b87f49 Drop string methods various some types (again)
They were only for debugging and are prone to falling out of sync.
2021-03-02 13:18:14 -05:00
Christian Rocha
dedf7967b0 Bump Bubble Tea dependency 2021-03-02 13:18:14 -05:00
Christian Rocha
da08ebc0da Don't mutate paginators in the view!! 2021-03-02 13:18:14 -05:00
Christian Rocha
3a572216ad Update pagintion after changing sections when resetting the filter
Fixes #251
2021-03-02 13:18:14 -05:00
Christian Rocha
851d21f6c4 Implement String method on some enum types for easier debugging 2021-03-02 13:18:14 -05:00
Christian Muehlhaeuser
08a8d4b5ed Bump gitcha to v0.2.0 2021-03-01 14:01:27 +01:00
purofle
2279690267
Add termux support to README (#248) 2021-02-16 08:03:01 +01:00
Christian Muehlhaeuser
b67e23e7df
Update default config and documentation 2021-02-04 01:38:52 +01:00
lestrrat
3285903aae
Enable/Disable pager from config (#242) 2021-02-04 01:34:44 +01:00
Christian Muehlhaeuser
2088bac668 Make TUI find markdowns with a variety of file extensions
List from GitHub's supported extensions:

- .md
- .markdown
- .mdown
- .mkdn
- .mkd

Fixes #233.
2021-02-01 05:00:33 +01:00
Bruno Arueira
0da9927f37 Removes duplicate doc about stashing 2021-02-01 02:47:21 +01:00
Christian Rocha
542133c42b Bump Bubble Tea and Bubbles dependencies 2021-01-19 15:30:44 -05:00
Christian Rocha
e1d8162428 Add missing newline in pager 2021-01-19 15:30:44 -05:00
Christian Rocha
8d0ebb7249 If the only arguement is a directory open a TUI at that path (see #234) 2021-01-19 15:18:24 -05:00
Christian Rocha
3d15953707 Update outdated comment 2021-01-19 15:18:24 -05:00
Christian Rocha
0a83049488
Other README updates for better dark mode compatibility 2020-12-24 17:06:48 -05:00
Christian Rocha
2e6f5c336c
Update demo GIF in README 2020-12-24 15:45:42 -05:00
Christian Rocha
fd2a6ecbdf Show stash error details in the error message view 2020-12-24 10:29:22 -05:00
Christian Rocha
6be1af4103 Add more context to stash fail status messages + make them red 2020-12-24 10:29:22 -05:00
Christian Rocha
35f1ed21c3 More logging around markdown stash failures 2020-12-24 10:29:22 -05:00
Christian Rocha
025dcee3da Fix case where stashDocument could erroneously return a command in a command 2020-12-24 10:29:22 -05:00
Christian Rocha
f2c3ef12ea Show stash feedback optimistically 2020-12-24 10:29:22 -05:00
Christian Rocha
dd936ac1cd Adjust "secondary" green color in stash highlight + help 2020-12-24 10:29:22 -05:00
Christian Rocha
379dc74d0e Secret keystroke: navigate tabs with shift+h, shift+l 2020-12-24 10:29:22 -05:00
Nicolas Martin
8f3c5df236 Highlight the most recent stash item in green in the stash view
...as long as the statusMessage is `Stashed!` is present
2020-12-24 10:29:22 -05:00
Christian Rocha
a0fb0e4cd0 Fix a case where file listings could jitter 2020-12-24 10:29:22 -05:00
Christian Rocha
70a155ee30 Only underline matched chars in filter section when a filter is applied 2020-12-24 10:29:22 -05:00
Christian Rocha
58df854466 When clearing the filter forget which markdowns were stashed in-session 2020-12-24 10:29:22 -05:00
Christian Rocha
3c74079669 In a filter, deleting stashed news removes those items from the filter 2020-12-24 10:29:22 -05:00
Christian Rocha
0c3df027cc Correct empty stashed state when filter is applied 2020-12-24 10:29:22 -05:00
Christian Rocha
2a688fc71e In a filter, deleting docs stashed in-session converts them to local ones 2020-12-24 10:29:22 -05:00
Christian Rocha
12f3209238 When stashing in a filter, turn local docs to stashed ones 2020-12-24 10:29:22 -05:00
Christian Rocha
03db9a6869 Put filter results in a tab 2020-12-24 10:29:22 -05:00
Christian Rocha
84843e4f13 Sort local ID as a last resort as not all markdowns will have an SQL ID 2020-12-24 10:29:22 -05:00
Christian Rocha
ca0dfecb1c Fix bug where deleting newly stashed news could delete the wrong item 2020-12-24 10:29:22 -05:00
Christian Rocha
37a9b6846f Filter is now called find 2020-12-24 10:29:22 -05:00
Christian Rocha
47b24f66d0 Move status message next to the logo in the file browser 2020-12-24 10:29:22 -05:00
Christian Rocha
28ccb85b2e Use ANSI-aware truncation 2020-12-24 10:29:22 -05:00
Christian Rocha
59f8a002d0 Make "already stashed" status messages more subtle 2020-12-24 10:29:22 -05:00
Christian Rocha
53155f1e16 Adjust spinner thresholds and timing so stashing feels snappier 2020-12-24 10:29:22 -05:00
Christian Rocha
8d7b614688 Fine tuning + subtle behavioral adjustments to spinners + status messages 2020-12-24 10:29:22 -05:00
Christian Rocha
64da53ede6 Rename a function 2020-12-24 10:29:22 -05:00
Christian Rocha
25dd530fb7 Improve comments 2020-12-24 10:29:22 -05:00
Christian Rocha
6946f7e581 Rename 'common' model and associated keys for sake of clarity 2020-12-24 10:29:22 -05:00
Christian Rocha
683d2c0cf6 Move TUI config into its own file 2020-12-24 10:29:22 -05:00
Christian Rocha
8a6c6ef85d Remove extraneous error interface implementations 2020-12-24 10:29:22 -05:00
Christian Rocha
6953c06b46 Improve markdown stashing and spinner logic in file listing 2020-12-24 10:29:22 -05:00
Christian Rocha
617f0980d8 Handle stash failures 2020-12-24 10:29:22 -05:00
Christian Rocha
426f4787e5 Fix panic that could happen when deleting stuff really fast 2020-12-24 10:29:22 -05:00
Christian Rocha
03d93169f2 Also make news stashable from the pager 2020-12-24 10:29:22 -05:00
Christian Rocha
88806c8abc Make news stashable 2020-12-24 10:29:22 -05:00
Christian Rocha
4fcf48f92a Filter against local and stashed files, regardless of the current section 2020-12-24 10:29:22 -05:00
Christian Rocha
cfba912be6 Maintain pagination and cursor on a per-section basis 2020-12-24 10:29:22 -05:00
Christian Rocha
a67f00a813 Press tab/shift+tab to switch between local, stash and news 2020-12-24 10:29:22 -05:00
Christian Rocha
f1ede23b3c Add logging for stash/news fetching 2020-12-24 10:29:22 -05:00
Christian Rocha
f680a9b96a Correct logic for checking when we're in stashed-only-mode 2020-12-24 10:29:22 -05:00
Christian Rocha
e179741544 Lighten mid-gray color for dark backgrounds 2020-12-24 10:29:22 -05:00
Christian Rocha
fa6952ea88 Simplify news header 2020-12-24 10:29:22 -05:00
Christian Rocha
1922bfaa68 Only show news in help if we're online 2020-12-24 10:29:22 -05:00
Christian Rocha
2eb5f46b01 Make help renderer a little less functional and more structured 2020-12-24 10:29:22 -05:00
Christian Rocha
c235780eee Appease the linter 2020-12-24 10:29:22 -05:00
Christian Rocha
20a44c5025 Replace deprecated transforms.RemoveFunc with runes.Remove 2020-12-24 10:29:22 -05:00
Christian Rocha
d21802dbc6 Style detailed help view and re-style mini help view 2020-12-24 10:29:22 -05:00
Christian Rocha
92d7696c76 Disable news toggling while filtering 2020-12-24 10:29:22 -05:00
Christian Rocha
4febcb4bff Render larger help view with the ? key 2020-12-24 10:29:22 -05:00
Christian Rocha
b50614c64c Let's try an an emoji for the news header 2020-12-24 10:29:22 -05:00
Christian Rocha
c4230ca3d9 Rework mini stash help view builder + stub out larger stash help 2020-12-24 10:29:22 -05:00
Christian Rocha
cffa611dfb When exiting pager or filter, stay in news if we were on news prior 2020-12-24 10:29:22 -05:00
Christian Rocha
4c63c62bfa Replace bit-based document type implementation with a set-based one 2020-12-24 10:29:22 -05:00
Christian Rocha
7fabcea06d Make stash note editing and deletion independent of main state 2020-12-24 10:29:22 -05:00
Christian Rocha
15494e99b4 Also disable news if we're in local-only mode 2020-12-24 10:29:22 -05:00
Christian Rocha
dd9179a9ae Remove "News:" prefix from news items 2020-12-24 10:29:22 -05:00
Christian Rocha
2748e75165 If we're offline disable the news section 2020-12-24 10:29:22 -05:00
Christian Rocha
fc898831af Now you can filter news too
Also note that the main stash state and the filter state now operate
independently.
2020-12-24 10:29:22 -05:00
Christian Rocha
40a02259fc Only show news in news section 2020-12-24 10:29:22 -05:00
Christian Rocha
c10a84adc1 Sort news by date 2020-12-24 10:29:22 -05:00
Christian Rocha
dfeb60770e The n key also exits news 2020-12-24 10:29:22 -05:00
Christian Rocha
2d10fb8749 Adjust news header syle 2020-12-24 10:29:22 -05:00
Nicolas Martin
537ef308d2 Add a menu item for only display news 2020-12-24 10:29:22 -05:00
Nicolas Martin
d9242c9a72 Expand the path for a specified style file 2020-12-16 17:04:35 +01:00
Selwyn
753a7c6993 Include installation command for Solus
Closes #203
2020-12-09 17:54:26 +01:00
shaun mitchell
d98cda4b2b fixed showStatusMessage(string) to display passed parameter 2020-12-08 10:45:12 -05:00
Christian Muehlhaeuser
53592dc04a
Add .envrc to .gitignore 2020-11-25 01:52:04 +01:00
Christian Rocha
56a9edd312 Filter markdowns asynchronously so filtering doesn't block 2020-11-25 00:33:04 +00:00
Christian Rocha
e9d728cd96 Add "hidden" command/config for switching on mouse wheel support
See (#206)
2020-11-20 15:28:36 -05:00
Christian Rocha
c16a146c00
Load the whole stash, page by page, automatically 2020-11-20 15:02:15 -05:00
Christian Rocha
5023d4a0be
Remove some code duplication with stash and news message handling 2020-11-19 22:45:02 -05:00
Christian Rocha
86e581c79c
Add matching stash/news documents to filter results as they're loaded 2020-11-19 22:26:31 -05:00
Christian Rocha
53b9c7ee62 Open correct document when filtered down to one item 2020-11-18 12:33:39 -05:00
Christian Rocha
929b1ed3b3 Remove pointer reference from stashModel.getNotes()
It's not altering the model, so removing the pointer reference to
reflect that.
2020-11-18 12:33:39 -05:00
Christian Rocha
4e6460682e Consolidate common model properties to reduce duplication 2020-11-18 12:33:39 -05:00
Christian Rocha
ae4c85e97d Maintain positions when renaming documents in a filtered listing 2020-11-17 15:57:55 -05:00
Christian Rocha
de09997fdb Normalize strings during filtering
This makes it easier to match characters with diacritics (i.e. you can type 'o'
and match an 'ö').
2020-11-17 15:57:55 -05:00
Christian Rocha
6cf1152f87 Also highlight filtered characters on selected filter results 2020-11-17 15:57:55 -05:00
Christian Rocha
58eca2414a Match documents which were stashed in-session then renamed
Note that we've also removed field `displayPath` from struct `markdown`
as it turned out to not be necssary in the first place.
2020-11-17 15:57:55 -05:00
Nicolas Martin
d98e3c11ba Switch to sahilm/fuzzy for fuzzy-filtering and text highlighting 2020-11-17 15:57:55 -05:00
Christian Rocha
43a5ac1b64 Highlight relevant characters in filter results 2020-11-17 15:57:55 -05:00
Christian Rocha
f9883e8f8d Change 'search' to 'filter' 2020-11-17 15:57:55 -05:00
Christian Rocha
fb5a81f4d6 Upgrade to Bubble Tea v0.12.2 and Bubbles v0.7.5 2020-11-17 15:57:55 -05:00
Christian Rocha
7722b6e6bc Minor comment improvement 2020-11-17 15:57:55 -05:00
Christian Rocha
464c84bb13 Fix a regression where esc wouldn't clear filtered results 2020-11-17 15:57:55 -05:00
Christian Rocha
005aa65d7b The q key should quit when browsing a list of filtered documents 2020-11-17 15:57:55 -05:00
Christian Rocha
000a0baae8 Use path separator const when building relative path 2020-11-17 15:57:55 -05:00
Christian Rocha
d09b225087 Filter against relative path rather than full path 2020-11-17 15:57:55 -05:00
Christian Rocha
8277cbb71f Remove some consts + check cell widths of prompts, not byte lengths
We're able to remove those consts by virtue of reflow's new-ish
ansi.PrintableRuneWidth.
2020-11-17 15:57:55 -05:00
Christian Rocha
4b121593d4 Mop up styling big time 2020-11-17 15:57:55 -05:00
Christian Rocha
7b7a398c74 Don't dim the search entry when blurring the search field 2020-11-17 15:57:55 -05:00
Christian Rocha
759b8707f8 Tidying up 2020-11-17 15:57:55 -05:00
Christian Rocha
899c038402 Also add tab/shift+tab bindings to file browser 2020-11-17 15:57:55 -05:00
Christian Rocha
a17cfa6d26 Shift+tab also moves into filtered results 2020-11-17 15:57:55 -05:00
Christian Rocha
2e1579542e Don't highlight filtered items until search is blurred 2020-11-17 15:57:55 -05:00
Christian Rocha
ef0f5338f4 Pressing return with no items filtered clears the filter 2020-11-17 15:57:55 -05:00
Christian Rocha
9797f225a7 Change help text if we've filtered down to one item 2020-11-17 15:57:55 -05:00
Christian Rocha
75528db9f1 Maintain previous filtering after unloading a document 2020-11-17 15:57:55 -05:00
Christian Rocha
6965caf3af When stashing/unstashing an item during filtering maintain its position 2020-11-17 15:57:55 -05:00
Christian Rocha
d717b7e330 Add dimmed colors for light backgrounds 2020-11-17 15:57:55 -05:00
Nicolas Martin
356cd57c0e Only dim items when starting a new search 2020-11-17 15:57:55 -05:00
Nicolas Martin
5e27376af4 Keep displaying local markdowns after stashing & deleting them 2020-11-17 15:57:55 -05:00
Christian Rocha
8d03dd5731 Adjust dimmed colors for dark backgrounds; light colors outstanding 2020-11-17 15:57:55 -05:00
Nicolas Martin
2131b255ef Reset cursor and page positions when entering the search state 2020-11-17 15:57:55 -05:00
Nicolas Martin
aba133b7e6 There's always at least 1 page available in the paginator
Passing 0 to `paginator.SetTotalPages()` is ineffective as it won't alter the
model (paginator needs at least 1 page) and therefore doesn't update the page
count. Filtering can lead to 0 items in the stash view - in this case we still
want to have a page available in the paginator - so we also have pass 1 to
`setTotalPages()`.
2020-11-17 15:57:55 -05:00
Nicolas Martin
7500d88e93 TIL: 'News' is singular 2020-11-17 15:57:55 -05:00
Nicolas Martin
89965743a1 Ensure the search binding "/" always works 2020-11-17 15:57:55 -05:00
Nicolas Martin
c91501655f Dont panic during actions in the filtered stash view w/o items
Filterng down the stash to 0 items (no cursor is displayed as there are no items
left) caused a panic during execution of one of these actions:
- x: Delete
- m: Set Memo
- s: Stash file

This change prevents that from happening by breaking the switch statement when
there are no items left.
Also glow now uses the filtered markdown notes in the `stashHelpView` so
`m.selectedMarkdown()` cannot return a nil refernce anymore.
2020-11-17 15:57:55 -05:00
Nicolas Martin
56847c1b01 Dim search results during filtering 2020-11-17 15:57:55 -05:00
Nicolas Martin
4782b99154 Display news item count in stash view 2020-11-17 15:57:55 -05:00
Nicolas Martin
7fb4a43474 Fix cursor placement when paging trough filtered notes 2020-11-17 15:57:55 -05:00
Nicolas Martin
fbe93a90ff Add a prefix according to the markdownType when filtering notes 2020-11-17 15:57:55 -05:00
Nicolas Martin
faeef13e55 Get the cursor movement going while filtering stashitems 2020-11-17 15:57:55 -05:00
Nicolas Martin
259f4b584b Use setTotalPages() to set the number of items in the paginator
This takes the filtered stash state in account
2020-11-17 15:57:55 -05:00
Nicolas Martin
e985f96dc4 Add fuzzy filtering for stash notes 2020-11-17 15:57:55 -05:00
Nicolas Martin
9fd3dfef5a Add a searchinput box to the stashModel 2020-11-17 15:57:55 -05:00
Nicolas Martin
23fb61f752 Add stash states for searching through notes
- stashStateSearchNotes will be used while entering the search term
- stashStateShowFiltered will only display the filtered notes in the stash view.
2020-11-17 15:57:55 -05:00
Cristian Dominguez
998cbf1cc4 Ignore YAML frontmatter 2020-10-30 19:06:46 +01:00
batkiz
9ce4ec9149 Now in Scoop!
check https://github.com/ScoopInstaller/Main/pull/1510
2020-10-27 15:49:34 +01:00
Christian Muehlhaeuser
d4239a55db Error codes should be in the range of 0 to 125 2020-10-27 15:13:20 +01:00
Christian Muehlhaeuser
7a9452dbff Point out the 'glow config' command in README 2020-10-27 15:13:07 +01:00
Nicolas Martin
c5adc8fc2d
Saner default values in the defaultConfig 2020-10-26 21:02:54 +01:00
Nicolas Martin
858beaf143
Check for $EDITOR before writing the file 2020-10-26 21:02:09 +01:00
Nicolas Martin
4b4fb783d2
Open the configuration file with $EDITOR via 'glow config' 2020-10-26 20:21:38 +01:00
Christian Rocha
7a700d2511
Stub out config sub-command 2020-10-26 12:21:23 -04:00
Christian Rocha
7ea7cc59e3
Respect width flag/config setting in the TUI; closes #168 2020-10-23 23:38:28 -04:00
Christian Muehlhaeuser
2091f03235 Support width as a config setting 2020-10-24 05:16:00 +02:00
Christian Muehlhaeuser
c45b2fc4b0 Add paragraph about the config file to README 2020-10-24 05:16:00 +02:00
Christian Muehlhaeuser
61a2533d41 Display default value for config flag in --help 2020-10-24 05:16:00 +02:00
Christian Muehlhaeuser
d5446bfddd Add config file support
The combination of Viper & go-app-paths lets us detect a config file
'glow.yml' in various default config paths (XDG-compliant), across all
platforms.

Currently there are only two configuration values:

local: true
style: "/some/style.json"

Additionally, this introduces mapping environment variables to config
values:

GLOW_LOCAL="true"
GLOW_STYLE="/some/style.json"

The values will be used in this order:

CLI-arg > ENV-var > config file > default
2020-10-24 05:16:00 +02:00
Christian Muehlhaeuser
f586716b77 Split up workflows and automatically pick latest Go version 2020-10-23 13:11:56 +02:00
Christian Rocha
cde8999cf6
Bump Bubble Tea and Bubbles deps for keybinding improvements 2020-10-22 15:59:22 -04:00
Christian Rocha
53aeb75c92
Update footer in README 2020-10-20 10:16:30 -04:00
AristoChen
4baff6cc45 Fix typo in README.md 2020-10-20 14:37:14 +02:00
Christian Rocha
186d07da55 Don't show stash/memo info in pager help for news 2020-10-19 15:50:50 +00:00
Christian Rocha
3b06ccbc34 Fix bug where pager help isn't updated after an in-pager stash 2020-10-19 15:50:50 +00:00
Christian Rocha
0949a6656b Update to bubbletea 0.12.0; minimum required changes 2020-10-19 15:50:50 +00:00
Christian Rocha
32f0be2688 Use the same type to track docs to load and docs loaded 2020-10-19 15:50:50 +00:00
Christian Rocha
54a7394448 Simplify loading completion check 2020-10-19 15:50:50 +00:00
Christian Rocha
fcf109aaff Add flag for local files only (aka disable network) in TUI 2020-10-19 15:50:50 +00:00
Christian Rocha
e73190a217 Move "stashed only" flag from the package-level into the model 2020-10-19 15:50:50 +00:00
Christian Rocha
5847578f42 Convert pager to bubbletea 0.12.0 format 2020-10-19 15:50:50 +00:00
Christian Muehlhaeuser
e63aa950d4 Fix coverage reporting from GitHub workflow 2020-10-19 06:15:12 +02:00
Christian Rocha
477979bce9 Correct placement of stash hint in pager help 2020-10-19 02:53:55 +00:00
Christian Rocha
8b78086f56 Remove extra line at the bottom of pager help 2020-10-18 22:57:11 +00:00
Christian Muehlhaeuser
dfa54ada45 Add golangci linter to GitHub workflows 2020-10-18 22:28:50 +02:00
Christian Muehlhaeuser
d36b9d4838 Clean up and re-enable Windows workflow 2020-10-18 18:18:10 +02:00
Prashanta Paudel
7f2c30065d
Fix typo in README.md (#176) 2020-10-18 18:11:45 +02:00
Herby Gillot
3e6e1a17b0 README: add MacPorts install instructions 2020-10-17 22:32:16 +02:00
Christian Muehlhaeuser
07aa18e350
Update build instructions
Fixes #171.
2020-10-17 14:30:03 +02:00
Christian Muehlhaeuser
d1bb323316
State minimum required Go version in README
Fixes #164.
2020-10-15 06:53:03 +02:00
Nicolas Martin
7f359969b7 Don't handle filenames with a colon in it as http(s) url 2020-10-15 06:50:34 +02:00
Christian Muehlhaeuser
30d37a1eeb
Fix README typo 2020-10-14 04:57:57 +02:00
Christian Rocha
f58ec4d948
Note new Windows, OpenBSD, and Alpine support in README 2020-10-13 22:56:33 -04:00
Christian Muehlhaeuser
b9e5d6b144
Enable Windows, FreeBSD, OpenBSD and Alpine builds 2020-10-14 04:38:57 +02:00
Christian Rocha
90499914a8
Bump bubbletea dependency for arrow key support on Windows 2020-10-13 15:50:43 -04:00
Christian Rocha
d4adf0d856
Don't use emojis on Windows 2020-10-13 15:14:07 -04:00
Christian Rocha
8ed7bb7717
Restore ANSI support to non-TUI Glow on Windows 2020-10-12 23:57:08 -04:00
Christian Rocha
5f9c5b708d
Windows support + bump termenv dependency 2020-10-12 23:23:38 -04:00
Christian Rocha
5a6544d71e
Fix typo in README 2020-10-12 08:02:32 -04:00
Christian Rocha
11d585870c
Don't show add memo help in pager for local items 2020-10-09 14:27:19 -04:00
bubersson
66a8ac10bc Remove duplicate "down/j" from the "?" help 2020-10-09 13:50:37 +02:00
Christian Muehlhaeuser
6a1c227231
Disable CGO for goreleaser builds 2020-10-07 19:02:12 +02:00
Christian Muehlhaeuser
281426270c
Enable brew Linux builds in the charmbracelet/tap 2020-10-06 18:46:52 +02:00
Christian Muehlhaeuser
66caa28d64
Bump bubbletea, bubbles, charm & glamour dependencies 2020-10-06 18:34:39 +02:00
Christian Rocha
b30bf4cf6f Bump bubbletea for post-panic terminal restoration 2020-10-05 13:54:50 +02:00
Christian Rocha
18e591bafe Use filepath.Join rather than string concatenation 2020-10-05 13:54:39 +02:00
Christian Rocha
21eccfd2c3 Hop back to Muesli's gitcha repo at master 2020-10-05 13:54:38 +02:00
Christian Rocha
26b3292f57 Don't allow stashing until authed + introduce "offline mode" in UI 2020-10-05 13:54:24 +02:00
Christian Rocha
17ea86e3ed Bump charm lib for stash-on-first-run fix 2020-10-05 13:54:24 +02:00
Christian Rocha
555462462f More logging around keygen 2020-10-05 13:54:24 +02:00
Christian Rocha
b94615f5ac Fix debug log in stash deletion command 2020-10-05 13:54:24 +02:00
Christian Rocha
2b064546b9 Move markdown types into a separate file 2020-10-05 13:54:24 +02:00
Christian Rocha
485774d0f9 Bump charm lib to fix crash if user data dir doesn't exist 2020-10-05 13:54:24 +02:00
Christian Rocha
5c8da55a5b Simplify environment vars 2020-10-05 13:54:24 +02:00
Christian Rocha
f2537b1811 Update loading message if you're just loading your stash 2020-10-05 13:54:24 +02:00
Christian Rocha
dc74e17cb8 Add missing comma 2020-10-05 13:54:24 +02:00
Christian Rocha
eb6fa55ec3 Run glow stash to browse stashed files only 2020-10-05 13:54:24 +02:00
Christian Rocha
6e7cea7e52 Remove more old code for specifying SSH paths 2020-10-05 13:54:24 +02:00
Christian Rocha
a705921a96 Update -a help language 2020-10-05 13:54:24 +02:00
Christian Rocha
a3a05fde9c Move ignore stuff to gitcha (and temporarily use the meowgorithm fork) 2020-10-05 13:54:23 +02:00
Christian Rocha
f8fa5a04dc use strings.TrimPrefix instead of Replace when checking paths 2020-10-05 13:54:01 +02:00
Christian Rocha
9903ea8dcf Ignore $GOPATH everywhere and ~/Library on macOS 2020-10-05 13:54:01 +02:00
Christian Rocha
5b20e3edae Hide files and dirs starting with a dot by default 2020-10-05 13:54:01 +02:00
Christian Rocha
05a77ff655 Fix make target for logging to the local directory 2020-10-05 13:54:01 +02:00
Christian Rocha
f8c4ddc388 Remove disabled force key/identity file stuff for now 2020-10-05 13:54:01 +02:00
Christian Rocha
45cde43dd4 Add more checks for nil Charm clients 2020-10-05 13:54:01 +02:00
Christian Rocha
b20a06d92b Fix stash capitalization in help 2020-10-05 13:54:01 +02:00
Christian Rocha
805a26090f Format help and add examples to stash command 2020-10-05 13:54:01 +02:00
Christian Rocha
2c411a3f28 Rename stash.go to stash_cmd.go for clarity 2020-10-05 13:54:01 +02:00
Christian Rocha
b3909354aa Use base name when stashing from the CLI if no memo is set 2020-10-05 13:54:01 +02:00
Christian Rocha
a5b67ba721 Separate out the TUI and CLI sections of the README 2020-10-05 13:54:01 +02:00
Christian Rocha
1443d3e5f3 Improve README wording around stashing 2020-10-05 13:54:01 +02:00
Christian Rocha
f203f845b7 Fix README typos 2020-10-05 13:54:01 +02:00
Christian Rocha
dde57403c6 Add info about new Glow version to README 2020-10-05 13:54:01 +02:00
Christian Rocha
676d215966 Keybindings for navigating to the start/end of file listing 2020-10-05 13:54:01 +02:00
Christian Rocha
ee4ed1bad5 Keybindings for jumping to the top and the bottom of the pager 2020-10-05 13:54:01 +02:00
Christian Muehlhaeuser
a0ddc315de Bump gitcha dep 2020-10-05 13:54:01 +02:00
Christian Rocha
3588f45ee9 Cleanup status message vars 2020-10-05 13:54:01 +02:00
Christian Rocha
6aba7afb8b Remove some magic numbers to calm the linter down 2020-10-05 13:54:01 +02:00
Christian Rocha
ae0c747a4b Remove unused variables and members, fix ineffectual assignment 2020-10-05 13:54:01 +02:00
Christian Rocha
88523ee94f Remove extraneous formatting call 2020-10-05 13:54:01 +02:00
Christian Rocha
c1496854a8 Remove newline 2020-10-05 13:54:01 +02:00
Christian Rocha
50e8782aa8 Remove unnecessary comparison 2020-10-05 13:54:01 +02:00
Christian Rocha
b296cbb780 Remove newlines the linter is complaining about 2020-10-05 13:54:01 +02:00
Christian Rocha
06cbbd7b6b Use lock emoji for stashed item in pager 2020-10-05 13:54:01 +02:00
Christian Rocha
b2511c54b0 Correct header counts for local items converted to stashed ones 2020-10-05 13:54:01 +02:00
Christian Rocha
912c80651a Apply fine-grained spinner display rules to file listing view 2020-10-05 13:54:01 +02:00
Christian Rocha
60b1d6a6df Fix bug where user couldn't exit pager in various stash-related states 2020-10-05 13:53:50 +02:00
Christian Rocha
cdc55a4b28 Always show spinner for a minimum amount of time when stashing 2020-10-05 13:53:49 +02:00
Christian Rocha
2f9babce80 Restore original displayed path when deleting a newly stashed item 2020-10-05 13:53:26 +02:00
Christian Rocha
5515460f32 Fix regression where note-setting on newly stashed items couldn't be done 2020-10-05 13:53:26 +02:00
Christian Rocha
82acc62783 Move common commands to the main UI file 2020-10-05 13:53:26 +02:00
Christian Rocha
a02cc01049 Pull an always-nil return value 2020-10-05 13:53:26 +02:00
Christian Rocha
23817355c6 Remove unnecessary conversion 2020-10-05 13:53:26 +02:00
Christian Rocha
c0daa74358 Add periods to comments to make the linter happy 2020-10-05 13:53:26 +02:00
Christian Rocha
2fdef64478 Stashed docs become local upon delete if they were stashed in-session 2020-10-05 13:53:26 +02:00
Christian Rocha
cdf11136f4 Log when stash item deletions fail 2020-10-05 13:53:26 +02:00
Christian Rocha
e9c47a185f Be specific that we're deleting only from the stash 2020-10-05 13:53:26 +02:00
Christian Rocha
089b66f622 Fix extreme edge case error logging where local file has no path 2020-10-05 13:53:26 +02:00
Christian Rocha
2e7b0ec60f Fix regression where items stashed in the pager were treated as local 2020-10-05 13:53:26 +02:00
Christian Rocha
d7974f035c When stashing, replace local file with stashed file in the file listing 2020-10-05 13:53:26 +02:00
Christian Rocha
575017eb12 More integrated-looking stash confirmation message in pager 2020-10-05 13:53:26 +02:00
Christian Muehlhaeuser
b29f172c80 Bump gitcha dep 2020-10-05 13:53:26 +02:00
Christian Rocha
7166f99358 Update Charm badge 2020-10-05 13:53:26 +02:00
Christian Rocha
5d267e900a Update UI image + minor copy edits 2020-10-05 13:53:26 +02:00
Christian Rocha
4b732ada50 Bump Termenv to 0.7.2 2020-10-05 13:53:26 +02:00
Christian Rocha
a10e7862ca Update error message to match new error type 2020-10-05 13:53:26 +02:00
Christian Rocha
7d67711cdf Show spinner while stashing in the pager 2020-10-05 13:53:26 +02:00
Christian Rocha
1d434d64ad Stashed items get a green indicator in the pager 2020-10-05 13:53:26 +02:00
Christian Rocha
66dae71253 Show spinner in stash view while stashing 2020-10-05 13:53:26 +02:00
Christian Rocha
6e36891857 If a file is currently mid-stash don't let the user stash it again 2020-10-05 13:53:26 +02:00
Christian Rocha
2907abd351 Don't show status message in file listing when stashing in pager 2020-10-05 13:53:26 +02:00
Christian Rocha
e01dc95d7d Shorten status messages to 2 seconds 2020-10-05 13:53:26 +02:00
Christian Rocha
d6f296c44b Load document body before stashing when stashing from the file listing 2020-10-05 13:53:26 +02:00
Christian Rocha
9db7c2e3ad Make general error msg a struct for type safety 2020-10-05 13:53:26 +02:00
Christian Rocha
0897472ca2 Attempt to cleanup the Msg grab bag formatting 2020-10-05 13:53:26 +02:00
Christian Rocha
5bdf7649b0 Set ID of newly stashed markdowns so we can operate on them 2020-10-05 13:53:26 +02:00
Christian Rocha
d9b1d335d4 Use "just now" rather than seconds for documents under 1 minute old 2020-10-05 13:53:26 +02:00
Christian Rocha
40724af7e1 Short circuit help view sooner if we can 2020-10-05 13:53:26 +02:00
Christian Rocha
2e16f2682e Highlight stash choice in file listing footer 2020-10-05 13:53:26 +02:00
Christian Rocha
04b6089bd9 Stash documents from the stash view 2020-10-05 13:53:26 +02:00
Christian Rocha
3572ef5470 Log when local file search finishes 2020-10-05 13:53:26 +02:00
Christian Rocha
651683cd21 Treat Markdown.CreatedAt as values per the updates in the charm lib 2020-10-05 13:53:26 +02:00
Christian Rocha
5613e36154 Adjust dark green color in status message bar 2020-10-05 13:53:26 +02:00
Christian Rocha
593d9f07e7 Stash local files from the pager 2020-10-05 13:53:26 +02:00
Christian Muehlhaeuser
93eea84f3b Add golangci config 2020-10-05 13:53:26 +02:00
Christian Muehlhaeuser
2821cf2fb4 Disable identity & force-key flags 2020-10-05 13:53:26 +02:00
Christian Muehlhaeuser
be478cbaf3 Adapt to upstream gitcha API 2020-10-05 13:53:26 +02:00
Christian Muehlhaeuser
df15d81dbb Bump gitcha dependency 2020-10-05 13:53:26 +02:00
Christian Rocha
d34a687ddc Pin charm, glamour, and bubbles deps at master 2020-10-05 13:53:17 +02:00
Christian Rocha
3c04fc891c Update deprecated homebrew tap section in goreleaser config 2020-10-05 13:52:51 +02:00
Christian Rocha
b39b2e64fe Fix checksums in go.sum 2020-10-05 13:52:51 +02:00
Christian Rocha
881504e603 Bump Charm and Bubble Tea + de-bump Termenv to 0.7.0 2020-10-05 13:52:51 +02:00
Christian Rocha
ddbacffc0b Log IO errors if a logfile is set 2020-10-05 13:52:51 +02:00
Christian Muehlhaeuser
705665bda9 Bump charm dependency 2020-10-05 13:52:50 +02:00
Christian Rocha
7f26d27e84 Bump termenv 2020-10-05 13:52:36 +02:00
Toby Padilla
ad13b07382 Updated charm version 2020-10-05 13:52:36 +02:00
Christian Rocha
994b85b9bd Add Go 1.15.x to build matrix 2020-10-05 13:52:36 +02:00
Christian Rocha
c55bb23744 Minor comments and cleanup in the TUI 2020-10-05 13:52:36 +02:00
Christian Rocha
3d5d203217 Remove unused variable 2020-10-05 13:52:36 +02:00
Christian Rocha
2ed9e71533 Bump Charm library + tiny associated code update 2020-10-05 13:52:35 +02:00
Christian Rocha
2a3fa6139c Simplify messaging in (rare) state where there's absolutely nothing to show 2020-10-05 13:52:22 +02:00
Christian Rocha
2c3c5a8c87 Replace commented-out code about stash-related pagination with a note 2020-10-05 13:52:22 +02:00
Christian Rocha
bf683538f8 Bump charm dependency and update keygen import 2020-10-05 13:52:22 +02:00
Christian Rocha
428a315947 Restore Glamour rendering, performance pager, and debug logging
I accidentally hosed all of these features in the last revision.
2020-10-05 13:52:22 +02:00
Christian Rocha
fa7c8f09f9 Respect -i/--identity flag in the TUI 2020-10-05 13:52:22 +02:00
Christian Rocha
956fc9ec57 Bump dependencies 2020-10-05 13:52:21 +02:00
Christian Rocha
d5045fa4db Remove windows from CI for now 2020-10-05 13:52:07 +02:00
Toby Padilla
accb5fb7d5 Stash documentation draft 2020-10-05 13:52:07 +02:00
Christian Rocha
844cdcdaaf Pull out Windows language from README for now 2020-10-05 13:52:07 +02:00
Christian Muehlhaeuser
a5febb92ff Disable Windows & FreeBSD builds temporarily 2020-10-05 13:52:07 +02:00
Christian Rocha
60cee4a9a1 u/d also pages back/forth in the the file listing 2020-10-05 13:52:07 +02:00
Christian Rocha
8471c52fd7 Keep the stash help view from wrapping in very narrow windows 2020-10-05 13:52:07 +02:00
Christian Rocha
b831afc966 Don't say we haven't found anything until we're done searching 2020-10-05 13:52:07 +02:00
Christian Rocha
25a5e6ee10 Bump Charm, Bubble Tea, and Termenv 2020-10-05 13:52:07 +02:00
Christian Rocha
6aadaf8a69 Remove stash list, get and delete commands 2020-10-05 13:52:07 +02:00
Christian Rocha
d16c13f929 Disable mouse for now 2020-10-05 13:52:07 +02:00
Christian Rocha
89a794be7b Bump charm lib dependency to get prod server settings 2020-10-05 13:52:07 +02:00
Christian Rocha
7dbd264deb Bump bubbletea and bubbles for paste support in notes 2020-10-05 13:52:06 +02:00
Christian Rocha
a2fd25d0f0 Sort local items by filename rather than date 2020-10-05 13:51:54 +02:00
Christian Muehlhaeuser
6b21789417 Ensure sorting is stable: type -> timestamp -> path/ID 2020-10-05 13:51:54 +02:00
Christian Muehlhaeuser
71d6ab0476 Bump gitcha dependency 2020-10-05 13:51:54 +02:00
Christian Rocha
66dd3a74cb Bump bubbles to fix crash when deleting memo text 2020-10-05 13:51:43 +02:00
Christian Rocha
a04ca3f61e Fix bug where pressing left to exit the pager would also page the stash 2020-10-05 13:51:31 +02:00
Christian Rocha
8903ad88cd Update stash help to push the notion that q quits 2020-10-05 13:51:31 +02:00
Christian Rocha
e0d6d7bea1 Use the little green dot from the TUI when stashing on the CLI 2020-10-05 13:51:31 +02:00
Christian Rocha
f611b6e71f Also use f and b to change pages in the file listing view 2020-10-05 13:51:31 +02:00
Christian Rocha
fc2f05626e Pressing q quits no matter where you are 2020-10-05 13:51:31 +02:00
Christian Rocha
b31aba3c4e Fix case where pager commands could be lost 2020-10-05 13:51:31 +02:00
Christian Rocha
d941957b92 Also exit the pager with left arrow, h and delete/backspace 2020-10-05 13:51:31 +02:00
Christian Rocha
45163be06f Refer to local files as "local" instead of "files" 2020-10-05 13:51:31 +02:00
Christian Rocha
01de366703 Truncate long lines in stash get command + cosmetic stuff 2020-10-05 13:51:31 +02:00
Christian Rocha
97d11c0065 Bump bubbles for fix where scroll wheel would render extra lines
In particular, the extra lines would be on the top and bottom of the
pager depending on the size of the terminal window.
2020-10-05 13:51:30 +02:00
Christian Rocha
83eec3f833 Don't change the state when initializing the Charm client
This was residue from an earlier version in the application when it opened
with a spinner rather than directly in the stash. It caused the pager to
lock up and it's status bar to disappear if a local markdown file was
opened before the Charm client initialized.
2020-10-05 13:51:19 +02:00
Christian Rocha
2c71185ba6 Remove mysterious "stash may not be totally loaded" dots for now 2020-10-05 13:51:19 +02:00
Christian Rocha
3f22be5ae4 Bump gitcha and adjust for gitcha API changes 2020-10-05 13:51:19 +02:00
Christian Rocha
2b42115599 Bump bubbles to get pgup/pgdown in the stash (via the paginator) 2020-10-05 13:51:08 +02:00
Christian Rocha
f2fce69671 Bump Bubbletea dep for pgup/pgdown fixes (also bump Termenv to 0.6) 2020-10-05 13:50:53 +02:00
Christian Rocha
85c121d0ac Add Arabic pagination if the dot nav gets wider than the terminal width 2020-10-05 13:50:39 +02:00
Christian Rocha
3619ad137a Remove the bottom gap when closing the pager help whilst at the bottom 2020-10-05 13:50:38 +02:00
Christian Rocha
3b06c1fcd6 Correction help-toggling method name in pager 2020-10-05 13:50:19 +02:00
Christian Rocha
ad77b91f64 Bump bubbles for latest viewport fixes 2020-10-05 13:50:18 +02:00
Christian Rocha
c00fe7fc4a Correct env var placement in Makefile 2020-10-05 13:50:04 +02:00
Christian Rocha
3b3bf684f9 Detect when stash and news loading fails (and update the UI accordingly) 2020-10-05 13:50:04 +02:00
Christian Rocha
a35d83d5d2 View errors with "!" 2020-10-05 13:50:04 +02:00
Christian Rocha
74aad5324a Don't quit if user uses esc/q to exit the non-fatal error view 2020-10-05 13:50:04 +02:00
Christian Rocha
4cfd1fc897 Better error view language depending on whether error is fatal 2020-10-05 13:50:04 +02:00
Christian Rocha
3409ca342a Don't show navigation help in the stash if there's only one item 2020-10-05 13:50:04 +02:00
Christian Rocha
d156142f57 Press "r" to view errors in the stash 2020-10-05 13:50:04 +02:00
Christian Rocha
e8eea0b1a1 Don't shut down when we encounter network errors 2020-10-05 13:50:04 +02:00
Christian Rocha
abff9d47de Better header messages depending on what we find 2020-10-05 13:50:04 +02:00
Christian Rocha
aa0c48f983 Show local and stashed counts in header 2020-10-05 13:50:04 +02:00
Christian Rocha
9263f82ebc Avoid potential variable capture situations 2020-10-05 13:50:04 +02:00
Christian Rocha
091c3be039 Keep loading stuff in the background while viewing a document 2020-10-05 13:50:04 +02:00
Christian Rocha
70833af87f Use keygen directly; we don't need the Bubble Tea component anymore 2020-10-05 13:50:04 +02:00
Christian Rocha
70070d2948 Draw a spinner in the file listing while stuff is loading 2020-10-05 13:50:04 +02:00
Christian Rocha
df95fa6ba9 Selection index in file shouldn't dip below 0 2020-10-05 13:50:04 +02:00
Christian Rocha
1bf2298536 Start Glow in the file loader while file and stash listing load 2020-10-05 13:50:04 +02:00
Christian Rocha
d4f192487c Start looking for local files the moment Glow starts 2020-10-05 13:50:04 +02:00
Christian Rocha
1200b86729 Cleanup pager status bar view thanks to the latest updates in reflow 2020-10-05 13:50:03 +02:00
Christian Rocha
f52311af88 Rework/cleanup styling in stash/file listing 2020-10-05 13:49:47 +02:00
Christian Rocha
930f15fbee Bump termenv to get ANSI-aware rune width support 2020-10-05 13:49:47 +02:00
Christian Rocha
dbb32354a3 Flag stashed items with a little dot 2020-10-05 13:49:47 +02:00
Christian Rocha
87d2a9a143 Update header depending on what's stashed 2020-10-05 13:49:47 +02:00
Christian Rocha
cb8f7fcd19 Take note internally when the local file search is finished 2020-10-05 13:49:47 +02:00
Christian Rocha
ee058c4816 Update spinners for Bubbles 0.4.0 2020-10-05 13:49:47 +02:00
Christian Rocha
e7cea6280a Bump dependencies 2020-10-05 13:49:46 +02:00
Christian Rocha
dd8c650830 Only show memo key in pager help for user stashed markdown 2020-10-05 13:49:31 +02:00
Christian Rocha
ac872bf41f Let's now refer to userMarkdown as stashedMarkdown 2020-10-05 13:49:31 +02:00
Christian Rocha
68d2d344b2 Insert files into the file listing as we find 'em 2020-10-05 13:49:31 +02:00
Christian Rocha
7b6c3b3740 Don't allow users to set notes on local files 2020-10-05 13:49:31 +02:00
Christian Rocha
3d1adeec52 Use the proper method for adding markdown docs to the stash 2020-10-05 13:49:31 +02:00
Christian Rocha
2581f7625f Load local markdown files into the pager 2020-10-05 13:49:31 +02:00
Christian Rocha
17dcda28ea Strip cwd from local file paths 2020-10-05 13:49:31 +02:00
Christian Rocha
58abda8cf7 Sort stash by local files first, then date 2020-10-05 13:49:31 +02:00
Christian Rocha
e0bd2dd466 Crudely load local markdown files into the stash 2020-10-05 13:49:31 +02:00
Christian Rocha
71b9387e01 Add gitcha 2020-10-05 13:49:30 +02:00
Christian Rocha
c9fcf3b58e Bump bubletea to fix macOS term bug where memo got sucked into scroll area 2020-10-05 13:49:03 +02:00
Christian Rocha
9931472aec Bump Bubble Tea to fix bux where short docs show loader underneath 2020-10-05 13:49:03 +02:00
Christian Rocha
89e5e38953 Bump Bubble Tea to fix redraw bug with short documents 2020-10-05 13:49:02 +02:00
Christian Rocha
8370f7441e Fix a crash when opening pager help in very narrow windows 2020-10-05 13:48:22 +02:00
Christian Rocha
1280ed3bbe Bump Bubble Tea to get captured variable/race condition fix on init 2020-10-05 13:48:22 +02:00
Christian Rocha
40dfa9e1b7 Comments 2020-10-05 13:48:22 +02:00
Christian Rocha
93e80b0319 Bump Charm dependency to improve SSH connection stuff 2020-10-05 13:48:21 +02:00
Christian Rocha
3660182cdb Bump bubbles dependency for mouse wheel correction 2020-10-05 13:47:16 +02:00
Christian Rocha
c954898009 Add basic mouse wheel support to pager 2020-10-05 13:47:16 +02:00
Christian Rocha
5b3feb41a2 Bump Bubble Tea dependency for mouse support 2020-10-05 13:47:16 +02:00
Christian Rocha
7f9cc95893 Update altscreen calls per latest Bubble Tea update 2020-10-05 13:47:16 +02:00
Christian Rocha
fcaa654984 Remove spinner custom message stuff which is no longer supported 2020-10-05 13:47:16 +02:00
Christian Rocha
0f508533ac Bump Bubble Tea and Bubbles for hotfixes 2020-10-05 13:47:16 +02:00
Christian Rocha
a664da30c1 Bump dependencies 2020-10-05 13:47:15 +02:00
Christian Rocha
6e72cbdc4f Consolidate debug config stuff 2020-10-05 13:46:49 +02:00
Christian Rocha
fa06996383 Dispell a magic number 2020-10-05 13:46:09 +02:00
Christian Rocha
048b484df2 Reset y offset when resetting pager 2020-10-05 13:46:09 +02:00
Christian Rocha
dd828966eb Remove extra height and positioning stuff 2020-10-05 13:46:09 +02:00
Christian Rocha
d3ac07c70a Fix bottom line jitteriness with performance rendering 2020-10-05 13:46:09 +02:00
Christian Rocha
232a9919c2 Respect high performance flag 2020-10-05 13:46:09 +02:00
Christian Rocha
b2df0cae8f Integrate high performance viewport, but not without issue 2020-10-05 13:46:09 +02:00
Christian Rocha
9eac9e2253 Use new, simplified window resize handling 2020-10-05 13:46:09 +02:00
Christian Rocha
250bf29f61 Bump termenv 2020-10-05 13:46:09 +02:00
Christian Rocha
16088b6366 Bump Bubble Tea dependency 2020-10-05 13:46:09 +02:00
Christian Rocha
16ae4b5563 Bump 'bubbles' for unicode support 2020-10-05 13:46:09 +02:00
Christian Rocha
dde45d99e8 Pressing 'q' when setting a note in the pager shouldn't cancel the note 2020-10-05 13:46:09 +02:00
Christian Rocha
b9c02d756b Fix a bug where pressing q would cancel note input 2020-10-05 13:46:09 +02:00
Christian Rocha
562d02dc43 Properly truncate fullwidth/double-byte chars 2020-10-05 13:46:09 +02:00
Christian Rocha
9179515e62 Close help when unloading/resetting pager 2020-10-05 13:46:09 +02:00
Christian Rocha
1c804b5a3a Update to new Bubble Tea renderer 2020-10-05 13:46:09 +02:00
Christian Rocha
7ee203a987 Correct keystroke in pager help 2020-10-05 13:46:09 +02:00
Christian Rocha
5cd6accce3 Nice looking system-level errors 2020-10-05 13:46:09 +02:00
Christian Rocha
939fdef913 Fix panic when deleting item at end of stash 2020-10-05 13:46:09 +02:00
Christian Rocha
ea12614a53 Add memo key to stash help + change memo keys to 'm' 2020-10-05 13:46:09 +02:00
Christian Rocha
5019331dff Mini help view for pager 2020-10-05 13:46:09 +02:00
Christian Rocha
61bcd80012 Don't try and open stuff if there's nothing to open 2020-10-05 13:45:44 +02:00
Christian Rocha
d4203ed1a6 Fix stash empty/not-empty rendering 2020-10-05 13:45:44 +02:00
Christian Rocha
4849e0489d Remove commented code 2020-10-05 13:45:44 +02:00
Christian Rocha
2688a8c4a6 Fix stash view when no items are stashed 2020-10-05 13:45:44 +02:00
Christian Rocha
e50aff65c2 Fix bug where spinner would spin forever if the stash is empty 2020-10-05 13:45:44 +02:00
Christian Rocha
213df4ad7f Bump Bubble Tea dependency 2020-10-05 13:45:44 +02:00
Christian Rocha
ae5e158bbd Unstyled indent (for now) 2020-10-05 13:45:43 +02:00
Christian Rocha
671eade0b5 Remove Boba residue 2020-10-05 13:45:22 +02:00
Christian Rocha
c0ca639f3a Remove tests for Go 1.11 and 1.12 (keygen is 1.13 and up) 2020-10-05 13:45:22 +02:00
Christian Rocha
553d064c92 Fix tests (don't test glow with no arguments) 2020-10-05 13:45:22 +02:00
Christian Rocha
1d6341f970 Use strings.Builder to build pager view 2020-10-05 13:45:22 +02:00
Christian Rocha
77ba75d680 Migrate from Boba to Bubble Tea and Bubbles 2020-10-05 13:45:22 +02:00
Christian Rocha
21b1a2822f Render stash view with a strings.Builder 2020-10-05 13:44:20 +02:00
Christian Rocha
56ab8e5309 Rework stash design and simplify code 2020-10-05 13:44:20 +02:00
Christian Rocha
964be85e6d Parse subcommands before main command arguments int the CLI 2020-10-05 13:44:20 +02:00
Christian Rocha
05a56388a9 Comments 2020-10-05 13:44:20 +02:00
Christian Rocha
28f8f185d7 Make --force-key/-f a persistent flag, like --identity 2020-10-05 13:44:20 +02:00
Christian Rocha
0407c7d990 Update to Charm v0.5.0 for a new, thread-safe Charm client 2020-10-05 13:44:18 +02:00
Christian Rocha
4acb642b15 Let's just call documents "markdowns" 2020-10-05 13:43:19 +02:00
Christian Rocha
414040400a Don't allow note-setting on news 2020-10-05 13:43:19 +02:00
Christian Rocha
4f250ebb81 Render all errors at the main view level 2020-10-05 13:43:19 +02:00
Christian Rocha
251aacf79a Fix markdown sorting error 2020-10-05 13:43:18 +02:00
Christian Rocha
c66351127c Remove replace directive in go.mod 2020-10-05 13:43:07 +02:00
Christian Rocha
93e8370b2a Whoops, remove thing where pressing f would synthesize an error 2020-10-05 13:43:07 +02:00
Christian Rocha
9bee37ee93 Fetch and render news markdown 2020-10-05 13:43:06 +02:00
Christian Rocha
e5663a5912 Set note character limit to 256 to match the database 2020-10-05 13:42:30 +02:00
Christian Rocha
ddef264606 Don't quit on "fatal" errors 2020-10-05 13:42:30 +02:00
Christian Rocha
e069be7af9 Bump charm dependency to master 2020-10-05 13:42:29 +02:00
Christian Rocha
b464001446 Bump boba dependency 2020-10-05 13:41:38 +02:00
Christian Rocha
81bcce80c8 Improve stash item deletion cancellation experience 2020-10-05 13:41:17 +02:00
Christian Rocha
eba22f08b1 Also set/edit notes in stash view 2020-10-05 13:41:17 +02:00
Christian Rocha
c3ceed2c04 Log to a given path if GLOW_LOG_TO_FILE is set 2020-10-05 13:41:17 +02:00
Christian Rocha
f474e2490d Don't render stash until both stash and news are loaded 2020-10-05 13:41:17 +02:00
Christian Rocha
055ae6e3c2 Load, browse and render news 2020-10-05 13:41:17 +02:00
Christian Rocha
1b7c70a829 Remove right document padding per Glamour update (pinned at current master) 2020-10-05 13:41:16 +02:00
Christian Rocha
1971c8abd8 Add right padding to markdown rendering 2020-10-05 13:40:43 +02:00
Christian Rocha
f3021822f3 More contrasty note titles in pager status bar 2020-10-05 13:40:43 +02:00
Christian Rocha
1219d22834 Don't set a note if the note didn't actually change 2020-10-05 13:40:42 +02:00
Christian Rocha
8695bffa2e Pre-populate note field with exisiting note 2020-10-05 13:40:23 +02:00
Christian Rocha
04f1ebfa23 Add functionality for setting document note in the pager 2020-10-05 13:40:22 +02:00
Christian Rocha
c2bb505182 Break pager off into its own set of Boba functions 2020-10-05 13:39:47 +02:00
Christian Rocha
476e01449d Sort stash in descending order by date 2020-10-05 13:39:45 +02:00
Christian Rocha
51ad1c3619 Use relative times when under a week 2020-10-05 13:39:06 +02:00
Christian Rocha
3ab3b8fcb4 Also keep the scroll percentage at 0% or higher 2020-10-05 13:39:06 +02:00
Christian Rocha
f43a0271d2 Bugfix: scroll percentage could read higher than 100% ater a resize 2020-10-05 13:39:06 +02:00
Christian Rocha
d299ee3365 Fix a panic that could happen with very tiny terminal windows 2020-10-05 13:39:06 +02:00
Christian Rocha
2a7006949e Re-render with Glamour terminal resize 2020-10-05 13:39:06 +02:00
Christian Rocha
883fda2b06 Repaint on terminal resize 2020-10-05 13:39:05 +02:00
Christian Rocha
95fe84ebb4 Truncate long memo title in pager + fix associated panic 2020-10-05 13:38:15 +02:00
Christian Rocha
df0a3ba21c Truncate long memos in stash 2020-10-05 13:38:15 +02:00
Christian Rocha
e685dea17a Don't query for terminal dimensions when starting up 2020-10-05 13:38:15 +02:00
Christian Rocha
131685b5f2 Clearer const names for stash view stuff 2020-10-05 13:38:15 +02:00
Toby Padilla
f00e5ccdba Only do the dark background check once 2020-10-05 13:38:15 +02:00
Christian Rocha
d06f4914b9 Add GLOW_DISABLE_GLAMOUR in the env for testing 2020-10-05 13:38:15 +02:00
Christian Rocha
7a8a1f7aff Load more pages, if any, when the user reaches the last page 2020-10-05 13:38:15 +02:00
Christian Rocha
198a785ac4 Don't show the paginator if there's only one page 2020-10-05 13:38:15 +02:00
Christian Rocha
d0542b6d25 Fix stash spinner 2020-10-05 13:38:15 +02:00
Christian Rocha
e500ed695e Add stash item deletion functionality 2020-10-05 13:38:15 +02:00
Christian Rocha
531645e73e Keep selection in-bounds when paginating 2020-10-05 13:38:15 +02:00
Christian Rocha
211e66c738 Update Charm and Boba libraries 2020-10-05 13:38:14 +02:00
Christian Rocha
1f4970e25b Update for simpler pager library 2020-10-05 13:37:35 +02:00
Christian Rocha
f5df688860 Render with glamour asyncronously 2020-10-05 13:37:35 +02:00
Christian Rocha
c622816a43 Load selected item from stash into the pager 2020-10-05 13:37:35 +02:00
Christian Rocha
8ba5c1fa0d Spilling over top/bottom in stash browser goes to the prev/next page 2020-10-05 13:37:35 +02:00
Christian Rocha
5d2ca090ac Pass style argument to the Boba program 2020-10-05 13:37:35 +02:00
Christian Rocha
8bb2812a6b Keep page in bounds after resetting terminal size 2020-10-05 13:37:35 +02:00
Christian Rocha
7ea79793d7 Add Glow "logo" to stash browser 2020-10-05 13:37:35 +02:00
Christian Rocha
f3a91f0a40 For now, keep selection within current page 2020-10-05 13:37:35 +02:00
Christian Rocha
fd62cebfd1 Left/right + h/l to navigate pages 2020-10-05 13:37:35 +02:00
Christian Rocha
d2e2da70e2 Properly render one page of the stash 2020-10-05 13:37:35 +02:00
Christian Rocha
c7e584c68e Improve state on selected items in stash view 2020-10-05 13:37:35 +02:00
Christian Rocha
6e13ce3968 Fix status bar colors on light backgrouds 2020-10-05 13:37:35 +02:00
Christian Rocha
9bc86c1944 Fix character-chomping weirdness with a well-placed newline 2020-10-05 13:37:35 +02:00
Christian Rocha
c9979901fd Add status bar 2020-10-05 13:37:35 +02:00
Christian Rocha
05340539de Load chosen document into pager 2020-10-05 13:37:35 +02:00
Christian Rocha
5ea351abf5 Navigate over unpaginated stash 2020-10-05 13:37:35 +02:00
Christian Rocha
359828a719 Silence this Go linter message 2020-10-05 13:37:35 +02:00
Christian Rocha
25340455a7 Do it fullscreen 2020-10-05 13:37:33 +02:00
Christian Rocha
796504238f Render stash items 2020-10-05 13:36:52 +02:00
Christian Rocha
bb6904afe2 Load stash + fix spinner issues + render nothing useful
Note that this is we're currently building aginst unreleased versions of Charm and Boba.
2020-10-05 13:36:51 +02:00
Christian Rocha
da4e98f749 Stub out Stash UI + update for Boba v0.5.0 2020-10-05 13:36:50 +02:00
Christian Rocha
b58ee10487 Stub out Tea UI: create Charm client and generate keys if necessary 2020-10-05 13:36:11 +02:00
Christian Rocha
cd9ee93dca Configure git for private modules 2020-10-05 13:35:18 +02:00
Toby Padilla
20f7168306 Add pagination call 2020-10-05 13:35:17 +02:00
Christian Rocha
787b988663 Add Charm logo, remove SSH stuff 2020-10-05 13:33:39 +02:00
Christian Rocha
29b825b5d8 Remove authors in README 2020-10-05 13:33:39 +02:00
Toby Padilla
bbd6b1acc7 Update charm lib 2020-10-05 13:33:37 +02:00
Toby Padilla
4792345f31 Delete from stash command 2020-10-05 13:32:36 +02:00
Toby Padilla
bd52d1cee4 Get markdown command 2020-10-05 13:32:36 +02:00
Toby Padilla
395ae998dd List stash 2020-10-05 13:32:36 +02:00
Toby Padilla
03b7b9200c Print successful stash 2020-10-05 13:32:36 +02:00
Toby Padilla
a106bc0163 Working stash command 2020-10-05 13:32:34 +02:00
Christian Muehlhaeuser
294e4e292d
Bump glamour dependency 2020-09-11 06:24:30 +02:00
Jesse Millar
6025e736ea
Enable use of the GLAMOUR_STYLE environment variable (#152) 2020-07-23 18:03:03 +02:00
Christian Muehlhaeuser
a3552fd2ab
Fix goreportcard link 2020-05-05 07:51:26 +02:00
Christian Muehlhaeuser
d433820a1b
Validate options once 2020-05-05 07:48:05 +02:00
Christian Muehlhaeuser
c9af84b75a
Automatically detect terminal width, with a 120 char limit 2020-05-05 07:43:11 +02:00
Christian Muehlhaeuser
18fcf68c0f
Change default rendering width to 80 2020-05-05 07:28:33 +02:00
Christian Muehlhaeuser
c5d7aa0155
Rename readerFromArg to sourceFromArg 2020-03-31 08:57:49 +02:00
Christian Muehlhaeuser
ec9cc0bde4
Make source private 2020-03-31 08:53:35 +02:00
Christian Muehlhaeuser
5cdf8294a8
Use glamour's WithAutoStyle option as default 2020-03-31 06:58:21 +02:00
Christian Rocha
8d6da194eb
Add Glow logo to README 2020-03-06 13:27:10 -05:00
Christian Muehlhaeuser
b4398fca33
Link to pkg.go.dev instead of godoc.org 2020-03-04 17:42:26 +01:00
Christian Muehlhaeuser
0483c3d42a
Test against Go 1.14 2020-03-04 17:42:14 +01:00
Christian Muehlhaeuser
e4263b1286
Bump indirect goldmark & chroma dependencies 2020-03-04 14:48:22 +01:00
Christian Muehlhaeuser
e4091b0ff6 Update README 2020-02-28 07:19:51 +01:00
Christian Muehlhaeuser
360aca7ee5 Bump glamour & reflow dependencies 2020-02-28 07:19:51 +01:00
Christian Rocha
ba7c9a5ea5
Add Void Linux installation instructions 2020-02-26 11:47:09 -05:00
Christian Muehlhaeuser
fdaa2ca91a
Bump indirect glamour/termenv dependency 2020-01-31 13:09:23 +01:00
Christian Muehlhaeuser
8d7f683321
Bump glamour dependency 2020-01-23 19:40:47 +01:00
Christian Muehlhaeuser
5038a7b69f Use glamour's 'auto' style per default 2020-01-14 02:20:33 +01:00
Christian Rocha
ce8ca8f27c
Add Nix installation instructions + note about Charm loving open source 2020-01-13 12:45:22 -05:00
Christian Rocha
52e50a4008
Update example image with unicode characters 2020-01-13 12:21:08 -05:00
Christian Muehlhaeuser
c5517add19
Search for READMEs case-insensitively & recursively 2020-01-13 01:42:59 +01:00
Christian Rocha
85cd31dfc4
Muesli informs me of the correct capitalization of "macOS" 2020-01-12 14:24:07 -05:00
Christian Rocha
8ef13d49e6
Remove deprecated homebrew tap installation note 2020-01-12 14:18:15 -05:00
Dawid Dziurla
4c6633e8a3 Now in Homebrew! 2020-01-12 17:01:33 +01:00
Christian Muehlhaeuser
15459db3fe
Add release badge to README 2020-01-09 15:42:56 +01:00
46 changed files with 4082 additions and 406 deletions

18
.editorconfig Normal file
View file

@ -0,0 +1,18 @@
# https://editorconfig.org/
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.go]
indent_style = tab
indent_size = 8
[*.golden]
insert_final_newline = false
trim_trailing_whitespace = false

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Setup**
Please complete the following information along with version numbers, if applicable.
- OS [e.g. Ubuntu, macOS]
- Shell [e.g. zsh, fish]
- Terminal Emulator [e.g. kitty, iterm]
- Terminal Multiplexer [e.g. tmux]
- Locale [e.g. en_US.UTF-8, zh_CN.UTF-8, etc.]
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Source Code**
Please include source code if needed to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
Add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Discord
url: https://charm.sh/discord
about: Chat on our Discord.

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

57
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,57 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "America/New_York"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
groups:
all:
patterns:
- "*"
ignore:
- dependency-name: github.com/charmbracelet/bubbletea/v2
versions:
- v2.0.0-beta1
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "America/New_York"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
groups:
all:
patterns:
- "*"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "America/New_York"
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
groups:
all:
patterns:
- "*"

25
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: build
on: [push, pull_request]
jobs:
build:
uses: charmbracelet/meta/.github/workflows/build.yml@main
snapshot:
uses: charmbracelet/meta/.github/workflows/snapshot.yml@main
secrets:
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
govulncheck:
uses: charmbracelet/meta/.github/workflows/govulncheck.yml@main
with:
go-version: stable
semgrep:
uses: charmbracelet/meta/.github/workflows/semgrep.yml@main
ruleguard:
uses: charmbracelet/meta/.github/workflows/ruleguard.yml@main
with:
go-version: stable

24
.github/workflows/coverage.yml vendored Normal file
View file

@ -0,0 +1,24 @@
name: coverage
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: stable
- name: Coverage
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go test -race -covermode atomic -coverprofile=profile.cov ./...
go install github.com/mattn/goveralls@latest
goveralls -coverprofile=profile.cov -service=github

17
.github/workflows/dependabot-sync.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: dependabot-sync
on:
schedule:
- cron: "0 0 * * 0" # every Sunday at midnight
workflow_dispatch: # allows manual triggering
permissions:
contents: write
pull-requests: write
jobs:
dependabot-sync:
uses: charmbracelet/meta/.github/workflows/dependabot-sync.yml@main
with:
repo_name: ${{ github.event.repository.name }}
secrets:
gh_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}

View file

@ -1,38 +0,0 @@
name: build
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
go-version: [1.11.x, 1.12.x, 1.13.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
env:
GO111MODULE: "on"
steps:
- name: Install Go
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v1
- name: Download Go modules
run: go mod download
- name: Build
run: go build -v ./...
- name: Test
run: go test ./...
- name: Coverage
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go test -race -covermode atomic -coverprofile=profile.cov ./...
GO111MODULE=off go get github.com/mattn/goveralls
$(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github
if: matrix.go-version == '1.13.x' && matrix.platform == 'ubuntu-latest'

25
.github/workflows/goreleaser.yml vendored Normal file
View file

@ -0,0 +1,25 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: goreleaser
on:
push:
tags:
- v*.*.*
concurrency:
group: goreleaser
cancel-in-progress: true
jobs:
goreleaser:
uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main
secrets:
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
docker_token: ${{ secrets.DOCKERHUB_TOKEN }}
gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
fury_token: ${{ secrets.FURY_TOKEN }}
nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }}
nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }}
snapcraft_token: ${{ secrets.SNAPCRAFT_TOKEN }}

13
.github/workflows/lint-sync.yml vendored Normal file
View file

@ -0,0 +1,13 @@
name: lint-sync
on:
schedule:
- cron: "0 0 * * 0" # every sunday at midnight
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
lint:
uses: charmbracelet/meta/.github/workflows/lint-sync.yml@main

18
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: lint
on:
push:
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: golangci-lint
uses: golangci/golangci-lint-action@v9.2.0
with:
# Optional: golangci-lint command line arguments.
args: --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true

3
.gitignore vendored
View file

@ -1,2 +1,5 @@
glow
dist/
.envrc
completions/
manpages/

44
.golangci.yml Normal file
View file

@ -0,0 +1,44 @@
version: "2"
run:
tests: false
linters:
enable:
- bodyclose
- exhaustive
- goconst
- godot
- gomoddirectives
- goprintffuncname
- gosec
- misspell
- nakedret
- nestif
- nilerr
- noctx
- nolintlint
- prealloc
- revive
- rowserrcheck
- sqlclosecheck
- tparallel
- unconvert
- unparam
- whitespace
- wrapcheck
exclusions:
rules:
- text: '(slog|log)\.\w+'
linters:
- noctx
generated: lax
presets:
- common-false-positives
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- gofumpt
- goimports
exclusions:
generated: lax

View file

@ -1,90 +1,14 @@
env:
- GO111MODULE=on
before:
hooks:
- go mod download
builds:
-
id: "glow"
binary: glow
ldflags: -s -w -X main.Version={{ .Version }} -X main.CommitSHA={{ .Commit }}
goos:
- linux
- freebsd
- windows
goarch:
- amd64
- arm64
- 386
- arm
goarm:
- 6
- 7
-
id: "darwin"
binary: "glow"
ldflags: -s -w -X main.Version={{ .Version }} -X main.CommitSHA={{ .Commit }}
goos:
- darwin
goarch:
- amd64
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
archives:
- id: default
builds:
- glow
format_overrides:
- goos: windows
format: zip
replacements:
windows: Windows
386: i386
amd64: x86_64
- id: darwin
builds:
- darwin
replacements:
darwin: Darwin
amd64: x86_64
version: 2
nfpms:
-
builds:
- glow
includes:
- from_url:
url: charmbracelet/meta/main/goreleaser-glow.yaml
vendor: charmbracelet
homepage: "https://charm.sh/"
maintainer: "Christian Muehlhaeuser <muesli@gmail.com>"
description: "Render markdown on the CLI"
license: MIT
formats:
- deb
- rpm
bindir: /usr/bin
brews:
- ids:
- darwin
github:
owner: charmbracelet
name: homebrew-tap
commit_author:
name: "Christian Muehlhaeuser"
email: "muesli@gmail.com"
homepage: "https://charm.sh/"
description: "Render markdown on the CLI"
# skip_upload: true
signs:
- artifacts: checksum
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
variables:
description: "Render markdown on the CLI, with pizzazz!"
github_url: "https://github.com/charmbracelet/glow"
maintainer: "Christian Muehlhaeuser <muesli@charm.sh>"
brew_commit_author_name: "Christian Muehlhaeuser"
brew_commit_author_email: "muesli@charm.sh"

3
Dockerfile Normal file
View file

@ -0,0 +1,3 @@
FROM gcr.io/distroless/static
COPY glow /usr/local/bin/glow
ENTRYPOINT [ "/usr/local/bin/glow" ]

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 Charmbracelet, Inc
Copyright (c) 2019-2024 Charmbracelet, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

196
README.md
View file

@ -1,49 +1,147 @@
# Glow
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/charmbracelet/glow) [![Build Status](https://github.com/charmbracelet/glow/workflows/build/badge.svg)](https://github.com/charmbracelet/glow/actions) [![Go ReportCard](http://goreportcard.com/badge/charmbracelet/glow)](http://goreportcard.com/report/charmbracelet/glow)
Render markdown on the CLI, with _pizzazz_!
![Glow example output](https://github.com/charmbracelet/glow/raw/master/example.png)
<p align="center">
<img src="https://stuff.charm.sh/glow/glow-banner-github.gif" alt="Glow Logo">
<a href="https://github.com/charmbracelet/glow/releases"><img src="https://img.shields.io/github/release/charmbracelet/glow.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/charmbracelet/glow?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
<a href="https://github.com/charmbracelet/glow/actions"><img src="https://github.com/charmbracelet/glow/workflows/build/badge.svg" alt="Build Status"></a>
<a href="https://goreportcard.com/report/github.com/charmbracelet/glow"><img src="https://goreportcard.com/badge/charmbracelet/glow" alt="Go ReportCard"></a>
</p>
<p align="center">
<img src="https://github.com/user-attachments/assets/c2246366-f84b-4847-b431-32a61ca07b74" width="800" alt="Glow UI Demo">
</p>
## What is it?
Glow is a terminal based markdown reader designed from the ground up to bring
out the beauty—and power—of the CLI.
Use it to discover markdown files, read documentation directly on the command
line. Glow will find local markdown files in subdirectories or a local
Git repository.
## Installation
Use your fave package manager:
### Package Manager
```bash
# MacOS
brew install charmbracelet/homebrew-tap/glow
# macOS or Linux
brew install glow
```
```bash
# macOS (with MacPorts)
sudo port install glow
```
```bash
# Arch Linux (btw)
yay -S glow
pacman -S glow
```
```bash
# Void Linux
xbps-install -S glow
```
```bash
# Nix shell
nix-shell -p glow --command glow
```
```bash
# FreeBSD
pkg install glow
```
Or download a binary from the [releases][] page. Windows, MacOS, and Linux
(including ARM) binaries are available, as well as Debian and RPM packages.
Or just use `go get`:
```bash
# Solus
eopkg install glow
```
```bash
go get github.com/charmbracelet/glow
# Windows (with Chocolatey, Scoop, or Winget)
choco install glow
scoop install glow
winget install charmbracelet.glow
```
```bash
# Android (with termux)
pkg install glow
```
```bash
# Ubuntu (Snapcraft)
sudo snap install glow
```
```bash
# Debian/Ubuntu
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
sudo apt update && sudo apt install glow
```
```bash
# Fedora/RHEL
echo '[charm]
name=Charm
baseurl=https://repo.charm.sh/yum/
enabled=1
gpgcheck=1
gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo
sudo yum install glow
```
Or download a binary from the [releases][releases] page. MacOS, Linux, Windows,
FreeBSD and OpenBSD binaries are available, as well as Debian, RPM, and Alpine
packages. ARM builds are also available for macOS, Linux, FreeBSD and OpenBSD.
### Go
Or just install it with `go`:
```bash
go install github.com/charmbracelet/glow/v2@latest
```
### Build (requires Go 1.21+)
```bash
git clone https://github.com/charmbracelet/glow.git
cd glow
go build
```
[releases]: https://github.com/charmbracelet/glow/releases
## The TUI
## Usage
Simply run `glow` without arguments to start the textual user interface and
browse local. Glow will find local markdown files in the
current directory and below or, if youre in a Git repository, Glow will search
the repo.
Use a markdown source as the primary argument:
Markdown files can be read with Glow's high-performance pager. Most of the
keystrokes you know from `less` are the same, but you can press `?` to list
the hotkeys.
## The CLI
In addition to a TUI, Glow has a CLI for working with Markdown. To format a
document use a markdown source as the primary argument:
```bash
# Read from file
glow README.md
# Read from stdin
glow -
echo "[Glow](https://github.com/charmbracelet/glow)" | glow -
# Fetch README from GitHub / GitLab
glow github.com/charmbracelet/glow
@ -52,9 +150,6 @@ glow github.com/charmbracelet/glow
glow https://host.tld/file.md
```
When `glow` is started without a markdown source, it will try to find a
`README.md` or `README` file in the current working directory.
### Word Wrapping
The `-w` flag lets you set a maximum width at which the output will be wrapped:
@ -65,12 +160,14 @@ glow -w 60
### Paging
The output can be displayed in the user's preferred pager with the `-p` flag.
This defaults to the ANSI-aware `less -r` if `$PAGER` is not explicitly set.
CLI output can be displayed in your preferred pager with the `-p` flag. This defaults
to the ANSI-aware `less -r` if `$PAGER` is not explicitly set.
### Styles
You can choose a style with the `-s` flag (`dark` being the default):
You can choose a style with the `-s` flag. When no flag is provided `glow` tries
to detect your terminal's current background color and automatically picks
either the `dark` or the `light` style for you.
```bash
glow -s [dark|light]
@ -82,27 +179,64 @@ Alternatively you can also supply a custom JSON stylesheet:
glow -s mystyle.json
```
Check out the [Glamour Style Section](https://github.com/charmbracelet/glamour/blob/master/styles/gallery/README.md)
to find more styles. Or [make your own](https://github.com/charmbracelet/glamour/tree/master/styles)!
***
For additional usage details see:
```bash
glow --help
```
Check out the [Glamour Style Section](https://github.com/charmbracelet/glamour/blob/master/styles/gallery/README.md)
to find more styles. Or [make your own](https://github.com/charmbracelet/glamour/tree/master/styles)!
## Authors
## The Config File
* [Christian Muehlhaeuser](https://github.com/muesli)
* [Toby Padilla](https://github.com/toby)
* [Christian Rocha](https://github.com/meowgorithm)
If you find yourself supplying the same flags to `glow` all the time, it's
probably a good idea to create a config file. Run `glow config`, which will open
it in your favorite $EDITOR. Alternatively you can manually put a file named
`glow.yml` in the default config path of you platform. If you're not sure where
that is, please refer to `glow --help`.
Part of [Charm](https://charm.sh). For more info see `ssh charm.sh`
Here's an example config:
```yaml
# style name or JSON path (default "auto")
style: "light"
# mouse wheel support (TUI-mode only)
mouse: true
# use pager to display markdown
pager: true
# at which column should we word wrap?
width: 80
# show all files, including hidden and ignored.
all: false
# show line numbers (TUI-mode only)
showLineNumbers: false
# preserve newlines in the output
preserveNewLines: false
```
## Contributing
See [contributing][contribute].
[contribute]: https://github.com/charmbracelet/glow/contribute
## Feedback
Wed love to hear your thoughts on this project. Feel free to drop us a note!
- [Twitter](https://twitter.com/charmcli)
- [The Fediverse](https://mastodon.social/@charmcli)
- [Discord](https://charm.sh/chat)
## License
[MIT](https://github.com/charmbracelet/glow/raw/master/LICENSE)
---
Part of [Charm](https://charm.sh).
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
Charm热爱开源 • Charm loves open source

23
Taskfile.yaml Normal file
View file

@ -0,0 +1,23 @@
# https://taskfile.dev
version: '3'
tasks:
lint:
desc: Run base linters
cmds:
- golangci-lint run
test:
desc: Run tests
cmds:
- go test ./... {{.CLI_ARGS}}
log:
desc: Watch for glow logs
aliases: [tail]
cmds:
- cmd: tail -f ~/Library/Caches/glow/glow.log
platforms: [darwin]
- cmd: tail -f ~/.cache/glow/glow.log
platforms: [linux, windows]

88
config_cmd.go Normal file
View file

@ -0,0 +1,88 @@
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"github.com/charmbracelet/x/editor"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const defaultConfig = `# style name or JSON path (default "auto")
style: "auto"
# mouse support (TUI-mode only)
mouse: false
# use pager to display markdown
pager: false
# word-wrap at width
width: 80
# show all files, including hidden and ignored.
all: false
`
var configCmd = &cobra.Command{
Use: "config",
Hidden: false,
Short: "Edit the glow config file",
Long: paragraph(fmt.Sprintf("\n%s the glow config file. Well use EDITOR to determine which editor to use. If the config file doesn't exist, it will be created.", keyword("Edit"))),
Example: paragraph("glow config\nglow config --config path/to/config.yml"),
Args: cobra.NoArgs,
RunE: func(*cobra.Command, []string) error {
if err := ensureConfigFile(); err != nil {
return err
}
c, err := editor.Cmd("Glow", configFile)
if err != nil {
return fmt.Errorf("unable to set config file: %w", err)
}
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Run(); err != nil {
return fmt.Errorf("unable to run command: %w", err)
}
fmt.Println("Wrote config file to:", configFile)
return nil
},
}
func ensureConfigFile() error {
if configFile == "" {
configFile = viper.GetViper().ConfigFileUsed()
if err := os.MkdirAll(filepath.Dir(configFile), 0o755); err != nil { //nolint:gosec
return fmt.Errorf("could not write configuration file: %w", err)
}
}
if ext := path.Ext(configFile); ext != ".yaml" && ext != ".yml" {
return fmt.Errorf("'%s' is not a supported configuration type: use '%s' or '%s'", ext, ".yaml", ".yml")
}
if _, err := os.Stat(configFile); errors.Is(err, fs.ErrNotExist) {
// File doesn't exist yet, create all necessary directories and
// write the default config file
if err := os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {
return fmt.Errorf("unable create directory: %w", err)
}
f, err := os.Create(configFile)
if err != nil {
return fmt.Errorf("unable to create config file: %w", err)
}
defer func() { _ = f.Close() }()
if _, err := f.WriteString(defaultConfig); err != nil {
return fmt.Errorf("unable to write config file: %w", err)
}
} else if err != nil { // some other error occurred
return fmt.Errorf("unable to stat config file: %w", err)
}
return nil
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 499 KiB

Before After
Before After

View file

@ -1,45 +1,55 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// isGitHubURL tests a string to determine if it is a well-structured GitHub URL
func isGitHubURL(s string) (string, bool) {
if strings.HasPrefix(s, "github.com/") {
s = "https://" + s
// findGitHubREADME tries to find the correct README filename in a repository using GitHub API.
func findGitHubREADME(u *url.URL) (*source, error) {
owner, repo, ok := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
if !ok {
return nil, fmt.Errorf("invalid url: %s", u.String())
}
u, err := url.ParseRequestURI(s)
type readme struct {
DownloadURL string `json:"download_url"`
}
apiURL := fmt.Sprintf("https://api.%s/repos/%s/%s/readme", u.Hostname(), owner, repo)
//nolint:bodyclose
// it is closed on the caller
res, err := http.Get(apiURL) //nolint: gosec,noctx
if err != nil {
return "", false
return nil, fmt.Errorf("unable to get url: %w", err)
}
return u.String(), strings.ToLower(u.Host) == "github.com"
}
// findGitHubREADME tries to find the correct README filename in a repository
func findGitHubREADME(s string) (*Source, error) {
u, err := url.ParseRequestURI(s)
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to read http response body: %w", err)
}
u.Host = "raw.githubusercontent.com"
for _, r := range readmeNames {
v := u
v.Path += "/master/" + r
var result readme
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("unable to parse json: %w", err)
}
resp, err := http.Get(v.String())
if res.StatusCode == http.StatusOK {
//nolint:bodyclose
// it is closed on the caller
resp, err := http.Get(result.DownloadURL) //nolint: noctx
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get url: %w", err)
}
if resp.StatusCode == http.StatusOK {
return &Source{resp.Body, v.String()}, nil
return &source{resp.Body, result.DownloadURL}, nil
}
}

View file

@ -1,44 +1,59 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// isGitLabURL tests a string to determine if it is a well-structured GitLab URL
func isGitLabURL(s string) (string, bool) {
if strings.HasPrefix(s, "gitlab.com/") {
s = "https://" + s
// findGitLabREADME tries to find the correct README filename in a repository using GitLab API.
func findGitLabREADME(u *url.URL) (*source, error) {
owner, repo, ok := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
if !ok {
return nil, fmt.Errorf("invalid url: %s", u.String())
}
u, err := url.ParseRequestURI(s)
projectPath := url.QueryEscape(owner + "/" + repo)
type readme struct {
ReadmeURL string `json:"readme_url"`
}
apiURL := fmt.Sprintf("https://%s/api/v4/projects/%s", u.Hostname(), projectPath)
//nolint:bodyclose
// it is closed on the caller
res, err := http.Get(apiURL) //nolint: gosec,noctx
if err != nil {
return "", false
return nil, fmt.Errorf("unable to get url: %w", err)
}
return u.String(), strings.ToLower(u.Host) == "gitlab.com"
}
// findGitLabREADME tries to find the correct README filename in a repository
func findGitLabREADME(s string) (*Source, error) {
u, err := url.ParseRequestURI(s)
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to read http response body: %w", err)
}
for _, r := range readmeNames {
v := u
v.Path += "/raw/master/" + r
var result readme
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("unable to parse json: %w", err)
}
resp, err := http.Get(v.String())
readmeRawURL := strings.ReplaceAll(result.ReadmeURL, "blob", "raw")
if res.StatusCode == http.StatusOK {
//nolint:bodyclose
// it is closed on the caller
resp, err := http.Get(readmeRawURL) //nolint: gosec,noctx
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get url: %w", err)
}
if resp.StatusCode == http.StatusOK {
return &Source{resp.Body, v.String()}, nil
return &source{resp.Body, readmeRawURL}, nil
}
}

View file

@ -1,32 +1,9 @@
package main
import (
"bytes"
"testing"
)
func TestGlowSources(t *testing.T) {
tt := []string{
"",
".",
"README.md",
"github.com/charmbracelet/glow",
"https://github.com/charmbracelet/glow",
}
for _, v := range tt {
buf := &bytes.Buffer{}
err := executeArg(rootCmd, v, buf)
if err != nil {
t.Errorf("Error during execution (args: %s): %v", v, err)
}
if buf.Len() == 0 {
t.Errorf("Output buffer should not be empty (args: %s)", v)
}
}
}
func TestGlowFlags(t *testing.T) {
tt := []struct {
args []string

79
go.mod
View file

@ -1,12 +1,75 @@
module github.com/charmbracelet/glow
module github.com/charmbracelet/glow/v2
go 1.13
go 1.25.12
toolchain go1.26.5
require (
github.com/charmbracelet/glamour v0.1.0
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.4
github.com/spf13/cobra v0.0.5
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
github.com/atotto/clipboard v0.1.4
github.com/caarlos0/env/v11 v11.3.1
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/charmbracelet/log v0.4.2
github.com/charmbracelet/x/editor v0.1.0
github.com/dustin/go-humanize v1.0.1
github.com/fsnotify/fsnotify v1.9.0
github.com/mattn/go-runewidth v0.0.19
github.com/mitchellh/go-homedir v1.1.0
github.com/muesli/gitcha v0.3.0
github.com/muesli/go-app-paths v0.2.2
github.com/muesli/mango-cobra v1.3.0
github.com/muesli/reflow v0.3.0
github.com/muesli/roff v0.1.0
github.com/muesli/termenv v0.16.0
github.com/sahilm/fuzzy v0.1.1
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
golang.org/x/sys v0.43.0
golang.org/x/term v0.42.0
golang.org/x/text v0.39.0
mvdan.cc/sh/v3 v3.13.1
)
require (
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/mango v0.2.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.17 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
golang.org/x/net v0.53.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

260
go.sum
View file

@ -1,99 +1,177 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw=
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/charmbracelet/glamour v0.1.0 h1:BHCtc+YJjoBjNUnFKBtXyyM4Bp9u7L2kf49qV+/AGYw=
github.com/charmbracelet/glamour v0.1.0/go.mod h1:Z1C2JkVGBom/RYfoKcPBZ81lHMR3xp3W6OCLNWWEIMc=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/editor v0.1.0 h1:p69/dpvlwRTs9uYiPeAWruwsHqTFzHhTvQOd/WVSX98=
github.com/charmbracelet/x/editor v0.1.0/go.mod h1:oivrEbcP/AYt/Hpvk5pwDXXrQ933gQS6UzL6fxqAGSA=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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=
github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23 h1:Wp7NjqGKGN9te9N/rvXYRhlVcrulGdxnz8zadXWs7fc=
github.com/logrusorgru/aurora v0.0.0-20191116043053-66b7ad493a23/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/muesli/reflow v0.0.0-20191216070243-e5efeac4e302 h1:jOh3Kh03uOFkRPV3PI4Am5tqACv2aELgbPgr7YgNX00=
github.com/muesli/reflow v0.0.0-20191216070243-e5efeac4e302/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/gitcha v0.3.0 h1:+PJkVKrDXVB0VgRn/yVx2CqSVSDGMSepzvohsCrPYtQ=
github.com/muesli/gitcha v0.3.0/go.mod h1:vX3jFL+XcEUq1uY74RCjLSZfAV+ZuvLg70/NGPdXn84=
github.com/muesli/go-app-paths v0.2.2 h1:NqG4EEZwNIhBq/pREgfBmgDmt3h1Smr1MjZiXbpZUnI=
github.com/muesli/go-app-paths v0.2.2/go.mod h1:SxS3Umca63pcFcLtbjVb+J0oD7cl4ixQWoBKhGEtEho=
github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=
github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
github.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec=
github.com/muesli/mango-cobra v1.3.0/go.mod h1:Cj1ZrBu3806Qw7UjxnAUgE+7tllUBj1NCLQDwwGx19E=
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.19 h1:0s2/60x0XsFCXHeFut+F3azDVAAyIMyUfJRbRexiTYs=
github.com/yuin/goldmark v1.1.19/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA=
github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk=
mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=

40
log.go Normal file
View file

@ -0,0 +1,40 @@
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/charmbracelet/log"
gap "github.com/muesli/go-app-paths"
)
func getLogFilePath() (string, error) {
dir, err := gap.NewScope(gap.User, "glow").CacheDir()
if err != nil {
return "", fmt.Errorf("unable to get cache dir: %w", err)
}
return filepath.Join(dir, "glow.log"), nil
}
func setupLog() (func() error, error) {
log.SetOutput(io.Discard)
// Log to file, if set
logFile, err := getLogFilePath()
if err != nil {
return nil, err
}
if err := os.MkdirAll(filepath.Dir(logFile), 0o755); err != nil { //nolint:gosec
// log disabled
return func() error { return nil }, nil //nolint:nilerr
}
f, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) //nolint:gosec
if err != nil {
// log disabled
return func() error { return nil }, nil //nolint:nilerr
}
log.SetOutput(f)
log.SetLevel(log.DebugLevel)
return f.Close, nil
}

455
main.go
View file

@ -1,10 +1,11 @@
// Package main provides the entry point for the Glow CLI application.
package main
import (
"errors"
"fmt"
"io"
"io/ioutil"
"io/fs"
"net/http"
"net/url"
"os"
@ -12,126 +13,273 @@ import (
"path/filepath"
"strings"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"mvdan.cc/sh/v3/shell"
"github.com/caarlos0/env/v11"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glamour/styles"
"github.com/charmbracelet/glow/v2/ui"
"github.com/charmbracelet/glow/v2/utils"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
gap "github.com/muesli/go-app-paths"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
)
var (
Version = ""
// Version as provided by goreleaser.
Version = ""
// CommitSHA as provided by goreleaser.
CommitSHA = ""
readmeNames = []string{"README.md", "README"}
pager bool
style string
width uint
readmeNames = []string{"README.md", "README", "Readme.md", "Readme", "readme.md", "readme"}
configFile string
pager bool
tui bool
style string
width uint
showAllFiles bool
showLineNumbers bool
preserveNewLines bool
mouse bool
rootCmd = &cobra.Command{
Use: "glow SOURCE",
Short: "Render markdown on the CLI, with pizzazz!",
SilenceErrors: false,
SilenceUsage: false,
RunE: execute,
Use: "glow [SOURCE|DIR]",
Short: "Render markdown on the CLI, with pizzazz!",
Long: paragraph(
fmt.Sprintf("\nRender markdown on the CLI, %s!", keyword("with pizzazz")),
),
SilenceErrors: false,
SilenceUsage: true,
TraverseChildren: true,
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
},
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
return validateOptions(cmd)
},
RunE: execute,
}
)
type Source struct {
// source provides a readable markdown source.
type source struct {
reader io.ReadCloser
URL string
}
func readerFromArg(s string) (*Source, error) {
if s == "-" {
return &Source{reader: os.Stdin}, nil
// sourceFromArg parses an argument and creates a readable source for it.
func sourceFromArg(arg string) (*source, error) {
// from stdin
if arg == "-" {
return &source{reader: os.Stdin}, nil
}
// a GitHub or GitLab URL (even without the protocol):
if u, ok := isGitHubURL(s); ok {
src, err := findGitHubREADME(u)
if err != nil {
return nil, err
}
return src, nil
}
if u, ok := isGitLabURL(s); ok {
src, err := findGitLabREADME(u)
if err != nil {
return nil, err
}
src, err := readmeURL(arg)
if src != nil && err == nil {
// if there's an error, try next methods...
return src, nil
}
// HTTP(S) URLs:
if u, err := url.ParseRequestURI(s); err == nil {
if u, err := url.ParseRequestURI(arg); err == nil && strings.Contains(arg, "://") { //nolint:nestif
if u.Scheme != "" {
if u.Scheme != "http" && u.Scheme != "https" {
return nil, fmt.Errorf("%s is not a supported protocol", u.Scheme)
}
resp, err := http.Get(u.String())
// consumer of the source is responsible for closing the ReadCloser.
resp, err := http.Get(u.String()) //nolint: noctx,bodyclose
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get url: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP status %d", resp.StatusCode)
}
return &Source{resp.Body, u.String()}, nil
return &source{resp.Body, u.String()}, nil
}
}
// a valid file or directory:
st, err := os.Stat(s)
if len(s) == 0 || (err == nil && st.IsDir()) {
for _, v := range readmeNames {
n := filepath.Join(s, v)
r, err := os.Open(n)
if err == nil {
u, _ := filepath.Abs(n)
return &Source{r, u}, nil
// a directory:
if len(arg) == 0 {
// use the current working dir if no argument was supplied
arg = "."
}
st, err := os.Stat(arg)
if err == nil && st.IsDir() { //nolint:nestif
var src *source
_ = filepath.Walk(arg, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return err
}
for _, v := range readmeNames {
if strings.EqualFold(filepath.Base(path), v) {
r, err := os.Open(path)
if err != nil {
continue
}
u, _ := filepath.Abs(path)
src = &source{r, u}
// abort filepath.Walk
return errors.New("source found")
}
}
return nil
})
if src != nil {
return src, nil
}
return nil, errors.New("missing markdown source")
}
r, err := os.Open(s)
u, _ := filepath.Abs(s)
return &Source{r, u}, err
r, err := os.Open(arg)
if err != nil {
return nil, fmt.Errorf("unable to open file: %w", err)
}
u, err := filepath.Abs(arg)
if err != nil {
return nil, fmt.Errorf("unable to get absolute path: %w", err)
}
return &source{r, u}, nil
}
func execute(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return executeArg(cmd, "", os.Stdout)
}
for _, arg := range args {
if err := executeArg(cmd, arg, os.Stdout); err != nil {
return err
// validateStyle checks if the style is a default style, if not, checks that
// the custom style exists.
func validateStyle(style string) error {
if style != "auto" && styles.DefaultStyles[style] == nil {
style = utils.ExpandPath(style)
if _, err := os.Stat(style); errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("specified style does not exist: %s", style)
} else if err != nil {
return fmt.Errorf("unable to stat file: %w", err)
}
}
return nil
}
func executeArg(cmd *cobra.Command, arg string, w io.Writer) error {
// create an io.Reader from the markdown source in cli-args
src, err := readerFromArg(arg)
if err != nil {
return err
func validateOptions(cmd *cobra.Command) error {
// grab config values from Viper
width = viper.GetUint("width")
mouse = viper.GetBool("mouse")
pager = viper.GetBool("pager")
tui = viper.GetBool("tui")
showAllFiles = viper.GetBool("all")
preserveNewLines = viper.GetBool("preserveNewLines")
showLineNumbers = viper.GetBool("showLineNumbers")
if pager && tui {
return errors.New("cannot use both pager and tui")
}
defer src.reader.Close()
b, err := ioutil.ReadAll(src.reader)
if err != nil {
// validate the glamour style
style = viper.GetString("style")
if err := validateStyle(style); err != nil {
return err
}
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
// We want to use a special no-TTY style, when stdout is not a terminal
// and there was no specific style passed by arg
if !isatty.IsTerminal(os.Stdout.Fd()) &&
!cmd.Flags().Changed("style") {
if !isTerminal && !cmd.Flags().Changed("style") {
style = "notty"
}
// Detect terminal width
if !cmd.Flags().Changed("width") { //nolint:nestif
if isTerminal && width == 0 {
w, _, err := term.GetSize(int(os.Stdout.Fd()))
if err == nil {
width = uint(w) //nolint:gosec
}
if width > 120 {
width = 120
}
}
if width == 0 {
width = 80
}
}
return nil
}
func stdinIsPipe() (bool, error) {
stat, err := os.Stdin.Stat()
if err != nil {
return false, fmt.Errorf("unable to open file: %w", err)
}
if stat.Mode()&os.ModeCharDevice == 0 || stat.Size() > 0 {
return true, nil
}
return false, nil
}
func execute(cmd *cobra.Command, args []string) error {
// if stdin is a pipe then use stdin for input. note that you can also
// explicitly use a - to read from stdin.
if yes, err := stdinIsPipe(); err != nil {
return err
} else if yes {
src := &source{reader: os.Stdin}
defer src.reader.Close() //nolint:errcheck
return executeCLI(cmd, src, os.Stdout)
}
switch len(args) {
// TUI running on cwd
case 0:
return runTUI("", "")
// TUI with possible dir argument
case 1:
// Validate that the argument is a directory. If it's not treat it as
// an argument to the non-TUI version of Glow (via fallthrough).
info, err := os.Stat(args[0])
if err == nil && info.IsDir() {
p, err := filepath.Abs(args[0])
if err == nil {
return runTUI(p, "")
}
}
fallthrough
// CLI
default:
for _, arg := range args {
if err := executeArg(cmd, arg, os.Stdout); err != nil {
return err
}
}
}
return nil
}
func executeArg(cmd *cobra.Command, arg string, w io.Writer) error {
// create an io.Reader from the markdown source in cli-args
src, err := sourceFromArg(arg)
if err != nil {
return err
}
defer src.reader.Close() //nolint:errcheck
return executeCLI(cmd, src, w)
}
func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error {
b, err := io.ReadAll(src.reader)
if err != nil {
return fmt.Errorf("unable to read from reader: %w", err)
}
b = utils.RemoveFrontmatter(b)
// render
var baseURL string
u, err := url.ParseRequestURI(src.URL)
@ -140,57 +288,106 @@ func executeArg(cmd *cobra.Command, arg string, w io.Writer) error {
baseURL = u.String() + "/"
}
isCode := !utils.IsMarkdownFile(src.URL)
// initialize glamour
r, err := glamour.NewTermRenderer(
glamour.WithStylePath(style),
glamour.WithWordWrap(int(width)),
glamour.WithColorProfile(lipgloss.ColorProfile()),
utils.GlamourStyle(style, isCode),
glamour.WithWordWrap(int(width)), //nolint:gosec
glamour.WithBaseURL(baseURL),
glamour.WithPreservedNewLines(),
)
if err != nil {
return err
return fmt.Errorf("unable to create renderer: %w", err)
}
out, err := r.RenderBytes(b)
content := string(b)
ext := filepath.Ext(src.URL)
if isCode {
content = utils.WrapCodeBlock(string(b), ext)
}
out, err := r.Render(content)
if err != nil {
return err
}
// trim lines
lines := strings.Split(string(out), "\n")
var content string
for i, s := range lines {
content += strings.TrimSpace(s)
// don't add an artificial newline after the last split
if i+1 < len(lines) {
content += "\n"
}
return fmt.Errorf("unable to render markdown: %w", err)
}
// display
if cmd.Flags().Changed("pager") {
pager := os.Getenv("PAGER")
if pager == "" {
pager = "less -r"
switch {
case pager || cmd.Flags().Changed("pager"):
pagerCmd := os.Getenv("PAGER")
if pagerCmd == "" {
pagerCmd = "less -r"
}
pa := strings.Split(pager, " ")
c := exec.Command(pa[0], pa[1:]...)
c.Stdin = strings.NewReader(content)
fields, err := shell.Fields(pagerCmd, os.Getenv)
if err != nil || len(fields) == 0 {
return fmt.Errorf("unable to parse PAGER command: %s", pagerCmd)
}
c := exec.Command(fields[0], fields[1:]...) //nolint:gosec
c.Stdin = strings.NewReader(out)
c.Stdout = os.Stdout
return c.Run()
if err := c.Run(); err != nil {
return fmt.Errorf("unable to run command: %w", err)
}
return nil
case tui || cmd.Flags().Changed("tui"):
path := ""
if !isURL(src.URL) {
path = src.URL
}
return runTUI(path, content)
default:
if _, err = fmt.Fprint(w, out); err != nil {
return fmt.Errorf("unable to write to writer: %w", err)
}
return nil
}
}
func runTUI(path string, content string) error {
// Read environment to get debugging stuff
cfg, err := env.ParseAs[ui.Config]()
if err != nil {
return fmt.Errorf("error parsing config: %v", err)
}
// use style set in env, or auto if unset
if err := validateStyle(cfg.GlamourStyle); err != nil {
cfg.GlamourStyle = style
}
cfg.Path = path
cfg.ShowAllFiles = showAllFiles
cfg.ShowLineNumbers = showLineNumbers
cfg.GlamourMaxWidth = width
cfg.EnableMouse = mouse
cfg.PreserveNewLines = preserveNewLines
// Run Bubble Tea program
if _, err := ui.NewProgram(cfg, content).Run(); err != nil {
return fmt.Errorf("unable to run tui program: %w", err)
}
fmt.Fprint(w, content)
return nil
}
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(-1)
closer, err := setupLog()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := rootCmd.Execute(); err != nil {
_ = closer()
os.Exit(1)
}
_ = closer()
}
func init() {
tryLoadConfigFromDefaultPlaces()
if len(CommitSHA) >= 7 {
vt := rootCmd.VersionTemplate()
rootCmd.SetVersionTemplate(vt[:len(vt)-1] + " (" + CommitSHA[0:7] + ")\n")
@ -199,8 +396,78 @@ func init() {
Version = "unknown (built from source)"
}
rootCmd.Version = Version
rootCmd.InitDefaultCompletionCmd()
// "Glow Classic" cli arguments
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", fmt.Sprintf("config file (default %s)", viper.GetViper().ConfigFileUsed()))
rootCmd.Flags().BoolVarP(&pager, "pager", "p", false, "display with pager")
rootCmd.Flags().StringVarP(&style, "style", "s", "dark", "style name or JSON path")
rootCmd.Flags().UintVarP(&width, "width", "w", 100, "word-wrap at width")
rootCmd.Flags().BoolVarP(&tui, "tui", "t", false, "display with tui")
rootCmd.Flags().StringVarP(&style, "style", "s", styles.AutoStyle, "style name or JSON path")
rootCmd.Flags().UintVarP(&width, "width", "w", 0, "word-wrap at width (set to 0 to disable)")
rootCmd.Flags().BoolVarP(&showAllFiles, "all", "a", false, "show system files and directories (TUI-mode only)")
rootCmd.Flags().BoolVarP(&showLineNumbers, "line-numbers", "l", false, "show line numbers (TUI-mode only)")
rootCmd.Flags().BoolVarP(&preserveNewLines, "preserve-new-lines", "n", false, "preserve newlines in the output")
rootCmd.Flags().BoolVarP(&mouse, "mouse", "m", false, "enable mouse wheel (TUI-mode only)")
_ = rootCmd.Flags().MarkHidden("mouse")
// Config bindings
_ = viper.BindPFlag("pager", rootCmd.Flags().Lookup("pager"))
_ = viper.BindPFlag("tui", rootCmd.Flags().Lookup("tui"))
_ = viper.BindPFlag("style", rootCmd.Flags().Lookup("style"))
_ = viper.BindPFlag("width", rootCmd.Flags().Lookup("width"))
_ = viper.BindPFlag("debug", rootCmd.Flags().Lookup("debug"))
_ = viper.BindPFlag("mouse", rootCmd.Flags().Lookup("mouse"))
_ = viper.BindPFlag("preserveNewLines", rootCmd.Flags().Lookup("preserve-new-lines"))
_ = viper.BindPFlag("showLineNumbers", rootCmd.Flags().Lookup("line-numbers"))
_ = viper.BindPFlag("all", rootCmd.Flags().Lookup("all"))
viper.SetDefault("style", styles.AutoStyle)
viper.SetDefault("width", 0)
viper.SetDefault("all", true)
rootCmd.AddCommand(configCmd, manCmd)
}
func tryLoadConfigFromDefaultPlaces() {
scope := gap.NewScope(gap.User, "glow")
dirs, err := scope.ConfigDirs()
if err != nil {
fmt.Println("Could not load find configuration directory.")
os.Exit(1)
}
if c := os.Getenv("XDG_CONFIG_HOME"); c != "" {
dirs = append([]string{filepath.Join(c, "glow")}, dirs...)
}
if c := os.Getenv("GLOW_CONFIG_HOME"); c != "" {
dirs = append([]string{c}, dirs...)
}
for _, v := range dirs {
viper.AddConfigPath(v)
}
viper.SetConfigName("glow")
viper.SetConfigType("yaml")
viper.SetEnvPrefix("glow")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
log.Warn("Could not parse configuration file", "err", err)
}
}
if used := viper.ConfigFileUsed(); used != "" {
log.Debug("Using configuration file", "path", viper.ConfigFileUsed())
return
}
if viper.ConfigFileUsed() == "" {
configFile = filepath.Join(dirs[0], "glow.yml")
}
if err := ensureConfigFile(); err != nil {
log.Error("Could not create default configuration", "error", err)
}
}

29
man_cmd.go Normal file
View file

@ -0,0 +1,29 @@
package main
import (
"fmt"
"os"
mcobra "github.com/muesli/mango-cobra"
"github.com/muesli/roff"
"github.com/spf13/cobra"
)
var manCmd = &cobra.Command{
Use: "man",
Short: "Generates manpages",
SilenceUsage: true,
DisableFlagsInUseLine: true,
Hidden: true,
Args: cobra.NoArgs,
RunE: func(*cobra.Command, []string) error {
manPage, err := mcobra.NewManPage(1, rootCmd)
if err != nil {
return fmt.Errorf("unable to instantiate man page: %w", err)
}
if _, err := fmt.Fprint(os.Stdout, manPage.Build(roff.NewDocument())); err != nil {
return fmt.Errorf("unable to build man page: %w", err)
}
return nil
},
}

14
style.go Normal file
View file

@ -0,0 +1,14 @@
package main
import "github.com/charmbracelet/lipgloss"
var (
keyword = lipgloss.NewStyle().
Foreground(lipgloss.Color("#04B575")).
Render
paragraph = lipgloss.NewStyle().
Width(78).
Padding(0, 0, 0, 2).
Render
)

20
ui/config.go Normal file
View file

@ -0,0 +1,20 @@
package ui
// Config contains TUI-specific configuration.
type Config struct {
ShowAllFiles bool
ShowLineNumbers bool
Gopath string `env:"GOPATH"`
HomeDir string `env:"HOME"`
GlamourMaxWidth uint
GlamourStyle string `env:"GLAMOUR_STYLE"`
EnableMouse bool
PreserveNewLines bool
// Working directory or file path
Path string
// For debugging the UI
HighPerformancePager bool `env:"GLOW_HIGH_PERFORMANCE_PAGER" envDefault:"true"`
GlamourEnabled bool `env:"GLOW_ENABLE_GLAMOUR" envDefault:"true"`
}

19
ui/editor.go Normal file
View file

@ -0,0 +1,19 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/x/editor"
)
type editorFinishedMsg struct{ err error }
func openEditor(path string, lineno int) tea.Cmd {
cb := func(err error) tea.Msg {
return editorFinishedMsg{err}
}
cmd, err := editor.Cmd("Glow", path, editor.LineNumber(uint(lineno))) //nolint:gosec
if err != nil {
return func() tea.Msg { return cb(err) }
}
return tea.ExecProcess(cmd, cb)
}

15
ui/ignore_darwin.go Normal file
View file

@ -0,0 +1,15 @@
//go:build darwin
// +build darwin
package ui
import "path/filepath"
func ignorePatterns(m commonModel) []string {
return []string{
filepath.Join(m.cfg.HomeDir, "Library"),
m.cfg.Gopath,
"node_modules",
".*",
}
}

12
ui/ignore_general.go Normal file
View file

@ -0,0 +1,12 @@
//go:build !darwin
// +build !darwin
package ui
func ignorePatterns(m commonModel) []string {
return []string{
m.cfg.Gopath,
"node_modules",
".*",
}
}

6
ui/keys.go Normal file
View file

@ -0,0 +1,6 @@
package ui
const (
keyEnter = "enter"
keyEsc = "esc"
)

88
ui/markdown.go Normal file
View file

@ -0,0 +1,88 @@
package ui
import (
"fmt"
"math"
"time"
"unicode"
"github.com/charmbracelet/log"
"github.com/dustin/go-humanize"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)
type markdown struct {
// Full path of a local markdown file. Only relevant to local documents and
// those that have been stashed in this session.
localPath string
// Value we filter against. This exists so that we can maintain positions
// of filtered items if notes are edited while a filter is active. This
// field is ephemeral, and should only be referenced during filtering.
filterValue string
Body string
Note string
Modtime time.Time
}
// Generate the value we're doing to filter against.
func (m *markdown) buildFilterValue() {
note, err := normalize(m.Note)
if err != nil {
log.Error("error normalizing", "note", m.Note, "error", err)
m.filterValue = m.Note
}
m.filterValue = note
}
func (m markdown) relativeTime() string {
return relativeTime(m.Modtime)
}
// Normalize text to aid in the filtering process. In particular, we remove
// diacritics, "ö" becomes "o". Note that Mn is the unicode key for nonspacing
// marks.
func normalize(in string) (string, error) {
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
out, _, err := transform.String(t, in)
if err != nil {
return "", fmt.Errorf("error normalizing: %w", err)
}
return out, nil
}
// Return the time in a human-readable format relative to the current time.
func relativeTime(then time.Time) string {
now := time.Now()
if ago := now.Sub(then); ago < time.Minute {
return "just now"
} else if ago < humanize.Week {
return humanize.CustomRelTime(then, now, "ago", "from now", magnitudes)
}
return then.Format("02 Jan 2006 15:04 MST")
}
// Magnitudes for relative time.
var magnitudes = []humanize.RelTimeMagnitude{
{D: time.Second, Format: "now", DivBy: time.Second},
{D: 2 * time.Second, Format: "1 second %s", DivBy: 1},
{D: time.Minute, Format: "%d seconds %s", DivBy: time.Second},
{D: 2 * time.Minute, Format: "1 minute %s", DivBy: 1},
{D: time.Hour, Format: "%d minutes %s", DivBy: time.Minute},
{D: 2 * time.Hour, Format: "1 hour %s", DivBy: 1},
{D: humanize.Day, Format: "%d hours %s", DivBy: time.Hour},
{D: 2 * humanize.Day, Format: "1 day %s", DivBy: 1},
{D: humanize.Week, Format: "%d days %s", DivBy: humanize.Day},
{D: 2 * humanize.Week, Format: "1 week %s", DivBy: 1},
{D: humanize.Month, Format: "%d weeks %s", DivBy: humanize.Week},
{D: 2 * humanize.Month, Format: "1 month %s", DivBy: 1},
{D: humanize.Year, Format: "%d months %s", DivBy: humanize.Month},
{D: 18 * humanize.Month, Format: "1 year %s", DivBy: 1},
{D: 2 * humanize.Year, Format: "2 years %s", DivBy: 1},
{D: humanize.LongTime, Format: "%d years %s", DivBy: humanize.Year},
{D: math.MaxInt64, Format: "a long while %s", DivBy: 1},
}

535
ui/pager.go Normal file
View file

@ -0,0 +1,535 @@
package ui
import (
"fmt"
"math"
"path/filepath"
"strings"
"time"
"github.com/atotto/clipboard"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glow/v2/utils"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/fsnotify/fsnotify"
runewidth "github.com/mattn/go-runewidth"
"github.com/muesli/reflow/ansi"
"github.com/muesli/reflow/truncate"
"github.com/muesli/termenv"
)
const (
statusBarHeight = 1
lineNumberWidth = 4
)
var (
pagerHelpHeight int
mintGreen = lipgloss.AdaptiveColor{Light: "#89F0CB", Dark: "#89F0CB"}
darkGreen = lipgloss.AdaptiveColor{Light: "#1C8760", Dark: "#1C8760"}
lineNumberFg = lipgloss.AdaptiveColor{Light: "#656565", Dark: "#7D7D7D"}
statusBarNoteFg = lipgloss.AdaptiveColor{Light: "#656565", Dark: "#7D7D7D"}
statusBarBg = lipgloss.AdaptiveColor{Light: "#E6E6E6", Dark: "#242424"}
statusBarScrollPosStyle = lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#949494", Dark: "#5A5A5A"}).
Background(statusBarBg).
Render
statusBarNoteStyle = lipgloss.NewStyle().
Foreground(statusBarNoteFg).
Background(statusBarBg).
Render
statusBarHelpStyle = lipgloss.NewStyle().
Foreground(statusBarNoteFg).
Background(lipgloss.AdaptiveColor{Light: "#DCDCDC", Dark: "#323232"}).
Render
statusBarMessageStyle = lipgloss.NewStyle().
Foreground(mintGreen).
Background(darkGreen).
Render
statusBarMessageScrollPosStyle = lipgloss.NewStyle().
Foreground(mintGreen).
Background(darkGreen).
Render
statusBarMessageHelpStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#B6FFE4")).
Background(green).
Render
helpViewStyle = lipgloss.NewStyle().
Foreground(statusBarNoteFg).
Background(lipgloss.AdaptiveColor{Light: "#f2f2f2", Dark: "#1B1B1B"}).
Render
lineNumberStyle = lipgloss.NewStyle().
Foreground(lineNumberFg).
Render
)
type (
contentRenderedMsg string
reloadMsg struct{}
)
type pagerState int
const (
pagerStateBrowse pagerState = iota
pagerStateStatusMessage
)
type pagerModel struct {
common *commonModel
viewport viewport.Model
state pagerState
showHelp bool
statusMessage string
statusMessageTimer *time.Timer
// Current document being rendered, sans-glamour rendering. We cache
// it here so we can re-render it on resize.
currentDocument markdown
watcher *fsnotify.Watcher
}
func newPagerModel(common *commonModel) pagerModel {
// Init viewport
vp := viewport.New(0, 0)
vp.YPosition = 0
vp.HighPerformanceRendering = config.HighPerformancePager
m := pagerModel{
common: common,
state: pagerStateBrowse,
viewport: vp,
}
m.initWatcher()
return m
}
func (m *pagerModel) setSize(w, h int) {
m.viewport.Width = w
m.viewport.Height = h - statusBarHeight
if m.showHelp {
if pagerHelpHeight == 0 {
pagerHelpHeight = strings.Count(m.helpView(), "\n")
}
m.viewport.Height -= (statusBarHeight + pagerHelpHeight)
}
}
func (m *pagerModel) setContent(s string) {
m.viewport.SetContent(s)
}
func (m *pagerModel) toggleHelp() {
m.showHelp = !m.showHelp
m.setSize(m.common.width, m.common.height)
if m.viewport.PastBottom() {
m.viewport.GotoBottom()
}
}
type pagerStatusMessage struct {
message string
isError bool
}
// Perform stuff that needs to happen after a successful markdown stash. Note
// that the returned command should be sent back the through the pager
// update function.
func (m *pagerModel) showStatusMessage(msg pagerStatusMessage) tea.Cmd {
// Show a success message to the user
m.state = pagerStateStatusMessage
m.statusMessage = msg.message
if m.statusMessageTimer != nil {
m.statusMessageTimer.Stop()
}
m.statusMessageTimer = time.NewTimer(statusMessageTimeout)
return waitForStatusMessageTimeout(pagerContext, m.statusMessageTimer)
}
func (m *pagerModel) unload() {
log.Debug("unload")
if m.showHelp {
m.toggleHelp()
}
if m.statusMessageTimer != nil {
m.statusMessageTimer.Stop()
}
m.state = pagerStateBrowse
m.viewport.SetContent("")
m.viewport.YOffset = 0
m.unwatchFile()
}
func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", keyEsc:
if m.state != pagerStateBrowse {
m.state = pagerStateBrowse
return m, nil
}
case "home", "g":
m.viewport.GotoTop()
if m.viewport.HighPerformanceRendering {
cmds = append(cmds, viewport.Sync(m.viewport))
}
case "end", "G":
m.viewport.GotoBottom()
if m.viewport.HighPerformanceRendering {
cmds = append(cmds, viewport.Sync(m.viewport))
}
case "d":
m.viewport.HalfViewDown()
if m.viewport.HighPerformanceRendering {
cmds = append(cmds, viewport.Sync(m.viewport))
}
case "u":
m.viewport.HalfViewUp()
if m.viewport.HighPerformanceRendering {
cmds = append(cmds, viewport.Sync(m.viewport))
}
case "e":
lineno := int(math.RoundToEven(float64(m.viewport.TotalLineCount()) * m.viewport.ScrollPercent()))
if m.viewport.AtTop() {
lineno = 0
}
log.Info(
"opening editor",
"file", m.currentDocument.localPath,
"line", fmt.Sprintf("%d/%d", lineno, m.viewport.TotalLineCount()),
)
return m, openEditor(m.currentDocument.localPath, lineno)
case "c":
// Copy using OSC 52
termenv.Copy(m.currentDocument.Body)
// Copy using native system clipboard
_ = clipboard.WriteAll(m.currentDocument.Body)
cmds = append(cmds, m.showStatusMessage(pagerStatusMessage{"Copied contents", false}))
case "r":
return m, loadLocalMarkdown(&m.currentDocument)
case "?":
m.toggleHelp()
if m.viewport.HighPerformanceRendering {
cmds = append(cmds, viewport.Sync(m.viewport))
}
}
// Glow has rendered the content
case contentRenderedMsg:
log.Info("content rendered", "state", m.state)
m.setContent(string(msg))
if m.viewport.HighPerformanceRendering {
cmds = append(cmds, viewport.Sync(m.viewport))
}
cmds = append(cmds, m.watchFile)
// The file was changed on disk and we're reloading it
case reloadMsg:
return m, loadLocalMarkdown(&m.currentDocument)
// We've finished editing the document, potentially making changes. Let's
// retrieve the latest version of the document so that we display
// up-to-date contents.
case editorFinishedMsg:
return m, loadLocalMarkdown(&m.currentDocument)
// We've received terminal dimensions, either for the first time or
// after a resize
case tea.WindowSizeMsg:
return m, renderWithGlamour(m, m.currentDocument.Body)
case statusMessageTimeoutMsg:
m.state = pagerStateBrowse
}
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m pagerModel) View() string {
var b strings.Builder
fmt.Fprint(&b, m.viewport.View()+"\n")
// Footer
m.statusBarView(&b)
if m.showHelp {
fmt.Fprint(&b, "\n"+m.helpView())
}
return b.String()
}
func (m pagerModel) statusBarView(b *strings.Builder) {
const (
minPercent float64 = 0.0
maxPercent float64 = 1.0
percentToStringMagnitude float64 = 100.0
)
showStatusMessage := m.state == pagerStateStatusMessage
// Logo
logo := glowLogoView()
// Scroll percent
percent := math.Max(minPercent, math.Min(maxPercent, m.viewport.ScrollPercent()))
scrollPercent := fmt.Sprintf(" %3.f%% ", percent*percentToStringMagnitude)
if showStatusMessage {
scrollPercent = statusBarMessageScrollPosStyle(scrollPercent)
} else {
scrollPercent = statusBarScrollPosStyle(scrollPercent)
}
// "Help" note
var helpNote string
if showStatusMessage {
helpNote = statusBarMessageHelpStyle(" ? Help ")
} else {
helpNote = statusBarHelpStyle(" ? Help ")
}
// Note
var note string
if showStatusMessage {
note = m.statusMessage
} else {
note = m.currentDocument.Note
}
note = truncate.StringWithTail(" "+note+" ", uint(max(0, //nolint:gosec
m.common.width-
ansi.PrintableRuneWidth(logo)-
ansi.PrintableRuneWidth(scrollPercent)-
ansi.PrintableRuneWidth(helpNote),
)), ellipsis)
if showStatusMessage {
note = statusBarMessageStyle(note)
} else {
note = statusBarNoteStyle(note)
}
// Empty space
padding := max(0,
m.common.width-
ansi.PrintableRuneWidth(logo)-
ansi.PrintableRuneWidth(note)-
ansi.PrintableRuneWidth(scrollPercent)-
ansi.PrintableRuneWidth(helpNote),
)
emptySpace := strings.Repeat(" ", padding)
if showStatusMessage {
emptySpace = statusBarMessageStyle(emptySpace)
} else {
emptySpace = statusBarNoteStyle(emptySpace)
}
fmt.Fprintf(b, "%s%s%s%s%s",
logo,
note,
emptySpace,
scrollPercent,
helpNote,
)
}
func (m pagerModel) helpView() (s string) {
col1 := []string{
"g/home go to top",
"G/end go to bottom",
"c copy contents",
"e edit this document",
"r reload this document",
"esc back to files",
"q quit",
}
s += "\n"
s += "k/↑ up " + col1[0] + "\n"
s += "j/↓ down " + col1[1] + "\n"
s += "b/pgup page up " + col1[2] + "\n"
s += "f/pgdn page down " + col1[3] + "\n"
s += "u ½ page up " + col1[4] + "\n"
s += "d ½ page down "
if len(col1) > 5 {
s += col1[5]
}
s = indent(s, 2)
// Fill up empty cells with spaces for background coloring
if m.common.width > 0 {
lines := strings.Split(s, "\n")
for i := 0; i < len(lines); i++ {
l := runewidth.StringWidth(lines[i])
n := max(m.common.width-l, 0)
lines[i] += strings.Repeat(" ", n)
}
s = strings.Join(lines, "\n")
}
return helpViewStyle(s)
}
// COMMANDS
func renderWithGlamour(m pagerModel, md string) tea.Cmd {
return func() tea.Msg {
s, err := glamourRender(m, md)
if err != nil {
log.Error("error rendering with Glamour", "error", err)
return errMsg{err}
}
return contentRenderedMsg(s)
}
}
// This is where the magic happens.
func glamourRender(m pagerModel, markdown string) (string, error) {
trunc := lipgloss.NewStyle().MaxWidth(m.viewport.Width - lineNumberWidth).Render
if !config.GlamourEnabled {
return markdown, nil
}
isCode := !utils.IsMarkdownFile(m.currentDocument.Note)
width := max(0, min(int(m.common.cfg.GlamourMaxWidth), m.viewport.Width)) //nolint:gosec
if isCode {
width = 0
}
options := []glamour.TermRendererOption{
utils.GlamourStyle(m.common.cfg.GlamourStyle, isCode),
glamour.WithWordWrap(width),
}
if m.common.cfg.PreserveNewLines {
options = append(options, glamour.WithPreservedNewLines())
}
r, err := glamour.NewTermRenderer(options...)
if err != nil {
return "", fmt.Errorf("error creating glamour renderer: %w", err)
}
if isCode {
markdown = utils.WrapCodeBlock(markdown, filepath.Ext(m.currentDocument.Note))
}
out, err := r.Render(markdown)
if err != nil {
return "", fmt.Errorf("error rendering markdown: %w", err)
}
if isCode {
out = strings.TrimSpace(out)
}
// trim lines
lines := strings.Split(out, "\n")
var content strings.Builder
for i, s := range lines {
if isCode || m.common.cfg.ShowLineNumbers {
content.WriteString(lineNumberStyle(fmt.Sprintf("%"+fmt.Sprint(lineNumberWidth)+"d", i+1)))
content.WriteString(trunc(s))
} else {
content.WriteString(s)
}
// don't add an artificial newline after the last split
if i+1 < len(lines) {
content.WriteRune('\n')
}
}
return content.String(), nil
}
func (m *pagerModel) initWatcher() {
var err error
m.watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Error("error creating fsnotify watcher", "error", err)
}
}
func (m *pagerModel) watchFile() tea.Msg {
dir := m.localDir()
if err := m.watcher.Add(dir); err != nil {
log.Error("error adding dir to fsnotify watcher", "error", err)
return nil
}
log.Info("fsnotify watching dir", "dir", dir)
for {
select {
case event, ok := <-m.watcher.Events:
if !ok || event.Name != m.currentDocument.localPath {
continue
}
if !event.Has(fsnotify.Write) && !event.Has(fsnotify.Create) {
continue
}
log.Debug("fsnotify event", "file", event.Name, "event", event.Op)
return reloadMsg{}
case err, ok := <-m.watcher.Errors:
if !ok {
continue
}
log.Debug("fsnotify error", "dir", dir, "error", err)
}
}
}
func (m *pagerModel) unwatchFile() {
dir := m.localDir()
err := m.watcher.Remove(dir)
if err == nil {
log.Debug("fsnotify dir unwatched", "dir", dir)
} else {
log.Error("fsnotify fail to unwatch dir", "dir", dir, "error", err)
}
}
func (m *pagerModel) localDir() string {
return filepath.Dir(m.currentDocument.localPath)
}

12
ui/sort.go Normal file
View file

@ -0,0 +1,12 @@
package ui
import (
"cmp"
"slices"
)
func sortMarkdowns(mds []*markdown) {
slices.SortStableFunc(mds, func(a, b *markdown) int {
return cmp.Compare(a.Note, b.Note)
})
}

891
ui/stash.go Normal file
View file

@ -0,0 +1,891 @@
package ui
import (
"errors"
"fmt"
"os"
"sort"
"strings"
"time"
"github.com/charmbracelet/bubbles/paginator"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/muesli/reflow/ansi"
"github.com/muesli/reflow/truncate"
"github.com/sahilm/fuzzy"
)
const (
stashIndent = 1
stashViewItemHeight = 3 // height of stash entry, including gap
stashViewTopPadding = 5 // logo, status bar, gaps
stashViewBottomPadding = 3 // pagination and gaps, but not help
stashViewHorizontalPadding = 6
)
var stashingStatusMessage = statusMessage{normalStatusMessage, "Stashing..."}
var (
dividerDot = darkGrayFg.SetString(" • ")
dividerBar = darkGrayFg.SetString(" │ ")
logoStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#ECFD65")).
Background(fuchsia).
Bold(true)
stashSpinnerStyle = lipgloss.NewStyle().
Foreground(gray)
stashInputPromptStyle = lipgloss.NewStyle().
Foreground(yellowGreen).
MarginRight(1)
stashInputCursorStyle = lipgloss.NewStyle().
Foreground(fuchsia).
MarginRight(1)
)
// MSG
type (
filteredMarkdownMsg []*markdown
fetchedMarkdownMsg *markdown
)
// MODEL
// stashViewState is the high-level state of the file listing.
type stashViewState int
const (
stashStateReady stashViewState = iota
stashStateLoadingDocument
stashStateShowingError
)
// The types of documents we are currently showing to the user.
type sectionKey int
const (
documentsSection = iota
filterSection
)
// section contains definitions and state information for displaying a tab and
// its contents in the file listing view.
type section struct {
key sectionKey
paginator paginator.Model
cursor int
}
// map sections to their associated types.
var sections = map[sectionKey]section{}
// filterState is the current filtering state in the file listing.
type filterState int
const (
unfiltered filterState = iota // no filter set
filtering // user is actively setting a filter
filterApplied // a filter is applied and user is not editing filter
)
// statusMessageType adds some context to the status message being sent.
type statusMessageType int
// Types of status messages.
const (
normalStatusMessage statusMessageType = iota
subtleStatusMessage
errorStatusMessage
)
// statusMessage is an ephemeral note displayed in the UI.
type statusMessage struct {
status statusMessageType
message string
}
func initSections() {
sections = map[sectionKey]section{
documentsSection: {
key: documentsSection,
paginator: newStashPaginator(),
},
filterSection: {
key: filterSection,
paginator: newStashPaginator(),
},
}
}
// String returns a styled version of the status message appropriate for the
// given context.
func (s statusMessage) String() string {
switch s.status { //nolint:exhaustive
case subtleStatusMessage:
return dimGreenFg(s.message)
case errorStatusMessage:
return redFg(s.message)
default:
return greenFg(s.message)
}
}
type stashModel struct {
common *commonModel
err error
spinner spinner.Model
filterInput textinput.Model
viewState stashViewState
filterState filterState
showFullHelp bool
showStatusMessage bool
statusMessage statusMessage
statusMessageTimer *time.Timer
// Available document sections we can cycle through. We use a slice, rather
// than a map, because order is important.
sections []section
// Index of the section we're currently looking at
sectionIndex int
// Tracks if docs were loaded
loaded bool
// The master set of markdown documents we're working with.
markdowns []*markdown
// Markdown documents we're currently displaying. Filtering, toggles and so
// on will alter this slice so we can show what is relevant. For that
// reason, this field should be considered ephemeral.
filteredMarkdowns []*markdown
// Page we're fetching stash items from on the server, which is different
// from the local pagination. Generally, the server will return more items
// than we can display at a time so we can paginate locally without having
// to fetch every time.
serverPage int64
}
func (m stashModel) loadingDone() bool {
return m.loaded
}
func (m stashModel) currentSection() *section {
return &m.sections[m.sectionIndex]
}
func (m stashModel) paginator() *paginator.Model {
return &m.currentSection().paginator
}
func (m *stashModel) setPaginator(p paginator.Model) {
m.currentSection().paginator = p
}
func (m stashModel) cursor() int {
return m.currentSection().cursor
}
func (m *stashModel) setCursor(i int) {
m.currentSection().cursor = i
}
// Whether or not the spinner should be spinning.
func (m stashModel) shouldSpin() bool {
loading := !m.loadingDone()
openingDocument := m.viewState == stashStateLoadingDocument
return loading || openingDocument
}
func (m *stashModel) setSize(width, height int) {
m.common.width = width
m.common.height = height
m.filterInput.Width = width - stashViewHorizontalPadding*2 - ansi.PrintableRuneWidth(
m.filterInput.Prompt,
)
m.updatePagination()
}
func (m *stashModel) resetFiltering() {
m.filterState = unfiltered
m.filterInput.Reset()
m.filteredMarkdowns = nil
sortMarkdowns(m.markdowns)
// If the filtered section is present (it's always at the end) slice it out
// of the sections slice to remove it from the UI.
if m.sections[len(m.sections)-1].key == filterSection {
m.sections = m.sections[:len(m.sections)-1]
}
// If the current section is out of bounds (it would be if we cut down the
// slice above) then return to the first section.
if m.sectionIndex > len(m.sections)-1 {
m.sectionIndex = 0
}
// Update pagination after we've switched sections.
m.updatePagination()
}
// Is a filter currently being applied?
func (m stashModel) filterApplied() bool {
return m.filterState != unfiltered
}
// Should we be updating the filter?
func (m stashModel) shouldUpdateFilter() bool {
// If we're in the middle of setting a note don't update the filter so that
// the focus won't jump around.
return m.filterApplied()
}
// Update pagination according to the amount of markdowns for the current
// state.
func (m *stashModel) updatePagination() {
_, helpHeight := m.helpView()
availableHeight := m.common.height -
stashViewTopPadding -
helpHeight -
stashViewBottomPadding
m.paginator().PerPage = max(1, availableHeight/stashViewItemHeight)
if pages := len(m.getVisibleMarkdowns()); pages < 1 {
m.paginator().SetTotalPages(1)
} else {
m.paginator().SetTotalPages(pages)
}
// Make sure the page stays in bounds
if m.paginator().Page >= m.paginator().TotalPages-1 {
m.paginator().Page = max(0, m.paginator().TotalPages-1)
}
}
// MarkdownIndex returns the index of the currently selected markdown item.
func (m stashModel) markdownIndex() int {
return m.paginator().Page*m.paginator().PerPage + m.cursor()
}
// Return the current selected markdown in the stash.
func (m stashModel) selectedMarkdown() *markdown {
i := m.markdownIndex()
mds := m.getVisibleMarkdowns()
if i < 0 || len(mds) == 0 || len(mds) <= i {
return nil
}
return mds[i]
}
// Adds markdown documents to the model.
func (m *stashModel) addMarkdowns(mds ...*markdown) {
if len(mds) == 0 {
return
}
m.markdowns = append(m.markdowns, mds...)
if !m.filterApplied() {
sortMarkdowns(m.markdowns)
}
m.updatePagination()
}
// Returns the markdowns that should be currently shown.
func (m stashModel) getVisibleMarkdowns() []*markdown {
if m.filterState == filtering || m.currentSection().key == filterSection {
return m.filteredMarkdowns
}
return m.markdowns
}
// Command for opening a markdown document in the pager. Note that this also
// alters the model.
func (m *stashModel) openMarkdown(md *markdown) tea.Cmd {
m.viewState = stashStateLoadingDocument
cmd := loadLocalMarkdown(md)
return tea.Batch(cmd, m.spinner.Tick)
}
func (m *stashModel) hideStatusMessage() {
m.showStatusMessage = false
m.statusMessage = statusMessage{}
if m.statusMessageTimer != nil {
m.statusMessageTimer.Stop()
}
}
func (m *stashModel) moveCursorUp() {
m.setCursor(m.cursor() - 1)
if m.cursor() < 0 && m.paginator().Page == 0 {
// Stop
m.setCursor(0)
return
}
if m.cursor() >= 0 {
return
}
// Go to previous page
m.paginator().PrevPage()
m.setCursor(m.paginator().ItemsOnPage(len(m.getVisibleMarkdowns())) - 1)
}
func (m *stashModel) moveCursorDown() {
itemsOnPage := m.paginator().ItemsOnPage(len(m.getVisibleMarkdowns()))
m.setCursor(m.cursor() + 1)
if m.cursor() < itemsOnPage {
return
}
if !m.paginator().OnLastPage() {
m.paginator().NextPage()
m.setCursor(0)
return
}
// During filtering the cursor position can exceed the number of
// itemsOnPage. It's more intuitive to start the cursor at the
// topmost position when moving it down in this scenario.
if m.cursor() > itemsOnPage {
m.setCursor(0)
return
}
m.setCursor(itemsOnPage - 1)
}
// INIT
func newStashModel(common *commonModel) stashModel {
sp := spinner.New()
sp.Spinner = spinner.Line
sp.Style = stashSpinnerStyle
si := textinput.New()
si.Prompt = "Find:"
si.PromptStyle = stashInputPromptStyle
si.Cursor.Style = stashInputCursorStyle
si.Focus()
s := []section{
sections[documentsSection],
}
m := stashModel{
common: common,
spinner: sp,
filterInput: si,
serverPage: 1,
sections: s,
}
return m
}
func newStashPaginator() paginator.Model {
p := paginator.New()
p.Type = paginator.Dots
p.ActiveDot = brightGrayFg("•")
p.InactiveDot = darkGrayFg.Render("•")
return p
}
// UPDATE
func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case errMsg:
m.err = msg
case localFileSearchFinished:
// We're finished searching for local files
m.loaded = true
case filteredMarkdownMsg:
m.filteredMarkdowns = msg
m.setCursor(0)
return m, nil
case spinner.TickMsg:
if m.shouldSpin() {
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
cmds = append(cmds, cmd)
}
case statusMessageTimeoutMsg:
if applicationContext(msg) == stashContext {
m.hideStatusMessage()
}
}
if m.filterState == filtering {
cmds = append(cmds, m.handleFiltering(msg))
return m, tea.Batch(cmds...)
}
// Updates per the current state
switch m.viewState { //nolint:exhaustive
case stashStateReady:
cmds = append(cmds, m.handleDocumentBrowsing(msg))
case stashStateShowingError:
// Any key exists the error view
if _, ok := msg.(tea.KeyMsg); ok {
m.viewState = stashStateReady
}
}
return m, tea.Batch(cmds...)
}
// Updates for when a user is browsing the markdown listing.
func (m *stashModel) handleDocumentBrowsing(msg tea.Msg) tea.Cmd {
var cmds []tea.Cmd
numDocs := len(m.getVisibleMarkdowns())
switch msg := msg.(type) {
// Handle keys
case tea.KeyMsg:
switch msg.String() {
case "k", "ctrl+k", "up":
m.moveCursorUp()
case "j", "ctrl+j", "down":
m.moveCursorDown()
// Go to the very start
case "home", "g":
m.paginator().Page = 0
m.setCursor(0)
// Go to the very end
case "end", "G":
m.paginator().Page = m.paginator().TotalPages - 1
m.setCursor(m.paginator().ItemsOnPage(numDocs) - 1)
// Clear filter (if applicable)
case keyEsc:
if m.filterApplied() {
m.resetFiltering()
}
// Next section
case "tab", "L":
if len(m.sections) == 0 || m.filterState == filtering {
break
}
m.sectionIndex++
if m.sectionIndex >= len(m.sections) {
m.sectionIndex = 0
}
m.updatePagination()
// Previous section
case "shift+tab", "H":
if len(m.sections) == 0 || m.filterState == filtering {
break
}
m.sectionIndex--
if m.sectionIndex < 0 {
m.sectionIndex = len(m.sections) - 1
}
m.updatePagination()
case "F":
m.loaded = false
return findLocalFiles(*m.common)
// Edit document in EDITOR
case "e":
md := m.selectedMarkdown()
// In case no file is available
if md == nil {
return nil
}
return openEditor(md.localPath, 0)
// Open document
case keyEnter:
m.hideStatusMessage()
if numDocs == 0 {
break
}
// Load the document from the server. We'll handle the message
// that comes back in the main update function.
md := m.selectedMarkdown()
cmds = append(cmds, m.openMarkdown(md))
// Filter your notes
case "/":
m.hideStatusMessage()
// Build values we'll filter against
for _, md := range m.markdowns {
md.buildFilterValue()
}
m.filteredMarkdowns = m.markdowns
m.paginator().Page = 0
m.setCursor(0)
m.filterState = filtering
m.filterInput.CursorEnd()
m.filterInput.Focus()
return textinput.Blink
// Toggle full help
case "?":
m.showFullHelp = !m.showFullHelp
m.updatePagination()
// Show errors
case "!":
if m.err != nil && m.viewState == stashStateReady {
m.viewState = stashStateShowingError
return nil
}
}
}
// Update paginator. Pagination key handling is done here, but it could
// also be moved up to this level, in which case we'd use model methods
// like model.PageUp().
newPaginatorModel, cmd := m.paginator().Update(msg)
m.setPaginator(newPaginatorModel)
cmds = append(cmds, cmd)
// Extra paginator keystrokes
if key, ok := msg.(tea.KeyMsg); ok {
switch key.String() {
case "b", "u":
m.paginator().PrevPage()
case "f", "d":
m.paginator().NextPage()
}
}
// Keep the index in bounds when paginating
itemsOnPage := m.paginator().ItemsOnPage(len(m.getVisibleMarkdowns()))
if m.cursor() > itemsOnPage-1 {
m.setCursor(max(0, itemsOnPage-1))
}
return tea.Batch(cmds...)
}
// Updates for when a user is in the filter editing interface.
func (m *stashModel) handleFiltering(msg tea.Msg) tea.Cmd {
var cmds []tea.Cmd
// Handle keys
if msg, ok := msg.(tea.KeyMsg); ok { //nolint:nestif
switch msg.String() {
case keyEsc:
// Cancel filtering
m.resetFiltering()
case keyEnter, "tab", "shift+tab", "ctrl+k", "up", "ctrl+j", "down":
m.hideStatusMessage()
if len(m.markdowns) == 0 {
break
}
h := m.getVisibleMarkdowns()
// If we've filtered down to nothing, clear the filter
if len(h) == 0 {
m.viewState = stashStateReady
m.resetFiltering()
break
}
// When there's only one filtered markdown left we can just
// "open" it directly
if len(h) == 1 {
m.viewState = stashStateReady
m.resetFiltering()
cmds = append(cmds, m.openMarkdown(h[0]))
break
}
// Add new section if it's not present
if m.sections[len(m.sections)-1].key != filterSection {
m.sections = append(m.sections, sections[filterSection])
}
m.sectionIndex = len(m.sections) - 1
m.filterInput.Blur()
m.filterState = filterApplied
if m.filterInput.Value() == "" {
m.resetFiltering()
}
}
}
// Update the filter text input component
newFilterInputModel, inputCmd := m.filterInput.Update(msg)
currentFilterVal := m.filterInput.Value()
newFilterVal := newFilterInputModel.Value()
m.filterInput = newFilterInputModel
cmds = append(cmds, inputCmd)
// If the filtering input has changed, request updated filtering
if newFilterVal != currentFilterVal {
cmds = append(cmds, filterMarkdowns(*m))
}
// Update pagination
m.updatePagination()
return tea.Batch(cmds...)
}
// VIEW
func (m stashModel) view() string {
var s string
switch m.viewState {
case stashStateShowingError:
return errorView(m.err, false)
case stashStateLoadingDocument:
s += " " + m.spinner.View() + " Loading document..."
case stashStateReady:
loadingIndicator := " "
if m.shouldSpin() {
loadingIndicator = m.spinner.View()
}
// Only draw the normal header if we're not using the header area for
// something else (like a note or delete prompt).
header := m.headerView()
// Rules for the logo, filter and status message.
logoOrFilter := " "
if m.showStatusMessage && m.filterState == filtering {
logoOrFilter += m.statusMessage.String()
} else if m.filterState == filtering {
logoOrFilter += m.filterInput.View()
} else {
logoOrFilter += glowLogoView()
if m.showStatusMessage {
logoOrFilter += " " + m.statusMessage.String()
}
}
logoOrFilter = truncate.StringWithTail(logoOrFilter, uint(m.common.width-1), ellipsis) //nolint:gosec
help, helpHeight := m.helpView()
populatedView := m.populatedView()
populatedViewHeight := strings.Count(populatedView, "\n") + 2
// We need to fill any empty height with newlines so the footer reaches
// the bottom.
availHeight := m.common.height -
stashViewTopPadding -
populatedViewHeight -
helpHeight -
stashViewBottomPadding
blankLines := strings.Repeat("\n", max(0, availHeight))
var pagination string
if m.paginator().TotalPages > 1 {
pagination = m.paginator().View()
// If the dot pagination is wider than the width of the window
// use the arabic paginator.
if ansi.PrintableRuneWidth(pagination) > m.common.width-stashViewHorizontalPadding {
// Copy the paginator since m.paginator() returns a pointer to
// the active paginator and we don't want to mutate it. In
// normal cases, where the paginator is not a pointer, we could
// safely change the model parameters for rendering here as the
// current model is discarded after reuturning from a View().
// One could argue, in fact, that using pointers in
// a functional framework is an antipattern and our use of
// pointers in our model should be refactored away.
p := *(m.paginator())
p.Type = paginator.Arabic
pagination = paginationStyle.Render(p.View())
}
}
s += fmt.Sprintf(
"%s%s\n\n %s\n\n%s\n\n%s %s\n\n%s",
loadingIndicator,
logoOrFilter,
header,
populatedView,
blankLines,
pagination,
help,
)
}
return "\n" + indent(s, stashIndent)
}
func glowLogoView() string {
return logoStyle.Render(" Glow ")
}
func (m stashModel) headerView() string {
localCount := len(m.markdowns)
var sections []string //nolint:prealloc
// Filter results
if m.filterState == filtering {
if localCount == 0 {
return grayFg("Nothing found.")
}
if localCount > 0 {
sections = append(sections, fmt.Sprintf("%d local", localCount))
}
for i := range sections {
sections[i] = grayFg(sections[i])
}
return strings.Join(sections, dividerDot.String())
}
// Tabs
for i, v := range m.sections {
var s string
switch v.key {
case documentsSection:
s = fmt.Sprintf("%d documents", localCount)
case filterSection:
s = fmt.Sprintf("%d “%s”", len(m.filteredMarkdowns), m.filterInput.Value())
}
if m.sectionIndex == i && len(m.sections) > 1 {
s = selectedTabStyle.Render(s)
} else {
s = tabStyle.Render(s)
}
sections = append(sections, s)
}
return strings.Join(sections, dividerBar.String())
}
func (m stashModel) populatedView() string {
mds := m.getVisibleMarkdowns()
var b strings.Builder
// Empty states
if len(mds) == 0 {
f := func(s string) {
b.WriteString(" " + grayFg(s))
}
switch m.sections[m.sectionIndex].key {
case documentsSection:
if m.loadingDone() {
f("No files found.")
} else {
f("Looking for local files...")
}
case filterSection:
return ""
}
}
if len(mds) > 0 {
start, end := m.paginator().GetSliceBounds(len(mds))
docs := mds[start:end]
for i, md := range docs {
stashItemView(&b, m, i, md)
if i != len(docs)-1 {
fmt.Fprintf(&b, "\n\n")
}
}
}
// If there aren't enough items to fill up this page (always the last page)
// then we need to add some newlines to fill up the space where stash items
// would have been.
itemsOnPage := m.paginator().ItemsOnPage(len(mds))
if itemsOnPage < m.paginator().PerPage {
n := (m.paginator().PerPage - itemsOnPage) * stashViewItemHeight
if len(mds) == 0 {
n -= stashViewItemHeight - 1
}
for i := 0; i < n; i++ {
fmt.Fprint(&b, "\n")
}
}
return b.String()
}
// COMMANDS
func loadLocalMarkdown(md *markdown) tea.Cmd {
return func() tea.Msg {
if md.localPath == "" {
return errMsg{errors.New("could not load file: missing path")}
}
data, err := os.ReadFile(md.localPath)
if err != nil {
log.Debug("error reading local file", "error", err)
return errMsg{err}
}
md.Body = string(data)
return fetchedMarkdownMsg(md)
}
}
func filterMarkdowns(m stashModel) tea.Cmd {
return func() tea.Msg {
if m.filterInput.Value() == "" || !m.filterApplied() {
return filteredMarkdownMsg(m.markdowns) // return everything
}
targets := []string{}
mds := m.markdowns
for _, t := range mds {
targets = append(targets, t.filterValue)
}
ranks := fuzzy.Find(m.filterInput.Value(), targets)
sort.Stable(ranks)
filtered := []*markdown{}
for _, r := range ranks {
filtered = append(filtered, mds[r.Index])
}
return filteredMarkdownMsg(filtered)
}
}

297
ui/stashhelp.go Normal file
View file

@ -0,0 +1,297 @@
package ui
import (
"fmt"
"strings"
"github.com/muesli/reflow/ansi"
)
// helpEntry is a entry in a help menu containing values for a keystroke and
// it's associated action.
type helpEntry struct{ key, val string }
// helpColumn is a group of helpEntries which will be rendered into a column.
type helpColumn []helpEntry
// newHelpColumn creates a help column from pairs of string arguments
// representing keys and values. If the arguments are not even (and therein
// not every key has a matching value) the function will panic.
func newHelpColumn(pairs ...string) (h helpColumn) {
if len(pairs)%2 != 0 {
panic("help text group must have an even number of items")
}
for i := 0; i < len(pairs); i = i + 2 {
h = append(h, helpEntry{key: pairs[i], val: pairs[i+1]})
}
return
}
// render returns styled and formatted rows from keys and values.
func (h helpColumn) render(height int) (rows []string) {
keyWidth, valWidth := h.maxWidths()
for i := 0; i < height; i++ {
var (
b = strings.Builder{}
k, v string
)
if i < len(h) {
k = h[i].key
v = h[i].val
switch k {
case "s":
k = greenFg(k)
v = semiDimGreenFg(v)
default:
k = grayFg(k)
v = midGrayFg(v)
}
}
b.WriteString(k)
b.WriteString(strings.Repeat(" ", keyWidth-ansi.PrintableRuneWidth(k))) // pad keys
b.WriteString(" ") // gap
b.WriteString(v)
b.WriteString(strings.Repeat(" ", valWidth-ansi.PrintableRuneWidth(v))) // pad vals
rows = append(rows, b.String())
}
return
}
// maxWidths returns the widest key and values in the column, respectively.
func (h helpColumn) maxWidths() (maxKey int, maxVal int) {
for _, v := range h {
kw := ansi.PrintableRuneWidth(v.key)
vw := ansi.PrintableRuneWidth(v.val)
if kw > maxKey {
maxKey = kw
}
if vw > maxVal {
maxVal = vw
}
}
return
}
// helpView returns either the mini or full help view depending on the state of
// the model, as well as the total height of the help view.
func (m stashModel) helpView() (string, int) {
numDocs := len(m.getVisibleMarkdowns())
// Help for when we're filtering
if m.filterState == filtering {
var h []string
switch numDocs {
case 0:
h = []string{"enter/esc", "cancel"}
case 1:
h = []string{"enter", "open", "esc", "cancel"}
default:
h = []string{"enter", "confirm", "esc", "cancel", "ctrl+j/ctrl+k ↑/↓", "choose"}
}
return m.renderHelp(h)
}
var (
navHelp []string
filterHelp []string
selectionHelp []string
editHelp []string
sectionHelp []string
appHelp []string
)
if numDocs > 0 && m.showFullHelp {
navHelp = []string{"enter", "open", "j/k ↑/↓", "choose"}
}
if len(m.sections) > 1 {
if m.showFullHelp {
navHelp = append(navHelp, "tab/shift+tab", "section")
} else {
navHelp = append(navHelp, "tab", "section")
}
}
if m.paginator().TotalPages > 1 {
navHelp = append(navHelp, "h/l ←/→", "page")
}
// If we're browsing a filtered set
if m.filterApplied() {
filterHelp = []string{"/", "edit search", "esc", "clear filter"}
} else {
filterHelp = []string{"/", "find"}
}
// If there are errors
if m.err != nil {
appHelp = append(appHelp, "!", "errors")
}
appHelp = append(appHelp, "r", "refresh")
if numDocs > 0 {
appHelp = append(appHelp, "e", "edit")
}
appHelp = append(appHelp, "q", "quit")
// Detailed help
if m.showFullHelp {
if m.filterState != filtering {
appHelp = append(appHelp, "?", "close help")
}
return m.renderHelp(navHelp, filterHelp, append(selectionHelp, editHelp...), sectionHelp, appHelp)
}
// Mini help
if m.filterState != filtering {
appHelp = append(appHelp, "?", "more")
}
return m.renderHelp(navHelp, filterHelp, selectionHelp, editHelp, sectionHelp, appHelp)
}
const minHelpViewHeight = 5
// renderHelp returns the rendered help view and associated line height for
// the given groups of help items.
func (m stashModel) renderHelp(groups ...[]string) (string, int) {
if m.showFullHelp {
str := m.fullHelpView(groups...)
numLines := strings.Count(str, "\n") + 1
return str, max(numLines, minHelpViewHeight)
}
return m.miniHelpView(concatStringSlices(groups...)...), 1
}
// Builds the help view from various sections pieces, truncating it if the view
// would otherwise wrap to two lines. Help view entries should come in as pairs,
// with the first being the key and the second being the help text.
func (m stashModel) miniHelpView(entries ...string) string {
if len(entries) == 0 {
return ""
}
var (
truncationChar = subtleStyle.Render("…")
truncationWidth = ansi.PrintableRuneWidth(truncationChar)
)
var (
next string
leftGutter = " "
maxWidth = m.common.width -
stashViewHorizontalPadding -
truncationWidth -
ansi.PrintableRuneWidth(leftGutter)
s = leftGutter
)
for i := 0; i < len(entries); i = i + 2 {
k := entries[i]
v := entries[i+1]
k = grayFg(k)
v = midGrayFg(v)
next = fmt.Sprintf("%s %s", k, v)
if i < len(entries)-2 {
next += dividerDot.String()
}
// Only this (and the following) help text items if we have the
// horizontal space
if ansi.PrintableRuneWidth(s)+ansi.PrintableRuneWidth(next) >= maxWidth {
s += truncationChar
break
}
s += next
}
return s
}
func (m stashModel) fullHelpView(groups ...[]string) string {
var tallestCol int
columns := make([]helpColumn, 0, len(groups))
renderedCols := make([][]string, 0, len(groups)) // final rows grouped by column
// Get key/value pairs
for _, g := range groups {
if len(g) == 0 {
continue // ignore empty columns
}
columns = append(columns, newHelpColumn(g...))
}
// Find the tallest column
for _, c := range columns {
if len(c) > tallestCol {
tallestCol = len(c)
}
}
// Build columns
for _, c := range columns {
renderedCols = append(renderedCols, c.render(tallestCol))
}
// Merge columns
return mergeColumns(renderedCols...)
}
// Merge columns together to build the help view.
func mergeColumns(cols ...[]string) string {
const minimumHeight = 3
// Find the tallest column
var tallestCol int
for _, v := range cols {
n := len(v)
if n > tallestCol {
tallestCol = n
}
}
// Make sure the tallest column meets the minimum height
if tallestCol < minimumHeight {
tallestCol = minimumHeight
}
b := strings.Builder{}
for i := 0; i < tallestCol; i++ {
for j, col := range cols {
if i >= len(col) {
continue // skip if we're past the length of this column
}
if j == 0 {
b.WriteString(" ") // gutter
} else if j > 0 {
b.WriteString(" ") // gap
}
b.WriteString(col[i])
}
if i < tallestCol-1 {
b.WriteRune('\n')
}
}
return b.String()
}
func concatStringSlices(s ...[]string) (agg []string) {
for _, v := range s {
agg = append(agg, v...)
}
return
}

120
ui/stashitem.go Normal file
View file

@ -0,0 +1,120 @@
package ui
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/muesli/reflow/truncate"
"github.com/sahilm/fuzzy"
)
const (
verticalLine = "│"
fileListingStashIcon = "• "
)
func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) {
var (
truncateTo = uint(m.common.width - stashViewHorizontalPadding*2) //nolint:gosec
gutter string
title = truncate.StringWithTail(md.Note, truncateTo, ellipsis)
date = md.relativeTime()
editedBy = ""
hasEditedBy = false
icon = ""
separator = ""
)
isSelected := index == m.cursor()
isFiltering := m.filterState == filtering
singleFilteredItem := isFiltering && len(m.getVisibleMarkdowns()) == 1
// If there are multiple items being filtered don't highlight a selected
// item in the results. If we've filtered down to one item, however,
// highlight that first item since pressing return will open it.
if isSelected && !isFiltering || singleFilteredItem { //nolint:nestif
// Selected item
if m.statusMessage == stashingStatusMessage {
gutter = greenFg(verticalLine)
icon = dimGreenFg(icon)
title = greenFg(title)
date = semiDimGreenFg(date)
editedBy = semiDimGreenFg(editedBy)
separator = semiDimGreenFg(separator)
} else {
gutter = dullFuchsiaFg(verticalLine)
if m.currentSection().key == filterSection &&
m.filterState == filterApplied || singleFilteredItem {
s := lipgloss.NewStyle().Foreground(fuchsia)
title = styleFilteredText(title, m.filterInput.Value(), s, s.Underline(true))
} else {
title = fuchsiaFg(title)
icon = fuchsiaFg(icon)
}
date = dimFuchsiaFg(date)
editedBy = dimDullFuchsiaFg(editedBy)
separator = dullFuchsiaFg(separator)
}
} else {
gutter = " "
if m.statusMessage == stashingStatusMessage {
icon = dimGreenFg(icon)
title = greenFg(title)
date = semiDimGreenFg(date)
editedBy = semiDimGreenFg(editedBy)
separator = semiDimGreenFg(separator)
} else if isFiltering && m.filterInput.Value() == "" {
icon = dimGreenFg(icon)
title = dimNormalFg(title)
date = dimBrightGrayFg(date)
editedBy = dimBrightGrayFg(editedBy)
separator = dimBrightGrayFg(separator)
} else {
icon = greenFg(icon)
s := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"})
title = styleFilteredText(title, m.filterInput.Value(), s, s.Underline(true))
date = grayFg(date)
editedBy = midGrayFg(editedBy)
separator = brightGrayFg(separator)
}
}
fmt.Fprintf(b, "%s %s%s%s%s\n", gutter, icon, separator, separator, title)
fmt.Fprintf(b, "%s %s", gutter, date)
if hasEditedBy {
fmt.Fprintf(b, " %s", editedBy)
}
}
func styleFilteredText(haystack, needles string, defaultStyle, matchedStyle lipgloss.Style) string {
b := strings.Builder{}
normalizedHay, err := normalize(haystack)
if err != nil {
log.Error("error normalizing", "haystack", haystack, "error", err)
}
matches := fuzzy.Find(needles, []string{normalizedHay})
if len(matches) == 0 {
return defaultStyle.Render(haystack)
}
m := matches[0] // only one match exists
for i, rune := range []rune(haystack) {
styled := false
for _, mi := range m.MatchedIndexes {
if i == mi {
b.WriteString(matchedStyle.Render(string(rune)))
styled = true
}
}
if !styled {
b.WriteString(defaultStyle.Render(string(rune)))
}
}
return b.String()
}

46
ui/styles.go Normal file
View file

@ -0,0 +1,46 @@
package ui
import "github.com/charmbracelet/lipgloss"
// Colors.
var (
normalDim = lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}
gray = lipgloss.AdaptiveColor{Light: "#909090", Dark: "#626262"}
midGray = lipgloss.AdaptiveColor{Light: "#B2B2B2", Dark: "#4A4A4A"}
darkGray = lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"}
brightGray = lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"}
dimBrightGray = lipgloss.AdaptiveColor{Light: "#C2B8C2", Dark: "#4D4D4D"}
cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
yellowGreen = lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}
fuchsia = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
dimFuchsia = lipgloss.AdaptiveColor{Light: "#F1A8FF", Dark: "#99519E"}
dullFuchsia = lipgloss.AdaptiveColor{Dark: "#AD58B4", Light: "#F793FF"}
dimDullFuchsia = lipgloss.AdaptiveColor{Light: "#F6C9FF", Dark: "#7B4380"}
green = lipgloss.Color("#04B575")
red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
semiDimGreen = lipgloss.AdaptiveColor{Light: "#35D79C", Dark: "#036B46"}
dimGreen = lipgloss.AdaptiveColor{Light: "#72D2B0", Dark: "#0B5137"}
)
// Ulimately, we'll transition to named styles.
var (
dimNormalFg = lipgloss.NewStyle().Foreground(normalDim).Render
brightGrayFg = lipgloss.NewStyle().Foreground(brightGray).Render
dimBrightGrayFg = lipgloss.NewStyle().Foreground(dimBrightGray).Render
grayFg = lipgloss.NewStyle().Foreground(gray).Render
midGrayFg = lipgloss.NewStyle().Foreground(midGray).Render
darkGrayFg = lipgloss.NewStyle().Foreground(darkGray)
greenFg = lipgloss.NewStyle().Foreground(green).Render
semiDimGreenFg = lipgloss.NewStyle().Foreground(semiDimGreen).Render
dimGreenFg = lipgloss.NewStyle().Foreground(dimGreen).Render
fuchsiaFg = lipgloss.NewStyle().Foreground(fuchsia).Render
dimFuchsiaFg = lipgloss.NewStyle().Foreground(dimFuchsia).Render
dullFuchsiaFg = lipgloss.NewStyle().Foreground(dullFuchsia).Render
dimDullFuchsiaFg = lipgloss.NewStyle().Foreground(dimDullFuchsia).Render
redFg = lipgloss.NewStyle().Foreground(red).Render
tabStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#909090", Dark: "#626262"})
selectedTabStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#333333", Dark: "#979797"})
errorTitleStyle = lipgloss.NewStyle().Foreground(cream).Background(red).Padding(0, 1)
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
paginationStyle = subtleStyle
)

452
ui/ui.go Normal file
View file

@ -0,0 +1,452 @@
// Package ui provides the main UI for the glow application.
package ui
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour/styles"
"github.com/charmbracelet/glow/v2/utils"
"github.com/charmbracelet/log"
"github.com/muesli/gitcha"
te "github.com/muesli/termenv"
)
const (
statusMessageTimeout = time.Second * 3 // how long to show status messages like "stashed!"
ellipsis = "…"
)
var (
config Config
markdownExtensions = []string{
"*.md", "*.mdown", "*.mkdn", "*.mkd", "*.markdown",
}
)
// NewProgram returns a new Tea program.
func NewProgram(cfg Config, content string) *tea.Program {
log.Debug(
"Starting glow",
"high_perf_pager",
cfg.HighPerformancePager,
"glamour",
cfg.GlamourEnabled,
)
config = cfg
opts := []tea.ProgramOption{tea.WithAltScreen()}
if cfg.EnableMouse {
opts = append(opts, tea.WithMouseCellMotion())
}
m := newModel(cfg, content)
return tea.NewProgram(m, opts...)
}
type errMsg struct{ err error }
func (e errMsg) Error() string { return e.err.Error() }
type (
initLocalFileSearchMsg struct {
cwd string
ch chan gitcha.SearchResult
}
)
type (
foundLocalFileMsg gitcha.SearchResult
localFileSearchFinished struct{}
statusMessageTimeoutMsg applicationContext
)
// applicationContext indicates the area of the application something applies
// to. Occasionally used as an argument to commands and messages.
type applicationContext int
const (
stashContext applicationContext = iota
pagerContext
)
// state is the top-level application state.
type state int
const (
stateShowStash state = iota
stateShowDocument
)
func (s state) String() string {
return map[state]string{
stateShowStash: "showing file listing",
stateShowDocument: "showing document",
}[s]
}
// Common stuff we'll need to access in all models.
type commonModel struct {
cfg Config
cwd string
width int
height int
}
type model struct {
common *commonModel
state state
fatalErr error
// Sub-models
stash stashModel
pager pagerModel
// Channel that receives paths to local markdown files
// (via the github.com/muesli/gitcha package)
localFileFinder chan gitcha.SearchResult
}
// unloadDocument unloads a document from the pager. Note that while this
// method alters the model we also need to send along any commands returned.
func (m *model) unloadDocument() []tea.Cmd {
m.state = stateShowStash
m.stash.viewState = stashStateReady
m.pager.unload()
m.pager.showHelp = false
var batch []tea.Cmd
if m.pager.viewport.HighPerformanceRendering {
batch = append(batch, tea.ClearScrollArea) //nolint:staticcheck
}
if !m.stash.shouldSpin() {
batch = append(batch, m.stash.spinner.Tick)
}
return batch
}
func newModel(cfg Config, content string) tea.Model {
initSections()
if cfg.GlamourStyle == styles.AutoStyle {
if te.HasDarkBackground() {
cfg.GlamourStyle = styles.DarkStyle
} else {
cfg.GlamourStyle = styles.LightStyle
}
}
common := commonModel{
cfg: cfg,
}
m := model{
common: &common,
state: stateShowStash,
pager: newPagerModel(&common),
stash: newStashModel(&common),
}
path := cfg.Path
if path == "" && content != "" {
m.state = stateShowDocument
m.pager.currentDocument = markdown{Body: content}
return m
}
if path == "" {
path = "."
}
info, err := os.Stat(path)
if err != nil {
log.Error("unable to stat file", "file", path, "error", err)
m.fatalErr = err
return m
}
if info.IsDir() {
m.state = stateShowStash
} else {
cwd, _ := os.Getwd()
m.state = stateShowDocument
m.pager.currentDocument = markdown{
localPath: path,
Note: stripAbsolutePath(path, cwd),
Modtime: info.ModTime(),
}
}
return m
}
func (m model) Init() tea.Cmd {
cmds := []tea.Cmd{m.stash.spinner.Tick}
switch m.state {
case stateShowStash:
cmds = append(cmds, findLocalFiles(*m.common))
case stateShowDocument:
content, err := os.ReadFile(m.common.cfg.Path)
if err != nil {
log.Error("unable to read file", "file", m.common.cfg.Path, "error", err)
return func() tea.Msg { return errMsg{err} }
}
body := string(utils.RemoveFrontmatter(content))
cmds = append(cmds, renderWithGlamour(m.pager, body))
}
return tea.Batch(cmds...)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// If there's been an error, any key exits
if m.fatalErr != nil {
if _, ok := msg.(tea.KeyMsg); ok {
return m, tea.Quit
}
}
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "esc":
if m.state == stateShowDocument || m.stash.viewState == stashStateLoadingDocument {
batch := m.unloadDocument()
return m, tea.Batch(batch...)
}
case "r":
var cmd tea.Cmd
if m.state == stateShowStash {
// pass through all keys if we're editing the filter
if m.stash.filterState == filtering {
m.stash, cmd = m.stash.update(msg)
return m, cmd
}
m.stash.markdowns = nil
return m, m.Init()
}
case "q":
var cmd tea.Cmd
switch m.state { //nolint:exhaustive
case stateShowStash:
// pass through all keys if we're editing the filter
if m.stash.filterState == filtering {
m.stash, cmd = m.stash.update(msg)
return m, cmd
}
}
return m, tea.Quit
case "left", "h", "delete":
if m.state == stateShowDocument {
cmds = append(cmds, m.unloadDocument()...)
return m, tea.Batch(cmds...)
}
case "ctrl+z":
return m, tea.Suspend
// Ctrl+C always quits no matter where in the application you are.
case "ctrl+c":
return m, tea.Quit
}
// Window size is received when starting up and on every resize
case tea.WindowSizeMsg:
m.common.width = msg.Width
m.common.height = msg.Height
m.stash.setSize(msg.Width, msg.Height)
m.pager.setSize(msg.Width, msg.Height)
case initLocalFileSearchMsg:
m.localFileFinder = msg.ch
m.common.cwd = msg.cwd
cmds = append(cmds, findNextLocalFile(m))
case fetchedMarkdownMsg:
// We've loaded a markdown file's contents for rendering
m.pager.currentDocument = *msg
body := string(utils.RemoveFrontmatter([]byte(msg.Body)))
cmds = append(cmds, renderWithGlamour(m.pager, body))
case contentRenderedMsg:
m.state = stateShowDocument
case localFileSearchFinished:
// Always pass these messages to the stash so we can keep it updated
// about network activity, even if the user isn't currently viewing
// the stash.
stashModel, cmd := m.stash.update(msg)
m.stash = stashModel
return m, cmd
case foundLocalFileMsg:
newMd := localFileToMarkdown(m.common.cwd, gitcha.SearchResult(msg))
m.stash.addMarkdowns(newMd)
if m.stash.filterApplied() {
newMd.buildFilterValue()
}
if m.stash.shouldUpdateFilter() {
cmds = append(cmds, filterMarkdowns(m.stash))
}
cmds = append(cmds, findNextLocalFile(m))
case filteredMarkdownMsg:
if m.state == stateShowDocument {
newStashModel, cmd := m.stash.update(msg)
m.stash = newStashModel
cmds = append(cmds, cmd)
}
}
// Process children
switch m.state {
case stateShowStash:
newStashModel, cmd := m.stash.update(msg)
m.stash = newStashModel
cmds = append(cmds, cmd)
case stateShowDocument:
newPagerModel, cmd := m.pager.update(msg)
m.pager = newPagerModel
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
}
func (m model) View() string {
if m.fatalErr != nil {
return errorView(m.fatalErr, true)
}
switch m.state { //nolint:exhaustive
case stateShowDocument:
return m.pager.View()
default:
return m.stash.view()
}
}
func errorView(err error, fatal bool) string {
exitMsg := "press any key to "
if fatal {
exitMsg += "exit"
} else {
exitMsg += "return"
}
s := fmt.Sprintf("%s\n\n%v\n\n%s",
errorTitleStyle.Render("ERROR"),
err,
subtleStyle.Render(exitMsg),
)
return "\n" + indent(s, 3)
}
// COMMANDS
func findLocalFiles(m commonModel) tea.Cmd {
return func() tea.Msg {
log.Info("findLocalFiles")
var (
cwd = m.cfg.Path
err error
)
if cwd == "" {
cwd, err = os.Getwd()
} else {
var info os.FileInfo
info, err = os.Stat(cwd)
if err == nil && info.IsDir() {
cwd, err = filepath.Abs(cwd)
}
}
// Note that this is one error check for both cases above
if err != nil {
log.Error("error finding local files", "error", err)
return errMsg{err}
}
log.Debug("local directory is", "cwd", cwd)
// Switch between FindFiles and FindAllFiles to bypass .gitignore rules
var ch chan gitcha.SearchResult
if m.cfg.ShowAllFiles {
ch, err = gitcha.FindAllFilesExcept(cwd, markdownExtensions, nil)
} else {
ch, err = gitcha.FindFilesExcept(cwd, markdownExtensions, ignorePatterns(m))
}
if err != nil {
log.Error("error finding local files", "error", err)
return errMsg{err}
}
return initLocalFileSearchMsg{ch: ch, cwd: cwd}
}
}
func findNextLocalFile(m model) tea.Cmd {
return func() tea.Msg {
res, ok := <-m.localFileFinder
if ok {
// Okay now find the next one
return foundLocalFileMsg(res)
}
// We're done
log.Debug("local file search finished")
return localFileSearchFinished{}
}
}
func waitForStatusMessageTimeout(appCtx applicationContext, t *time.Timer) tea.Cmd {
return func() tea.Msg {
<-t.C
return statusMessageTimeoutMsg(appCtx)
}
}
// ETC
// Convert a Gitcha result to an internal representation of a markdown
// document. Note that we could be doing things like checking if the file is
// a directory, but we trust that gitcha has already done that.
func localFileToMarkdown(cwd string, res gitcha.SearchResult) *markdown {
return &markdown{
localPath: res.Path,
Note: stripAbsolutePath(res.Path, cwd),
Modtime: res.Info.ModTime(),
}
}
func stripAbsolutePath(fullPath, cwd string) string {
fp, _ := filepath.EvalSymlinks(fullPath)
cp, _ := filepath.EvalSymlinks(cwd)
return strings.ReplaceAll(fp, cp+string(os.PathSeparator), "")
}
// Lightweight version of reflow's indent function.
func indent(s string, n int) string {
if n <= 0 || s == "" {
return s
}
l := strings.Split(s, "\n")
b := strings.Builder{}
i := strings.Repeat(" ", n)
for _, v := range l {
fmt.Fprintf(&b, "%s%s\n", i, v)
}
return b.String()
}

86
url.go Normal file
View file

@ -0,0 +1,86 @@
package main
import (
"fmt"
"net/url"
"strings"
"sync"
)
const (
protoGithub = "github://"
protoGitlab = "gitlab://"
protoHTTPS = "https://"
)
var (
githubURL *url.URL
gitlabURL *url.URL
urlsOnce sync.Once
)
func init() {
urlsOnce.Do(func() {
githubURL, _ = url.Parse("https://github.com")
gitlabURL, _ = url.Parse("https://gitlab.com")
})
}
func readmeURL(path string) (*source, error) {
switch {
case strings.HasPrefix(path, protoGithub):
if u := githubReadmeURL(path); u != nil {
return readmeURL(u.String())
}
return nil, nil
case strings.HasPrefix(path, protoGitlab):
if u := gitlabReadmeURL(path); u != nil {
return readmeURL(u.String())
}
return nil, nil
}
if !strings.HasPrefix(path, protoHTTPS) {
path = protoHTTPS + path
}
u, err := url.Parse(path)
if err != nil {
return nil, fmt.Errorf("unable to parse url: %w", err)
}
switch {
case u.Hostname() == githubURL.Hostname():
return findGitHubREADME(u)
case u.Hostname() == gitlabURL.Hostname():
return findGitLabREADME(u)
}
return nil, nil
}
func githubReadmeURL(path string) *url.URL {
path = strings.TrimPrefix(path, protoGithub)
parts := strings.Split(path, "/")
if len(parts) != 2 {
// custom hostnames are not supported yet
return nil
}
u, _ := url.Parse(githubURL.String())
return u.JoinPath(path)
}
func gitlabReadmeURL(path string) *url.URL {
path = strings.TrimPrefix(path, protoGitlab)
parts := strings.Split(path, "/")
if len(parts) != 2 {
// custom hostnames are not supported yet
return nil
}
u, _ := url.Parse(gitlabURL.String())
return u.JoinPath(path)
}
func isURL(path string) bool {
_, err := url.ParseRequestURI(path)
return err == nil && strings.Contains(path, "://")
}

30
url_test.go Normal file
View file

@ -0,0 +1,30 @@
package main
import "testing"
func TestURLParser(t *testing.T) {
for path, url := range map[string]string{
"github.com/charmbracelet/glow": "https://raw.githubusercontent.com/charmbracelet/glow/master/README.md",
"github://charmbracelet/glow": "https://raw.githubusercontent.com/charmbracelet/glow/master/README.md",
"github://caarlos0/dotfiles.fish": "https://raw.githubusercontent.com/caarlos0/dotfiles.fish/main/README.md",
"github://tj/git-extras": "https://raw.githubusercontent.com/tj/git-extras/main/Readme.md",
"https://github.com/goreleaser/nfpm": "https://raw.githubusercontent.com/goreleaser/nfpm/main/README.md",
"gitlab.com/caarlos0/test": "https://gitlab.com/caarlos0/test/-/raw/master/README.md",
"gitlab://caarlos0/test": "https://gitlab.com/caarlos0/test/-/raw/master/README.md",
"https://gitlab.com/terrakok/gitlab-client": "https://gitlab.com/terrakok/gitlab-client/-/raw/develop/Readme.md",
} {
t.Run(path, func(t *testing.T) {
t.Skip("test uses network, sometimes fails for no reason")
got, err := readmeURL(path)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if got == nil {
t.Fatalf("should not be nil")
}
if url != got.URL {
t.Errorf("expected url for %s to be %s, was %s", path, url, got.URL)
}
})
}
}

113
utils/utils.go Normal file
View file

@ -0,0 +1,113 @@
// Package utils provides utility functions.
package utils
import (
"os"
"path/filepath"
"regexp"
"strings"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glamour/ansi"
"github.com/charmbracelet/glamour/styles"
"github.com/charmbracelet/lipgloss"
"github.com/mitchellh/go-homedir"
)
// RemoveFrontmatter removes the front matter header of a markdown file.
func RemoveFrontmatter(content []byte) []byte {
if frontmatterBoundaries := detectFrontmatter(content); frontmatterBoundaries[0] == 0 {
return content[frontmatterBoundaries[1]:]
}
return content
}
var yamlPattern = regexp.MustCompile(`(?m)^---\r?\n(\s*\r?\n)?`)
func detectFrontmatter(c []byte) []int {
if matches := yamlPattern.FindAllIndex(c, 2); len(matches) > 1 {
return []int{matches[0][0], matches[1][1]}
}
return []int{-1, -1}
}
// ExpandPath expands tilde and all environment variables from the given path.
func ExpandPath(path string) string {
s, err := homedir.Expand(path)
if err == nil {
return os.ExpandEnv(s)
}
return os.ExpandEnv(path)
}
// WrapCodeBlock wraps a string in a code block with the given language.
func WrapCodeBlock(s, language string) string {
return "```" + language + "\n" + s + "```"
}
var markdownExtensions = []string{
".md", ".mdown", ".mkdn", ".mkd", ".markdown",
}
// IsMarkdownFile returns whether the filename has a markdown extension.
func IsMarkdownFile(filename string) bool {
ext := filepath.Ext(filename)
if ext == "" {
// By default, assume it's a markdown file.
return true
}
for _, v := range markdownExtensions {
if strings.EqualFold(ext, v) {
return true
}
}
// Has an extension but not markdown
// so assume this is a code file.
return false
}
// GlamourStyle returns a glamour.TermRendererOption based on the given style.
func GlamourStyle(style string, isCode bool) glamour.TermRendererOption {
if !isCode {
if style == styles.AutoStyle {
return glamour.WithAutoStyle()
}
return glamour.WithStylePath(style)
}
// If we are rendering a pure code block, we need to modify the style to
// remove the indentation.
var styleConfig ansi.StyleConfig
switch style {
case styles.AutoStyle:
if lipgloss.HasDarkBackground() {
styleConfig = styles.DarkStyleConfig
} else {
styleConfig = styles.LightStyleConfig
}
case styles.DarkStyle:
styleConfig = styles.DarkStyleConfig
case styles.LightStyle:
styleConfig = styles.LightStyleConfig
case styles.PinkStyle:
styleConfig = styles.PinkStyleConfig
case styles.NoTTYStyle:
styleConfig = styles.NoTTYStyleConfig
case styles.DraculaStyle:
styleConfig = styles.DraculaStyleConfig
case styles.TokyoNightStyle:
styleConfig = styles.DraculaStyleConfig
default:
return glamour.WithStylesFromJSONFile(style)
}
var margin uint
styleConfig.CodeBlock.Margin = &margin
return glamour.WithStyles(styleConfig)
}