Skip to content

Commit

Permalink
feat: implement cross-platform support
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinJWendt committed May 8, 2021
1 parent 93d678f commit c9e4c6a
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 232 deletions.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ func CloseAlternativeScreen()
```go
func Down(n int)
```
Down moves the cursor n cells down. If the cursor is already at the edge of the
screen, this has no effect.

Down moves the cursor n cells down. If the cursor is already at the edge of the screen, this has no effect.

#### func Hide

Expand All @@ -107,14 +107,15 @@ func Hide()
```go
func Left(n int)
```
Left moves the cursor n cells left. If the cursor is already at the edge of the
screen, this has no effect.

Left moves the cursor n cells left. If the cursor is already at the edge of the screen, this has no effect.

#### func Move

```go
func Move(row int, column int)
```

Move moves the cursor to a specific row and column.

#### func NextLine
Expand Down Expand Up @@ -146,8 +147,8 @@ func RestorePosition()
```go
func Right(n int)
```
Right moves the cursor n cells right. If the cursor is already at the edge of
the screen, this has no effect.

Right moves the cursor n cells right. If the cursor is already at the edge of the screen, this has no effect.

#### func SavePosition

Expand All @@ -166,8 +167,8 @@ func Show()
```go
func Up(n int)
```
Up moves the cursor n cells up. If the cursor is already at the edge of the
screen, this has no effect.

Up moves the cursor n cells up. If the cursor is already at the edge of the screen, this has no effect.

---

Expand Down
117 changes: 32 additions & 85 deletions cursor.go
Original file line number Diff line number Diff line change
@@ -1,110 +1,57 @@
// +build !windows

package cursor

import (
"fmt"
"strings"
)

const (
ansiPrefix = "\x1b["

upTemplate = "%dA"
downTemplate = "%dB"
leftTemplate = "%dD"
rightTemplate = "%dC"

nextLineTemplate = "%dE"
previousLineTemplate = "%dF"

positionTemplate = "%d;%dH"
savePositionTemplate = "s"
restorePositionTemplate = "u"

openAlternativeScreenTemplate = "?1049h"
closeAlternativeScreenTemplate = "?1049l"

clearDisplayTemplate = "%dJ"
clearLineTemplate = "%dK"

showTemplate = "?25h"
hideTemplate = "?25l"
"path/filepath"
)

// Move cursor.

// Up moves the cursor n cells up. If the cursor is already at the edge of the screen, this has no effect.
// Up moves the cursor n lines up relative to the current position.
func Up(n int) {
fmt.Printf(ansiPrefix+upTemplate, n)
fmt.Printf("\x1b[%dA", n)
height += n
}

// Down moves the cursor n cells down. If the cursor is already at the edge of the screen, this has no effect.
// Down moves the cursor n lines down relative to the current position.
func Down(n int) {
fmt.Printf(ansiPrefix+downTemplate, n)
fmt.Printf("\x1b[%dB", n)
height -= n
}

// Left moves the cursor n cells left. If the cursor is already at the edge of the screen, this has no effect.
func Left(n int) {
fmt.Printf(ansiPrefix+leftTemplate, n)
}

// Right moves the cursor n cells right. If the cursor is already at the edge of the screen, this has no effect.
// Right moves the cursor n characters to the right relative to the current position.
func Right(n int) {
fmt.Printf(ansiPrefix+rightTemplate, n)
fmt.Printf("\x1b[%dC", n)
}

// Move moves the cursor to a specific row and column.
func Move(row int, column int) {
fmt.Printf(ansiPrefix+positionTemplate, row, column)
}

func NextLine(n int) {
fmt.Printf(ansiPrefix+nextLineTemplate, n)
}

func PrevLine(n int) {
fmt.Printf(ansiPrefix+previousLineTemplate, n)
}

func SavePosition() {
fmt.Print(ansiPrefix + savePositionTemplate)
}

func RestorePosition() {
fmt.Print(ansiPrefix + restorePositionTemplate)
// Left moves the cursor n characters to the left relative to the current position.
func Left(n int) {
fmt.Printf("\x1b[%dD", n)
}

// Alternative screen.

func OpenAlternativeScreen() {
fmt.Print(ansiPrefix + openAlternativeScreenTemplate)
// HorizontalAbsolute moves the cursor to n horizontally.
// The position n is absolute to the start of the line.
func HorizontalAbsolute(n int) {
n += 1 // Moves the line to the character after n
fmt.Printf("\x1b[%dG", n)
filepath.Base()
}

func CloseAlternativeScreen() {
fmt.Print(ansiPrefix + closeAlternativeScreenTemplate)
// Show the cursor if it was hidden previously.
// Don't forget to show the cursor atleast at the end of your application.
// Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal.
func Show() {
fmt.Print("\x1b[?25h")
}

// Erase

func ClearScreen() {
fmt.Printf(ansiPrefix+clearDisplayTemplate, 2)
Move(1, 1)
// Hide the cursor.
// Don't forget to show the cursor atleast at the end of your application with Show.
// Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal.
func Hide() {
fmt.Print("\x1b[?25l")
}

// ClearLine clears the current line and moves the cursor to it's start position.
func ClearLine() {
fmt.Printf(ansiPrefix+clearLineTemplate, 2)
}

func ClearLines(n int) {
clear := fmt.Sprintf(ansiPrefix+clearLineTemplate, 2)
fmt.Print(clear + strings.Repeat(fmt.Sprintf(ansiPrefix+upTemplate, 1)+clear, n))
}

// Show and hide cursor

func Show() {
fmt.Printf(ansiPrefix + showTemplate)
}

func Hide() {
fmt.Printf(ansiPrefix + hideTemplate)
fmt.Print("\x1b[2K")
}
151 changes: 17 additions & 134 deletions cursor_test.go
Original file line number Diff line number Diff line change
@@ -1,144 +1,27 @@
package cursor

import (
"io"
"fmt"
"testing"

"github.com/atomicgo/testutil"
)

func equal(t *testing.T, output string, input func(w io.Writer) error) {
t.Helper()
in, _ := testutil.CaptureStdout(input)
if in != output {
t.Errorf("Input: '%s' is not equal to '%s'", in, output)
func TestHeightChanges(t *testing.T) {
for i := 0; i < 4; i++ {
fmt.Println()
}
Up(3)
if height != 3 {
t.Errorf("height should be 3 but is %d", height)
}
Down(3)
if height != 0 {
t.Errorf("height should be 0 but is %d", height)
}
}

func TestClearLine(t *testing.T) {
equal(t, "\x1b[2K", func(w io.Writer) error {
ClearLine()

return nil
})
}

func TestClearLines(t *testing.T) {
equal(t, "\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K", func(w io.Writer) error {
ClearLines(3)

return nil
})
}

func TestClearScreen(t *testing.T) {
equal(t, "\x1b[2J\x1b[1;1H", func(w io.Writer) error {
ClearScreen()

return nil
})
}

func TestCloseAlternativeScreen(t *testing.T) {
equal(t, "\x1b[?1049l", func(w io.Writer) error {
CloseAlternativeScreen()

return nil
})
}

func TestDown(t *testing.T) {
equal(t, "\x1b[6B", func(w io.Writer) error {
Down(6)

return nil
})
}

func TestHide(t *testing.T) {
equal(t, "\x1b[?25l", func(w io.Writer) error {
Hide()

return nil
})
}

func TestLeft(t *testing.T) {
equal(t, "\x1b[6D", func(w io.Writer) error {
Left(6)

return nil
})
}

func TestMove(t *testing.T) {
equal(t, "\x1b[6;7H", func(w io.Writer) error {
Move(6, 7)

return nil
})
}

func TestNextLine(t *testing.T) {
equal(t, "\x1b[6E", func(w io.Writer) error {
NextLine(6)

return nil
})
}

func TestOpenAlternativeScreen(t *testing.T) {
equal(t, "\x1b[?1049h", func(w io.Writer) error {
OpenAlternativeScreen()

return nil
})
}

func TestPrevLine(t *testing.T) {
equal(t, "\x1b[6F", func(w io.Writer) error {
PrevLine(6)

return nil
})
}

func TestRestorePosition(t *testing.T) {
equal(t, "\x1b[u", func(w io.Writer) error {
RestorePosition()

return nil
})
}

func TestRight(t *testing.T) {
equal(t, "\x1b[6C", func(w io.Writer) error {
Right(6)

return nil
})
}

func TestSavePosition(t *testing.T) {
equal(t, "\x1b[s", func(w io.Writer) error {
SavePosition()

return nil
})
}

func TestShow(t *testing.T) {
equal(t, "\x1b[?25h", func(w io.Writer) error {
Show()

return nil
})
}

func TestUp(t *testing.T) {
equal(t, "\x1b[6A", func(w io.Writer) error {
Up(6)

return nil
})
func TestHeightCannotBeNegative(t *testing.T) {
Down(10)
if height < 0 {
t.Errorf("height is negative: %d", height)
}
}
Loading

0 comments on commit c9e4c6a

Please sign in to comment.