From 59c8382e766297be047a2de85837e336ec258764 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Mon, 12 Apr 2021 00:50:18 +0200 Subject: [PATCH 01/12] feat: implement cursor functions --- .github/settings.yml | 2 +- .gitignore | 1 + cursor.go | 110 +++++++++++++++++++++++++++++++++ cursor_test.go | 144 +++++++++++++++++++++++++++++++++++++++++++ doc.go | 9 +-- go.mod | 4 +- go.sum | 2 + template.go | 6 -- template_test.go | 9 --- 9 files changed, 263 insertions(+), 24 deletions(-) create mode 100644 cursor.go create mode 100644 cursor_test.go delete mode 100644 template.go delete mode 100644 template_test.go diff --git a/.github/settings.yml b/.github/settings.yml index 0ed80b6..ec51498 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -4,7 +4,7 @@ repository: # See https://developer.github.com/v3/repos/#edit for all available settings. # A short description of the repository that will show up on GitHub - description: 🔹 Golang module + description: 🔹 Golang module to utilize ANSI functions, like moving the cursor # A comma-separated list of topics to set on the repository topics: atomicgo, go, golang, golang-library diff --git a/.gitignore b/.gitignore index e30b64b..99e2741 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ gen ### macOS # General .DS_Store +experimenting diff --git a/cursor.go b/cursor.go new file mode 100644 index 0000000..f415948 --- /dev/null +++ b/cursor.go @@ -0,0 +1,110 @@ +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" +) + +// Move cursor. + +// Up moves the cursor n cells up. If the cursor is already at the edge of the screen, this has no effect. +func Up(n int) { + fmt.Printf(ansiPrefix+upTemplate, n) +} + +// Down moves the cursor n cells down. If the cursor is already at the edge of the screen, this has no effect. +func Down(n int) { + fmt.Printf(ansiPrefix+downTemplate, 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. +func Right(n int) { + fmt.Printf(ansiPrefix+rightTemplate, 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) +} + +// Alternative screen. + +func OpenAlternativeScreen() { + fmt.Print(ansiPrefix + openAlternativeScreenTemplate) +} + +func CloseAlternativeScreen() { + fmt.Print(ansiPrefix + closeAlternativeScreenTemplate) +} + +// Erase + +func ClearScreen() { + fmt.Printf(ansiPrefix+clearDisplayTemplate, 2) + Move(1, 1) +} + +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) +} diff --git a/cursor_test.go b/cursor_test.go new file mode 100644 index 0000000..0e18283 --- /dev/null +++ b/cursor_test.go @@ -0,0 +1,144 @@ +package cursor + +import ( + "io" + "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 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 + }) +} diff --git a/doc.go b/doc.go index 0731a16..af9f4ca 100644 --- a/doc.go +++ b/doc.go @@ -1,9 +1,4 @@ /* -Package template is used to generate new AtomicGo repositories. - -Write the description of the module here. You can use **markdown**! -This description should clearly explain what the package does. - -Example description: https://golang.org/src/encoding/gob/doc.go +Package cursor contains methods to move the cursor inside a terminal. */ -package template +package cursor diff --git a/go.mod b/go.mod index eb6e377..21d48e7 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ -module github.com/atomicgo/template +module github.com/atomicgo/cursor go 1.15 + +require github.com/atomicgo/testutil v0.0.1 // indirect diff --git a/go.sum b/go.sum index e69de29..62246a7 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/atomicgo/testutil v0.0.1 h1:3Si1wzSCaGihKYc2w3brYbBDWZ2/4MayjzzzUTL7esA= +github.com/atomicgo/testutil v0.0.1/go.mod h1:/hQIxa6fUSFwnzJd096BeLtDdRBbssw1MZN6FhUCOLs= diff --git a/template.go b/template.go deleted file mode 100644 index f74b2c0..0000000 --- a/template.go +++ /dev/null @@ -1,6 +0,0 @@ -package template - -// HelloWorld returns `Hello, World!`. -func HelloWorld() string { - return "Hello, World!" -} diff --git a/template_test.go b/template_test.go deleted file mode 100644 index b2fd824..0000000 --- a/template_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package template - -import "testing" - -func TestHelloWorld(t *testing.T) { - if HelloWorld() != "Hello, World!" { - t.Fatal("Not equal") - } -} From 93d678f3bdbef5945276e4c1445a95c036d490b9 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sun, 11 Apr 2021 22:51:19 +0000 Subject: [PATCH 02/12] docs: autoupdate --- README.md | 139 +++++++++++++++++++++++++++++++++++++++++++++--------- go.mod | 2 +- 2 files changed, 117 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 3be0dfc..3d402de 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ -

AtomicGo | template

+

AtomicGo | cursor

- -Latest Release + +Latest Release - -Tests + +Tests - -Coverage + +Coverage - -Unit test count + +Unit test count - -Issues + +Issues @@ -33,7 +33,7 @@

Get The Module | -Documentation +Documentation | Contributing | @@ -48,33 +48,126 @@ ## Description -Package template is used to generate new AtomicGo repositories. - -Write the description of the module here. You can use **markdown**! This -description should clearly explain what the package does. - -Example description: https://golang.org/src/encoding/gob/doc.go +Package cursor contains methods to move the cursor inside a terminal. ## Install ```console # Execute this command inside your project -go get -u github.com/atomicgo/template +go get -u github.com/atomicgo/cursor ``` ```go // Add this to your imports -import "github.com/atomicgo/template" +import "github.com/atomicgo/cursor" ``` ## Usage -#### func HelloWorld +#### func ClearLine + +```go +func ClearLine() +``` + +#### func ClearLines + +```go +func ClearLines(n int) +``` + +#### func ClearScreen + +```go +func ClearScreen() +``` + +#### func CloseAlternativeScreen + +```go +func CloseAlternativeScreen() +``` + +#### func Down + +```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. + +#### func Hide + +```go +func Hide() +``` + +#### func Left + +```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. + +#### func Move + +```go +func Move(row int, column int) +``` +Move moves the cursor to a specific row and column. + +#### func NextLine + +```go +func NextLine(n int) +``` + +#### func OpenAlternativeScreen + +```go +func OpenAlternativeScreen() +``` + +#### func PrevLine + +```go +func PrevLine(n int) +``` + +#### func RestorePosition + +```go +func RestorePosition() +``` + +#### func Right + +```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. + +#### func SavePosition + +```go +func SavePosition() +``` + +#### func Show + +```go +func Show() +``` + +#### func Up ```go -func HelloWorld() string +func Up(n int) ``` -HelloWorld returns `Hello, World!`. +Up moves the cursor n cells up. If the cursor is already at the edge of the +screen, this has no effect. --- diff --git a/go.mod b/go.mod index 21d48e7..8e9874e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/atomicgo/cursor go 1.15 -require github.com/atomicgo/testutil v0.0.1 // indirect +require github.com/atomicgo/testutil v0.0.1 From c9e4c6a6f74af56e319e16e0e07eb67f45efac58 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 22:47:08 +0200 Subject: [PATCH 03/12] feat: implement cross-platform support --- README.md | 17 ++--- cursor.go | 117 ++++++++++------------------------- cursor_test.go | 151 +++++---------------------------------------- cursor_windows.go | 105 +++++++++++++++++++++++++++++++ doc.go | 5 +- go.mod | 2 - go.sum | 2 - syscall_windows.go | 43 +++++++++++++ utils.go | 65 +++++++++++++++++++ 9 files changed, 275 insertions(+), 232 deletions(-) create mode 100644 cursor_windows.go delete mode 100644 go.sum create mode 100644 syscall_windows.go create mode 100644 utils.go diff --git a/README.md b/README.md index 3d402de..8a99584 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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. --- diff --git a/cursor.go b/cursor.go index f415948..10ccf20 100644 --- a/cursor.go +++ b/cursor.go @@ -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") } diff --git a/cursor_test.go b/cursor_test.go index 0e18283..b74902c 100644 --- a/cursor_test.go +++ b/cursor_test.go @@ -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) + } } diff --git a/cursor_windows.go b/cursor_windows.go new file mode 100644 index 0000000..4c9d314 --- /dev/null +++ b/cursor_windows.go @@ -0,0 +1,105 @@ +package cursor + +import ( + "os" + "syscall" + "unsafe" +) + +// Up moves the cursor n lines up relative to the current position. +func Up(n int) { + move(0, -n) + height += n +} + +// Down moves the cursor n lines down relative to the current position. +func Down(n int) { + move(0, n) + if height-n < 0 { + height = 0 + } else { + height -= n + } +} + +// Right moves the cursor n characters to the right relative to the current position. +func Right(n int) { + move(n, 0) +} + +// Left moves the cursor n characters to the left relative to the current position. +func Left(n int) { + move(-n, 0) +} + +func move(x int, y int) { + handle := syscall.Handle(os.Stdout.Fd()) + + var csbi consoleScreenBufferInfo + _, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + + var cursor coord + cursor.x = csbi.cursorPosition.x + short(x) + cursor.y = csbi.cursorPosition.y + short(y) + + _, _, _ = procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) +} + +// HorizontalAbsolute moves the cursor to n horizontally. +// The position n is absolute to the start of the line. +func HorizontalAbsolute(n int) { + handle := syscall.Handle(os.Stdout.Fd()) + + var csbi consoleScreenBufferInfo + _, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + + var cursor coord + cursor.x = short(n) + cursor.y = csbi.cursorPosition.y + + if csbi.size.x < cursor.x { + cursor.x = csbi.size.x + } + + _, _, _ = procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) +} + +// 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() { + handle := syscall.Handle(os.Stdout.Fd()) + + var cci consoleCursorInfo + _, _, _ = procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) + cci.visible = 1 + + _, _, _ = procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) +} + +// 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() { + handle := syscall.Handle(os.Stdout.Fd()) + + var cci consoleCursorInfo + _, _, _ = procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) + cci.visible = 0 + + _, _, _ = procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) +} + +// ClearLine clears the current line and moves the cursor to it's start position. +func ClearLine() { + handle := syscall.Handle(os.Stdout.Fd()) + + var csbi consoleScreenBufferInfo + _, _, _ = procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + + var w uint32 + var x short + cursor := csbi.cursorPosition + x = csbi.size.x + _, _, _ = procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w))) +} diff --git a/doc.go b/doc.go index af9f4ca..6ecdf2b 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,7 @@ /* -Package cursor contains methods to move the cursor inside a terminal. +Package cursor contains cross-platform methods to move the terminal cursor in different directions. +This package can be used to create interactive CLI tools and games, live charts, algorithm visualization and other updatable output of any kind. + +Special thanks to github.com/k0kubun/go-ansi which this project is based on. */ package cursor diff --git a/go.mod b/go.mod index 8e9874e..8d34575 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/atomicgo/cursor go 1.15 - -require github.com/atomicgo/testutil v0.0.1 diff --git a/go.sum b/go.sum deleted file mode 100644 index 62246a7..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/atomicgo/testutil v0.0.1 h1:3Si1wzSCaGihKYc2w3brYbBDWZ2/4MayjzzzUTL7esA= -github.com/atomicgo/testutil v0.0.1/go.mod h1:/hQIxa6fUSFwnzJd096BeLtDdRBbssw1MZN6FhUCOLs= diff --git a/syscall_windows.go b/syscall_windows.go new file mode 100644 index 0000000..d4bcdf7 --- /dev/null +++ b/syscall_windows.go @@ -0,0 +1,43 @@ +package cursor + +import ( + "syscall" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") + procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") +) + +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + bottom short + left short + right short + top short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +type consoleCursorInfo struct { + size dword + visible int32 +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..f7babc2 --- /dev/null +++ b/utils.go @@ -0,0 +1,65 @@ +package cursor + +import ( + "runtime" +) + +var height int + +// Bottom moves the cursor to the bottom of the terminal. +// This is done by calculating how many lines were moved by Up and Down. +func Bottom() { + if runtime.GOOS == "windows" { + Down(height) + } else { + Down(height) + } + StartOfLine() + height = 0 +} + +// StartOfLine moves the cursor to the start of the current line. +func StartOfLine() { + HorizontalAbsolute(0) +} + +// StartOfLineDown moves the cursor down by n lines, then moves to cursor to the start of the line. +func StartOfLineDown(n int) { + Down(n) + StartOfLine() +} + +// StartOfLineUp moves the cursor up by n lines, then moves to cursor to the start of the line. +func StartOfLineUp(n int) { + Up(n) + StartOfLine() +} + +// UpAndClear moves the cursor up by n lines, then clears the line. +func UpAndClear(n int) { + Up(n) + ClearLine() +} + +// DownAndClear moves the cursor down by n lines, then clears the line. +func DownAndClear(n int) { + Down(n) + ClearLine() +} + +// Move moves the cursor relative by x and y +func Move(x, y int) { + if x > 0 { + Right(x) + } else if x < 0 { + x *= -1 + Left(x) + } + + if y > 0 { + Up(y) + } else if y < 0 { + y *= -1 + Down(y) + } +} From e236c6194ac7e6c1dd4e4219666b97eebe6fabbb Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 20:48:06 +0000 Subject: [PATCH 04/12] docs: autoupdate --- README.md | 103 ++++++++++++++++++++++++++++++------------------------ go.sum | 0 2 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 go.sum diff --git a/README.md b/README.md index 8a99584..91313c8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ -Unit test count +Unit test count @@ -48,7 +48,12 @@ ## Description -Package cursor contains methods to move the cursor inside a terminal. +Package cursor contains cross-platform methods to move the terminal cursor in +different directions. This package can be used to create interactive CLI tools +and games, live charts, algorithm visualization and other updatable output of +any kind. + +Special thanks to github.com/k0kubun/go-ansi which this project is based on. ## Install @@ -64,111 +69,119 @@ import "github.com/atomicgo/cursor" ## Usage -#### func ClearLine +#### func Bottom ```go -func ClearLine() +func Bottom() ``` +Bottom moves the cursor to the bottom of the terminal. This is done by +calculating how many lines were moved by Up and Down. -#### func ClearLines +#### func ClearLine ```go -func ClearLines(n int) +func ClearLine() ``` +ClearLine clears the current line and moves the cursor to it's start position. -#### func ClearScreen +#### func Down ```go -func ClearScreen() +func Down(n int) ``` +Down moves the cursor n lines down relative to the current position. -#### func CloseAlternativeScreen +#### func DownAndClear ```go -func CloseAlternativeScreen() +func DownAndClear(n int) ``` +DownAndClear moves the cursor down by n lines, then clears the line. -#### func Down +#### func Hide ```go -func Down(n int) +func Hide() ``` +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. -Down moves the cursor n cells down. If the cursor is already at the edge of the screen, this has no effect. - -#### func Hide +#### func HorizontalAbsolute ```go -func Hide() +func HorizontalAbsolute(n int) ``` +HorizontalAbsolute moves the cursor to n horizontally. The position n is +absolute to the start of the line. #### func Left ```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 characters to the left relative to the current position. #### func Move ```go -func Move(row int, column int) -``` - -Move moves the cursor to a specific row and column. - -#### func NextLine - -```go -func NextLine(n int) +func Move(x, y int) ``` +Move moves the cursor relative by x and y -#### func OpenAlternativeScreen +#### func Right ```go -func OpenAlternativeScreen() +func Right(n int) ``` +Right moves the cursor n characters to the right relative to the current +position. -#### func PrevLine +#### func Show ```go -func PrevLine(n int) +func Show() ``` +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 RestorePosition +#### func StartOfLine ```go -func RestorePosition() +func StartOfLine() ``` +StartOfLine moves the cursor to the start of the current line. -#### func Right +#### func StartOfLineDown ```go -func Right(n int) +func StartOfLineDown(n int) ``` +StartOfLineDown moves the cursor down by n lines, then moves to cursor to the +start of the line. -Right moves the cursor n cells right. If the cursor is already at the edge of the screen, this has no effect. - -#### func SavePosition +#### func StartOfLineUp ```go -func SavePosition() +func StartOfLineUp(n int) ``` +StartOfLineUp moves the cursor up by n lines, then moves to cursor to the start +of the line. -#### func Show +#### func Up ```go -func Show() +func Up(n int) ``` +Up moves the cursor n lines up relative to the current position. -#### func Up +#### func UpAndClear ```go -func Up(n int) +func UpAndClear(n int) ``` - -Up moves the cursor n cells up. If the cursor is already at the edge of the screen, this has no effect. +UpAndClear moves the cursor up by n lines, then clears the line. --- diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 From 815db82cde98923cf430bf756f300fda5bdbafa7 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 22:48:59 +0200 Subject: [PATCH 05/12] chore: update repository settings --- .github/settings.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index ec51498..1ee791d 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -4,10 +4,10 @@ repository: # See https://developer.github.com/v3/repos/#edit for all available settings. # A short description of the repository that will show up on GitHub - description: 🔹 Golang module to utilize ANSI functions, like moving the cursor + description: 🔹 Golang module to move the terminal cursor in any direction on every operating system. # A comma-separated list of topics to set on the repository - topics: atomicgo, go, golang, golang-library + topics: atomicgo, go, golang, golang-library, terminal, cursor, tui # Either `true` to make the repository private, or `false` to make it public. # private: false From 60e357da33bfcc995466db0698272751ad614aa2 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 22:51:31 +0200 Subject: [PATCH 06/12] refactor: remove debug code --- cursor.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cursor.go b/cursor.go index 10ccf20..e4c303d 100644 --- a/cursor.go +++ b/cursor.go @@ -4,7 +4,6 @@ package cursor import ( "fmt" - "path/filepath" ) // Up moves the cursor n lines up relative to the current position. @@ -34,7 +33,6 @@ func Left(n int) { func HorizontalAbsolute(n int) { n += 1 // Moves the line to the character after n fmt.Printf("\x1b[%dG", n) - filepath.Base() } // Show the cursor if it was hidden previously. From 86bf2800897c50207607877c128763c30104dc7f Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 20:52:22 +0000 Subject: [PATCH 07/12] docs: autoupdate --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91313c8..87cf849 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ -Unit test count +Unit test count From a715e62f443637aa55fd681186284ecfb6e7644b Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 22:52:29 +0200 Subject: [PATCH 08/12] docs: fix typo --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 6ecdf2b..444326d 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ /* Package cursor contains cross-platform methods to move the terminal cursor in different directions. -This package can be used to create interactive CLI tools and games, live charts, algorithm visualization and other updatable output of any kind. +This package can be used to create interactive CLI tools and games, live charts, algorithm visualizations and other updatable output of any kind. Special thanks to github.com/k0kubun/go-ansi which this project is based on. */ From 43c5749fc59a959301216ba35549d56bf8ed5650 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 20:53:28 +0000 Subject: [PATCH 09/12] docs: autoupdate --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87cf849..08dc9f7 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Package cursor contains cross-platform methods to move the terminal cursor in different directions. This package can be used to create interactive CLI tools -and games, live charts, algorithm visualization and other updatable output of +and games, live charts, algorithm visualizations and other updatable output of any kind. Special thanks to github.com/k0kubun/go-ansi which this project is based on. From 5eeccbcd7d206db344ca3f16df39e1ec3b7bc456 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 22:54:20 +0200 Subject: [PATCH 10/12] fix: height can no longer be negative on non windows systems --- cursor.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cursor.go b/cursor.go index e4c303d..f668ff8 100644 --- a/cursor.go +++ b/cursor.go @@ -15,7 +15,11 @@ func Up(n int) { // Down moves the cursor n lines down relative to the current position. func Down(n int) { fmt.Printf("\x1b[%dB", n) - height -= n + if height-n < 0 { + height = 0 + } else { + height -= n + } } // Right moves the cursor n characters to the right relative to the current position. From 076114b235599089a8f00c881043ddc21b582444 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 22:59:49 +0200 Subject: [PATCH 11/12] style: fix linting errors --- cursor.go | 4 ++-- cursor_windows.go | 4 ++-- utils.go | 12 ++---------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/cursor.go b/cursor.go index f668ff8..ef66ecf 100644 --- a/cursor.go +++ b/cursor.go @@ -40,14 +40,14 @@ func HorizontalAbsolute(n int) { } // Show the cursor if it was hidden previously. -// Don't forget to show the cursor atleast at the end of your application. +// Don't forget to show the cursor at least 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") } // Hide the cursor. -// Don't forget to show the cursor atleast at the end of your application with Show. +// Don't forget to show the cursor at least 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") diff --git a/cursor_windows.go b/cursor_windows.go index 4c9d314..232882b 100644 --- a/cursor_windows.go +++ b/cursor_windows.go @@ -65,7 +65,7 @@ func HorizontalAbsolute(n int) { } // Show the cursor if it was hidden previously. -// Don't forget to show the cursor atleast at the end of your application. +// Don't forget to show the cursor at least 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() { handle := syscall.Handle(os.Stdout.Fd()) @@ -78,7 +78,7 @@ func Show() { } // Hide the cursor. -// Don't forget to show the cursor atleast at the end of your application with Show. +// Don't forget to show the cursor at least 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() { handle := syscall.Handle(os.Stdout.Fd()) diff --git a/utils.go b/utils.go index f7babc2..85fd05a 100644 --- a/utils.go +++ b/utils.go @@ -1,19 +1,11 @@ package cursor -import ( - "runtime" -) - var height int // Bottom moves the cursor to the bottom of the terminal. // This is done by calculating how many lines were moved by Up and Down. func Bottom() { - if runtime.GOOS == "windows" { - Down(height) - } else { - Down(height) - } + Down(height) StartOfLine() height = 0 } @@ -47,7 +39,7 @@ func DownAndClear(n int) { ClearLine() } -// Move moves the cursor relative by x and y +// Move moves the cursor relative by x and y. func Move(x, y int) { if x > 0 { Right(x) From f25f8360bcd82b4d334ceb24408d1904a23e50f9 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sat, 8 May 2021 21:00:56 +0000 Subject: [PATCH 12/12] docs: autoupdate --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 08dc9f7..ed70d2a 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ DownAndClear moves the cursor down by n lines, then clears the line. ```go func Hide() ``` -Hide the cursor. Don't forget to show the cursor atleast at the end of your +Hide the cursor. Don't forget to show the cursor at least 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. @@ -127,7 +127,7 @@ Left moves the cursor n characters to the left relative to the current position. ```go func Move(x, y int) ``` -Move moves the cursor relative by x and y +Move moves the cursor relative by x and y. #### func Right @@ -142,8 +142,8 @@ position. ```go func Show() ``` -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 +Show the cursor if it was hidden previously. Don't forget to show the cursor at +least at the end of your application. Otherwise the user might have a terminal with a permanently hidden cursor, until he reopens the terminal. #### func StartOfLine