diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2012-01-13 05:11:45 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2012-01-13 05:11:45 +0000 |
commit | df4aa89a5e7acb315655f193e7f549e8d32367e2 (patch) | |
tree | eb5eccc07097c5fcf940967f33ab84a7d47c96fe /libgo/go/exp | |
parent | f83fa0bf8f411697ec908cfa86ee6faf4cd9c476 (diff) | |
download | gcc-df4aa89a5e7acb315655f193e7f549e8d32367e2.zip gcc-df4aa89a5e7acb315655f193e7f549e8d32367e2.tar.gz gcc-df4aa89a5e7acb315655f193e7f549e8d32367e2.tar.bz2 |
libgo: Update to weekly.2011-12-22.
From-SVN: r183150
Diffstat (limited to 'libgo/go/exp')
-rw-r--r-- | libgo/go/exp/inotify/inotify_linux_test.go | 5 | ||||
-rw-r--r-- | libgo/go/exp/sql/sql.go | 41 | ||||
-rw-r--r-- | libgo/go/exp/sql/sql_test.go | 25 | ||||
-rw-r--r-- | libgo/go/exp/ssh/client_auth.go | 4 | ||||
-rw-r--r-- | libgo/go/exp/ssh/client_auth_test.go | 10 | ||||
-rw-r--r-- | libgo/go/exp/ssh/client_func_test.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/ssh/server.go | 12 | ||||
-rw-r--r-- | libgo/go/exp/ssh/session.go | 59 | ||||
-rw-r--r-- | libgo/go/exp/ssh/session_test.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/ssh/tcpip.go | 1 | ||||
-rw-r--r-- | libgo/go/exp/terminal/terminal.go | 206 | ||||
-rw-r--r-- | libgo/go/exp/terminal/terminal_test.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/terminal/util.go | 17 | ||||
-rw-r--r-- | libgo/go/exp/winfsnotify/winfsnotify.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/winfsnotify/winfsnotify_test.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/wingui/gui.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/wingui/winapi.go | 2 | ||||
-rw-r--r-- | libgo/go/exp/wingui/zwinapi.go | 1 |
18 files changed, 304 insertions, 91 deletions
diff --git a/libgo/go/exp/inotify/inotify_linux_test.go b/libgo/go/exp/inotify/inotify_linux_test.go index 92384b6..d035ec1 100644 --- a/libgo/go/exp/inotify/inotify_linux_test.go +++ b/libgo/go/exp/inotify/inotify_linux_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build linux + package inotify import ( @@ -17,6 +19,9 @@ func TestInotifyEvents(t *testing.T) { t.Fatalf("NewWatcher() failed: %s", err) } + t.Logf("NEEDS TO BE CONVERTED TO NEW GO TOOL") // TODO + return + // Add a watch for "_test" err = watcher.Watch("_test") if err != nil { diff --git a/libgo/go/exp/sql/sql.go b/libgo/go/exp/sql/sql.go index 948b911..937982c 100644 --- a/libgo/go/exp/sql/sql.go +++ b/libgo/go/exp/sql/sql.go @@ -22,10 +22,10 @@ var drivers = make(map[string]driver.Driver) // it panics. func Register(name string, driver driver.Driver) { if driver == nil { - panic("db: Register driver is nil") + panic("sql: Register driver is nil") } if _, dup := drivers[name]; dup { - panic("db: Register called twice for driver " + name) + panic("sql: Register called twice for driver " + name) } drivers[name] = driver } @@ -80,7 +80,7 @@ type ScannerInto interface { // ErrNoRows is returned by Scan when QueryRow doesn't return a // row. In such a case, QueryRow returns a placeholder *Row value that // defers this error until a Scan. -var ErrNoRows = errors.New("db: no rows in result set") +var ErrNoRows = errors.New("sql: no rows in result set") // DB is a database handle. It's safe for concurrent use by multiple // goroutines. @@ -102,7 +102,7 @@ type DB struct { func Open(driverName, dataSourceName string) (*DB, error) { driver, ok := drivers[driverName] if !ok { - return nil, fmt.Errorf("db: unknown driver %q (forgotten import?)", driverName) + return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName) } return &DB{driver: driver, dsn: dataSourceName}, nil } @@ -514,7 +514,7 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) { // placeholders, so we won't sanity check input here and instead let the // driver deal with errors. if want := si.NumInput(); want != -1 && len(args) != want { - return nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args)) + return nil, fmt.Errorf("sql: expected %d arguments, got %d", want, len(args)) } // Convert args to subset types. @@ -522,10 +522,10 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) { for n, arg := range args { args[n], err = cc.ColumnConverter(n).ConvertValue(arg) if err != nil { - return nil, fmt.Errorf("db: converting Exec argument #%d's type: %v", n, err) + return nil, fmt.Errorf("sql: converting Exec argument #%d's type: %v", n, err) } if !driver.IsParameterSubsetType(args[n]) { - return nil, fmt.Errorf("db: driver ColumnConverter error converted %T to unsupported type %T", + return nil, fmt.Errorf("sql: driver ColumnConverter error converted %T to unsupported type %T", arg, args[n]) } } @@ -533,7 +533,7 @@ func (s *Stmt) Exec(args ...interface{}) (Result, error) { for n, arg := range args { args[n], err = driver.DefaultParameterConverter.ConvertValue(arg) if err != nil { - return nil, fmt.Errorf("db: converting Exec argument #%d's type: %v", n, err) + return nil, fmt.Errorf("sql: converting Exec argument #%d's type: %v", n, err) } } } @@ -555,7 +555,7 @@ func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, e s.mu.Lock() if s.closed { s.mu.Unlock() - err = errors.New("db: statement is closed") + err = errors.New("sql: statement is closed") return } @@ -617,7 +617,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { // placeholders, so we won't sanity check input here and instead let the // driver deal with errors. if want := si.NumInput(); want != -1 && len(args) != want { - return nil, fmt.Errorf("db: statement expects %d inputs; got %d", si.NumInput(), len(args)) + return nil, fmt.Errorf("sql: statement expects %d inputs; got %d", si.NumInput(), len(args)) } sargs, err := subsetTypeArgs(args) if err != nil { @@ -737,27 +737,40 @@ func (rs *Rows) Err() error { return rs.lasterr } +// Columns returns the column names. +// Columns returns an error if the rows are closed, or if the rows +// are from QueryRow and there was a deferred error. +func (rs *Rows) Columns() ([]string, error) { + if rs.closed { + return nil, errors.New("sql: Rows are closed") + } + if rs.rowsi == nil { + return nil, errors.New("sql: no Rows available") + } + return rs.rowsi.Columns(), nil +} + // Scan copies the columns in the current row into the values pointed // at by dest. If dest contains pointers to []byte, the slices should // not be modified and should only be considered valid until the next // call to Next or Scan. func (rs *Rows) Scan(dest ...interface{}) error { if rs.closed { - return errors.New("db: Rows closed") + return errors.New("sql: Rows closed") } if rs.lasterr != nil { return rs.lasterr } if rs.lastcols == nil { - return errors.New("db: Scan called without calling Next") + return errors.New("sql: Scan called without calling Next") } if len(dest) != len(rs.lastcols) { - return fmt.Errorf("db: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest)) + return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest)) } for i, sv := range rs.lastcols { err := convertAssign(dest[i], sv) if err != nil { - return fmt.Errorf("db: Scan error on column index %d: %v", i, err) + return fmt.Errorf("sql: Scan error on column index %d: %v", i, err) } } return nil diff --git a/libgo/go/exp/sql/sql_test.go b/libgo/go/exp/sql/sql_test.go index f8ccf76..5307a23 100644 --- a/libgo/go/exp/sql/sql_test.go +++ b/libgo/go/exp/sql/sql_test.go @@ -75,6 +75,23 @@ func TestQuery(t *testing.T) { } } +func TestRowsColumns(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + rows, err := db.Query("SELECT|people|age,name|") + if err != nil { + t.Fatalf("Query: %v", err) + } + cols, err := rows.Columns() + if err != nil { + t.Fatalf("Columns: %v", err) + } + want := []string{"age", "name"} + if !reflect.DeepEqual(cols, want) { + t.Errorf("got %#v; want %#v", cols, want) + } +} + func TestQueryRow(t *testing.T) { db := newTestDB(t, "people") defer closeDB(t, db) @@ -187,12 +204,12 @@ func TestExec(t *testing.T) { {[]interface{}{7, 9}, ""}, // Invalid conversions: - {[]interface{}{"Brad", int64(0xFFFFFFFF)}, "db: converting Exec argument #1's type: sql/driver: value 4294967295 overflows int32"}, - {[]interface{}{"Brad", "strconv fail"}, "db: converting Exec argument #1's type: sql/driver: value \"strconv fail\" can't be converted to int32"}, + {[]interface{}{"Brad", int64(0xFFFFFFFF)}, "sql: converting Exec argument #1's type: sql/driver: value 4294967295 overflows int32"}, + {[]interface{}{"Brad", "strconv fail"}, "sql: converting Exec argument #1's type: sql/driver: value \"strconv fail\" can't be converted to int32"}, // Wrong number of args: - {[]interface{}{}, "db: expected 2 arguments, got 0"}, - {[]interface{}{1, 2, 3}, "db: expected 2 arguments, got 3"}, + {[]interface{}{}, "sql: expected 2 arguments, got 0"}, + {[]interface{}{1, 2, 3}, "sql: expected 2 arguments, got 3"}, } for n, et := range execTests { _, err := stmt.Exec(et.args...) diff --git a/libgo/go/exp/ssh/client_auth.go b/libgo/go/exp/ssh/client_auth.go index 1a38235..3a7e9fb 100644 --- a/libgo/go/exp/ssh/client_auth.go +++ b/libgo/go/exp/ssh/client_auth.go @@ -283,8 +283,8 @@ func (p *publickeyAuth) method() string { return "publickey" } -// ClientAuthPublickey returns a ClientAuth using public key authentication. -func ClientAuthPublickey(impl ClientKeyring) ClientAuth { +// ClientAuthKeyring returns a ClientAuth using public key authentication. +func ClientAuthKeyring(impl ClientKeyring) ClientAuth { return &publickeyAuth{impl} } diff --git a/libgo/go/exp/ssh/client_auth_test.go b/libgo/go/exp/ssh/client_auth_test.go index 2b89e97..c41a93b 100644 --- a/libgo/go/exp/ssh/client_auth_test.go +++ b/libgo/go/exp/ssh/client_auth_test.go @@ -122,7 +122,7 @@ var ( PasswordCallback: func(user, pass string) bool { return user == "testuser" && pass == string(clientPassword) }, - PubKeyCallback: func(user, algo string, pubkey []byte) bool { + PublicKeyCallback: func(user, algo string, pubkey []byte) bool { key := clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey expected := []byte(serializePublickey(key)) algoname := algoName(key) @@ -179,7 +179,7 @@ func TestClientAuthPublickey(t *testing.T) { config := &ClientConfig{ User: "testuser", Auth: []ClientAuth{ - ClientAuthPublickey(clientKeychain), + ClientAuthKeyring(clientKeychain), }, } c, err := Dial("tcp", newMockAuthServer(t), config) @@ -210,7 +210,7 @@ func TestClientAuthWrongPassword(t *testing.T) { User: "testuser", Auth: []ClientAuth{ ClientAuthPassword(wrongPw), - ClientAuthPublickey(clientKeychain), + ClientAuthKeyring(clientKeychain), }, } @@ -228,7 +228,7 @@ func TestClientAuthInvalidPublickey(t *testing.T) { config := &ClientConfig{ User: "testuser", Auth: []ClientAuth{ - ClientAuthPublickey(kc), + ClientAuthKeyring(kc), }, } @@ -246,7 +246,7 @@ func TestClientAuthRSAandDSA(t *testing.T) { config := &ClientConfig{ User: "testuser", Auth: []ClientAuth{ - ClientAuthPublickey(kc), + ClientAuthKeyring(kc), }, } c, err := Dial("tcp", newMockAuthServer(t), config) diff --git a/libgo/go/exp/ssh/client_func_test.go b/libgo/go/exp/ssh/client_func_test.go index 24e3a63..b4bdba9 100644 --- a/libgo/go/exp/ssh/client_func_test.go +++ b/libgo/go/exp/ssh/client_func_test.go @@ -50,7 +50,7 @@ func TestFuncPublickeyAuth(t *testing.T) { config := &ClientConfig{ User: *sshuser, Auth: []ClientAuth{ - ClientAuthPublickey(kc), + ClientAuthKeyring(kc), }, } conn, err := Dial("tcp", "localhost:22", config) diff --git a/libgo/go/exp/ssh/server.go b/libgo/go/exp/ssh/server.go index 1eee9a4..31011c6 100644 --- a/libgo/go/exp/ssh/server.go +++ b/libgo/go/exp/ssh/server.go @@ -36,10 +36,10 @@ type ServerConfig struct { // several goroutines. PasswordCallback func(user, password string) bool - // PubKeyCallback, if non-nil, is called when a client attempts public + // PublicKeyCallback, if non-nil, is called when a client attempts public // key authentication. It must return true iff the given public key is // valid for the given user. - PubKeyCallback func(user, algo string, pubkey []byte) bool + PublicKeyCallback func(user, algo string, pubkey []byte) bool // Cryptographic-related configuration. Crypto CryptoConfig @@ -359,7 +359,7 @@ func isAcceptableAlgo(algo string) bool { // testPubKey returns true if the given public key is acceptable for the user. func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool { - if s.config.PubKeyCallback == nil || !isAcceptableAlgo(algo) { + if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) { return false } @@ -369,7 +369,7 @@ func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool { } } - result := s.config.PubKeyCallback(user, algo, pubKey) + result := s.config.PublicKeyCallback(user, algo, pubKey) if len(s.cachedPubKeys) < maxCachedPubKeys { c := cachedPubKey{ user: user, @@ -425,7 +425,7 @@ userAuthLoop: break userAuthLoop } case "publickey": - if s.config.PubKeyCallback == nil { + if s.config.PublicKeyCallback == nil { break } payload := userAuthReq.Payload @@ -499,7 +499,7 @@ userAuthLoop: if s.config.PasswordCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "password") } - if s.config.PubKeyCallback != nil { + if s.config.PublicKeyCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "publickey") } diff --git a/libgo/go/exp/ssh/session.go b/libgo/go/exp/ssh/session.go index bf9a88e..807dd87 100644 --- a/libgo/go/exp/ssh/session.go +++ b/libgo/go/exp/ssh/session.go @@ -68,10 +68,12 @@ type Session struct { *clientChan // the channel backing this session - started bool // true once Start, Run or Shell is invoked. - closeAfterWait []io.Closer - copyFuncs []func() error - errch chan error // one send per copyFunc + started bool // true once Start, Run or Shell is invoked. + copyFuncs []func() error + errch chan error // one send per copyFunc + + // true if pipe method is active + stdinpipe, stdoutpipe, stderrpipe bool } // RFC 4254 Section 6.4. @@ -237,11 +239,9 @@ func (s *Session) waitForResponse() error { func (s *Session) start() error { s.started = true - type F func(*Session) error + type F func(*Session) for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { - if err := setupFd(s); err != nil { - return err - } + setupFd(s) } s.errch = make(chan error, len(s.copyFuncs)) @@ -274,9 +274,6 @@ func (s *Session) Wait() error { copyError = err } } - for _, fd := range s.closeAfterWait { - fd.Close() - } if waitErr != nil { return waitErr } @@ -341,7 +338,10 @@ func (s *Session) wait() error { return &ExitError{wm} } -func (s *Session) stdin() error { +func (s *Session) stdin() { + if s.stdinpipe { + return + } if s.Stdin == nil { s.Stdin = new(bytes.Buffer) } @@ -352,10 +352,12 @@ func (s *Session) stdin() error { } return err }) - return nil } -func (s *Session) stdout() error { +func (s *Session) stdout() { + if s.stdoutpipe { + return + } if s.Stdout == nil { s.Stdout = ioutil.Discard } @@ -363,10 +365,12 @@ func (s *Session) stdout() error { _, err := io.Copy(s.Stdout, s.clientChan.stdout) return err }) - return nil } -func (s *Session) stderr() error { +func (s *Session) stderr() { + if s.stderrpipe { + return + } if s.Stderr == nil { s.Stderr = ioutil.Discard } @@ -374,7 +378,6 @@ func (s *Session) stderr() error { _, err := io.Copy(s.Stderr, s.clientChan.stderr) return err }) - return nil } // StdinPipe returns a pipe that will be connected to the @@ -386,10 +389,8 @@ func (s *Session) StdinPipe() (io.WriteCloser, error) { if s.started { return nil, errors.New("ssh: StdinPipe after process started") } - pr, pw := io.Pipe() - s.Stdin = pr - s.closeAfterWait = append(s.closeAfterWait, pr) - return pw, nil + s.stdinpipe = true + return s.clientChan.stdin, nil } // StdoutPipe returns a pipe that will be connected to the @@ -398,17 +399,15 @@ func (s *Session) StdinPipe() (io.WriteCloser, error) { // stdout and stderr streams. If the StdoutPipe reader is // not serviced fast enought it may eventually cause the // remote command to block. -func (s *Session) StdoutPipe() (io.ReadCloser, error) { +func (s *Session) StdoutPipe() (io.Reader, error) { if s.Stdout != nil { return nil, errors.New("ssh: Stdout already set") } if s.started { return nil, errors.New("ssh: StdoutPipe after process started") } - pr, pw := io.Pipe() - s.Stdout = pw - s.closeAfterWait = append(s.closeAfterWait, pw) - return pr, nil + s.stdoutpipe = true + return s.clientChan.stdout, nil } // StderrPipe returns a pipe that will be connected to the @@ -417,17 +416,15 @@ func (s *Session) StdoutPipe() (io.ReadCloser, error) { // stdout and stderr streams. If the StderrPipe reader is // not serviced fast enought it may eventually cause the // remote command to block. -func (s *Session) StderrPipe() (io.ReadCloser, error) { +func (s *Session) StderrPipe() (io.Reader, error) { if s.Stderr != nil { return nil, errors.New("ssh: Stderr already set") } if s.started { return nil, errors.New("ssh: StderrPipe after process started") } - pr, pw := io.Pipe() - s.Stderr = pw - s.closeAfterWait = append(s.closeAfterWait, pw) - return pr, nil + s.stderrpipe = true + return s.clientChan.stderr, nil } // TODO(dfc) add Output and CombinedOutput helpers diff --git a/libgo/go/exp/ssh/session_test.go b/libgo/go/exp/ssh/session_test.go index a28ead0..2882620 100644 --- a/libgo/go/exp/ssh/session_test.go +++ b/libgo/go/exp/ssh/session_test.go @@ -20,7 +20,7 @@ func dial(handler serverType, t *testing.T) *ClientConn { serverConfig.PasswordCallback = func(user, pass string) bool { return user == "testuser" && pass == string(pw) } - serverConfig.PubKeyCallback = nil + serverConfig.PublicKeyCallback = nil l, err := Listen("tcp", "127.0.0.1:0", serverConfig) if err != nil { diff --git a/libgo/go/exp/ssh/tcpip.go b/libgo/go/exp/ssh/tcpip.go index a85044a..bee41ee 100644 --- a/libgo/go/exp/ssh/tcpip.go +++ b/libgo/go/exp/ssh/tcpip.go @@ -10,6 +10,7 @@ import ( "io" "net" ) + // Dial initiates a connection to the addr from the remote host. // addr is resolved using net.ResolveTCPAddr before connection. // This could allow an observer to observe the DNS name of the diff --git a/libgo/go/exp/terminal/terminal.go b/libgo/go/exp/terminal/terminal.go index 18d76cd..809e88c 100644 --- a/libgo/go/exp/terminal/terminal.go +++ b/libgo/go/exp/terminal/terminal.go @@ -2,13 +2,56 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build linux + package terminal -import "io" +import ( + "io" + "sync" +) + +// EscapeCodes contains escape sequences that can be written to the terminal in +// order to achieve different styles of text. +type EscapeCodes struct { + // Foreground colors + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte + + // Reset all attributes + Reset []byte +} + +var vt100EscapeCodes = EscapeCodes{ + Black: []byte{keyEscape, '[', '3', '0', 'm'}, + Red: []byte{keyEscape, '[', '3', '1', 'm'}, + Green: []byte{keyEscape, '[', '3', '2', 'm'}, + Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, + Blue: []byte{keyEscape, '[', '3', '4', 'm'}, + Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, + Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, + White: []byte{keyEscape, '[', '3', '7', 'm'}, + + Reset: []byte{keyEscape, '[', '0', 'm'}, +} // Terminal contains the state for running a VT100 terminal that is capable of // reading lines of input. type Terminal struct { + // AutoCompleteCallback, if non-null, is called for each keypress + // with the full input line and the current position of the cursor. + // If it returns a nil newLine, the key press is processed normally. + // Otherwise it returns a replacement line and the new cursor position. + AutoCompleteCallback func(line []byte, pos, key int) (newLine []byte, newPos int) + + // Escape contains a pointer to the escape codes for this terminal. + // It's always a valid pointer, although the escape codes themselves + // may be empty if the terminal doesn't support them. + Escape *EscapeCodes + + // lock protects the terminal and the state in this object from + // concurrent processing of a key press and a Write() call. + lock sync.Mutex + c io.ReadWriter prompt string @@ -16,6 +59,8 @@ type Terminal struct { line []byte // pos is the logical position of the cursor in line pos int + // echo is true if local echo is enabled + echo bool // cursorX contains the current X value of the cursor where the left // edge is 0. cursorY contains the row number where the first row of @@ -40,10 +85,12 @@ type Terminal struct { // "> "). func NewTerminal(c io.ReadWriter, prompt string) *Terminal { return &Terminal{ + Escape: &vt100EscapeCodes, c: c, prompt: prompt, termWidth: 80, termHeight: 24, + echo: true, } } @@ -109,18 +156,11 @@ func bytesToKey(b []byte) (int, []byte) { // queue appends data to the end of t.outBuf func (t *Terminal) queue(data []byte) { - if len(t.outBuf)+len(data) > cap(t.outBuf) { - newOutBuf := make([]byte, len(t.outBuf), 2*(len(t.outBuf)+len(data))) - copy(newOutBuf, t.outBuf) - t.outBuf = newOutBuf - } - - oldLen := len(t.outBuf) - t.outBuf = t.outBuf[:len(t.outBuf)+len(data)] - copy(t.outBuf[oldLen:], data) + t.outBuf = append(t.outBuf, data...) } var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'} +var space = []byte{' '} func isPrintable(key int) bool { return key >= 32 && key < 127 @@ -129,6 +169,10 @@ func isPrintable(key int) bool { // moveCursorToPos appends data to t.outBuf which will move the cursor to the // given, logical position in the text. func (t *Terminal) moveCursorToPos(pos int) { + if !t.echo { + return + } + x := len(t.prompt) + pos y := x / t.termWidth x = x % t.termWidth @@ -153,6 +197,12 @@ func (t *Terminal) moveCursorToPos(pos int) { right = x - t.cursorX } + t.cursorX = x + t.cursorY = y + t.move(up, down, left, right) +} + +func (t *Terminal) move(up, down, left, right int) { movement := make([]byte, 3*(up+down+left+right)) m := movement for i := 0; i < up; i++ { @@ -180,11 +230,14 @@ func (t *Terminal) moveCursorToPos(pos int) { m = m[3:] } - t.cursorX = x - t.cursorY = y t.queue(movement) } +func (t *Terminal) clearLineToRight() { + op := []byte{keyEscape, '[', 'K'} + t.queue(op) +} + const maxLineLength = 4096 // handleKey processes the given key and, optionally, returns a line of text @@ -196,12 +249,15 @@ func (t *Terminal) handleKey(key int) (line string, ok bool) { return } t.pos-- + t.moveCursorToPos(t.pos) copy(t.line[t.pos:], t.line[1+t.pos:]) t.line = t.line[:len(t.line)-1] - t.writeLine(t.line[t.pos:]) - t.moveCursorToPos(t.pos) + if t.echo { + t.writeLine(t.line[t.pos:]) + } t.queue(eraseUnderCursor) + t.moveCursorToPos(t.pos) case keyAltLeft: // move left by a word. if t.pos == 0 { @@ -260,6 +316,25 @@ func (t *Terminal) handleKey(key int) (line string, ok bool) { t.cursorY = 0 t.maxLine = 0 default: + if t.AutoCompleteCallback != nil { + t.lock.Unlock() + newLine, newPos := t.AutoCompleteCallback(t.line, t.pos, key) + t.lock.Lock() + + if newLine != nil { + if t.echo { + t.moveCursorToPos(0) + t.writeLine(newLine) + for i := len(newLine); i < len(t.line); i++ { + t.writeLine(space) + } + t.moveCursorToPos(newPos) + } + t.line = newLine + t.pos = newPos + return + } + } if !isPrintable(key) { return } @@ -274,7 +349,9 @@ func (t *Terminal) handleKey(key int) (line string, ok bool) { t.line = t.line[:len(t.line)+1] copy(t.line[t.pos+1:], t.line[t.pos:]) t.line[t.pos] = byte(key) - t.writeLine(t.line[t.pos:]) + if t.echo { + t.writeLine(t.line[t.pos:]) + } t.pos++ t.moveCursorToPos(t.pos) } @@ -283,15 +360,6 @@ func (t *Terminal) handleKey(key int) (line string, ok bool) { func (t *Terminal) writeLine(line []byte) { for len(line) != 0 { - if t.cursorX == t.termWidth { - t.queue([]byte("\r\n")) - t.cursorX = 0 - t.cursorY++ - if t.cursorY > t.maxLine { - t.maxLine = t.cursorY - } - } - remainingOnLine := t.termWidth - t.cursorX todo := len(line) if todo > remainingOnLine { @@ -300,16 +368,95 @@ func (t *Terminal) writeLine(line []byte) { t.queue(line[:todo]) t.cursorX += todo line = line[todo:] + + if t.cursorX == t.termWidth { + t.cursorX = 0 + t.cursorY++ + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + } } } func (t *Terminal) Write(buf []byte) (n int, err error) { - return t.c.Write(buf) + t.lock.Lock() + defer t.lock.Unlock() + + if t.cursorX == 0 && t.cursorY == 0 { + // This is the easy case: there's nothing on the screen that we + // have to move out of the way. + return t.c.Write(buf) + } + + // We have a prompt and possibly user input on the screen. We + // have to clear it first. + t.move(0, /* up */ 0, /* down */ t.cursorX, /* left */ 0 /* right */ ) + t.cursorX = 0 + t.clearLineToRight() + + for t.cursorY > 0 { + t.move(1, /* up */ 0, 0, 0) + t.cursorY-- + t.clearLineToRight() + } + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + + if n, err = t.c.Write(buf); err != nil { + return + } + + t.queue([]byte(t.prompt)) + chars := len(t.prompt) + if t.echo { + t.queue(t.line) + chars += len(t.line) + } + t.cursorX = chars % t.termWidth + t.cursorY = chars / t.termWidth + t.moveCursorToPos(t.pos) + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + return +} + +// ReadPassword temporarily changes the prompt and reads a password, without +// echo, from the terminal. +func (t *Terminal) ReadPassword(prompt string) (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + oldPrompt := t.prompt + t.prompt = prompt + t.echo = false + + line, err = t.readLine() + + t.prompt = oldPrompt + t.echo = true + + return } // ReadLine returns a line of input from the terminal. func (t *Terminal) ReadLine() (line string, err error) { - if t.cursorX == 0 { + t.lock.Lock() + defer t.lock.Unlock() + + return t.readLine() +} + +func (t *Terminal) readLine() (line string, err error) { + // t.lock must be held at this point + + if t.cursorX == 0 && t.cursorY == 0 { t.writeLine([]byte(t.prompt)) t.c.Write(t.outBuf) t.outBuf = t.outBuf[:0] @@ -320,7 +467,11 @@ func (t *Terminal) ReadLine() (line string, err error) { // containing a partial key sequence readBuf := t.inBuf[len(t.remainder):] var n int + + t.lock.Unlock() n, err = t.c.Read(readBuf) + t.lock.Lock() + if err != nil { return } @@ -358,5 +509,8 @@ func (t *Terminal) ReadLine() (line string, err error) { } func (t *Terminal) SetSize(width, height int) { + t.lock.Lock() + defer t.lock.Unlock() + t.termWidth, t.termHeight = width, height } diff --git a/libgo/go/exp/terminal/terminal_test.go b/libgo/go/exp/terminal/terminal_test.go index a219721..75628f6 100644 --- a/libgo/go/exp/terminal/terminal_test.go +++ b/libgo/go/exp/terminal/terminal_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build linux + package terminal import ( diff --git a/libgo/go/exp/terminal/util.go b/libgo/go/exp/terminal/util.go index 0303567..a5bbfca 100644 --- a/libgo/go/exp/terminal/util.go +++ b/libgo/go/exp/terminal/util.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build linux + // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. // @@ -9,7 +11,7 @@ // // oldState, err := terminal.MakeRaw(0) // if err != nil { -// panic(err.String()) +// panic(err) // } // defer terminal.Restore(0, oldState) package terminal @@ -17,6 +19,7 @@ package terminal import ( "io" "syscall" + "unsafe" ) // State contains the state of a terminal. @@ -57,6 +60,18 @@ func Restore(fd int, state *State) error { return err } +func ioctl(int, int, unsafe.Pointer) int __asm__("ioctl") + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var dimensions [4]uint16 + + if ioctl(fd, syscall.TIOCGWINSZ, unsafe.Pointer(&dimensions)) < 0 { + return -1, -1, syscall.GetErrno() + } + return int(dimensions[1]), int(dimensions[0]), nil +} + // ReadPassword reads a line of input from a terminal without local echo. This // is commonly used for inputting passwords and other sensitive data. The slice // returned does not include the \n. diff --git a/libgo/go/exp/winfsnotify/winfsnotify.go b/libgo/go/exp/winfsnotify/winfsnotify.go index d47ffd1..a6e3a6a 100644 --- a/libgo/go/exp/winfsnotify/winfsnotify.go +++ b/libgo/go/exp/winfsnotify/winfsnotify.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + // Package winfsnotify allows the user to receive // file system event notifications on Windows. package winfsnotify diff --git a/libgo/go/exp/winfsnotify/winfsnotify_test.go b/libgo/go/exp/winfsnotify/winfsnotify_test.go index b9c43d9..59ac162 100644 --- a/libgo/go/exp/winfsnotify/winfsnotify_test.go +++ b/libgo/go/exp/winfsnotify/winfsnotify_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + package winfsnotify import ( diff --git a/libgo/go/exp/wingui/gui.go b/libgo/go/exp/wingui/gui.go index d58421b..3b79873 100644 --- a/libgo/go/exp/wingui/gui.go +++ b/libgo/go/exp/wingui/gui.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + package main import ( diff --git a/libgo/go/exp/wingui/winapi.go b/libgo/go/exp/wingui/winapi.go index 24f3dd4..f876088 100644 --- a/libgo/go/exp/wingui/winapi.go +++ b/libgo/go/exp/wingui/winapi.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + package main import ( diff --git a/libgo/go/exp/wingui/zwinapi.go b/libgo/go/exp/wingui/zwinapi.go index b062ca3..5666c6d 100644 --- a/libgo/go/exp/wingui/zwinapi.go +++ b/libgo/go/exp/wingui/zwinapi.go @@ -1,3 +1,4 @@ +// +build windows // mksyscall_windows.pl winapi.go // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT |