mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Manual sync of files pressing p (#6089)
* Set character mode for input stream * Move watchers in receiver + sync when p pressed * Integration tests manual sync * Add a console to DevSession * Vendor * Skip pressKey tests on Windows * Add interactive test for p press * Add info about pressing p key * Doc * Review * Rephrase Manul sync * Fix HotReloadCapable * Document timers * Document enableCharInput * Document geyKey and getKeyWatcher functions * Avoid to Kill in AfterEach after running Kill before
This commit is contained in:
@@ -58,6 +58,8 @@ function Run-Test {
|
|||||||
[Environment]::SetEnvironmentVariable("TEST_EXEC_NODES", "$TEST_EXEC_NODES")
|
[Environment]::SetEnvironmentVariable("TEST_EXEC_NODES", "$TEST_EXEC_NODES")
|
||||||
[Environment]::SetEnvironmentVariable("SKIP_USER_LOGIN_TESTS","true")
|
[Environment]::SetEnvironmentVariable("SKIP_USER_LOGIN_TESTS","true")
|
||||||
[Environment]::SetEnvironmentVariable("SKIP_WELCOMING_MESSAGES","true")
|
[Environment]::SetEnvironmentVariable("SKIP_WELCOMING_MESSAGES","true")
|
||||||
|
# Integration tests detecting key press when running DevSession are not working on Windows
|
||||||
|
[Environment]::SetEnvironmentVariable("SKIP_KEY_PRESS","true")
|
||||||
|
|
||||||
Shout "Login IBMcloud"
|
Shout "Login IBMcloud"
|
||||||
ibmcloud login --apikey ${API_KEY}
|
ibmcloud login --apikey ${API_KEY}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ Your application is now running on the cluster
|
|||||||
- Forwarding from 127.0.0.1:40001 -> 3000
|
- Forwarding from 127.0.0.1:40001 -> 3000
|
||||||
|
|
||||||
Watching for changes in the current directory /Users/user/nodejs
|
Watching for changes in the current directory /Users/user/nodejs
|
||||||
Press Ctrl+c to exit `odo dev` and delete resources from the cluster
|
|
||||||
|
[Ctrl+c] - Exit and delete resources from the cluster
|
||||||
|
[p] - Manually apply local changes to the application on the cluster
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above example, three things have happened:
|
In the above example, three things have happened:
|
||||||
@@ -46,6 +48,22 @@ In the above example, three things have happened:
|
|||||||
You can press Ctrl-c at any time to terminate the development session. The command can take a few moment to terminate, as it
|
You can press Ctrl-c at any time to terminate the development session. The command can take a few moment to terminate, as it
|
||||||
will first delete all resources deployed into the cluster for this session before terminating.
|
will first delete all resources deployed into the cluster for this session before terminating.
|
||||||
|
|
||||||
|
### Applying local changes to the application on the cluster
|
||||||
|
|
||||||
|
By default, the changes made by the user to the Devfile and source files are applied directly.
|
||||||
|
|
||||||
|
The flag `--no-watch` can be used to change this behaviour: when the user changes the devfile or any source file, the changes
|
||||||
|
won't be applied immediately, but the next time the user presses the `p` key.
|
||||||
|
|
||||||
|
Depending on the local changes, different events can occur on the cluster:
|
||||||
|
|
||||||
|
- if source files are modified, they are pushed to the container running the application, and:
|
||||||
|
- if the `run` command is marked as `HotReloadCapable`, the application is responsible for applying the new changes
|
||||||
|
- if the `run` command is not marked as `HotReloadCapable`, the application is stopped, then restarted by odo using the `build` and `run` commands again.
|
||||||
|
- if the Devfile is modified, the deployment of the application is modified with the new changes. In some circumstances, this may
|
||||||
|
cause the restart of the container running the application and therefore the application itself.
|
||||||
|
|
||||||
|
|
||||||
### Running an alternative command
|
### Running an alternative command
|
||||||
|
|
||||||
#### Running an alternative build command
|
#### Running an alternative build command
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -4,8 +4,8 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ActiveState/termtest v0.7.1
|
github.com/ActiveState/termtest v0.7.1
|
||||||
|
github.com/ActiveState/termtest/expect v0.7.0
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
|
|
||||||
github.com/Xuanwo/go-locale v1.1.0
|
github.com/Xuanwo/go-locale v1.1.0
|
||||||
github.com/blang/semver v3.5.1+incompatible
|
github.com/blang/semver v3.5.1+incompatible
|
||||||
github.com/devfile/api/v2 v2.0.0-20220309195345-48ebbf1e51cf
|
github.com/devfile/api/v2 v2.0.0-20220309195345-48ebbf1e51cf
|
||||||
@@ -19,9 +19,7 @@ require (
|
|||||||
github.com/go-git/go-git/v5 v5.3.0
|
github.com/go-git/go-git/v5 v5.3.0
|
||||||
github.com/go-openapi/spec v0.19.5
|
github.com/go-openapi/spec v0.19.5
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02
|
|
||||||
github.com/jedib0t/go-pretty/v6 v6.3.5
|
github.com/jedib0t/go-pretty/v6 v6.3.5
|
||||||
github.com/kr/pty v1.1.8
|
|
||||||
github.com/kubernetes-sigs/service-catalog v0.3.1
|
github.com/kubernetes-sigs/service-catalog v0.3.1
|
||||||
github.com/kylelemons/godebug v1.1.0
|
github.com/kylelemons/godebug v1.1.0
|
||||||
github.com/mattn/go-colorable v0.1.9
|
github.com/mattn/go-colorable v0.1.9
|
||||||
@@ -68,7 +66,6 @@ require (
|
|||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.81.0 // indirect
|
cloud.google.com/go v0.81.0 // indirect
|
||||||
github.com/ActiveState/termtest/conpty v0.5.0 // indirect
|
github.com/ActiveState/termtest/conpty v0.5.0 // indirect
|
||||||
github.com/ActiveState/termtest/expect v0.7.0 // indirect
|
|
||||||
github.com/ActiveState/termtest/xpty v0.6.0 // indirect
|
github.com/ActiveState/termtest/xpty v0.6.0 // indirect
|
||||||
github.com/ActiveState/vt10x v1.3.1 // indirect
|
github.com/ActiveState/vt10x v1.3.1 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
@@ -81,6 +78,7 @@ require (
|
|||||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||||
github.com/Microsoft/hcsshim v0.8.20 // indirect
|
github.com/Microsoft/hcsshim v0.8.20 // indirect
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2 // indirect
|
github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2 // indirect
|
||||||
@@ -129,6 +127,7 @@ require (
|
|||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
@@ -138,6 +137,7 @@ require (
|
|||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||||
github.com/klauspost/compress v1.11.13 // indirect
|
github.com/klauspost/compress v1.11.13 // indirect
|
||||||
|
github.com/kr/pty v1.1.8 // indirect
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
|||||||
137
pkg/watch/file_watcher.go
Normal file
137
pkg/watch/file_watcher.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
dfutil "github.com/devfile/library/pkg/util"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/redhat-developer/odo/pkg/util"
|
||||||
|
gitignore "github.com/sabhiram/go-gitignore"
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getFullSourcesWatcher(path string, fileIgnores []string) (*fsnotify.Watcher, error) {
|
||||||
|
absIgnorePaths := dfutil.GetAbsGlobExps(path, fileIgnores)
|
||||||
|
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error setting up filesystem watcher: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// adding watch on the root folder and the sub folders recursively
|
||||||
|
// so directory and the path in addRecursiveWatch() are the same
|
||||||
|
err = addRecursiveWatch(watcher, path, path, absIgnorePaths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error watching source path %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return watcher, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRecursiveWatch handles adding watches recursively for the path provided
|
||||||
|
// and its subdirectories. If a non-directory is specified, this call is a no-op.
|
||||||
|
// Files matching glob pattern defined in ignores will be ignored.
|
||||||
|
// Taken from https://github.com/openshift/origin/blob/85eb37b34f0657631592356d020cef5a58470f8e/pkg/util/fsnotification/fsnotification.go
|
||||||
|
// rootPath is the root path of the file or directory,
|
||||||
|
// path is the recursive path of the file or the directory,
|
||||||
|
// ignores contains the glob rules for matching
|
||||||
|
func addRecursiveWatch(watcher *fsnotify.Watcher, rootPath string, path string, ignores []string) error {
|
||||||
|
|
||||||
|
file, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("error introspecting path %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreMatcher := gitignore.CompileIgnoreLines(ignores...)
|
||||||
|
|
||||||
|
mode := file.Mode()
|
||||||
|
if mode.IsRegular() {
|
||||||
|
var rel string
|
||||||
|
rel, err = filepath.Rel(rootPath, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
matched := ignoreMatcher.MatchesPath(rel)
|
||||||
|
if !matched {
|
||||||
|
klog.V(4).Infof("adding watch on path %s", path)
|
||||||
|
|
||||||
|
// checking if the file exits before adding the watcher to it
|
||||||
|
if !util.CheckPathExists(path) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = watcher.Add(path)
|
||||||
|
if err != nil {
|
||||||
|
klog.V(4).Infof("error adding watcher for path %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folders := []string{}
|
||||||
|
err = filepath.Walk(path, func(newPath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
// Ignore the error if it's a 'path does not exist' error, no need to walk a non-existent path
|
||||||
|
if !util.CheckPathExists(newPath) {
|
||||||
|
klog.V(4).Infof("Walk func received an error for path %s, but the path doesn't exist so this is likely not an error. err: %v", path, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unable to walk path: %s: %w", newPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
// If the current directory matches any of the ignore patterns, ignore them so that their contents are also not ignored
|
||||||
|
rel, err := filepath.Rel(rootPath, newPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
matched := ignoreMatcher.MatchesPath(rel)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to addRecursiveWatch on %s: %w", newPath, err)
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
klog.V(4).Infof("ignoring watch on path %s", newPath)
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
// Append the folder we just walked on
|
||||||
|
folders = append(folders, newPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, folder := range folders {
|
||||||
|
|
||||||
|
rel, err := filepath.Rel(rootPath, folder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
matched := ignoreMatcher.MatchesPath(rel)
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
klog.V(4).Infof("ignoring watch for %s", folder)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking if the file exits before adding the watcher to it
|
||||||
|
if !util.CheckPathExists(path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(4).Infof("adding watch on path %s", folder)
|
||||||
|
err = watcher.Add(folder)
|
||||||
|
if err != nil {
|
||||||
|
// Linux "no space left on device" issues are usually resolved via
|
||||||
|
// $ sudo sysctl fs.inotify.max_user_watches=65536
|
||||||
|
// BSD / OSX: "too many open files" issues are ussualy resolved via
|
||||||
|
// $ sysctl variables "kern.maxfiles" and "kern.maxfilesperproc",
|
||||||
|
klog.V(4).Infof("error adding watcher for path %s: %v", folder, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
74
pkg/watch/key_watcher.go
Normal file
74
pkg/watch/key_watcher.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getKeyWatcher returns a channel which will emit
|
||||||
|
// characters when keys are pressed on the keyboard
|
||||||
|
func getKeyWatcher(ctx context.Context, out io.Writer) <-chan byte {
|
||||||
|
|
||||||
|
keyInput := make(chan byte)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
stdinfd := int(os.Stdin.Fd())
|
||||||
|
if !term.IsTerminal(stdinfd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// First set the terminal in character mode
|
||||||
|
// to be able to read characters as soon as
|
||||||
|
// they are emitted, instead of waiting
|
||||||
|
// for newline characters
|
||||||
|
oldState, err := term.GetState(stdinfd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(out, fmt.Errorf("getstate: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = enableCharInput(stdinfd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(out, fmt.Errorf("enableCharInput: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = term.Restore(stdinfd, oldState)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for the context to be cancelled
|
||||||
|
// or a character to be emitted
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case b := <-getKey(out):
|
||||||
|
keyInput <- b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return keyInput
|
||||||
|
}
|
||||||
|
|
||||||
|
// getKey returns a channel which will emit a character
|
||||||
|
// when a key is pressed on the keyboard
|
||||||
|
func getKey(out io.Writer) <-chan byte {
|
||||||
|
|
||||||
|
ch := make(chan byte)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err := os.Stdin.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(out, fmt.Errorf("read: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- b[0]
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
24
pkg/watch/key_watcher_unix.go
Normal file
24
pkg/watch/key_watcher_unix.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// enableCharInput is inspired from the Unix implementation of MakeRaw in golang.org/x/term
|
||||||
|
// It enables the treatment of input stream char by char instead of line by line
|
||||||
|
// See https://man7.org/linux/man-pages/man3/termios.3.html for reference
|
||||||
|
func enableCharInput(fd int) error {
|
||||||
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
termios.Lflag &^= unix.ICANON
|
||||||
|
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
20
pkg/watch/key_watcher_windows.go
Normal file
20
pkg/watch/key_watcher_windows.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// enableCharInput is inspired from Windows implementation of MakeRaw in golang.org/x/term
|
||||||
|
// It enables the treatment of input stream char by char instead of line by line
|
||||||
|
// See https://docs.microsoft.com/en-us/windows/console/setconsolemode for reference
|
||||||
|
func enableCharInput(fd int) error {
|
||||||
|
var st uint32
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT)
|
||||||
|
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
13
pkg/watch/term_unix_bsd.go
Normal file
13
pkg/watch/term_unix_bsd.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
const ioctlWriteTermios = unix.TIOCSETA
|
||||||
13
pkg/watch/term_unix_other.go
Normal file
13
pkg/watch/term_unix_other.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build aix || linux || solaris || zos
|
||||||
|
// +build aix linux solaris zos
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TCGETS
|
||||||
|
const ioctlWriteTermios = unix.TCSETS
|
||||||
@@ -24,9 +24,6 @@ import (
|
|||||||
gitignore "github.com/sabhiram/go-gitignore"
|
gitignore "github.com/sabhiram/go-gitignore"
|
||||||
|
|
||||||
"github.com/redhat-developer/odo/pkg/envinfo"
|
"github.com/redhat-developer/odo/pkg/envinfo"
|
||||||
"github.com/redhat-developer/odo/pkg/util"
|
|
||||||
|
|
||||||
dfutil "github.com/devfile/library/pkg/util"
|
|
||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -38,13 +35,26 @@ import (
|
|||||||
const (
|
const (
|
||||||
// PushErrorString is the string that is printed when an error occurs during watch's Push operation
|
// PushErrorString is the string that is printed when an error occurs during watch's Push operation
|
||||||
PushErrorString = "Error occurred on Push"
|
PushErrorString = "Error occurred on Push"
|
||||||
CtrlCMessage = "Press Ctrl+c to exit `odo dev` and delete resources from the cluster"
|
PromptMessage = `
|
||||||
|
[Ctrl+c] - Exit and delete resources from the cluster
|
||||||
|
[p] - Manually apply local changes to the application on the cluster
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
type WatchClient struct {
|
type WatchClient struct {
|
||||||
kubeClient kclient.ClientInterface
|
kubeClient kclient.ClientInterface
|
||||||
deleteClient _delete.Client
|
deleteClient _delete.Client
|
||||||
stateClient state.Client
|
stateClient state.Client
|
||||||
|
|
||||||
|
sourcesWatcher *fsnotify.Watcher
|
||||||
|
deploymentWatcher watch.Interface
|
||||||
|
devfileWatcher *fsnotify.Watcher
|
||||||
|
podWatcher watch.Interface
|
||||||
|
warningsWatcher watch.Interface
|
||||||
|
keyWatcher <-chan byte
|
||||||
|
|
||||||
|
// true to force sync, used when manual sync
|
||||||
|
forceSync bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Client = (*WatchClient)(nil)
|
var _ Client = (*WatchClient)(nil)
|
||||||
@@ -108,138 +118,30 @@ type evaluateChangesFunc func(events []fsnotify.Event, path string, fileIgnores
|
|||||||
// It returns a Duration after which to recall in case of error
|
// It returns a Duration after which to recall in case of error
|
||||||
type processEventsFunc func(changedFiles, deletedPaths []string, parameters WatchParameters, out io.Writer, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error)
|
type processEventsFunc func(changedFiles, deletedPaths []string, parameters WatchParameters, out io.Writer, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error)
|
||||||
|
|
||||||
// addRecursiveWatch handles adding watches recursively for the path provided
|
|
||||||
// and its subdirectories. If a non-directory is specified, this call is a no-op.
|
|
||||||
// Files matching glob pattern defined in ignores will be ignored.
|
|
||||||
// Taken from https://github.com/openshift/origin/blob/85eb37b34f0657631592356d020cef5a58470f8e/pkg/util/fsnotification/fsnotification.go
|
|
||||||
// rootPath is the root path of the file or directory,
|
|
||||||
// path is the recursive path of the file or the directory,
|
|
||||||
// ignores contains the glob rules for matching
|
|
||||||
func addRecursiveWatch(watcher *fsnotify.Watcher, rootPath string, path string, ignores []string) error {
|
|
||||||
|
|
||||||
file, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("error introspecting path %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreMatcher := gitignore.CompileIgnoreLines(ignores...)
|
|
||||||
|
|
||||||
mode := file.Mode()
|
|
||||||
if mode.IsRegular() {
|
|
||||||
var rel string
|
|
||||||
rel, err = filepath.Rel(rootPath, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
matched := ignoreMatcher.MatchesPath(rel)
|
|
||||||
if !matched {
|
|
||||||
klog.V(4).Infof("adding watch on path %s", path)
|
|
||||||
|
|
||||||
// checking if the file exits before adding the watcher to it
|
|
||||||
if !util.CheckPathExists(path) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = watcher.Add(path)
|
|
||||||
if err != nil {
|
|
||||||
klog.V(4).Infof("error adding watcher for path %s: %v", path, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
folders := []string{}
|
|
||||||
err = filepath.Walk(path, func(newPath string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
// Ignore the error if it's a 'path does not exist' error, no need to walk a non-existent path
|
|
||||||
if !util.CheckPathExists(newPath) {
|
|
||||||
klog.V(4).Infof("Walk func received an error for path %s, but the path doesn't exist so this is likely not an error. err: %v", path, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unable to walk path: %s: %w", newPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
// If the current directory matches any of the ignore patterns, ignore them so that their contents are also not ignored
|
|
||||||
rel, err := filepath.Rel(rootPath, newPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
matched := ignoreMatcher.MatchesPath(rel)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to addRecursiveWatch on %s: %w", newPath, err)
|
|
||||||
}
|
|
||||||
if matched {
|
|
||||||
klog.V(4).Infof("ignoring watch on path %s", newPath)
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
// Append the folder we just walked on
|
|
||||||
folders = append(folders, newPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, folder := range folders {
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(rootPath, folder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
matched := ignoreMatcher.MatchesPath(rel)
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
klog.V(4).Infof("ignoring watch for %s", folder)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// checking if the file exits before adding the watcher to it
|
|
||||||
if !util.CheckPathExists(path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(4).Infof("adding watch on path %s", folder)
|
|
||||||
err = watcher.Add(folder)
|
|
||||||
if err != nil {
|
|
||||||
// Linux "no space left on device" issues are usually resolved via
|
|
||||||
// $ sudo sysctl fs.inotify.max_user_watches=65536
|
|
||||||
// BSD / OSX: "too many open files" issues are ussualy resolved via
|
|
||||||
// $ sysctl variables "kern.maxfiles" and "kern.maxfilesperproc",
|
|
||||||
klog.V(4).Infof("error adding watcher for path %s: %v", folder, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error {
|
func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error {
|
||||||
klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", parameters.Path, parameters.ComponentName, parameters.FileIgnores)
|
klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", parameters.Path, parameters.ComponentName, parameters.FileIgnores)
|
||||||
|
|
||||||
var sourcesWatcher *fsnotify.Watcher
|
|
||||||
var err error
|
var err error
|
||||||
if parameters.WatchFiles {
|
if parameters.WatchFiles {
|
||||||
sourcesWatcher, err = getFullSourcesWatcher(parameters.Path, parameters.FileIgnores)
|
o.sourcesWatcher, err = getFullSourcesWatcher(parameters.Path, parameters.FileIgnores)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sourcesWatcher, err = fsnotify.NewWatcher()
|
o.sourcesWatcher, err = fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer sourcesWatcher.Close()
|
defer o.sourcesWatcher.Close()
|
||||||
|
|
||||||
selector := labels.GetSelector(parameters.ComponentName, parameters.ApplicationName, labels.ComponentDevMode, true)
|
selector := labels.GetSelector(parameters.ComponentName, parameters.ApplicationName, labels.ComponentDevMode, true)
|
||||||
deploymentWatcher, err := o.kubeClient.DeploymentWatcher(ctx, selector)
|
o.deploymentWatcher, err = o.kubeClient.DeploymentWatcher(ctx, selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error watching deployment: %v", err)
|
return fmt.Errorf("error watching deployment: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
devfileWatcher, err := fsnotify.NewWatcher()
|
o.devfileWatcher, err = fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -251,19 +153,20 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct
|
|||||||
}
|
}
|
||||||
devfileFiles = append(devfileFiles, parameters.DevfilePath)
|
devfileFiles = append(devfileFiles, parameters.DevfilePath)
|
||||||
for _, f := range devfileFiles {
|
for _, f := range devfileFiles {
|
||||||
err = devfileWatcher.Add(f)
|
err = o.devfileWatcher.Add(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.V(4).Infof("error adding watcher for path %s: %v", f, err)
|
klog.V(4).Infof("error adding watcher for path %s: %v", f, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
podWatcher, err := o.kubeClient.PodWatcher(ctx, selector)
|
o.podWatcher, err = o.kubeClient.PodWatcher(ctx, selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
warningsWatcher, isForbidden, err := o.kubeClient.PodWarningEventWatcher(ctx)
|
var isForbidden bool
|
||||||
|
o.warningsWatcher, isForbidden, err = o.kubeClient.PodWarningEventWatcher(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -271,30 +174,21 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct
|
|||||||
log.Fwarning(out, "Unable to watch Events resource, warning Events won't be displayed")
|
log.Fwarning(out, "Unable to watch Events resource, warning Events won't be displayed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.eventWatcher(ctx, sourcesWatcher, deploymentWatcher, devfileWatcher, podWatcher, warningsWatcher, parameters, out, evaluateFileChanges, processEvents, componentStatus)
|
o.keyWatcher = getKeyWatcher(ctx, out)
|
||||||
}
|
return o.eventWatcher(ctx, parameters, out, evaluateFileChanges, processEvents, componentStatus)
|
||||||
|
|
||||||
func getFullSourcesWatcher(path string, fileIgnores []string) (*fsnotify.Watcher, error) {
|
|
||||||
absIgnorePaths := dfutil.GetAbsGlobExps(path, fileIgnores)
|
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error setting up filesystem watcher: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// adding watch on the root folder and the sub folders recursively
|
|
||||||
// so directory and the path in addRecursiveWatch() are the same
|
|
||||||
err = addRecursiveWatch(watcher, path, path, absIgnorePaths)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error watching source path %s: %v", path, err)
|
|
||||||
}
|
|
||||||
return watcher, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventWatcher loops till the context's Done channel indicates it to stop looping, at which point it performs cleanup.
|
// eventWatcher loops till the context's Done channel indicates it to stop looping, at which point it performs cleanup.
|
||||||
// While looping, it listens for filesystem events and processes these events using the WatchParameters to push to the remote pod.
|
// While looping, it listens for filesystem events and processes these events using the WatchParameters to push to the remote pod.
|
||||||
// It outputs any logs to the out io Writer
|
// It outputs any logs to the out io Writer
|
||||||
func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify.Watcher, deploymentWatcher watch.Interface, devfileWatcher *fsnotify.Watcher, podWatcher watch.Interface, eventsWatcher watch.Interface, parameters WatchParameters, out io.Writer, evaluateChangesHandler evaluateChangesFunc, processEventsHandler processEventsFunc, componentStatus ComponentStatus) error {
|
func (o *WatchClient) eventWatcher(
|
||||||
|
ctx context.Context,
|
||||||
|
parameters WatchParameters,
|
||||||
|
out io.Writer,
|
||||||
|
evaluateChangesHandler evaluateChangesFunc,
|
||||||
|
processEventsHandler processEventsFunc,
|
||||||
|
componentStatus ComponentStatus,
|
||||||
|
) error {
|
||||||
|
|
||||||
expBackoff := NewExpBackoff()
|
expBackoff := NewExpBackoff()
|
||||||
|
|
||||||
@@ -309,12 +203,15 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
sourcesTimer := time.NewTimer(time.Millisecond)
|
sourcesTimer := time.NewTimer(time.Millisecond)
|
||||||
<-sourcesTimer.C
|
<-sourcesTimer.C
|
||||||
|
|
||||||
|
// devfileTimer has the same usage as sourcesTimer, for file events coming from devfileWatcher
|
||||||
devfileTimer := time.NewTimer(time.Millisecond)
|
devfileTimer := time.NewTimer(time.Millisecond)
|
||||||
<-devfileTimer.C
|
<-devfileTimer.C
|
||||||
|
|
||||||
|
// deployTimer has the same usage as sourcesTimer, for events coming from watching Deployments, from deploymentWatcher
|
||||||
deployTimer := time.NewTimer(time.Millisecond)
|
deployTimer := time.NewTimer(time.Millisecond)
|
||||||
<-deployTimer.C
|
<-deployTimer.C
|
||||||
|
|
||||||
|
// retryTimer is a timer used to retry later a sync that has failed
|
||||||
retryTimer := time.NewTimer(time.Millisecond)
|
retryTimer := time.NewTimer(time.Millisecond)
|
||||||
<-retryTimer.C
|
<-retryTimer.C
|
||||||
|
|
||||||
@@ -322,24 +219,31 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-sourcesWatcher.Events:
|
case event := <-o.sourcesWatcher.Events:
|
||||||
events = append(events, event)
|
events = append(events, event)
|
||||||
// We are waiting for more events in this interval
|
// We are waiting for more events in this interval
|
||||||
sourcesTimer.Reset(100 * time.Millisecond)
|
sourcesTimer.Reset(100 * time.Millisecond)
|
||||||
|
|
||||||
case <-sourcesTimer.C:
|
case <-sourcesTimer.C:
|
||||||
// timer has fired
|
// timer has fired
|
||||||
if !componentCanSyncFile(componentStatus.State) {
|
if !componentCanSyncFile(componentStatus.State) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// first find the files that have changed (also includes the ones newly created) or deleted
|
|
||||||
changedFiles, deletedPaths := evaluateChangesHandler(events, parameters.Path, parameters.FileIgnores, sourcesWatcher)
|
var changedFiles, deletedPaths []string
|
||||||
// process the changes and sync files with remote pod
|
if !o.forceSync {
|
||||||
if len(changedFiles) == 0 && len(deletedPaths) == 0 {
|
// first find the files that have changed (also includes the ones newly created) or deleted
|
||||||
continue
|
changedFiles, deletedPaths = evaluateChangesHandler(events, parameters.Path, parameters.FileIgnores, o.sourcesWatcher)
|
||||||
|
// process the changes and sync files with remote pod
|
||||||
|
if len(changedFiles) == 0 && len(deletedPaths) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentStatus.State = StateSyncOutdated
|
componentStatus.State = StateSyncOutdated
|
||||||
fmt.Fprintf(out, "Pushing files...\n\n")
|
fmt.Fprintf(out, "Pushing files...\n\n")
|
||||||
retry, err := processEventsHandler(changedFiles, deletedPaths, parameters, out, &componentStatus, expBackoff)
|
retry, err := processEventsHandler(changedFiles, deletedPaths, parameters, out, &componentStatus, expBackoff)
|
||||||
|
o.forceSync = false
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -355,10 +259,16 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
<-retryTimer.C
|
<-retryTimer.C
|
||||||
}
|
}
|
||||||
|
|
||||||
case watchErr := <-sourcesWatcher.Errors:
|
case watchErr := <-o.sourcesWatcher.Errors:
|
||||||
return watchErr
|
return watchErr
|
||||||
|
|
||||||
case ev := <-deploymentWatcher.ResultChan():
|
case key := <-o.keyWatcher:
|
||||||
|
if key == 'p' {
|
||||||
|
o.forceSync = true
|
||||||
|
sourcesTimer.Reset(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
case ev := <-o.deploymentWatcher.ResultChan():
|
||||||
switch obj := ev.Object.(type) {
|
switch obj := ev.Object.(type) {
|
||||||
case *appsv1.Deployment:
|
case *appsv1.Deployment:
|
||||||
klog.V(4).Infof("deployment watcher Event: Type: %s, name: %s, rv: %s, pods: %d\n",
|
klog.V(4).Infof("deployment watcher Event: Type: %s, name: %s, rv: %s, pods: %d\n",
|
||||||
@@ -381,7 +291,7 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
<-retryTimer.C
|
<-retryTimer.C
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-devfileWatcher.Events:
|
case <-o.devfileWatcher.Events:
|
||||||
devfileTimer.Reset(100 * time.Millisecond)
|
devfileTimer.Reset(100 * time.Millisecond)
|
||||||
|
|
||||||
case <-devfileTimer.C:
|
case <-devfileTimer.C:
|
||||||
@@ -409,7 +319,7 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
<-retryTimer.C
|
<-retryTimer.C
|
||||||
}
|
}
|
||||||
|
|
||||||
case ev := <-podWatcher.ResultChan():
|
case ev := <-o.podWatcher.ResultChan():
|
||||||
switch ev.Type {
|
switch ev.Type {
|
||||||
case watch.Deleted:
|
case watch.Deleted:
|
||||||
pod, ok := ev.Object.(*corev1.Pod)
|
pod, ok := ev.Object.(*corev1.Pod)
|
||||||
@@ -425,7 +335,7 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
podsPhases.Add(out, pod.GetCreationTimestamp(), pod)
|
podsPhases.Add(out, pod.GetCreationTimestamp(), pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ev := <-eventsWatcher.ResultChan():
|
case ev := <-o.warningsWatcher.ResultChan():
|
||||||
switch kevent := ev.Object.(type) {
|
switch kevent := ev.Object.(type) {
|
||||||
case *corev1.Event:
|
case *corev1.Event:
|
||||||
podName := kevent.InvolvedObject.Name
|
podName := kevent.InvolvedObject.Name
|
||||||
@@ -439,7 +349,7 @@ func (o *WatchClient) eventWatcher(ctx context.Context, sourcesWatcher *fsnotify
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case watchErr := <-devfileWatcher.Errors:
|
case watchErr := <-o.devfileWatcher.Errors:
|
||||||
return watchErr
|
return watchErr
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -635,7 +545,8 @@ func removeDuplicates(input []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printInfoMessage(out io.Writer, path string) {
|
func printInfoMessage(out io.Writer, path string) {
|
||||||
log.Finfof(out, "\nWatching for changes in the current directory %s\n"+CtrlCMessage+"\n", path)
|
log.Sectionf("Dev mode")
|
||||||
|
fmt.Fprintf(out, " %s\n Watching for changes in the current directory %s\n\n %s%s", log.Sbold("Status:"), path, log.Sbold("Keyboard Commands:"), PromptMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isFatal(err error) bool {
|
func isFatal(err error) bool {
|
||||||
|
|||||||
@@ -131,8 +131,15 @@ func Test_eventWatcher(t *testing.T) {
|
|||||||
State: StateReady,
|
State: StateReady,
|
||||||
}
|
}
|
||||||
|
|
||||||
o := WatchClient{}
|
o := WatchClient{
|
||||||
err := o.eventWatcher(ctx, watcher, fakeWatcher{}, fileWatcher, fakeWatcher{}, fakeWatcher{}, tt.args.parameters, out, evaluateChangesHandler, processEventsHandler, componentStatus)
|
sourcesWatcher: watcher,
|
||||||
|
deploymentWatcher: fakeWatcher{},
|
||||||
|
podWatcher: fakeWatcher{},
|
||||||
|
warningsWatcher: fakeWatcher{},
|
||||||
|
devfileWatcher: fileWatcher,
|
||||||
|
keyWatcher: make(chan byte),
|
||||||
|
}
|
||||||
|
err := o.eventWatcher(ctx, tt.args.parameters, out, evaluateChangesHandler, processEventsHandler, componentStatus)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("eventWatcher() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("eventWatcher() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ActiveState/termtest/expect"
|
||||||
"github.com/onsi/gomega"
|
"github.com/onsi/gomega"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/onsi/gomega/gexec"
|
"github.com/onsi/gomega/gexec"
|
||||||
@@ -109,6 +111,7 @@ import (
|
|||||||
type DevSession struct {
|
type DevSession struct {
|
||||||
session *gexec.Session
|
session *gexec.Session
|
||||||
stopped bool
|
stopped bool
|
||||||
|
console *expect.Console
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartDevMode starts a dev session with `odo dev`
|
// StartDevMode starts a dev session with `odo dev`
|
||||||
@@ -116,16 +119,28 @@ type DevSession struct {
|
|||||||
// and the redirections endpoints to access ports opened by component
|
// and the redirections endpoints to access ports opened by component
|
||||||
// when the dev mode is completely started
|
// when the dev mode is completely started
|
||||||
func StartDevMode(envvars []string, opts ...string) (DevSession, []byte, []byte, map[string]string, error) {
|
func StartDevMode(envvars []string, opts ...string) (DevSession, []byte, []byte, map[string]string, error) {
|
||||||
|
|
||||||
|
c, err := expect.NewConsole(expect.WithStdout(os.Stdout))
|
||||||
|
if err != nil {
|
||||||
|
return DevSession{}, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
args := []string{"dev", "--random-ports"}
|
args := []string{"dev", "--random-ports"}
|
||||||
args = append(args, opts...)
|
args = append(args, opts...)
|
||||||
session := Cmd("odo", args...).AddEnv(envvars...).Runner().session
|
cmd := Cmd("odo", args...)
|
||||||
WaitForOutputToContain("Press Ctrl+c to exit `odo dev` and delete resources from the cluster", 360, 10, session)
|
cmd.Cmd.Stdin = c.Tty()
|
||||||
|
cmd.Cmd.Stdout = c.Tty()
|
||||||
|
cmd.Cmd.Stderr = c.Tty()
|
||||||
|
|
||||||
|
session := cmd.AddEnv(envvars...).Runner().session
|
||||||
|
WaitForOutputToContain("[Ctrl+c] - Exit", 360, 10, session)
|
||||||
result := DevSession{
|
result := DevSession{
|
||||||
session: session,
|
session: session,
|
||||||
|
console: c,
|
||||||
}
|
}
|
||||||
outContents := session.Out.Contents()
|
outContents := session.Out.Contents()
|
||||||
errContents := session.Err.Contents()
|
errContents := session.Err.Contents()
|
||||||
err := session.Out.Clear()
|
err = session.Out.Clear()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DevSession{}, nil, nil, nil, err
|
return DevSession{}, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -139,11 +154,19 @@ func StartDevMode(envvars []string, opts ...string) (DevSession, []byte, []byte,
|
|||||||
|
|
||||||
// Kill a Dev session abruptly, without handling any cleanup
|
// Kill a Dev session abruptly, without handling any cleanup
|
||||||
func (o DevSession) Kill() {
|
func (o DevSession) Kill() {
|
||||||
|
if o.console != nil {
|
||||||
|
err := o.console.Close()
|
||||||
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||||
|
}
|
||||||
o.session.Kill()
|
o.session.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop a Dev session cleanly (equivalent as hitting Ctrl-c)
|
// Stop a Dev session cleanly (equivalent as hitting Ctrl-c)
|
||||||
func (o *DevSession) Stop() {
|
func (o *DevSession) Stop() {
|
||||||
|
if o.console != nil {
|
||||||
|
err := o.console.Close()
|
||||||
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||||
|
}
|
||||||
if o.stopped {
|
if o.stopped {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -152,6 +175,14 @@ func (o *DevSession) Stop() {
|
|||||||
o.stopped = true
|
o.stopped = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *DevSession) PressKey(p byte) {
|
||||||
|
if o.console == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := o.console.Write([]byte{p})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
func (o DevSession) WaitEnd() {
|
func (o DevSession) WaitEnd() {
|
||||||
o.session.Wait(3 * time.Minute)
|
o.session.Wait(3 * time.Minute)
|
||||||
}
|
}
|
||||||
@@ -240,7 +271,8 @@ func DevModeShouldFail(envvars []string, substring string, opts ...string) (DevS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getPorts returns a map of ports redirected depending on the information in s
|
// getPorts returns a map of ports redirected depending on the information in s
|
||||||
// `- Forwarding from 127.0.0.1:40001 -> 3000` will return { "3000": "127.0.0.1:40001" }
|
//
|
||||||
|
// `- Forwarding from 127.0.0.1:40001 -> 3000` will return { "3000": "127.0.0.1:40001" }
|
||||||
func getPorts(s string) map[string]string {
|
func getPorts(s string) map[string]string {
|
||||||
result := map[string]string{}
|
result := map[string]string{}
|
||||||
re := regexp.MustCompile("(127.0.0.1:[0-9]+) -> ([0-9]+)")
|
re := regexp.MustCompile("(127.0.0.1:[0-9]+) -> ([0-9]+)")
|
||||||
|
|||||||
@@ -101,6 +101,10 @@ func SendLine(ctx InteractiveContext, line string) {
|
|||||||
ctx.cp.Send(line)
|
ctx.cp.Send(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PressKey(ctx InteractiveContext, c byte) {
|
||||||
|
ctx.cp.SendUnterminated(string(c))
|
||||||
|
}
|
||||||
|
|
||||||
func ExpectString(ctx InteractiveContext, line string) {
|
func ExpectString(ctx InteractiveContext, line string) {
|
||||||
res, err := ctx.cp.Expect(line, 120*time.Second)
|
res, err := ctx.cp.Expect(line, 120*time.Second)
|
||||||
fmt.Fprint(ctx.buffer, res)
|
fmt.Fprint(ctx.buffer, res)
|
||||||
|
|||||||
@@ -254,6 +254,13 @@ status:
|
|||||||
stdout := commonVar.CliRunner.Run("get", "servicebinding", bindingName).Out.Contents()
|
stdout := commonVar.CliRunner.Run("get", "servicebinding", bindingName).Out.Contents()
|
||||||
Expect(stdout).To(ContainSubstring("ApplicationsBound"))
|
Expect(stdout).To(ContainSubstring("ApplicationsBound"))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("odo dev is run", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
devSession, _, _, _, err = helper.StartDevMode(nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
When("odo dev command is stopped", func() {
|
When("odo dev command is stopped", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ var _ = Describe("odo delete command tests", func() {
|
|||||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-valid-events.yaml")).ShouldPass()
|
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-valid-events.yaml")).ShouldPass()
|
||||||
session := helper.CmdRunner("odo", "dev", "--random-ports")
|
session := helper.CmdRunner("odo", "dev", "--random-ports")
|
||||||
defer session.Kill()
|
defer session.Kill()
|
||||||
helper.WaitForOutputToContain("Press Ctrl+c to exit", 180, 10, session)
|
helper.WaitForOutputToContain("[Ctrl+c] - Exit", 180, 10, session)
|
||||||
// Ensure that the pod is in running state
|
// Ensure that the pod is in running state
|
||||||
Eventually(string(commonVar.CliRunner.Run("get", "pods", "-n", commonVar.Project).Out.Contents()), 60, 3).Should(ContainSubstring(cmpName))
|
Eventually(string(commonVar.CliRunner.Run("get", "pods", "-n", commonVar.Project).Out.Contents()), 60, 3).Should(ContainSubstring(cmpName))
|
||||||
// running in verbosity since the preStop events information is only printed in v4
|
// running in verbosity since the preStop events information is only printed in v4
|
||||||
|
|||||||
@@ -171,10 +171,6 @@ var _ = Describe("odo dev debug command tests", func() {
|
|||||||
session, sessionOut, _, ports, err = helper.StartDevMode([]string{"PODMAN_CMD=echo"}, "--debug")
|
session, sessionOut, _, ports, err = helper.StartDevMode([]string{"PODMAN_CMD=echo"}, "--debug")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
AfterEach(func() {
|
|
||||||
session.Stop()
|
|
||||||
session.WaitEnd()
|
|
||||||
})
|
|
||||||
It("should execute the composite apply commands successfully", func() {
|
It("should execute the composite apply commands successfully", func() {
|
||||||
checkDeploymentExists := func() {
|
checkDeploymentExists := func() {
|
||||||
out := commonVar.CliRunner.Run("get", "deployments", deploymentName).Out.Contents()
|
out := commonVar.CliRunner.Run("get", "deployments", deploymentName).Out.Contents()
|
||||||
|
|||||||
@@ -255,11 +255,6 @@ ComponentSettings:
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
devSession.Kill()
|
|
||||||
devSession.WaitEnd()
|
|
||||||
})
|
|
||||||
|
|
||||||
When("odo dev is stopped", func() {
|
When("odo dev is stopped", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
devSession.Stop()
|
devSession.Stop()
|
||||||
@@ -274,6 +269,62 @@ ComponentSettings:
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
When("odo dev is executed and Ephemeral is set to false", func() {
|
||||||
|
|
||||||
|
var devSession helper.DevSession
|
||||||
|
BeforeEach(func() {
|
||||||
|
helper.Cmd("odo", "preference", "set", "-f", "Ephemeral", "false").ShouldPass()
|
||||||
|
var err error
|
||||||
|
devSession, _, _, _, err = helper.StartDevMode(nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
When("killing odo dev and running odo delete component --wait", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
devSession.Kill()
|
||||||
|
devSession.WaitEnd()
|
||||||
|
helper.Cmd("odo", "delete", "component", "--wait", "-f").ShouldPass()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have deleted all resources before returning", func() {
|
||||||
|
By("deleting the service", func() {
|
||||||
|
services := commonVar.CliRunner.GetServices(commonVar.Project)
|
||||||
|
Expect(services).To(BeEmpty())
|
||||||
|
})
|
||||||
|
By("deleting the PVC", func() {
|
||||||
|
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
|
||||||
|
Expect(pvcs).To(BeEmpty())
|
||||||
|
})
|
||||||
|
By("deleting the pod", func() {
|
||||||
|
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
|
||||||
|
Expect(pods).To(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
When("stopping odo dev normally", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
devSession.Stop()
|
||||||
|
devSession.WaitEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have deleted all resources before returning", func() {
|
||||||
|
By("deleting the service", func() {
|
||||||
|
services := commonVar.CliRunner.GetServices(commonVar.Project)
|
||||||
|
Expect(services).To(BeEmpty())
|
||||||
|
})
|
||||||
|
By("deleting the PVC", func() {
|
||||||
|
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
|
||||||
|
Expect(pvcs).To(BeEmpty())
|
||||||
|
})
|
||||||
|
By("deleting the pod", func() {
|
||||||
|
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
|
||||||
|
Expect(pods).To(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
When("odo dev is executed and Ephemeral is set to false", func() {
|
When("odo dev is executed and Ephemeral is set to false", func() {
|
||||||
|
|
||||||
var devSession helper.DevSession
|
var devSession helper.DevSession
|
||||||
@@ -319,51 +370,6 @@ ComponentSettings:
|
|||||||
Expect(string(output)).To(ContainSubstring(fmt.Sprintf("Deployment/%s-app", cmpName)))
|
Expect(string(output)).To(ContainSubstring(fmt.Sprintf("Deployment/%s-app", cmpName)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
When("killing odo dev and running odo delete component --wait", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
devSession.Kill()
|
|
||||||
devSession.WaitEnd()
|
|
||||||
helper.Cmd("odo", "delete", "component", "--wait", "-f").ShouldPass()
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should have deleted all resources before returning", func() {
|
|
||||||
By("deleting the service", func() {
|
|
||||||
services := commonVar.CliRunner.GetServices(commonVar.Project)
|
|
||||||
Expect(services).To(BeEmpty())
|
|
||||||
})
|
|
||||||
By("deleting the PVC", func() {
|
|
||||||
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
|
|
||||||
Expect(pvcs).To(BeEmpty())
|
|
||||||
})
|
|
||||||
By("deleting the pod", func() {
|
|
||||||
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
|
|
||||||
Expect(pods).To(BeEmpty())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
When("stopping odo dev normally", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
devSession.Stop()
|
|
||||||
devSession.WaitEnd()
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should have deleted all resources before returning", func() {
|
|
||||||
By("deleting the service", func() {
|
|
||||||
services := commonVar.CliRunner.GetServices(commonVar.Project)
|
|
||||||
Expect(services).To(BeEmpty())
|
|
||||||
})
|
|
||||||
By("deleting the PVC", func() {
|
|
||||||
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
|
|
||||||
Expect(pvcs).To(BeEmpty())
|
|
||||||
})
|
|
||||||
By("deleting the pod", func() {
|
|
||||||
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
|
|
||||||
Expect(pods).To(BeEmpty())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
When("odo is executed with --no-watch flag", func() {
|
When("odo is executed with --no-watch flag", func() {
|
||||||
@@ -382,14 +388,37 @@ ComponentSettings:
|
|||||||
})
|
})
|
||||||
|
|
||||||
When("a file in component directory is modified", func() {
|
When("a file in component directory is modified", func() {
|
||||||
It("should not trigger a push", func() {
|
|
||||||
|
BeforeEach(func() {
|
||||||
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "App started", "App is super started")
|
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "App started", "App is super started")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not trigger a push", func() {
|
||||||
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
||||||
execResult := commonVar.CliRunner.Exec(podName, commonVar.Project, "cat", "/projects/server.js")
|
execResult := commonVar.CliRunner.Exec(podName, commonVar.Project, "cat", "/projects/server.js")
|
||||||
Expect(execResult).To(ContainSubstring("App started"))
|
Expect(execResult).To(ContainSubstring("App started"))
|
||||||
Expect(execResult).ToNot(ContainSubstring("App is super started"))
|
Expect(execResult).ToNot(ContainSubstring("App is super started"))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
When("p is pressed", func() {
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||||
|
Skip("This is a unix-terminal specific scenario, skipping")
|
||||||
|
}
|
||||||
|
|
||||||
|
devSession.PressKey('p')
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should trigger a push", func() {
|
||||||
|
_, _, _, err := devSession.WaitSync()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
||||||
|
execResult := commonVar.CliRunner.Exec(podName, commonVar.Project, "cat", "/projects/server.js")
|
||||||
|
Expect(execResult).To(ContainSubstring("App is super started"))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -435,152 +464,113 @@ ComponentSettings:
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("port-forwarding for the component", func() {
|
for _, manual := range []bool{false, true} {
|
||||||
When("devfile has single endpoint", func() {
|
manual := manual
|
||||||
BeforeEach(func() {
|
Context("port-forwarding for the component", func() {
|
||||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
When("devfile has single endpoint", func() {
|
||||||
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
|
|
||||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
|
|
||||||
})
|
|
||||||
|
|
||||||
When("running odo dev", func() {
|
|
||||||
var devSession helper.DevSession
|
|
||||||
var ports map[string]string
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
var err error
|
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
||||||
devSession, _, _, ports, err = helper.StartDevMode(nil)
|
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
When("running odo dev", func() {
|
||||||
devSession.Stop()
|
var devSession helper.DevSession
|
||||||
devSession.WaitEnd()
|
var ports map[string]string
|
||||||
})
|
|
||||||
|
|
||||||
It("should expose the endpoint on localhost", func() {
|
|
||||||
url := fmt.Sprintf("http://%s", ports["3000"])
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
})
|
|
||||||
|
|
||||||
When("modifying memoryLimit for container in Devfile", func() {
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
src := "memoryLimit: 1024Mi"
|
|
||||||
dst := "memoryLimit: 1023Mi"
|
|
||||||
helper.ReplaceString("devfile.yaml", src, dst)
|
|
||||||
var err error
|
var err error
|
||||||
_, _, ports, err = devSession.WaitSync()
|
opts := []string{}
|
||||||
Expect(err).Should(Succeed())
|
if manual {
|
||||||
|
opts = append(opts, "--no-watch")
|
||||||
|
}
|
||||||
|
devSession, _, _, ports, err = helper.StartDevMode(nil, opts...)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
devSession.Stop()
|
||||||
|
devSession.WaitEnd()
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should expose the endpoint on localhost", func() {
|
It("should expose the endpoint on localhost", func() {
|
||||||
By("updating the pod", func() {
|
url := fmt.Sprintf("http://%s", ports["3000"])
|
||||||
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
resp, err := http.Get(url)
|
||||||
bufferOutput := commonVar.CliRunner.Run("get", "pods", podName, "-o", "jsonpath='{.spec.containers[0].resources.requests.memory}'").Out.Contents()
|
Expect(err).ToNot(HaveOccurred())
|
||||||
output := string(bufferOutput)
|
defer resp.Body.Close()
|
||||||
Expect(output).To(ContainSubstring("1023Mi"))
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
When("modifying memoryLimit for container in Devfile", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
src := "memoryLimit: 1024Mi"
|
||||||
|
dst := "memoryLimit: 1023Mi"
|
||||||
|
helper.ReplaceString("devfile.yaml", src, dst)
|
||||||
|
if manual {
|
||||||
|
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||||
|
Skip("This is a unix-terminal specific scenario, skipping")
|
||||||
|
}
|
||||||
|
|
||||||
|
devSession.PressKey('p')
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
_, _, ports, err = devSession.WaitSync()
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
By("exposing the endpoint", func() {
|
It("should expose the endpoint on localhost", func() {
|
||||||
url := fmt.Sprintf("http://%s", ports["3000"])
|
By("updating the pod", func() {
|
||||||
resp, err := http.Get(url)
|
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
bufferOutput := commonVar.CliRunner.Run("get", "pods", podName, "-o", "jsonpath='{.spec.containers[0].resources.requests.memory}'").Out.Contents()
|
||||||
defer resp.Body.Close()
|
output := string(bufferOutput)
|
||||||
|
Expect(output).To(ContainSubstring("1023Mi"))
|
||||||
|
})
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
By("exposing the endpoint", func() {
|
||||||
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
|
url := fmt.Sprintf("http://%s", ports["3000"])
|
||||||
Expect(err).ToNot(HaveOccurred())
|
resp, err := http.Get(url)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
When("devfile has multiple endpoints", func() {
|
When("devfile has multiple endpoints", func() {
|
||||||
BeforeEach(func() {
|
|
||||||
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
|
|
||||||
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
|
|
||||||
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
|
|
||||||
})
|
|
||||||
|
|
||||||
When("running odo dev", func() {
|
|
||||||
var devSession helper.DevSession
|
|
||||||
var ports map[string]string
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
var err error
|
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
|
||||||
devSession, _, _, ports, err = helper.StartDevMode(nil)
|
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
When("running odo dev", func() {
|
||||||
devSession.Stop()
|
var devSession helper.DevSession
|
||||||
devSession.WaitEnd()
|
var ports map[string]string
|
||||||
})
|
|
||||||
|
|
||||||
It("should expose two endpoints on localhost", func() {
|
|
||||||
url1 := fmt.Sprintf("http://%s", ports["3000"])
|
|
||||||
url2 := fmt.Sprintf("http://%s", ports["4567"])
|
|
||||||
|
|
||||||
resp1, err := http.Get(url1)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
defer resp1.Body.Close()
|
|
||||||
|
|
||||||
resp2, err := http.Get(url2)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
defer resp2.Body.Close()
|
|
||||||
|
|
||||||
body1, _ := io.ReadAll(resp1.Body)
|
|
||||||
helper.MatchAllInOutput(string(body1), []string{"Hello from Node.js Starter Application!"})
|
|
||||||
|
|
||||||
body2, _ := io.ReadAll(resp2.Body)
|
|
||||||
helper.MatchAllInOutput(string(body2), []string{"Hello from Node.js Starter Application!"})
|
|
||||||
|
|
||||||
helper.ReplaceString("server.js", "Hello from Node.js", "H3110 from Node.js")
|
|
||||||
|
|
||||||
_, _, _, err = devSession.WaitSync()
|
|
||||||
Expect(err).Should(Succeed())
|
|
||||||
|
|
||||||
Eventually(func() bool {
|
|
||||||
resp3, err := http.Get(url1)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer resp3.Body.Close()
|
|
||||||
|
|
||||||
resp4, err := http.Get(url2)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer resp4.Body.Close()
|
|
||||||
|
|
||||||
body3, _ := io.ReadAll(resp3.Body)
|
|
||||||
if string(body3) != "H3110 from Node.js Starter Application!" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
body4, _ := io.ReadAll(resp4.Body)
|
|
||||||
return string(body4) == "H3110 from Node.js Starter Application!"
|
|
||||||
}, 180, 10).Should(Equal(true))
|
|
||||||
})
|
|
||||||
|
|
||||||
When("an endpoint is added after first run of odo dev", func() {
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
helper.ReplaceString("devfile.yaml", "exposure: none", "exposure: public")
|
opts := []string{}
|
||||||
|
if manual {
|
||||||
|
opts = append(opts, "--no-watch")
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
_, _, ports, err = devSession.WaitSync()
|
devSession, _, _, ports, err = helper.StartDevMode(nil, opts...)
|
||||||
Expect(err).Should(Succeed())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
})
|
})
|
||||||
It("should expose three endpoints on localhost", func() {
|
|
||||||
|
AfterEach(func() {
|
||||||
|
devSession.Stop()
|
||||||
|
devSession.WaitEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should expose two endpoints on localhost", func() {
|
||||||
url1 := fmt.Sprintf("http://%s", ports["3000"])
|
url1 := fmt.Sprintf("http://%s", ports["3000"])
|
||||||
url2 := fmt.Sprintf("http://%s", ports["4567"])
|
url2 := fmt.Sprintf("http://%s", ports["4567"])
|
||||||
url3 := fmt.Sprintf("http://%s", ports["7890"])
|
|
||||||
|
|
||||||
resp1, err := http.Get(url1)
|
resp1, err := http.Get(url1)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
@@ -590,24 +580,98 @@ ComponentSettings:
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
defer resp2.Body.Close()
|
defer resp2.Body.Close()
|
||||||
|
|
||||||
resp3, err := http.Get(url3)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
defer resp3.Body.Close()
|
|
||||||
|
|
||||||
body1, _ := io.ReadAll(resp1.Body)
|
body1, _ := io.ReadAll(resp1.Body)
|
||||||
helper.MatchAllInOutput(string(body1), []string{"Hello from Node.js Starter Application!"})
|
helper.MatchAllInOutput(string(body1), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
|
||||||
body2, _ := io.ReadAll(resp2.Body)
|
body2, _ := io.ReadAll(resp2.Body)
|
||||||
helper.MatchAllInOutput(string(body2), []string{"Hello from Node.js Starter Application!"})
|
helper.MatchAllInOutput(string(body2), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
|
||||||
body3, _ := io.ReadAll(resp3.Body)
|
helper.ReplaceString("server.js", "Hello from Node.js", "H3110 from Node.js")
|
||||||
helper.MatchAllInOutput(string(body3), []string{"Hello from Node.js Starter Application!"})
|
|
||||||
|
if manual {
|
||||||
|
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||||
|
Skip("This is a unix-terminal specific scenario, skipping")
|
||||||
|
}
|
||||||
|
|
||||||
|
devSession.PressKey('p')
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, err = devSession.WaitSync()
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
|
|
||||||
|
Eventually(func() bool {
|
||||||
|
resp3, err := http.Get(url1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resp3.Body.Close()
|
||||||
|
|
||||||
|
resp4, err := http.Get(url2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer resp4.Body.Close()
|
||||||
|
|
||||||
|
body3, _ := io.ReadAll(resp3.Body)
|
||||||
|
if string(body3) != "H3110 from Node.js Starter Application!" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
body4, _ := io.ReadAll(resp4.Body)
|
||||||
|
return string(body4) == "H3110 from Node.js Starter Application!"
|
||||||
|
}, 180, 10).Should(Equal(true))
|
||||||
|
})
|
||||||
|
|
||||||
|
When("an endpoint is added after first run of odo dev", func() {
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
helper.ReplaceString("devfile.yaml", "exposure: none", "exposure: public")
|
||||||
|
|
||||||
|
if manual {
|
||||||
|
if os.Getenv("SKIP_KEY_PRESS") == "true" {
|
||||||
|
Skip("This is a unix-terminal specific scenario, skipping")
|
||||||
|
}
|
||||||
|
|
||||||
|
devSession.PressKey('p')
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, _, ports, err = devSession.WaitSync()
|
||||||
|
Expect(err).Should(Succeed())
|
||||||
|
|
||||||
|
})
|
||||||
|
It("should expose three endpoints on localhost", func() {
|
||||||
|
url1 := fmt.Sprintf("http://%s", ports["3000"])
|
||||||
|
url2 := fmt.Sprintf("http://%s", ports["4567"])
|
||||||
|
url3 := fmt.Sprintf("http://%s", ports["7890"])
|
||||||
|
|
||||||
|
resp1, err := http.Get(url1)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer resp1.Body.Close()
|
||||||
|
|
||||||
|
resp2, err := http.Get(url2)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer resp2.Body.Close()
|
||||||
|
|
||||||
|
resp3, err := http.Get(url3)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer resp3.Body.Close()
|
||||||
|
|
||||||
|
body1, _ := io.ReadAll(resp1.Body)
|
||||||
|
helper.MatchAllInOutput(string(body1), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
|
||||||
|
body2, _ := io.ReadAll(resp2.Body)
|
||||||
|
helper.MatchAllInOutput(string(body2), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
|
||||||
|
body3, _ := io.ReadAll(resp3.Body)
|
||||||
|
helper.MatchAllInOutput(string(body3), []string{"Hello from Node.js Starter Application!"})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
for _, devfileHandlerCtx := range []struct {
|
for _, devfileHandlerCtx := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -1292,10 +1356,6 @@ ComponentSettings:
|
|||||||
session, sessionOut, sessionErr, ports, err = helper.StartDevMode([]string{"PODMAN_CMD=echo"})
|
session, sessionOut, sessionErr, ports, err = helper.StartDevMode([]string{"PODMAN_CMD=echo"})
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
AfterEach(func() {
|
|
||||||
session.Stop()
|
|
||||||
session.WaitEnd()
|
|
||||||
})
|
|
||||||
It("should execute the composite apply commands successfully", func() {
|
It("should execute the composite apply commands successfully", func() {
|
||||||
checkDeploymentExists := func() {
|
checkDeploymentExists := func() {
|
||||||
out := commonVar.CliRunner.Run("get", "deployments", deploymentName).Out.Contents()
|
out := commonVar.CliRunner.Run("get", "deployments", deploymentName).Out.Contents()
|
||||||
@@ -2341,6 +2401,34 @@ CMD ["npm", "start"]
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
When("a component with multiple endpoints is run", func() {
|
||||||
|
stateFile := ".odo/devstate.json"
|
||||||
|
var devSession helper.DevSession
|
||||||
|
BeforeEach(func() {
|
||||||
|
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
|
||||||
|
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
|
||||||
|
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
|
||||||
|
Expect(helper.VerifyFileExists(".odo/devstate.json")).To(BeFalse())
|
||||||
|
var err error
|
||||||
|
devSession, _, _, _, err = helper.StartDevMode(nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
When("odo dev is stopped", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
devSession.Stop()
|
||||||
|
devSession.WaitEnd()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove forwarded ports from state file", func() {
|
||||||
|
Expect(helper.VerifyFileExists(stateFile)).To(BeTrue())
|
||||||
|
contentJSON, err := ioutil.ReadFile(stateFile)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts", "")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
When("a component with multiple endpoints is run", func() {
|
When("a component with multiple endpoints is run", func() {
|
||||||
stateFile := ".odo/devstate.json"
|
stateFile := ".odo/devstate.json"
|
||||||
var devSession helper.DevSession
|
var devSession helper.DevSession
|
||||||
@@ -2373,20 +2461,6 @@ CMD ["npm", "start"]
|
|||||||
helper.JsonPathContentIsValidUserPort(string(contentJSON), "forwardedPorts.0.localPort")
|
helper.JsonPathContentIsValidUserPort(string(contentJSON), "forwardedPorts.0.localPort")
|
||||||
helper.JsonPathContentIsValidUserPort(string(contentJSON), "forwardedPorts.1.localPort")
|
helper.JsonPathContentIsValidUserPort(string(contentJSON), "forwardedPorts.1.localPort")
|
||||||
})
|
})
|
||||||
|
|
||||||
When("odo dev is stopped", func() {
|
|
||||||
BeforeEach(func() {
|
|
||||||
devSession.Stop()
|
|
||||||
devSession.WaitEnd()
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should remove forwarded ports from state file", func() {
|
|
||||||
Expect(helper.VerifyFileExists(stateFile)).To(BeTrue())
|
|
||||||
contentJSON, err := ioutil.ReadFile(stateFile)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts", "")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
When("a devfile with a local parent is used for odo dev and the parent is not synced", func() {
|
When("a devfile with a local parent is used for odo dev and the parent is not synced", func() {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ var _ = Describe("odo dev interactive command tests", func() {
|
|||||||
helper.ExpectString(ctx, "Enter component name")
|
helper.ExpectString(ctx, "Enter component name")
|
||||||
helper.SendLine(ctx, "my-app")
|
helper.SendLine(ctx, "my-app")
|
||||||
|
|
||||||
helper.ExpectString(ctx, "Press Ctrl+c to exit")
|
helper.ExpectString(ctx, "[Ctrl+c] - Exit")
|
||||||
ctx.StopCommand()
|
ctx.StopCommand()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ var _ = Describe("odo dev interactive command tests", func() {
|
|||||||
helper.ExpectString(ctx, "Enter component name")
|
helper.ExpectString(ctx, "Enter component name")
|
||||||
helper.SendLine(ctx, "my-app")
|
helper.SendLine(ctx, "my-app")
|
||||||
|
|
||||||
helper.ExpectString(ctx, "Press Ctrl+c to exit")
|
helper.ExpectString(ctx, "[Ctrl+c] - Exit")
|
||||||
ctx.StopCommand()
|
ctx.StopCommand()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ var _ = Describe("odo dev interactive command tests", func() {
|
|||||||
helper.ExpectString(ctx, "Enter component name")
|
helper.ExpectString(ctx, "Enter component name")
|
||||||
helper.SendLine(ctx, "my-app")
|
helper.SendLine(ctx, "my-app")
|
||||||
|
|
||||||
helper.ExpectString(ctx, "Press Ctrl+c to exit")
|
helper.ExpectString(ctx, "[Ctrl+c] - Exit")
|
||||||
ctx.StopCommand()
|
ctx.StopCommand()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -140,4 +140,28 @@ var _ = Describe("odo dev interactive command tests", func() {
|
|||||||
"odo will try to autodetect the language and project type in order to select the best suited Devfile for your project."))
|
"odo will try to autodetect the language and project type in order to select the best suited Devfile for your project."))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
When("a component is bootstrapped", func() {
|
||||||
|
|
||||||
|
var cmpName string
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
cmpName = helper.RandString(6)
|
||||||
|
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
|
||||||
|
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should sync files when p is pressed", func() {
|
||||||
|
_, _ = helper.RunInteractive([]string{"odo", "dev", "--random-ports", "--no-watch"},
|
||||||
|
nil,
|
||||||
|
func(ctx helper.InteractiveContext) {
|
||||||
|
helper.ExpectString(ctx, "[p] - Manually apply")
|
||||||
|
|
||||||
|
helper.PressKey(ctx, 'p')
|
||||||
|
|
||||||
|
helper.ExpectString(ctx, "Pushing files")
|
||||||
|
ctx.StopCommand()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
5
vendor/github.com/hinshun/vt10x/.travis.yml
generated
vendored
5
vendor/github.com/hinshun/vt10x/.travis.yml
generated
vendored
@@ -1,5 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.10.2"
|
|
||||||
- master
|
|
||||||
19
vendor/github.com/hinshun/vt10x/LICENSE
generated
vendored
19
vendor/github.com/hinshun/vt10x/LICENSE
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
Copyright (C) 2013 James Gray
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without liitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and thismssion notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
9
vendor/github.com/hinshun/vt10x/README.md
generated
vendored
9
vendor/github.com/hinshun/vt10x/README.md
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
# vt10x
|
|
||||||
|
|
||||||
[](https://travis-ci.org/hinshun/vt10x)
|
|
||||||
[](https://godoc.org/github.com/hinshun/vt10x)
|
|
||||||
|
|
||||||
Package vt10x is a vt10x terminal emulation backend, influenced
|
|
||||||
largely by st, rxvt, xterm, and iTerm as reference. Use it for terminal
|
|
||||||
muxing, a terminal emulation frontend, or wherever else you need
|
|
||||||
terminal emulation.
|
|
||||||
38
vendor/github.com/hinshun/vt10x/color.go
generated
vendored
38
vendor/github.com/hinshun/vt10x/color.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
package vt10x
|
|
||||||
|
|
||||||
// ANSI color values
|
|
||||||
const (
|
|
||||||
Black Color = iota
|
|
||||||
Red
|
|
||||||
Green
|
|
||||||
Yellow
|
|
||||||
Blue
|
|
||||||
Magenta
|
|
||||||
Cyan
|
|
||||||
LightGrey
|
|
||||||
DarkGrey
|
|
||||||
LightRed
|
|
||||||
LightGreen
|
|
||||||
LightYellow
|
|
||||||
LightBlue
|
|
||||||
LightMagenta
|
|
||||||
LightCyan
|
|
||||||
White
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default colors are potentially distinct to allow for special behavior.
|
|
||||||
// For example, a transparent background. Otherwise, the simple case is to
|
|
||||||
// map default colors to another color.
|
|
||||||
const (
|
|
||||||
DefaultFG Color = 1<<24 + iota
|
|
||||||
DefaultBG
|
|
||||||
DefaultCursor
|
|
||||||
)
|
|
||||||
|
|
||||||
// Color maps to the ANSI colors [0, 16) and the xterm colors [16, 256).
|
|
||||||
type Color uint32
|
|
||||||
|
|
||||||
// ANSI returns true if Color is within [0, 16).
|
|
||||||
func (c Color) ANSI() bool {
|
|
||||||
return (c < 16)
|
|
||||||
}
|
|
||||||
189
vendor/github.com/hinshun/vt10x/csi.go
generated
vendored
189
vendor/github.com/hinshun/vt10x/csi.go
generated
vendored
@@ -1,189 +0,0 @@
|
|||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CSI (Control Sequence Introducer)
|
|
||||||
// ESC+[
|
|
||||||
type csiEscape struct {
|
|
||||||
buf []byte
|
|
||||||
args []int
|
|
||||||
mode byte
|
|
||||||
priv bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *csiEscape) reset() {
|
|
||||||
c.buf = c.buf[:0]
|
|
||||||
c.args = c.args[:0]
|
|
||||||
c.mode = 0
|
|
||||||
c.priv = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *csiEscape) put(b byte) bool {
|
|
||||||
c.buf = append(c.buf, b)
|
|
||||||
if b >= 0x40 && b <= 0x7E || len(c.buf) >= 256 {
|
|
||||||
c.parse()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *csiEscape) parse() {
|
|
||||||
c.mode = c.buf[len(c.buf)-1]
|
|
||||||
if len(c.buf) == 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s := string(c.buf)
|
|
||||||
c.args = c.args[:0]
|
|
||||||
if s[0] == '?' {
|
|
||||||
c.priv = true
|
|
||||||
s = s[1:]
|
|
||||||
}
|
|
||||||
s = s[:len(s)-1]
|
|
||||||
ss := strings.Split(s, ";")
|
|
||||||
for _, p := range ss {
|
|
||||||
i, err := strconv.Atoi(p)
|
|
||||||
if err != nil {
|
|
||||||
//t.logf("invalid CSI arg '%s'\n", p)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.args = append(c.args, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *csiEscape) arg(i, def int) int {
|
|
||||||
if i >= len(c.args) || i < 0 {
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
return c.args[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxarg takes the maximum of arg(i, def) and def
|
|
||||||
func (c *csiEscape) maxarg(i, def int) int {
|
|
||||||
return max(c.arg(i, def), def)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) handleCSI() {
|
|
||||||
c := &t.csi
|
|
||||||
switch c.mode {
|
|
||||||
default:
|
|
||||||
goto unknown
|
|
||||||
case '@': // ICH - insert <n> blank char
|
|
||||||
t.insertBlanks(c.arg(0, 1))
|
|
||||||
case 'A': // CUU - cursor <n> up
|
|
||||||
t.moveTo(t.cur.X, t.cur.Y-c.maxarg(0, 1))
|
|
||||||
case 'B', 'e': // CUD, VPR - cursor <n> down
|
|
||||||
t.moveTo(t.cur.X, t.cur.Y+c.maxarg(0, 1))
|
|
||||||
case 'c': // DA - device attributes
|
|
||||||
if c.arg(0, 0) == 0 {
|
|
||||||
// TODO: write vt102 id
|
|
||||||
}
|
|
||||||
case 'C', 'a': // CUF, HPR - cursor <n> forward
|
|
||||||
t.moveTo(t.cur.X+c.maxarg(0, 1), t.cur.Y)
|
|
||||||
case 'D': // CUB - cursor <n> backward
|
|
||||||
t.moveTo(t.cur.X-c.maxarg(0, 1), t.cur.Y)
|
|
||||||
case 'E': // CNL - cursor <n> down and first col
|
|
||||||
t.moveTo(0, t.cur.Y+c.arg(0, 1))
|
|
||||||
case 'F': // CPL - cursor <n> up and first col
|
|
||||||
t.moveTo(0, t.cur.Y-c.arg(0, 1))
|
|
||||||
case 'g': // TBC - tabulation clear
|
|
||||||
switch c.arg(0, 0) {
|
|
||||||
// clear current tab stop
|
|
||||||
case 0:
|
|
||||||
t.tabs[t.cur.X] = false
|
|
||||||
// clear all tabs
|
|
||||||
case 3:
|
|
||||||
for i := range t.tabs {
|
|
||||||
t.tabs[i] = false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
goto unknown
|
|
||||||
}
|
|
||||||
case 'G', '`': // CHA, HPA - Move to <col>
|
|
||||||
t.moveTo(c.arg(0, 1)-1, t.cur.Y)
|
|
||||||
case 'H', 'f': // CUP, HVP - move to <row> <col>
|
|
||||||
t.moveAbsTo(c.arg(1, 1)-1, c.arg(0, 1)-1)
|
|
||||||
case 'I': // CHT - cursor forward tabulation <n> tab stops
|
|
||||||
n := c.arg(0, 1)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
t.putTab(true)
|
|
||||||
}
|
|
||||||
case 'J': // ED - clear screen
|
|
||||||
// TODO: sel.ob.x = -1
|
|
||||||
switch c.arg(0, 0) {
|
|
||||||
case 0: // below
|
|
||||||
t.clear(t.cur.X, t.cur.Y, t.cols-1, t.cur.Y)
|
|
||||||
if t.cur.Y < t.rows-1 {
|
|
||||||
t.clear(0, t.cur.Y+1, t.cols-1, t.rows-1)
|
|
||||||
}
|
|
||||||
case 1: // above
|
|
||||||
if t.cur.Y > 1 {
|
|
||||||
t.clear(0, 0, t.cols-1, t.cur.Y-1)
|
|
||||||
}
|
|
||||||
t.clear(0, t.cur.Y, t.cur.X, t.cur.Y)
|
|
||||||
case 2: // all
|
|
||||||
t.clear(0, 0, t.cols-1, t.rows-1)
|
|
||||||
default:
|
|
||||||
goto unknown
|
|
||||||
}
|
|
||||||
case 'K': // EL - clear line
|
|
||||||
switch c.arg(0, 0) {
|
|
||||||
case 0: // right
|
|
||||||
t.clear(t.cur.X, t.cur.Y, t.cols-1, t.cur.Y)
|
|
||||||
case 1: // left
|
|
||||||
t.clear(0, t.cur.Y, t.cur.X, t.cur.Y)
|
|
||||||
case 2: // all
|
|
||||||
t.clear(0, t.cur.Y, t.cols-1, t.cur.Y)
|
|
||||||
}
|
|
||||||
case 'S': // SU - scroll <n> lines up
|
|
||||||
t.scrollUp(t.top, c.arg(0, 1))
|
|
||||||
case 'T': // SD - scroll <n> lines down
|
|
||||||
t.scrollDown(t.top, c.arg(0, 1))
|
|
||||||
case 'L': // IL - insert <n> blank lines
|
|
||||||
t.insertBlankLines(c.arg(0, 1))
|
|
||||||
case 'l': // RM - reset mode
|
|
||||||
t.setMode(c.priv, false, c.args)
|
|
||||||
case 'M': // DL - delete <n> lines
|
|
||||||
t.deleteLines(c.arg(0, 1))
|
|
||||||
case 'X': // ECH - erase <n> chars
|
|
||||||
t.clear(t.cur.X, t.cur.Y, t.cur.X+c.arg(0, 1)-1, t.cur.Y)
|
|
||||||
case 'P': // DCH - delete <n> chars
|
|
||||||
t.deleteChars(c.arg(0, 1))
|
|
||||||
case 'Z': // CBT - cursor backward tabulation <n> tab stops
|
|
||||||
n := c.arg(0, 1)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
t.putTab(false)
|
|
||||||
}
|
|
||||||
case 'd': // VPA - move to <row>
|
|
||||||
t.moveAbsTo(t.cur.X, c.arg(0, 1)-1)
|
|
||||||
case 'h': // SM - set terminal mode
|
|
||||||
t.setMode(c.priv, true, c.args)
|
|
||||||
case 'm': // SGR - terminal attribute (color)
|
|
||||||
t.setAttr(c.args)
|
|
||||||
case 'n':
|
|
||||||
switch c.arg(0, 0) {
|
|
||||||
case 5: // DSR - device status report
|
|
||||||
t.w.Write([]byte("\033[0n"))
|
|
||||||
case 6: // CPR - cursor position report
|
|
||||||
t.w.Write([]byte(fmt.Sprintf("\033[%d;%dR", t.cur.Y+1, t.cur.X+1)))
|
|
||||||
}
|
|
||||||
case 'r': // DECSTBM - set scrolling region
|
|
||||||
if c.priv {
|
|
||||||
goto unknown
|
|
||||||
} else {
|
|
||||||
t.setScroll(c.arg(0, 1)-1, c.arg(1, t.rows)-1)
|
|
||||||
t.moveAbsTo(0, 0)
|
|
||||||
}
|
|
||||||
case 's': // DECSC - save cursor position (ANSI.SYS)
|
|
||||||
t.saveCursor()
|
|
||||||
case 'u': // DECRC - restore cursor position (ANSI.SYS)
|
|
||||||
t.restoreCursor()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
unknown: // TODO: get rid of this goto
|
|
||||||
t.logf("unknown CSI sequence '%c'\n", c.mode)
|
|
||||||
// TODO: c.dump()
|
|
||||||
}
|
|
||||||
9
vendor/github.com/hinshun/vt10x/doc.go
generated
vendored
9
vendor/github.com/hinshun/vt10x/doc.go
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
Package terminal is a vt10x terminal emulation backend, influenced
|
|
||||||
largely by st, rxvt, xterm, and iTerm as reference. Use it for terminal
|
|
||||||
muxing, a terminal emulation frontend, or wherever else you need
|
|
||||||
terminal emulation.
|
|
||||||
|
|
||||||
In development, but very usable.
|
|
||||||
*/
|
|
||||||
package vt10x
|
|
||||||
15
vendor/github.com/hinshun/vt10x/ioctl_other.go
generated
vendored
15
vendor/github.com/hinshun/vt10x/ioctl_other.go
generated
vendored
@@ -1,15 +0,0 @@
|
|||||||
// +build plan9 nacl windows
|
|
||||||
|
|
||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ioctl(f *os.File, cmd, p uintptr) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResizePty(*os.File) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
31
vendor/github.com/hinshun/vt10x/ioctl_posix.go
generated
vendored
31
vendor/github.com/hinshun/vt10x/ioctl_posix.go
generated
vendored
@@ -1,31 +0,0 @@
|
|||||||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
|
||||||
|
|
||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ioctl(f *os.File, cmd, p uintptr) error {
|
|
||||||
_, _, errno := syscall.Syscall(
|
|
||||||
syscall.SYS_IOCTL,
|
|
||||||
f.Fd(),
|
|
||||||
syscall.TIOCSWINSZ,
|
|
||||||
p)
|
|
||||||
if errno != 0 {
|
|
||||||
return syscall.Errno(errno)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResizePty(pty *os.File, cols, rows int) error {
|
|
||||||
var w struct{ row, col, xpix, ypix uint16 }
|
|
||||||
w.row = uint16(rows)
|
|
||||||
w.col = uint16(cols)
|
|
||||||
w.xpix = 16 * uint16(cols)
|
|
||||||
w.ypix = 16 * uint16(rows)
|
|
||||||
return ioctl(pty, syscall.TIOCSWINSZ,
|
|
||||||
uintptr(unsafe.Pointer(&w)))
|
|
||||||
}
|
|
||||||
203
vendor/github.com/hinshun/vt10x/parse.go
generated
vendored
203
vendor/github.com/hinshun/vt10x/parse.go
generated
vendored
@@ -1,203 +0,0 @@
|
|||||||
package vt10x
|
|
||||||
|
|
||||||
func isControlCode(c rune) bool {
|
|
||||||
return c < 0x20 || c == 0177
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parse(c rune) {
|
|
||||||
t.logf("%q", string(c))
|
|
||||||
if isControlCode(c) {
|
|
||||||
if t.handleControlCodes(c) || t.cur.Attr.Mode&attrGfx == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: update selection; see st.c:2450
|
|
||||||
|
|
||||||
if t.mode&ModeWrap != 0 && t.cur.State&cursorWrapNext != 0 {
|
|
||||||
t.lines[t.cur.Y][t.cur.X].Mode |= attrWrap
|
|
||||||
t.newline(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.mode&ModeInsert != 0 && t.cur.X+1 < t.cols {
|
|
||||||
// TODO: move shiz, look at st.c:2458
|
|
||||||
t.logln("insert mode not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.setChar(c, &t.cur.Attr, t.cur.X, t.cur.Y)
|
|
||||||
if t.cur.X+1 < t.cols {
|
|
||||||
t.moveTo(t.cur.X+1, t.cur.Y)
|
|
||||||
} else {
|
|
||||||
t.cur.State |= cursorWrapNext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parseEsc(c rune) {
|
|
||||||
if t.handleControlCodes(c) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next := t.parse
|
|
||||||
t.logf("%q", string(c))
|
|
||||||
switch c {
|
|
||||||
case '[':
|
|
||||||
next = t.parseEscCSI
|
|
||||||
case '#':
|
|
||||||
next = t.parseEscTest
|
|
||||||
case 'P', // DCS - Device Control String
|
|
||||||
'_', // APC - Application Program Command
|
|
||||||
'^', // PM - Privacy Message
|
|
||||||
']', // OSC - Operating System Command
|
|
||||||
'k': // old title set compatibility
|
|
||||||
t.str.reset()
|
|
||||||
t.str.typ = c
|
|
||||||
next = t.parseEscStr
|
|
||||||
case '(': // set primary charset G0
|
|
||||||
next = t.parseEscAltCharset
|
|
||||||
case ')', // set secondary charset G1 (ignored)
|
|
||||||
'*', // set tertiary charset G2 (ignored)
|
|
||||||
'+': // set quaternary charset G3 (ignored)
|
|
||||||
case 'D': // IND - linefeed
|
|
||||||
if t.cur.Y == t.bottom {
|
|
||||||
t.scrollUp(t.top, 1)
|
|
||||||
} else {
|
|
||||||
t.moveTo(t.cur.X, t.cur.Y+1)
|
|
||||||
}
|
|
||||||
case 'E': // NEL - next line
|
|
||||||
t.newline(true)
|
|
||||||
case 'H': // HTS - horizontal tab stop
|
|
||||||
t.tabs[t.cur.X] = true
|
|
||||||
case 'M': // RI - reverse index
|
|
||||||
if t.cur.Y == t.top {
|
|
||||||
t.scrollDown(t.top, 1)
|
|
||||||
} else {
|
|
||||||
t.moveTo(t.cur.X, t.cur.Y-1)
|
|
||||||
}
|
|
||||||
case 'Z': // DECID - identify terminal
|
|
||||||
// TODO: write to our writer our id
|
|
||||||
case 'c': // RIS - reset to initial state
|
|
||||||
t.reset()
|
|
||||||
case '=': // DECPAM - application keypad
|
|
||||||
t.mode |= ModeAppKeypad
|
|
||||||
case '>': // DECPNM - normal keypad
|
|
||||||
t.mode &^= ModeAppKeypad
|
|
||||||
case '7': // DECSC - save cursor
|
|
||||||
t.saveCursor()
|
|
||||||
case '8': // DECRC - restore cursor
|
|
||||||
t.restoreCursor()
|
|
||||||
case '\\': // ST - stop
|
|
||||||
default:
|
|
||||||
t.logf("unknown ESC sequence '%c'\n", c)
|
|
||||||
}
|
|
||||||
t.state = next
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parseEscCSI(c rune) {
|
|
||||||
if t.handleControlCodes(c) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.logf("%q", string(c))
|
|
||||||
if t.csi.put(byte(c)) {
|
|
||||||
t.state = t.parse
|
|
||||||
t.handleCSI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parseEscStr(c rune) {
|
|
||||||
t.logf("%q", string(c))
|
|
||||||
switch c {
|
|
||||||
case '\033':
|
|
||||||
t.state = t.parseEscStrEnd
|
|
||||||
case '\a': // backwards compatiblity to xterm
|
|
||||||
t.state = t.parse
|
|
||||||
t.handleSTR()
|
|
||||||
default:
|
|
||||||
t.str.put(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parseEscStrEnd(c rune) {
|
|
||||||
if t.handleControlCodes(c) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.logf("%q", string(c))
|
|
||||||
t.state = t.parse
|
|
||||||
if c == '\\' {
|
|
||||||
t.handleSTR()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parseEscAltCharset(c rune) {
|
|
||||||
if t.handleControlCodes(c) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.logf("%q", string(c))
|
|
||||||
switch c {
|
|
||||||
case '0': // line drawing set
|
|
||||||
t.cur.Attr.Mode |= attrGfx
|
|
||||||
case 'B': // USASCII
|
|
||||||
t.cur.Attr.Mode &^= attrGfx
|
|
||||||
case 'A', // UK (ignored)
|
|
||||||
'<', // multinational (ignored)
|
|
||||||
'5', // Finnish (ignored)
|
|
||||||
'C', // Finnish (ignored)
|
|
||||||
'K': // German (ignored)
|
|
||||||
default:
|
|
||||||
t.logf("unknown alt. charset '%c'\n", c)
|
|
||||||
}
|
|
||||||
t.state = t.parse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) parseEscTest(c rune) {
|
|
||||||
if t.handleControlCodes(c) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// DEC screen alignment test
|
|
||||||
if c == '8' {
|
|
||||||
for y := 0; y < t.rows; y++ {
|
|
||||||
for x := 0; x < t.cols; x++ {
|
|
||||||
t.setChar('E', &t.cur.Attr, x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.state = t.parse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) handleControlCodes(c rune) bool {
|
|
||||||
if !isControlCode(c) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch c {
|
|
||||||
// HT
|
|
||||||
case '\t':
|
|
||||||
t.putTab(true)
|
|
||||||
// BS
|
|
||||||
case '\b':
|
|
||||||
t.moveTo(t.cur.X-1, t.cur.Y)
|
|
||||||
// CR
|
|
||||||
case '\r':
|
|
||||||
t.moveTo(0, t.cur.Y)
|
|
||||||
// LF, VT, LF
|
|
||||||
case '\f', '\v', '\n':
|
|
||||||
// go to first col if mode is set
|
|
||||||
t.newline(t.mode&ModeCRLF != 0)
|
|
||||||
// BEL
|
|
||||||
case '\a':
|
|
||||||
// TODO: emit sound
|
|
||||||
// TODO: window alert if not focused
|
|
||||||
// ESC
|
|
||||||
case 033:
|
|
||||||
t.csi.reset()
|
|
||||||
t.state = t.parseEsc
|
|
||||||
// SO, SI
|
|
||||||
case 016, 017:
|
|
||||||
// different charsets not supported. apps should use the correct
|
|
||||||
// alt charset escapes, probably for line drawing
|
|
||||||
// SUB, CAN
|
|
||||||
case 032, 030:
|
|
||||||
t.csi.reset()
|
|
||||||
// ignore ENQ, NUL, XON, XOFF, DEL
|
|
||||||
case 005, 000, 021, 023, 0177:
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
762
vendor/github.com/hinshun/vt10x/state.go
generated
vendored
762
vendor/github.com/hinshun/vt10x/state.go
generated
vendored
@@ -1,762 +0,0 @@
|
|||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tabspaces = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
attrReverse = 1 << iota
|
|
||||||
attrUnderline
|
|
||||||
attrBold
|
|
||||||
attrGfx
|
|
||||||
attrItalic
|
|
||||||
attrBlink
|
|
||||||
attrWrap
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cursorDefault = 1 << iota
|
|
||||||
cursorWrapNext
|
|
||||||
cursorOrigin
|
|
||||||
)
|
|
||||||
|
|
||||||
// ModeFlag represents various terminal mode states.
|
|
||||||
type ModeFlag uint32
|
|
||||||
|
|
||||||
// Terminal modes
|
|
||||||
const (
|
|
||||||
ModeWrap ModeFlag = 1 << iota
|
|
||||||
ModeInsert
|
|
||||||
ModeAppKeypad
|
|
||||||
ModeAltScreen
|
|
||||||
ModeCRLF
|
|
||||||
ModeMouseButton
|
|
||||||
ModeMouseMotion
|
|
||||||
ModeReverse
|
|
||||||
ModeKeyboardLock
|
|
||||||
ModeHide
|
|
||||||
ModeEcho
|
|
||||||
ModeAppCursor
|
|
||||||
ModeMouseSgr
|
|
||||||
Mode8bit
|
|
||||||
ModeBlink
|
|
||||||
ModeFBlink
|
|
||||||
ModeFocus
|
|
||||||
ModeMouseX10
|
|
||||||
ModeMouseMany
|
|
||||||
ModeMouseMask = ModeMouseButton | ModeMouseMotion | ModeMouseX10 | ModeMouseMany
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChangeFlag represents possible state changes of the terminal.
|
|
||||||
type ChangeFlag uint32
|
|
||||||
|
|
||||||
// Terminal changes to occur in VT.ReadState
|
|
||||||
const (
|
|
||||||
ChangedScreen ChangeFlag = 1 << iota
|
|
||||||
ChangedTitle
|
|
||||||
)
|
|
||||||
|
|
||||||
type Glyph struct {
|
|
||||||
Char rune
|
|
||||||
Mode int16
|
|
||||||
FG, BG Color
|
|
||||||
}
|
|
||||||
|
|
||||||
type line []Glyph
|
|
||||||
|
|
||||||
type Cursor struct {
|
|
||||||
Attr Glyph
|
|
||||||
X, Y int
|
|
||||||
State uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
type parseState func(c rune)
|
|
||||||
|
|
||||||
// State represents the terminal emulation state. Use Lock/Unlock
|
|
||||||
// methods to synchronize data access with VT.
|
|
||||||
type State struct {
|
|
||||||
DebugLogger *log.Logger
|
|
||||||
|
|
||||||
w io.Writer
|
|
||||||
mu sync.Mutex
|
|
||||||
changed ChangeFlag
|
|
||||||
cols, rows int
|
|
||||||
lines []line
|
|
||||||
altLines []line
|
|
||||||
dirty []bool // line dirtiness
|
|
||||||
anydirty bool
|
|
||||||
cur, curSaved Cursor
|
|
||||||
top, bottom int // scroll limits
|
|
||||||
mode ModeFlag
|
|
||||||
state parseState
|
|
||||||
str strEscape
|
|
||||||
csi csiEscape
|
|
||||||
numlock bool
|
|
||||||
tabs []bool
|
|
||||||
title string
|
|
||||||
colorOverride map[Color]Color
|
|
||||||
}
|
|
||||||
|
|
||||||
func newState(w io.Writer) *State {
|
|
||||||
return &State{
|
|
||||||
w: w,
|
|
||||||
colorOverride: make(map[Color]Color),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) logf(format string, args ...interface{}) {
|
|
||||||
if t.DebugLogger != nil {
|
|
||||||
t.DebugLogger.Printf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) logln(s string) {
|
|
||||||
if t.DebugLogger != nil {
|
|
||||||
t.DebugLogger.Println(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) lock() {
|
|
||||||
t.mu.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) unlock() {
|
|
||||||
t.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock locks the state object's mutex.
|
|
||||||
func (t *State) Lock() {
|
|
||||||
t.mu.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock resets change flags and unlocks the state object's mutex.
|
|
||||||
func (t *State) Unlock() {
|
|
||||||
t.resetChanges()
|
|
||||||
t.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cell returns the glyph containing the character code, foreground color, and
|
|
||||||
// background color at position (x, y) relative to the top left of the terminal.
|
|
||||||
func (t *State) Cell(x, y int) Glyph {
|
|
||||||
cell := t.lines[y][x]
|
|
||||||
fg, ok := t.colorOverride[cell.FG]
|
|
||||||
if ok {
|
|
||||||
cell.FG = fg
|
|
||||||
}
|
|
||||||
bg, ok := t.colorOverride[cell.BG]
|
|
||||||
if ok {
|
|
||||||
cell.BG = bg
|
|
||||||
}
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor returns the current position of the cursor.
|
|
||||||
func (t *State) Cursor() Cursor {
|
|
||||||
return t.cur
|
|
||||||
}
|
|
||||||
|
|
||||||
// CursorVisible returns the visible state of the cursor.
|
|
||||||
func (t *State) CursorVisible() bool {
|
|
||||||
return t.mode&ModeHide == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the current terminal mode.
|
|
||||||
func (t *State) Mode() ModeFlag {
|
|
||||||
return t.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title returns the current title set via the tty.
|
|
||||||
func (t *State) Title() string {
|
|
||||||
return t.title
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// ChangeMask returns a bitfield of changes that have occured by VT.
|
|
||||||
func (t *State) ChangeMask() ChangeFlag {
|
|
||||||
return t.changed
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Changed returns true if change has occured.
|
|
||||||
func (t *State) Changed(change ChangeFlag) bool {
|
|
||||||
return t.changed&change != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// resetChanges resets the change mask and dirtiness.
|
|
||||||
func (t *State) resetChanges() {
|
|
||||||
for i := range t.dirty {
|
|
||||||
t.dirty[i] = false
|
|
||||||
}
|
|
||||||
t.anydirty = false
|
|
||||||
t.changed = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) saveCursor() {
|
|
||||||
t.curSaved = t.cur
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) restoreCursor() {
|
|
||||||
t.cur = t.curSaved
|
|
||||||
t.moveTo(t.cur.X, t.cur.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) put(c rune) {
|
|
||||||
t.state(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) putTab(forward bool) {
|
|
||||||
x := t.cur.X
|
|
||||||
if forward {
|
|
||||||
if x == t.cols {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for x++; x < t.cols && !t.tabs[x]; x++ {
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if x == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for x--; x > 0 && !t.tabs[x]; x-- {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.moveTo(x, t.cur.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) newline(firstCol bool) {
|
|
||||||
y := t.cur.Y
|
|
||||||
if y == t.bottom {
|
|
||||||
cur := t.cur
|
|
||||||
t.cur = t.defaultCursor()
|
|
||||||
t.scrollUp(t.top, 1)
|
|
||||||
t.cur = cur
|
|
||||||
} else {
|
|
||||||
y++
|
|
||||||
}
|
|
||||||
if firstCol {
|
|
||||||
t.moveTo(0, y)
|
|
||||||
} else {
|
|
||||||
t.moveTo(t.cur.X, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// table from st, which in turn is from rxvt :)
|
|
||||||
var gfxCharTable = [62]rune{
|
|
||||||
'↑', '↓', '→', '←', '█', '▚', '☃', // A - G
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // H - O
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // P - W
|
|
||||||
0, 0, 0, 0, 0, 0, 0, ' ', // X - _
|
|
||||||
'◆', '▒', '␉', '␌', '␍', '␊', '°', '±', // ` - g
|
|
||||||
'', '␋', '┘', '┐', '┌', '└', '┼', '⎺', // h - o
|
|
||||||
'⎻', '─', '⎼', '⎽', '├', '┤', '┴', '┬', // p - w
|
|
||||||
'│', '≤', '≥', 'π', '≠', '£', '·', // x - ~
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) setChar(c rune, attr *Glyph, x, y int) {
|
|
||||||
if attr.Mode&attrGfx != 0 {
|
|
||||||
if c >= 0x41 && c <= 0x7e && gfxCharTable[c-0x41] != 0 {
|
|
||||||
c = gfxCharTable[c-0x41]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
t.dirty[y] = true
|
|
||||||
t.lines[y][x] = *attr
|
|
||||||
t.lines[y][x].Char = c
|
|
||||||
//if t.options.BrightBold && attr.Mode&attrBold != 0 && attr.FG < 8 {
|
|
||||||
if attr.Mode&attrBold != 0 && attr.FG < 8 {
|
|
||||||
t.lines[y][x].FG = attr.FG + 8
|
|
||||||
}
|
|
||||||
if attr.Mode&attrReverse != 0 {
|
|
||||||
t.lines[y][x].FG = attr.BG
|
|
||||||
t.lines[y][x].BG = attr.FG
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) defaultCursor() Cursor {
|
|
||||||
c := Cursor{}
|
|
||||||
c.Attr.FG = DefaultFG
|
|
||||||
c.Attr.BG = DefaultBG
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) reset() {
|
|
||||||
t.cur = t.defaultCursor()
|
|
||||||
t.saveCursor()
|
|
||||||
for i := range t.tabs {
|
|
||||||
t.tabs[i] = false
|
|
||||||
}
|
|
||||||
for i := tabspaces; i < len(t.tabs); i += tabspaces {
|
|
||||||
t.tabs[i] = true
|
|
||||||
}
|
|
||||||
t.top = 0
|
|
||||||
t.bottom = t.rows - 1
|
|
||||||
t.mode = ModeWrap
|
|
||||||
t.clear(0, 0, t.rows-1, t.cols-1)
|
|
||||||
t.moveTo(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: definitely can improve allocs
|
|
||||||
func (t *State) resize(cols, rows int) bool {
|
|
||||||
if cols == t.cols && rows == t.rows {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if cols < 1 || rows < 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
slide := t.cur.Y - rows + 1
|
|
||||||
if slide > 0 {
|
|
||||||
copy(t.lines, t.lines[slide:slide+rows])
|
|
||||||
copy(t.altLines, t.altLines[slide:slide+rows])
|
|
||||||
}
|
|
||||||
|
|
||||||
lines, altLines, tabs := t.lines, t.altLines, t.tabs
|
|
||||||
t.lines = make([]line, rows)
|
|
||||||
t.altLines = make([]line, rows)
|
|
||||||
t.dirty = make([]bool, rows)
|
|
||||||
t.tabs = make([]bool, cols)
|
|
||||||
|
|
||||||
minrows := min(rows, t.rows)
|
|
||||||
mincols := min(cols, t.cols)
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
for i := 0; i < rows; i++ {
|
|
||||||
t.dirty[i] = true
|
|
||||||
t.lines[i] = make(line, cols)
|
|
||||||
t.altLines[i] = make(line, cols)
|
|
||||||
}
|
|
||||||
for i := 0; i < minrows; i++ {
|
|
||||||
copy(t.lines[i], lines[i])
|
|
||||||
copy(t.altLines[i], altLines[i])
|
|
||||||
}
|
|
||||||
copy(t.tabs, tabs)
|
|
||||||
if cols > t.cols {
|
|
||||||
i := t.cols - 1
|
|
||||||
for i > 0 && !tabs[i] {
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
for i += tabspaces; i < len(tabs); i += tabspaces {
|
|
||||||
tabs[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.cols = cols
|
|
||||||
t.rows = rows
|
|
||||||
t.setScroll(0, rows-1)
|
|
||||||
t.moveTo(t.cur.X, t.cur.Y)
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
if mincols < cols && minrows > 0 {
|
|
||||||
t.clear(mincols, 0, cols-1, minrows-1)
|
|
||||||
}
|
|
||||||
if cols > 0 && minrows < rows {
|
|
||||||
t.clear(0, minrows, cols-1, rows-1)
|
|
||||||
}
|
|
||||||
t.swapScreen()
|
|
||||||
}
|
|
||||||
return slide > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) clear(x0, y0, x1, y1 int) {
|
|
||||||
if x0 > x1 {
|
|
||||||
x0, x1 = x1, x0
|
|
||||||
}
|
|
||||||
if y0 > y1 {
|
|
||||||
y0, y1 = y1, y0
|
|
||||||
}
|
|
||||||
x0 = clamp(x0, 0, t.cols-1)
|
|
||||||
x1 = clamp(x1, 0, t.cols-1)
|
|
||||||
y0 = clamp(y0, 0, t.rows-1)
|
|
||||||
y1 = clamp(y1, 0, t.rows-1)
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
for y := y0; y <= y1; y++ {
|
|
||||||
t.dirty[y] = true
|
|
||||||
for x := x0; x <= x1; x++ {
|
|
||||||
t.lines[y][x] = t.cur.Attr
|
|
||||||
t.lines[y][x].Char = ' '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) clearAll() {
|
|
||||||
t.clear(0, 0, t.cols-1, t.rows-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) moveAbsTo(x, y int) {
|
|
||||||
if t.cur.State&cursorOrigin != 0 {
|
|
||||||
y += t.top
|
|
||||||
}
|
|
||||||
t.moveTo(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) moveTo(x, y int) {
|
|
||||||
var miny, maxy int
|
|
||||||
if t.cur.State&cursorOrigin != 0 {
|
|
||||||
miny = t.top
|
|
||||||
maxy = t.bottom
|
|
||||||
} else {
|
|
||||||
miny = 0
|
|
||||||
maxy = t.rows - 1
|
|
||||||
}
|
|
||||||
x = clamp(x, 0, t.cols-1)
|
|
||||||
y = clamp(y, miny, maxy)
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
t.cur.State &^= cursorWrapNext
|
|
||||||
t.cur.X = x
|
|
||||||
t.cur.Y = y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) swapScreen() {
|
|
||||||
t.lines, t.altLines = t.altLines, t.lines
|
|
||||||
t.mode ^= ModeAltScreen
|
|
||||||
t.dirtyAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) dirtyAll() {
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
for y := 0; y < t.rows; y++ {
|
|
||||||
t.dirty[y] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) setScroll(top, bottom int) {
|
|
||||||
top = clamp(top, 0, t.rows-1)
|
|
||||||
bottom = clamp(bottom, 0, t.rows-1)
|
|
||||||
if top > bottom {
|
|
||||||
top, bottom = bottom, top
|
|
||||||
}
|
|
||||||
t.top = top
|
|
||||||
t.bottom = bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func clamp(val, min, max int) int {
|
|
||||||
if val < min {
|
|
||||||
return min
|
|
||||||
} else if val > max {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func between(val, min, max int) bool {
|
|
||||||
if val < min || val > max {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) scrollDown(orig, n int) {
|
|
||||||
n = clamp(n, 0, t.bottom-orig+1)
|
|
||||||
t.clear(0, t.bottom-n+1, t.cols-1, t.bottom)
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
for i := t.bottom; i >= orig+n; i-- {
|
|
||||||
t.lines[i], t.lines[i-n] = t.lines[i-n], t.lines[i]
|
|
||||||
t.dirty[i] = true
|
|
||||||
t.dirty[i-n] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: selection scroll
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) scrollUp(orig, n int) {
|
|
||||||
n = clamp(n, 0, t.bottom-orig+1)
|
|
||||||
t.clear(0, orig, t.cols-1, orig+n-1)
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
for i := orig; i <= t.bottom-n; i++ {
|
|
||||||
t.lines[i], t.lines[i+n] = t.lines[i+n], t.lines[i]
|
|
||||||
t.dirty[i] = true
|
|
||||||
t.dirty[i+n] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: selection scroll
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) modMode(set bool, bit ModeFlag) {
|
|
||||||
if set {
|
|
||||||
t.mode |= bit
|
|
||||||
} else {
|
|
||||||
t.mode &^= bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) setMode(priv bool, set bool, args []int) {
|
|
||||||
if priv {
|
|
||||||
for _, a := range args {
|
|
||||||
switch a {
|
|
||||||
case 1: // DECCKM - cursor key
|
|
||||||
t.modMode(set, ModeAppCursor)
|
|
||||||
case 5: // DECSCNM - reverse video
|
|
||||||
mode := t.mode
|
|
||||||
t.modMode(set, ModeReverse)
|
|
||||||
if mode != t.mode {
|
|
||||||
// TODO: redraw
|
|
||||||
}
|
|
||||||
case 6: // DECOM - origin
|
|
||||||
if set {
|
|
||||||
t.cur.State |= cursorOrigin
|
|
||||||
} else {
|
|
||||||
t.cur.State &^= cursorOrigin
|
|
||||||
}
|
|
||||||
t.moveAbsTo(0, 0)
|
|
||||||
case 7: // DECAWM - auto wrap
|
|
||||||
t.modMode(set, ModeWrap)
|
|
||||||
// IGNORED:
|
|
||||||
case 0, // error
|
|
||||||
2, // DECANM - ANSI/VT52
|
|
||||||
3, // DECCOLM - column
|
|
||||||
4, // DECSCLM - scroll
|
|
||||||
8, // DECARM - auto repeat
|
|
||||||
18, // DECPFF - printer feed
|
|
||||||
19, // DECPEX - printer extent
|
|
||||||
42, // DECNRCM - national characters
|
|
||||||
12: // att610 - start blinking cursor
|
|
||||||
break
|
|
||||||
case 25: // DECTCEM - text cursor enable mode
|
|
||||||
t.modMode(!set, ModeHide)
|
|
||||||
case 9: // X10 mouse compatibility mode
|
|
||||||
t.modMode(false, ModeMouseMask)
|
|
||||||
t.modMode(set, ModeMouseX10)
|
|
||||||
case 1000: // report button press
|
|
||||||
t.modMode(false, ModeMouseMask)
|
|
||||||
t.modMode(set, ModeMouseButton)
|
|
||||||
case 1002: // report motion on button press
|
|
||||||
t.modMode(false, ModeMouseMask)
|
|
||||||
t.modMode(set, ModeMouseMotion)
|
|
||||||
case 1003: // enable all mouse motions
|
|
||||||
t.modMode(false, ModeMouseMask)
|
|
||||||
t.modMode(set, ModeMouseMany)
|
|
||||||
case 1004: // send focus events to tty
|
|
||||||
t.modMode(set, ModeFocus)
|
|
||||||
case 1006: // extended reporting mode
|
|
||||||
t.modMode(set, ModeMouseSgr)
|
|
||||||
case 1034:
|
|
||||||
t.modMode(set, Mode8bit)
|
|
||||||
case 1049, // = 1047 and 1048
|
|
||||||
47, 1047:
|
|
||||||
alt := t.mode&ModeAltScreen != 0
|
|
||||||
if alt {
|
|
||||||
t.clear(0, 0, t.cols-1, t.rows-1)
|
|
||||||
}
|
|
||||||
if !set || !alt {
|
|
||||||
t.swapScreen()
|
|
||||||
}
|
|
||||||
if a != 1049 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 1048:
|
|
||||||
if set {
|
|
||||||
t.saveCursor()
|
|
||||||
} else {
|
|
||||||
t.restoreCursor()
|
|
||||||
}
|
|
||||||
case 1001:
|
|
||||||
// mouse highlight mode; can hang the terminal by design when
|
|
||||||
// implemented
|
|
||||||
case 1005:
|
|
||||||
// utf8 mouse mode; will confuse applications not supporting
|
|
||||||
// utf8 and luit
|
|
||||||
case 1015:
|
|
||||||
// urxvt mangled mouse mode; incompatiblt and can be mistaken
|
|
||||||
// for other control codes
|
|
||||||
default:
|
|
||||||
t.logf("unknown private set/reset mode %d\n", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, a := range args {
|
|
||||||
switch a {
|
|
||||||
case 0: // Error (ignored)
|
|
||||||
case 2: // KAM - keyboard action
|
|
||||||
t.modMode(set, ModeKeyboardLock)
|
|
||||||
case 4: // IRM - insertion-replacement
|
|
||||||
t.modMode(set, ModeInsert)
|
|
||||||
t.logln("insert mode not implemented")
|
|
||||||
case 12: // SRM - send/receive
|
|
||||||
t.modMode(set, ModeEcho)
|
|
||||||
case 20: // LNM - linefeed/newline
|
|
||||||
t.modMode(set, ModeCRLF)
|
|
||||||
case 34:
|
|
||||||
t.logln("right-to-left mode not implemented")
|
|
||||||
case 96:
|
|
||||||
t.logln("right-to-left copy mode not implemented")
|
|
||||||
default:
|
|
||||||
t.logf("unknown set/reset mode %d\n", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) setAttr(attr []int) {
|
|
||||||
if len(attr) == 0 {
|
|
||||||
attr = []int{0}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(attr); i++ {
|
|
||||||
a := attr[i]
|
|
||||||
switch a {
|
|
||||||
case 0:
|
|
||||||
t.cur.Attr.Mode &^= attrReverse | attrUnderline | attrBold | attrItalic | attrBlink
|
|
||||||
t.cur.Attr.FG = DefaultFG
|
|
||||||
t.cur.Attr.BG = DefaultBG
|
|
||||||
case 1:
|
|
||||||
t.cur.Attr.Mode |= attrBold
|
|
||||||
case 3:
|
|
||||||
t.cur.Attr.Mode |= attrItalic
|
|
||||||
case 4:
|
|
||||||
t.cur.Attr.Mode |= attrUnderline
|
|
||||||
case 5, 6: // slow, rapid blink
|
|
||||||
t.cur.Attr.Mode |= attrBlink
|
|
||||||
case 7:
|
|
||||||
t.cur.Attr.Mode |= attrReverse
|
|
||||||
case 21, 22:
|
|
||||||
t.cur.Attr.Mode &^= attrBold
|
|
||||||
case 23:
|
|
||||||
t.cur.Attr.Mode &^= attrItalic
|
|
||||||
case 24:
|
|
||||||
t.cur.Attr.Mode &^= attrUnderline
|
|
||||||
case 25, 26:
|
|
||||||
t.cur.Attr.Mode &^= attrBlink
|
|
||||||
case 27:
|
|
||||||
t.cur.Attr.Mode &^= attrReverse
|
|
||||||
case 38:
|
|
||||||
if i+2 < len(attr) && attr[i+1] == 5 {
|
|
||||||
i += 2
|
|
||||||
if between(attr[i], 0, 255) {
|
|
||||||
t.cur.Attr.FG = Color(attr[i])
|
|
||||||
} else {
|
|
||||||
t.logf("bad fgcolor %d\n", attr[i])
|
|
||||||
}
|
|
||||||
} else if i+4 < len(attr) && attr[i+1] == 2 {
|
|
||||||
i += 4
|
|
||||||
r, g, b := attr[i-2], attr[i-1], attr[i]
|
|
||||||
if !between(r, 0, 255) || !between(g, 0, 255) || !between(b, 0, 255) {
|
|
||||||
t.logf("bad fg rgb color (%d,%d,%d)\n", r, g, b)
|
|
||||||
} else {
|
|
||||||
t.cur.Attr.FG = Color(r<<16 | g<<8 | b)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.logf("gfx attr %d unknown\n", a)
|
|
||||||
}
|
|
||||||
case 39:
|
|
||||||
t.cur.Attr.FG = DefaultFG
|
|
||||||
case 48:
|
|
||||||
if i+2 < len(attr) && attr[i+1] == 5 {
|
|
||||||
i += 2
|
|
||||||
if between(attr[i], 0, 255) {
|
|
||||||
t.cur.Attr.BG = Color(attr[i])
|
|
||||||
} else {
|
|
||||||
t.logf("bad bgcolor %d\n", attr[i])
|
|
||||||
}
|
|
||||||
} else if i+4 < len(attr) && attr[i+1] == 2 {
|
|
||||||
i += 4
|
|
||||||
r, g, b := attr[i-2], attr[i-1], attr[i]
|
|
||||||
if !between(r, 0, 255) || !between(g, 0, 255) || !between(b, 0, 255) {
|
|
||||||
t.logf("bad bg rgb color (%d,%d,%d)\n", r, g, b)
|
|
||||||
} else {
|
|
||||||
t.cur.Attr.BG = Color(r<<16 | g<<8 | b)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.logf("gfx attr %d unknown\n", a)
|
|
||||||
}
|
|
||||||
case 49:
|
|
||||||
t.cur.Attr.BG = DefaultBG
|
|
||||||
default:
|
|
||||||
if between(a, 30, 37) {
|
|
||||||
t.cur.Attr.FG = Color(a - 30)
|
|
||||||
} else if between(a, 40, 47) {
|
|
||||||
t.cur.Attr.BG = Color(a - 40)
|
|
||||||
} else if between(a, 90, 97) {
|
|
||||||
t.cur.Attr.FG = Color(a - 90 + 8)
|
|
||||||
} else if between(a, 100, 107) {
|
|
||||||
t.cur.Attr.BG = Color(a - 100 + 8)
|
|
||||||
} else {
|
|
||||||
t.logf("gfx attr %d unknown\n", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) insertBlanks(n int) {
|
|
||||||
src := t.cur.X
|
|
||||||
dst := src + n
|
|
||||||
size := t.cols - dst
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
t.dirty[t.cur.Y] = true
|
|
||||||
|
|
||||||
if dst >= t.cols {
|
|
||||||
t.clear(t.cur.X, t.cur.Y, t.cols-1, t.cur.Y)
|
|
||||||
} else {
|
|
||||||
copy(t.lines[t.cur.Y][dst:dst+size], t.lines[t.cur.Y][src:src+size])
|
|
||||||
t.clear(src, t.cur.Y, dst-1, t.cur.Y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) insertBlankLines(n int) {
|
|
||||||
if t.cur.Y < t.top || t.cur.Y > t.bottom {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.scrollDown(t.cur.Y, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) deleteLines(n int) {
|
|
||||||
if t.cur.Y < t.top || t.cur.Y > t.bottom {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.scrollUp(t.cur.Y, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) deleteChars(n int) {
|
|
||||||
src := t.cur.X + n
|
|
||||||
dst := t.cur.X
|
|
||||||
size := t.cols - src
|
|
||||||
t.changed |= ChangedScreen
|
|
||||||
t.dirty[t.cur.Y] = true
|
|
||||||
|
|
||||||
if src >= t.cols {
|
|
||||||
t.clear(t.cur.X, t.cur.Y, t.cols-1, t.cur.Y)
|
|
||||||
} else {
|
|
||||||
copy(t.lines[t.cur.Y][dst:dst+size], t.lines[t.cur.Y][src:src+size])
|
|
||||||
t.clear(t.cols-n, t.cur.Y, t.cols-1, t.cur.Y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) setTitle(title string) {
|
|
||||||
t.changed |= ChangedTitle
|
|
||||||
t.title = title
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) Size() (cols, rows int) {
|
|
||||||
return t.cols, t.rows
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) String() string {
|
|
||||||
t.Lock()
|
|
||||||
defer t.Unlock()
|
|
||||||
|
|
||||||
var view []rune
|
|
||||||
for y := 0; y < t.rows; y++ {
|
|
||||||
for x := 0; x < t.cols; x++ {
|
|
||||||
attr := t.Cell(x, y)
|
|
||||||
view = append(view, attr.Char)
|
|
||||||
}
|
|
||||||
view = append(view, '\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(view)
|
|
||||||
}
|
|
||||||
330
vendor/github.com/hinshun/vt10x/str.go
generated
vendored
330
vendor/github.com/hinshun/vt10x/str.go
generated
vendored
@@ -1,330 +0,0 @@
|
|||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// STR sequences are similar to CSI sequences, but have string arguments (and
|
|
||||||
// as far as I can tell, don't really have a name; STR is the name I took from
|
|
||||||
// suckless which I imagine comes from rxvt or xterm).
|
|
||||||
type strEscape struct {
|
|
||||||
typ rune
|
|
||||||
buf []rune
|
|
||||||
args []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *strEscape) reset() {
|
|
||||||
s.typ = 0
|
|
||||||
s.buf = s.buf[:0]
|
|
||||||
s.args = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *strEscape) put(c rune) {
|
|
||||||
// TODO: improve allocs with an array backed slice; bench first
|
|
||||||
if len(s.buf) < 256 {
|
|
||||||
s.buf = append(s.buf, c)
|
|
||||||
}
|
|
||||||
// Going by st, it is better to remain silent when the STR sequence is not
|
|
||||||
// ended so that it is apparent to users something is wrong. The length sanity
|
|
||||||
// check ensures we don't absorb the entire stream into memory.
|
|
||||||
// TODO: see what rxvt or xterm does
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *strEscape) parse() {
|
|
||||||
s.args = strings.Split(string(s.buf), ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *strEscape) arg(i, def int) int {
|
|
||||||
if i >= len(s.args) || i < 0 {
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
i, err := strconv.Atoi(s.args[i])
|
|
||||||
if err != nil {
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *strEscape) argString(i int, def string) string {
|
|
||||||
if i >= len(s.args) || i < 0 {
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
return s.args[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) handleSTR() {
|
|
||||||
s := &t.str
|
|
||||||
s.parse()
|
|
||||||
|
|
||||||
switch s.typ {
|
|
||||||
case ']': // OSC - operating system command
|
|
||||||
var p *string
|
|
||||||
switch d := s.arg(0, 0); d {
|
|
||||||
case 0, 1, 2:
|
|
||||||
title := s.argString(1, "")
|
|
||||||
if title != "" {
|
|
||||||
t.setTitle(title)
|
|
||||||
}
|
|
||||||
case 10:
|
|
||||||
if len(s.args) < 2 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c := s.argString(1, "")
|
|
||||||
p := &c
|
|
||||||
if p != nil && *p == "?" {
|
|
||||||
t.oscColorResponse(int(DefaultFG), 10)
|
|
||||||
} else if err := t.setColorName(int(DefaultFG), p); err != nil {
|
|
||||||
t.logf("invalid foreground color: %s\n", maybe(p))
|
|
||||||
} else {
|
|
||||||
// TODO: redraw
|
|
||||||
}
|
|
||||||
case 11:
|
|
||||||
if len(s.args) < 2 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c := s.argString(1, "")
|
|
||||||
p := &c
|
|
||||||
if p != nil && *p == "?" {
|
|
||||||
t.oscColorResponse(int(DefaultBG), 11)
|
|
||||||
} else if err := t.setColorName(int(DefaultBG), p); err != nil {
|
|
||||||
t.logf("invalid cursor color: %s\n", maybe(p))
|
|
||||||
} else {
|
|
||||||
// TODO: redraw
|
|
||||||
}
|
|
||||||
// case 12:
|
|
||||||
// if len(s.args) < 2 {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
|
|
||||||
// c := s.argString(1, "")
|
|
||||||
// p := &c
|
|
||||||
// if p != nil && *p == "?" {
|
|
||||||
// t.oscColorResponse(int(DefaultCursor), 12)
|
|
||||||
// } else if err := t.setColorName(int(DefaultCursor), p); err != nil {
|
|
||||||
// t.logf("invalid background color: %s\n", p)
|
|
||||||
// } else {
|
|
||||||
// // TODO: redraw
|
|
||||||
// }
|
|
||||||
case 4: // color set
|
|
||||||
if len(s.args) < 3 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c := s.argString(2, "")
|
|
||||||
p = &c
|
|
||||||
fallthrough
|
|
||||||
case 104: // color reset
|
|
||||||
j := -1
|
|
||||||
if len(s.args) > 1 {
|
|
||||||
j = s.arg(1, 0)
|
|
||||||
}
|
|
||||||
if p != nil && *p == "?" { // report
|
|
||||||
t.osc4ColorResponse(j)
|
|
||||||
} else if err := t.setColorName(j, p); err != nil {
|
|
||||||
if !(d == 104 && len(s.args) <= 1) {
|
|
||||||
t.logf("invalid color j=%d, p=%s\n", j, maybe(p))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: redraw
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
t.logf("unknown OSC command %d\n", d)
|
|
||||||
// TODO: s.dump()
|
|
||||||
}
|
|
||||||
case 'k': // old title set compatibility
|
|
||||||
title := s.argString(0, "")
|
|
||||||
if title != "" {
|
|
||||||
t.setTitle(title)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// TODO: Ignore these codes instead of complain?
|
|
||||||
// 'P': // DSC - device control string
|
|
||||||
// '_': // APC - application program command
|
|
||||||
// '^': // PM - privacy message
|
|
||||||
|
|
||||||
t.logf("unhandled STR sequence '%c'\n", s.typ)
|
|
||||||
// t.str.dump()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) setColorName(j int, p *string) error {
|
|
||||||
if !between(j, 0, 1<<24) {
|
|
||||||
return fmt.Errorf("invalid color value %d", j)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p == nil {
|
|
||||||
// restore color
|
|
||||||
delete(t.colorOverride, Color(j))
|
|
||||||
} else {
|
|
||||||
// set color
|
|
||||||
r, g, b, err := parseColor(*p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.colorOverride[Color(j)] = Color(r<<16 | g<<8 | b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) oscColorResponse(j, num int) {
|
|
||||||
if j < 0 {
|
|
||||||
t.logf("failed to fetch osc color %d\n", j)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
k, ok := t.colorOverride[Color(j)]
|
|
||||||
if ok {
|
|
||||||
j = int(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, g, b := rgb(j)
|
|
||||||
t.w.Write([]byte(fmt.Sprintf("\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", num, r, r, g, g, b, b)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *State) osc4ColorResponse(j int) {
|
|
||||||
if j < 0 {
|
|
||||||
t.logf("failed to fetch osc4 color %d\n", j)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
k, ok := t.colorOverride[Color(j)]
|
|
||||||
if ok {
|
|
||||||
j = int(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, g, b := rgb(j)
|
|
||||||
t.w.Write([]byte(fmt.Sprintf("\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", j, r, r, g, g, b, b)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func rgb(j int) (r, g, b int) {
|
|
||||||
return (j >> 16) & 0xff, (j >> 8) & 0xff, j & 0xff
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
RGBPattern = regexp.MustCompile(`^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$`)
|
|
||||||
HashPattern = regexp.MustCompile(`[\da-f]`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseColor(p string) (r, g, b int, err error) {
|
|
||||||
if len(p) == 0 {
|
|
||||||
err = fmt.Errorf("empty color spec")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
low := strings.ToLower(p)
|
|
||||||
if strings.HasPrefix(low, "rgb:") {
|
|
||||||
low = low[4:]
|
|
||||||
sm := RGBPattern.FindAllStringSubmatch(low, -1)
|
|
||||||
if len(sm) != 1 || len(sm[0]) == 0 {
|
|
||||||
err = fmt.Errorf("invalid rgb color spec: %s", p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m := sm[0]
|
|
||||||
|
|
||||||
var base float64
|
|
||||||
if len(m[1]) > 0 {
|
|
||||||
base = 15
|
|
||||||
} else if len(m[4]) > 0 {
|
|
||||||
base = 255
|
|
||||||
} else if len(m[7]) > 0 {
|
|
||||||
base = 4095
|
|
||||||
} else {
|
|
||||||
base = 65535
|
|
||||||
}
|
|
||||||
|
|
||||||
r64, err := strconv.ParseInt(firstNonEmpty(m[1], m[4], m[7], m[10]), 16, 0)
|
|
||||||
if err != nil {
|
|
||||||
return r, g, b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
g64, err := strconv.ParseInt(firstNonEmpty(m[2], m[5], m[8], m[11]), 16, 0)
|
|
||||||
if err != nil {
|
|
||||||
return r, g, b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b64, err := strconv.ParseInt(firstNonEmpty(m[3], m[6], m[9], m[12]), 16, 0)
|
|
||||||
if err != nil {
|
|
||||||
return r, g, b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r = int(math.Round(float64(r64) / base * 255))
|
|
||||||
g = int(math.Round(float64(g64) / base * 255))
|
|
||||||
b = int(math.Round(float64(b64) / base * 255))
|
|
||||||
return r, g, b, nil
|
|
||||||
} else if strings.HasPrefix(low, "#") {
|
|
||||||
low = low[1:]
|
|
||||||
m := HashPattern.FindAllString(low, -1)
|
|
||||||
if !oneOf(len(m), []int{3, 6, 9, 12}) {
|
|
||||||
err = fmt.Errorf("invalid hash color spec: %s", p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
adv := len(low) / 3
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
c, err := strconv.ParseInt(low[adv*i:adv*i+adv], 16, 0)
|
|
||||||
if err != nil {
|
|
||||||
return r, g, b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var v int64
|
|
||||||
switch adv {
|
|
||||||
case 1:
|
|
||||||
v = c << 4
|
|
||||||
case 2:
|
|
||||||
v = c
|
|
||||||
case 3:
|
|
||||||
v = c >> 4
|
|
||||||
default:
|
|
||||||
v = c >> 8
|
|
||||||
}
|
|
||||||
|
|
||||||
switch i {
|
|
||||||
case 0:
|
|
||||||
r = int(v)
|
|
||||||
case 1:
|
|
||||||
g = int(v)
|
|
||||||
case 2:
|
|
||||||
b = int(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("invalid color spec: %s", p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func maybe(p *string) string {
|
|
||||||
if p == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
return *p
|
|
||||||
}
|
|
||||||
|
|
||||||
func firstNonEmpty(strs ...string) string {
|
|
||||||
if len(strs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
for _, str := range strs {
|
|
||||||
if len(str) > 0 {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strs[len(strs)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func oneOf(v int, is []int) bool {
|
|
||||||
for _, i := range is {
|
|
||||||
if v == i {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
89
vendor/github.com/hinshun/vt10x/vt.go
generated
vendored
89
vendor/github.com/hinshun/vt10x/vt.go
generated
vendored
@@ -1,89 +0,0 @@
|
|||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Terminal represents the virtual terminal emulator.
|
|
||||||
type Terminal interface {
|
|
||||||
// View displays the virtual terminal.
|
|
||||||
View
|
|
||||||
|
|
||||||
// Write parses input and writes terminal changes to state.
|
|
||||||
io.Writer
|
|
||||||
|
|
||||||
// Parse blocks on read on pty or io.Reader, then parses sequences until
|
|
||||||
// buffer empties. State is locked as soon as first rune is read, and unlocked
|
|
||||||
// when buffer is empty.
|
|
||||||
Parse(bf *bufio.Reader) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// View represents the view of the virtual terminal emulator.
|
|
||||||
type View interface {
|
|
||||||
// String dumps the virtual terminal contents.
|
|
||||||
fmt.Stringer
|
|
||||||
|
|
||||||
// Size returns the size of the virtual terminal.
|
|
||||||
Size() (cols, rows int)
|
|
||||||
|
|
||||||
// Resize changes the size of the virtual terminal.
|
|
||||||
Resize(cols, rows int)
|
|
||||||
|
|
||||||
// Mode returns the current terminal mode.//
|
|
||||||
Mode() ModeFlag
|
|
||||||
|
|
||||||
// Title represents the title of the console window.
|
|
||||||
Title() string
|
|
||||||
|
|
||||||
// Cell returns the glyph containing the character code, foreground color, and
|
|
||||||
// background color at position (x, y) relative to the top left of the terminal.
|
|
||||||
Cell(x, y int) Glyph
|
|
||||||
|
|
||||||
// Cursor returns the current position of the cursor.
|
|
||||||
Cursor() Cursor
|
|
||||||
|
|
||||||
// CursorVisible returns the visible state of the cursor.
|
|
||||||
CursorVisible() bool
|
|
||||||
|
|
||||||
// Lock locks the state object's mutex.
|
|
||||||
Lock()
|
|
||||||
|
|
||||||
// Unlock resets change flags and unlocks the state object's mutex.
|
|
||||||
Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type TerminalOption func(*TerminalInfo)
|
|
||||||
|
|
||||||
type TerminalInfo struct {
|
|
||||||
w io.Writer
|
|
||||||
cols, rows int
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithWriter(w io.Writer) TerminalOption {
|
|
||||||
return func(info *TerminalInfo) {
|
|
||||||
info.w = w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithSize(cols, rows int) TerminalOption {
|
|
||||||
return func(info *TerminalInfo) {
|
|
||||||
info.cols = cols
|
|
||||||
info.rows = rows
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new virtual terminal emulator.
|
|
||||||
func New(opts ...TerminalOption) Terminal {
|
|
||||||
info := TerminalInfo{
|
|
||||||
w: ioutil.Discard,
|
|
||||||
cols: 80,
|
|
||||||
rows: 24,
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&info)
|
|
||||||
}
|
|
||||||
return newTerminal(info)
|
|
||||||
}
|
|
||||||
107
vendor/github.com/hinshun/vt10x/vt_other.go
generated
vendored
107
vendor/github.com/hinshun/vt10x/vt_other.go
generated
vendored
@@ -1,107 +0,0 @@
|
|||||||
// +build plan9 nacl windows
|
|
||||||
|
|
||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type terminal struct {
|
|
||||||
*State
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTerminal(info TerminalInfo) *terminal {
|
|
||||||
t := &terminal{newState(info.w)}
|
|
||||||
t.init(info.cols, info.rows)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *terminal) init(cols, rows int) {
|
|
||||||
t.numlock = true
|
|
||||||
t.state = t.parse
|
|
||||||
t.cur.Attr.FG = DefaultFG
|
|
||||||
t.cur.Attr.BG = DefaultBG
|
|
||||||
t.Resize(cols, rows)
|
|
||||||
t.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *terminal) Write(p []byte) (int, error) {
|
|
||||||
var written int
|
|
||||||
r := bytes.NewReader(p)
|
|
||||||
t.lock()
|
|
||||||
defer t.unlock()
|
|
||||||
for {
|
|
||||||
c, sz, err := r.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += sz
|
|
||||||
if c == unicode.ReplacementChar && sz == 1 {
|
|
||||||
if r.Len() == 0 {
|
|
||||||
// not enough bytes for a full rune
|
|
||||||
return written - 1, nil
|
|
||||||
}
|
|
||||||
t.logln("invalid utf8 sequence")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.put(c)
|
|
||||||
}
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add tests for expected blocking behavior
|
|
||||||
func (t *terminal) Parse(br *bufio.Reader) error {
|
|
||||||
var locked bool
|
|
||||||
defer func() {
|
|
||||||
if locked {
|
|
||||||
t.unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
c, sz, err := br.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c == unicode.ReplacementChar && sz == 1 {
|
|
||||||
t.logln("invalid utf8 sequence")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !locked {
|
|
||||||
t.lock()
|
|
||||||
locked = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// put rune for parsing and update state
|
|
||||||
t.put(c)
|
|
||||||
|
|
||||||
// break if our buffer is empty, or if buffer contains an
|
|
||||||
// incomplete rune.
|
|
||||||
n := br.Buffered()
|
|
||||||
if n == 0 || (n < 4 && !fullRuneBuffered(br)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fullRuneBuffered(br *bufio.Reader) bool {
|
|
||||||
n := br.Buffered()
|
|
||||||
buf, err := br.Peek(n)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return utf8.FullRune(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *terminal) Resize(cols, rows int) {
|
|
||||||
t.lock()
|
|
||||||
defer t.unlock()
|
|
||||||
_ = t.resize(cols, rows)
|
|
||||||
}
|
|
||||||
108
vendor/github.com/hinshun/vt10x/vt_posix.go
generated
vendored
108
vendor/github.com/hinshun/vt10x/vt_posix.go
generated
vendored
@@ -1,108 +0,0 @@
|
|||||||
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
|
|
||||||
|
|
||||||
package vt10x
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type terminal struct {
|
|
||||||
*State
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTerminal(info TerminalInfo) *terminal {
|
|
||||||
t := &terminal{newState(info.w)}
|
|
||||||
t.init(info.cols, info.rows)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *terminal) init(cols, rows int) {
|
|
||||||
t.numlock = true
|
|
||||||
t.state = t.parse
|
|
||||||
t.cur.Attr.FG = DefaultFG
|
|
||||||
t.cur.Attr.BG = DefaultBG
|
|
||||||
t.Resize(cols, rows)
|
|
||||||
t.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write parses input and writes terminal changes to state.
|
|
||||||
func (t *terminal) Write(p []byte) (int, error) {
|
|
||||||
var written int
|
|
||||||
r := bytes.NewReader(p)
|
|
||||||
t.lock()
|
|
||||||
defer t.unlock()
|
|
||||||
for {
|
|
||||||
c, sz, err := r.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += sz
|
|
||||||
if c == unicode.ReplacementChar && sz == 1 {
|
|
||||||
if r.Len() == 0 {
|
|
||||||
// not enough bytes for a full rune
|
|
||||||
return written - 1, nil
|
|
||||||
}
|
|
||||||
t.logln("invalid utf8 sequence")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.put(c)
|
|
||||||
}
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add tests for expected blocking behavior
|
|
||||||
func (t *terminal) Parse(br *bufio.Reader) error {
|
|
||||||
var locked bool
|
|
||||||
defer func() {
|
|
||||||
if locked {
|
|
||||||
t.unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
c, sz, err := br.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c == unicode.ReplacementChar && sz == 1 {
|
|
||||||
t.logln("invalid utf8 sequence")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !locked {
|
|
||||||
t.lock()
|
|
||||||
locked = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// put rune for parsing and update state
|
|
||||||
t.put(c)
|
|
||||||
|
|
||||||
// break if our buffer is empty, or if buffer contains an
|
|
||||||
// incomplete rune.
|
|
||||||
n := br.Buffered()
|
|
||||||
if n == 0 || (n < 4 && !fullRuneBuffered(br)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fullRuneBuffered(br *bufio.Reader) bool {
|
|
||||||
n := br.Buffered()
|
|
||||||
buf, err := br.Peek(n)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return utf8.FullRune(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *terminal) Resize(cols, rows int) {
|
|
||||||
t.lock()
|
|
||||||
defer t.unlock()
|
|
||||||
_ = t.resize(cols, rows)
|
|
||||||
}
|
|
||||||
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@@ -385,7 +385,6 @@ github.com/hashicorp/go-multierror
|
|||||||
github.com/hashicorp/go-version
|
github.com/hashicorp/go-version
|
||||||
# github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02
|
# github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/hinshun/vt10x
|
|
||||||
# github.com/imdario/mergo v0.3.12
|
# github.com/imdario/mergo v0.3.12
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/imdario/mergo
|
github.com/imdario/mergo
|
||||||
|
|||||||
Reference in New Issue
Block a user