diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9fea58..72bed38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,14 +1,14 @@ name: build + on: [push, pull_request] + jobs: test: strategy: matrix: - go-version: [~1.22, ^1] + go-version: [stable, oldstable] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} - env: - GO111MODULE: "on" steps: - name: Install Go uses: actions/setup-go@v5 @@ -26,3 +26,22 @@ jobs: - name: Test run: go test ./... + + dependabot: + needs: [test] + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request'}} + steps: + - id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - run: | + gh pr review --approve "$PR_URL" + gh pr merge --squash --auto "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.golangci-soft.yml b/.golangci-soft.yml deleted file mode 100644 index d325d4f..0000000 --- a/.golangci-soft.yml +++ /dev/null @@ -1,40 +0,0 @@ -run: - tests: false - issues-exit-code: 0 - -issues: - include: - - EXC0001 - - EXC0005 - - EXC0011 - - EXC0012 - - EXC0013 - - max-issues-per-linter: 0 - max-same-issues: 0 - -linters: - enable: - - exhaustive - - goconst - - godot - - godox - - mnd - - gomoddirectives - - goprintffuncname - - misspell - - nakedret - - nestif - - noctx - - nolintlint - - prealloc - - wrapcheck - - # disable default linters, they are already enabled in .golangci.yml - disable: - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - unused diff --git a/.golangci.yml b/.golangci.yml index d6789e0..90c5c08 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,10 +15,22 @@ issues: linters: enable: - bodyclose + - exhaustive + - goconst + - godot + - godox - gofumpt - goimports + - gomoddirectives + - goprintffuncname - gosec + - misspell + - nakedret + - nestif - nilerr + - noctx + - nolintlint + - prealloc - revive - rowserrcheck - sqlclosecheck @@ -26,3 +38,4 @@ linters: - unconvert - unparam - whitespace + - wrapcheck diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..3507203 --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,14 @@ +# https://taskfile.dev + +version: '3' + +tasks: + lint: + desc: Run lint + cmds: + - golangci-lint run + + test: + desc: Run tests + cmds: + - go test ./... {{.CLI_ARGS}} diff --git a/filepicker/filepicker.go b/filepicker/filepicker.go index d370740..da6af56 100644 --- a/filepicker/filepicker.go +++ b/filepicker/filepicker.go @@ -182,10 +182,10 @@ func newStack() stack { } } -func (m *Model) pushView(selected, minIdx, maxIdx int) { +func (m *Model) pushView(selected, minimum, maximum int) { m.selectedStack.Push(selected) - m.minStack.Push(minIdx) - m.maxStack.Push(maxIdx) + m.minStack.Push(minimum) + m.maxStack.Push(maximum) } func (m *Model) popView() (int, int, int) { @@ -225,6 +225,9 @@ func (m Model) readDir(path string, showHidden bool) tea.Cmd { // SetHeight sets the height of the file picker. func (m *Model) SetHeight(h int) { m.height = h + if m.maxIdx > m.height-1 { + m.maxIdx = m.minIdx + m.height - 1 + } } // Height returns the height of the file picker. diff --git a/go.mod b/go.mod index b552b8f..24d9761 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/atotto/clipboard v0.1.4 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta1 github.com/charmbracelet/harmonica v0.2.0 - github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250204145343-96725424379d + github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250319221657-e0b75f7d5b68 github.com/charmbracelet/x/ansi v0.8.0 github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a github.com/dustin/go-humanize v1.0.1 diff --git a/go.sum b/go.sum index 4c5c9a3..fa99dec 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 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/charmbracelet/bubbletea/v2 v2.0.0-beta1 h1:yaxFt97mvofGY7bYZn8U/aSVoamXGE3O4AEvWhshUDI= @@ -14,8 +12,8 @@ github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi+ github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250204145343-96725424379d h1:wW4446FqrhqEHT96r2OVGNU0izi8siEybQVZ+qBRpJs= -github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250204145343-96725424379d/go.mod h1:ZWl23X8o1vsQu8dpju10HKXepcMMlsHO8SwLl2OhmEU= +github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250319221657-e0b75f7d5b68 h1:4Uhi39pfXraqtHyB8ejdU+PqmVyjhd6hhEKA+7sKflE= +github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250319221657-e0b75f7d5b68/go.mod h1:XhU7tcZRWVGzkjWQ6XYRH7tIVqYuWLx6XLjVqAz+7FU= github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= @@ -34,14 +32,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 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= diff --git a/textarea/textarea.go b/textarea/textarea.go index 790d2fa..8d29154 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -943,13 +943,12 @@ func (m Model) LineInfo() LineInfo { // repositionView repositions the view of the viewport based on the defined // scrolling behavior. func (m *Model) repositionView() { - minOffset := m.viewport.YOffset - maxOffset := minOffset + m.viewport.Height() - 1 - - if row := m.cursorLineNumber(); row < minOffset { - m.viewport.LineUp(minOffset - row) - } else if row > maxOffset { - m.viewport.LineDown(row - maxOffset) + minimum := m.viewport.YOffset + maximum := minimum + m.viewport.Height() - 1 + if row := m.cursorLineNumber(); row < minimum { + m.viewport.LineUp(minimum - row) + } else if row > maximum { + m.viewport.LineDown(row - maximum) } } diff --git a/textinput/textinput_test.go b/textinput/textinput_test.go index bac660d..3bfb958 100644 --- a/textinput/textinput_test.go +++ b/textinput/textinput_test.go @@ -1,6 +1,8 @@ package textinput import ( + "fmt" + "strconv" "strings" "testing" ) @@ -43,3 +45,36 @@ func Test_SlicingOutsideCap(t *testing.T) { textinput.SetWidth(32) textinput.View() } + +func ExampleValidateFunc() { + creditCardNumber := New() + creditCardNumber.Placeholder = "4505 **** **** 1234" + creditCardNumber.Focus() + creditCardNumber.CharLimit = 20 + creditCardNumber.SetWidth(30) + creditCardNumber.Prompt = "" + // This anonymous function is a valid function for ValidateFunc. + creditCardNumber.Validate = func(s string) error { + // Credit Card Number should a string less than 20 digits + // It should include 16 integers and 3 spaces + if len(s) > 16+3 { + return fmt.Errorf("CCN is too long") + } + + if len(s) == 0 || len(s)%5 != 0 && (s[len(s)-1] < '0' || s[len(s)-1] > '9') { + return fmt.Errorf("CCN is invalid") + } + + // The last digit should be a number unless it is a multiple of 4 in which + // case it should be a space + if len(s)%5 == 0 && s[len(s)-1] != ' ' { + return fmt.Errorf("CCN must separate groups with spaces") + } + + // The remaining digits should be integers + c := strings.ReplaceAll(s, " ", "") + _, err := strconv.ParseInt(c, 10, 64) + + return err + } +} diff --git a/viewport/viewport.go b/viewport/viewport.go index 6eaf0ae..30ee512 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -702,7 +702,7 @@ func (m Model) updateAsModel(msg tea.Msg) Model { break } - switch msg.Button { //nolint:exhaustive + switch msg.Button { case tea.MouseWheelDown: m.LineDown(m.MouseWheelDelta)