Skip to content

Commit

Permalink
Dynamic right-alignment of dates
Browse files Browse the repository at this point in the history
- Use COLUMNS env var or dynamic terminal width (up to UIWidth max)
- Refresh each time, in case the user’s terminal is resized
- Fix #53 (regression since e4e6d44)
  • Loading branch information
jnd-au authored and oz committed Oct 15, 2024
1 parent 4ac8666 commit 5e7ae52
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 3 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ require (
github.com/muesli/termenv v0.12.0
github.com/pelletier/go-toml/v2 v2.2.3
github.com/tkuchiki/go-timezone v0.2.2
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
golang.org/x/tools v0.7.0
)

require (
Expand All @@ -20,6 +22,5 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/rivo/uniseg v0.3.4 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.8 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
31 changes: 31 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,33 @@
package main

import (
"regexp"
"strings"
"testing"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/muesli/termenv"
)

var (
ansiControlSequenceRegexp = regexp.MustCompile(regexp.QuoteMeta(termenv.CSI) + "[^m]*m")
utcMinuteAfterMidnightTime = time.Date(
2017, // Year
11, // Month
5, // Day
0, // Hour
1, // Minutes
2, // Seconds
127, // Nanoseconds
time.UTC,
)
utcMinuteAfterMidnightModel = model{
zones: DefaultZones[len(DefaultZones) - 1:],
clock: *NewClock(utcMinuteAfterMidnightTime.Unix()),
isMilitary: true,
showDates: true,
}
)

func getTimestampWithHour(hour int) int64 {
Expand All @@ -40,6 +62,15 @@ func getTimestampWithHour(hour int) int64 {
).Unix()
}

func stripAnsiControlSequences(s string) string {
return ansiControlSequenceRegexp.ReplaceAllString(s, "")
}

func stripAnsiControlSequencesAndNewline(bytes []byte) string {
s := strings.TrimSuffix(string(bytes), "\n")
return ansiControlSequenceRegexp.ReplaceAllString(s, "")
}

func TestUpdateIncHour(t *testing.T) {
// "l" key -> go right
msg := tea.KeyMsg{
Expand Down
25 changes: 25 additions & 0 deletions testdata/view/test-right-alignment.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- expected --
🕛 UTC 00:01, Sun Nov 05, 2017
🕛 UTC 00:01, Sun Nov 05, 2017
🕛 UTC 00:01, Sun Nov 05, 2017
-- observed: narrow --

What time is it?

🕛 UTC 00:01, Sun Nov 05, 2017
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
📆 Sun 05
-- observed: medium --

What time is it?

🕛 UTC 00:01, Sun Nov 05, 2017
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
📆 Sun 05
-- observed: wide --

What time is it?

🕛 UTC 00:01, Sun Nov 05, 2017
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
📆 Sun 05
29 changes: 27 additions & 2 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,45 @@ package main

import (
"fmt"
"os"
"strconv"
"strings"

"github.com/muesli/termenv"
xterm "golang.org/x/term"
)

// Width required to display 24 hours
const UIWidth = 94
const MinimumZoneHeaderPadding = 6
const MaximumZoneHeaderColumns = UIWidth + MinimumZoneHeaderPadding

func (m model) View() string {
s := normalTextStyle("\n What time is it?\n\n").String()

zoneHeaderWidth := MaximumZoneHeaderColumns
envWidth, envErr := strconv.Atoi(os.Getenv("COLUMNS"))
if envErr == nil {
zoneHeaderWidth = min(envWidth, zoneHeaderWidth)
} else {
fd := int(os.Stdout.Fd())
if xterm.IsTerminal(fd) {
termWidth, _, termErr := xterm.GetSize(fd)
if termErr == nil {
zoneHeaderWidth = min(termWidth, zoneHeaderWidth)
}
}
}

// Show hours for each zone
for zi, zone := range m.zones {
hours := strings.Builder{}
dates := strings.Builder{}
currentTime := zone.currentTime(m.clock.t)

startHour := 0
if zi > 0 {
startHour = (zone.currentTime(m.clock.t).Hour() - m.zones[0].currentTime(m.clock.t).Hour()) % 24
startHour = (currentTime.Hour() - m.zones[0].currentTime(m.clock.t).Hour()) % 24
}

dateChanged := false
Expand Down Expand Up @@ -77,7 +97,12 @@ func (m model) View() string {
datetime = zone.ShortDT(m.clock.t)
}

zoneHeader := fmt.Sprintf("%s %-60s %76s", zone.ClockEmoji(m.clock.t), normalTextStyle(zone.String()), dateTimeStyle(datetime))
clockString := zone.ClockEmoji(m.clock.t)
zoneString := zone.String()
usedZoneHeaderWidth := termenv.String(clockString + zoneString + datetime).Width()
unusedZoneHeaderWidth := max(0, zoneHeaderWidth - usedZoneHeaderWidth - MinimumZoneHeaderPadding)
rightAlignmentSpace := strings.Repeat(" ", unusedZoneHeaderWidth)
zoneHeader := fmt.Sprintf("%s %s %s%s", clockString, normalTextStyle(zoneString), rightAlignmentSpace, dateTimeStyle(datetime))

s += fmt.Sprintf(" %s\n %s\n %s\n", zoneHeader, hours.String(), dates.String())
}
Expand Down
83 changes: 83 additions & 0 deletions view_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* This file is part of tz.
*
* tz is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* tz is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License
* along with tz. If not, see <https://www.gnu.org/licenses/>.
**/
package main

import (
"os"
"strconv"
"strings"
"testing"

"golang.org/x/tools/txtar"
)

func TestRightAlignment(t *testing.T) {
testDataFile := "testdata/view/test-right-alignment.txt"
testData, err := txtar.ParseFile(testDataFile)
if err != nil {
t.Fatal(err)
}

expected := stripAnsiControlSequencesAndNewline(testData.Files[0].Data)

tests := []struct {
columns int
}{
{columns: 1},
{columns: 80},
{columns: 999},
}

originalColumns := os.Getenv("COLUMNS")
var observations []string
for _, test := range tests {
os.Setenv("COLUMNS", strconv.Itoa(test.columns))
observed := stripAnsiControlSequences(utcMinuteAfterMidnightModel.View())
observations = append(observations, observed)
}
os.Setenv("COLUMNS", originalColumns)

archive := txtar.Archive{
Comment: testData.Comment,
Files: []txtar.File{
{
Name: "expected",
Data: []byte(expected),
},
{
Name: "observed: narrow",
Data: []byte(observations[0]),
},
{
Name: "observed: medium",
Data: []byte(observations[1]),
},
{
Name: "observed: wide",
Data: []byte(observations[2]),
},
},
}
os.WriteFile(testDataFile, txtar.Format(&archive), 0666)

expectations := strings.Split(expected, "\n")
for i, test := range tests {
if !strings.Contains(observations[i], expectations[i]) {
t.Errorf("Expected %d-column alignment “%s”, but got: “%s”", test.columns, expectations[i], observations[i])
}
}
}

0 comments on commit 5e7ae52

Please sign in to comment.