diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 00000000..07429dbd --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,41 @@ +package log + +import ( + "fmt" + "log" + "os" + "strings" +) + +var enabled bool + +func init() { + logFile := os.Getenv("SURVEY_LOG_FILE") + if logFile == "" { + return + } + + w, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "survey: enable to open file %q for logging\n", logFile) + return + } + + log.SetOutput(w) + log.Print(strings.Repeat("=", 80)) + enabled = true +} + +func Print(s string) { + if !enabled { + return + } + log.Print(s) +} + +func Printf(f string, args ...interface{}) { + if !enabled { + return + } + log.Printf(f, args...) +} diff --git a/renderer.go b/renderer.go index a16207de..5f77c4a6 100644 --- a/renderer.go +++ b/renderer.go @@ -3,7 +3,9 @@ package survey import ( "bytes" "fmt" + "github.com/AlecAivazis/survey/v2/core" + "github.com/AlecAivazis/survey/v2/internal/log" "github.com/AlecAivazis/survey/v2/terminal" "golang.org/x/term" ) @@ -58,6 +60,7 @@ func (r *Renderer) Error(config *PromptConfig, invalid error) error { return err } + log.Printf("Error() %q", userOut) // send the message to the user if _, err := fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut); err != nil { return err @@ -89,6 +92,7 @@ func (r *Renderer) Render(tmpl string, data interface{}) error { return err } + log.Printf("Render() %q", userOut) // print the summary if _, err := fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut); err != nil { return err @@ -120,6 +124,7 @@ func (r *Renderer) RenderWithCursorOffset(tmpl string, data IterableOpts, opts [ // which is used to track what has been printed. It is not exported // as errors should only be displayed via Error(config, error). func (r *Renderer) appendRenderedError(text string) { + log.Printf("appendRenderedError: %q", text) r.renderedErrors.WriteString(text) } @@ -127,6 +132,7 @@ func (r *Renderer) appendRenderedError(text string) { // which is used to track of what has been printed. The buffer is used // to calculate how many lines to erase before updating the prompt. func (r *Renderer) AppendRenderedText(text string) { + log.Printf("AppendRenderedText: %q", text) r.renderedText.WriteString(text) } @@ -134,9 +140,11 @@ func (r *Renderer) resetPrompt(lines int) { // clean out current line in case tmpl didnt end in newline cursor := r.NewCursor() cursor.HorizontalAbsolute(0) + log.Print("resetPrompt: erasing current line") terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL) // clean up what we left behind last time for i := 0; i < lines; i++ { + log.Print("resetPrompt: moving one line up and erasing line") cursor.PreviousLine(1) terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL) } diff --git a/survey.go b/survey.go index aad73bbe..74f01eff 100644 --- a/survey.go +++ b/survey.go @@ -9,6 +9,7 @@ import ( "unicode/utf8" "github.com/AlecAivazis/survey/v2/core" + "github.com/AlecAivazis/survey/v2/internal/log" "github.com/AlecAivazis/survey/v2/terminal" ) @@ -363,6 +364,7 @@ func Ask(qs []*Question, response interface{}, opts ...AskOpt) error { if promptAgainer, ok := q.Prompt.(PromptAgainer); ok && validationErr != nil { ans, err = promptAgainer.PromptAgain(&options.PromptConfig, ans, validationErr) } else { + log.Printf("Prompt() field: %s", q.Name) ans, err = q.Prompt.Prompt(&options.PromptConfig) } if err != nil { @@ -384,6 +386,7 @@ func Ask(qs []*Question, response interface{}, opts ...AskOpt) error { } // tell the prompt to cleanup with the validated value + log.Printf("Prompt cleanup for %q", q.Name) if err := q.Prompt.Cleanup(&options.PromptConfig, ans); err != nil { return err } diff --git a/terminal/cursor_windows.go b/terminal/cursor_windows.go index c2645919..3f1d394a 100644 --- a/terminal/cursor_windows.go +++ b/terminal/cursor_windows.go @@ -2,8 +2,11 @@ package terminal import ( "bytes" + "fmt" "syscall" "unsafe" + + "github.com/AlecAivazis/survey/v2/internal/log" ) var COORDINATE_SYSTEM_BEGIN Short = 0 @@ -17,19 +20,19 @@ type Cursor struct { } func (c *Cursor) Up(n int) error { - return c.cursorMove(0, n) + return c.cursorMove(0, -1*n, false) } func (c *Cursor) Down(n int) error { - return c.cursorMove(0, -1*n) + return c.cursorMove(0, n, false) } func (c *Cursor) Forward(n int) error { - return c.cursorMove(n, 0) + return c.cursorMove(n, 0, false) } func (c *Cursor) Back(n int) error { - return c.cursorMove(-1*n, 0) + return c.cursorMove(-1*n, 0, false) } // save the cursor location @@ -57,60 +60,56 @@ func (cur Coord) CursorIsAtLineBegin() bool { return cur.X == 0 } -func (c *Cursor) cursorMove(x int, y int) error { +func (c *Cursor) cursorMove(x int, y int, xIsAbs bool) error { handle := syscall.Handle(c.Out.Fd()) var csbi consoleScreenBufferInfo if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil { + log.Printf("cursorMove READ ERROR: %v", err) return err } var cursor Coord - cursor.X = csbi.cursorPosition.X + Short(x) + if xIsAbs { + cursor.X = Short(x) + } else { + cursor.X = csbi.cursorPosition.X + Short(x) + } cursor.Y = csbi.cursorPosition.Y + Short(y) + xAbsLabel := "" + if xIsAbs { + xAbsLabel = " (abs)" + } + log.Printf("cursorMove X:%d%s Y:%d => %d, %d", x, xAbsLabel, y, cursor.X, cursor.Y) + _, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) - return normalizeError(err) + if normalizeError(err) != nil { + log.Printf("cursorMove WRITE ERROR: %v", err) + return err + } + return nil } func (c *Cursor) NextLine(n int) error { - if err := c.Up(n); err != nil { - return err - } - return c.HorizontalAbsolute(0) + return c.cursorMove(0, n, true) } func (c *Cursor) PreviousLine(n int) error { - if err := c.Down(n); err != nil { - return err - } - return c.HorizontalAbsolute(0) + return c.cursorMove(0, -1*n, true) } -// for comparability purposes between windows -// in windows we don't have to print out a new line func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) error { - return c.NextLine(1) -} - -func (c *Cursor) HorizontalAbsolute(x int) error { - handle := syscall.Handle(c.Out.Fd()) - - var csbi consoleScreenBufferInfo - if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil { + if err := c.cursorMove(0, 1, true); err != nil { + // moving to the next line will fail when at the bottom of the terminal viewport + _, err = fmt.Fprint(c.Out, "\n") return err } + return nil +} - var cursor Coord - cursor.X = Short(x) - cursor.Y = csbi.cursorPosition.Y - - if csbi.size.X < cursor.X { - cursor.X = csbi.size.X - } - - _, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) - return normalizeError(err) +func (c *Cursor) HorizontalAbsolute(x int) error { + return c.cursorMove(0, 0, true) } func (c *Cursor) Show() error { diff --git a/terminal/display_windows.go b/terminal/display_windows.go index fc9db9f7..54c0f409 100644 --- a/terminal/display_windows.go +++ b/terminal/display_windows.go @@ -3,6 +3,8 @@ package terminal import ( "syscall" "unsafe" + + "github.com/AlecAivazis/survey/v2/internal/log" ) func EraseLine(out FileWriter, mode EraseLineMode) error { @@ -19,11 +21,14 @@ func EraseLine(out FileWriter, mode EraseLineMode) error { switch mode { case ERASE_LINE_END: x = csbi.size.X + log.Printf("EraseLine() END: %d", x) case ERASE_LINE_START: x = 0 + log.Printf("EraseLine() START: %d", x) case ERASE_LINE_ALL: cursor.X = 0 x = csbi.size.X + log.Printf("EraseLine() ALL: %d-%d", 0, x) } _, _, err := procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))