peterh / liner

Pure Go line editor with history, inspired by linenoise
MIT License
1.05k stars 132 forks source link

Printing Above Prompt #16

Closed lucacervasio closed 10 years ago

lucacervasio commented 10 years ago

Hi, I'm using your package to implement a CLI. While writing on the prompt line, I dump logs on the screen (using fmt.Println) and they overwrite the prompt, which is annoying to me.

So I tried to improve the package adding a line.PrintAbovePrompt func. Hope I didn't mess up the code. Let me know what you think and in case I'll try to correct.

Thanks Luca

Here a working example (I'm importing github.com/lucacervasio/liner just because I forked your repo for the PR).

package main

import (
    "fmt"
    "github.com/lucacervasio/liner"
    "os"
    "os/signal"
    "strings"
    "time"
)

var line *liner.State = liner.NewLiner()

func main() {
    go logsDumper()
    defer line.Close()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    go func() {
        for sig := range c {
            // sig is a ^C, handle it
            if sig.String() == "interrupt" {
                fmt.Printf("\n")
                line.Close()
                os.Exit(0)
            }
        }
    }()

    baseCmds := []string{"exit", "help", "version", "list", "status", "shutdown", "uptime"}

    line.SetCompleter(func(line string) (c []string) {
        for _, n := range baseCmds {
            if strings.HasPrefix(n, strings.ToLower(line)) {
                c = append(c, n)
            }
        }
        return
    })

    for {

        if cmd, err := line.Prompt("Prompt> "); err != nil {
            fmt.Println("Error reading line: ", err)
        } else {
            if cmd != "" && cmd != "\n" && cmd != "\r\n" {
                line.AppendHistory(cmd)
                fmt.Println("Ei you said", cmd)
            }
        }

    }

}

func logsDumper() {
    for {
        line.PrintAbovePrompt("Hello")
        time.Sleep(2 * time.Second)
    }
}
peterh commented 10 years ago

Thanks for the pull request.

Unfortunately, I cannot accept it, because it cannot work with TERM=dumb.

(There are also a few bugs, such as []line not being cleared upon return from Prompt, and PrintAbovePrompt showing a phantom prompt even if nothing is actually inside Prompt. I suspect there is also a race condition if you call PrintAbovePrompt during a refresh, but I haven't looked at it too closely)

In my applications, I send messages on a (buffered) channel to the UI goroutine, which drains the channel before calling Prompt. The user won't see messages as they happen, but generally the messages are shown "soon enough" in a truly interactive session.

peterh commented 10 years ago

It occurs to me that if you wanted, you could expose a chan string in State that is drained upon entry and exit to Prompt, and optionally added to the select in input.go (where it would clear the line, output the string, and then return winch) for immediate output on some platforms. The application would still be responsible for draining messages whenever it is not inside Prompt, however, so I'm not sure it's worth the extra complexity.

complyue commented 5 years ago

I submitted #119, anyone here interested to review?