diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..a57ac66f917f096729d52d0c3a4d8fc281b73c3d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,35 +1,2 @@ -*.7z filter=lfs diff=lfs merge=lfs -text -*.arrow filter=lfs diff=lfs merge=lfs -text -*.bin filter=lfs diff=lfs merge=lfs -text -*.bz2 filter=lfs diff=lfs merge=lfs -text -*.ckpt filter=lfs diff=lfs merge=lfs -text -*.ftz filter=lfs diff=lfs merge=lfs -text -*.gz filter=lfs diff=lfs merge=lfs -text -*.h5 filter=lfs diff=lfs merge=lfs -text -*.joblib filter=lfs diff=lfs merge=lfs -text -*.lfs.* filter=lfs diff=lfs merge=lfs -text -*.mlmodel filter=lfs diff=lfs merge=lfs -text -*.model filter=lfs diff=lfs merge=lfs -text -*.msgpack filter=lfs diff=lfs merge=lfs -text -*.npy filter=lfs diff=lfs merge=lfs -text -*.npz filter=lfs diff=lfs merge=lfs -text -*.onnx filter=lfs diff=lfs merge=lfs -text -*.ot filter=lfs diff=lfs merge=lfs -text -*.parquet filter=lfs diff=lfs merge=lfs -text -*.pb filter=lfs diff=lfs merge=lfs -text -*.pickle filter=lfs diff=lfs merge=lfs -text -*.pkl filter=lfs diff=lfs merge=lfs -text -*.pt filter=lfs diff=lfs merge=lfs -text -*.pth filter=lfs diff=lfs merge=lfs -text -*.rar filter=lfs diff=lfs merge=lfs -text -*.safetensors filter=lfs diff=lfs merge=lfs -text -saved_model/**/* filter=lfs diff=lfs merge=lfs -text -*.tar.* filter=lfs diff=lfs merge=lfs -text -*.tar filter=lfs diff=lfs merge=lfs -text -*.tflite filter=lfs diff=lfs merge=lfs -text -*.tgz filter=lfs diff=lfs merge=lfs -text -*.wasm filter=lfs diff=lfs merge=lfs -text -*.xz filter=lfs diff=lfs merge=lfs -text -*.zip filter=lfs diff=lfs merge=lfs -text -*.zst filter=lfs diff=lfs merge=lfs -text -*tfevents* filter=lfs diff=lfs merge=lfs -text +config/gen_bindata.go linguist-generated +handlers/www/assets/swarm.png filter=lfs diff=lfs merge=lfs -text diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..ef2f0e6664554e503d5eaa752795709d5e64eb1f --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Reporting a Vulnerability + +If there are any vulnerabilities in this project or in [the playground](https://labs.play-with-docker.com), don't hesitate to _report them_. + +1. Write an email to marcosnils (at) gmail.com +2. Describe the vulnerability. + + If you have a fix, that is most welcome -- please attach or summarize it in your message! + +3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report. + + Please **do not disclose the vulnerability publicly** until a fix is released! + +4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000000000000000000000000000000000..e9831926f1b694bf1e3de657a0c232da0eeb494c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +name: Go +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + go_version: ["1.16.0"] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go_version }} + - name: Generate + run: go generate ./... + - name: Test + run: go test ./... + - name: Verify clean commit + run: test -z "$(git status --porcelain)" || (git status; git diff; false) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5235eda96db431f511eaa57db7e7f9316f3f62af --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +play-with-docker +node_modules +/vendor diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..16b8281b6c54d49d771be3ac9d661fd2b48ca13a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.16 + +COPY . /go/src/github.com/play-with-docker/play-with-docker + +WORKDIR /go/src/github.com/play-with-docker/play-with-docker + +RUN ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null + +RUN CGO_ENABLED=0 go build -a -installsuffix nocgo -o /go/bin/play-with-docker . + + +FROM alpine + +RUN apk --update add ca-certificates +RUN mkdir -p /app/pwd + +COPY --from=0 /go/bin/play-with-docker /app/play-with-docker +COPY --from=0 /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key + +WORKDIR /app +CMD ["./play-with-docker"] + +EXPOSE 3000 diff --git a/Dockerfile.l2 b/Dockerfile.l2 new file mode 100644 index 0000000000000000000000000000000000000000..134ebecb2851dfca75266c4ae1ce3ce6e71c431c --- /dev/null +++ b/Dockerfile.l2 @@ -0,0 +1,27 @@ +FROM golang:1.9 + +# Copy the runtime dockerfile into the context as Dockerfile +COPY . /go/src/github.com/play-with-docker/play-with-docker + +WORKDIR /go/src/github.com/play-with-docker/play-with-docker + + +RUN ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null + +WORKDIR /go/src/github.com/play-with-docker/play-with-docker/router/l2 + +RUN CGO_ENABLED=0 go build -a -installsuffix nocgo -o /go/bin/play-with-docker-l2 . + + +FROM alpine + +RUN apk --update add ca-certificates +RUN mkdir /app + +COPY --from=0 /go/bin/play-with-docker-l2 /app/play-with-docker-l2 +COPY --from=0 /etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key + +WORKDIR /app +CMD ["./play-with-docker-l2", "-ssh_key_path", "/etc/ssh/ssh_host_rsa_key"] + +EXPOSE 22 53 443 8080 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bfcd944250c5b1e9dcf133c257b1ac287cf588b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Marcos Lilljedhal and Jonathan Leibiusky + +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 limitation 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 this permission 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. diff --git a/api.go b/api.go new file mode 100644 index 0000000000000000000000000000000000000000..e747a9b2f91c25c7b9180970f73e474c4f68f3a5 --- /dev/null +++ b/api.go @@ -0,0 +1,82 @@ +package main + +import ( + "log" + "os" + "time" + + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/handlers" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/k8s" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/scheduler" + "github.com/play-with-docker/play-with-docker/scheduler/task" + "github.com/play-with-docker/play-with-docker/storage" +) + +func main() { + config.ParseFlags() + + e := initEvent() + s := initStorage() + df := initDockerFactory(s) + kf := initK8sFactory(s) + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(df, s), provisioner.NewDinD(id.XIDGenerator{}, df, s)) + sp := provisioner.NewOverlaySessionProvisioner(df) + + core := pwd.NewPWD(df, e, s, sp, ipf) + + tasks := []scheduler.Task{ + task.NewCheckPorts(e, df), + task.NewCheckSwarmPorts(e, df), + task.NewCheckSwarmStatus(e, df), + task.NewCollectStats(e, df, s), + task.NewCheckK8sClusterStatus(e, kf), + task.NewCheckK8sClusterExposedPorts(e, kf), + } + sch, err := scheduler.NewScheduler(tasks, s, e, core) + if err != nil { + log.Fatal("Error initializing the scheduler: ", err) + } + + sch.Start() + + d, err := time.ParseDuration("2h") + if err != nil { + log.Fatalf("Cannot parse duration Got: %v", err) + } + + playground := types.Playground{Domain: config.PlaygroundDomain, DefaultDinDInstanceImage: "franela/dind", AvailableDinDInstanceImages: []string{"franela/dind"}, AllowWindowsInstances: config.NoWindows, DefaultSessionDuration: d, Extras: map[string]interface{}{"LoginRedirect": "http://localhost:3000"}, Privileged: true} + if _, err := core.PlaygroundNew(playground); err != nil { + log.Fatalf("Cannot create default playground. Got: %v", err) + } + + handlers.Bootstrap(core, e) + handlers.Register(nil) +} + +func initStorage() storage.StorageApi { + s, err := storage.NewFileStorage(config.SessionsFile) + if err != nil && !os.IsNotExist(err) { + log.Fatal("Error initializing StorageAPI: ", err) + } + return s +} + +func initEvent() event.EventApi { + return event.NewLocalBroker() +} + +func initDockerFactory(s storage.StorageApi) docker.FactoryApi { + return docker.NewLocalCachedFactory(s) +} + +func initK8sFactory(s storage.StorageApi) k8s.FactoryApi { + return k8s.NewLocalCachedFactory(s) +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..623d7637bdce7df7a0d745d8580b774007b58d7e --- /dev/null +++ b/config/config.go @@ -0,0 +1,78 @@ +package config + +import ( + "flag" + "os" + "regexp" + + "github.com/gorilla/securecookie" + + "golang.org/x/oauth2" +) + +const ( + PWDHostnameRegex = "[0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3}" + PortRegex = "[0-9]{1,5}" + AliasnameRegex = "[0-9|a-z|A-Z|-]*" + AliasSessionRegex = "[0-9|a-z|A-Z]{8}" + AliasGroupRegex = "(" + AliasnameRegex + ")-(" + AliasSessionRegex + ")" + PWDHostPortGroupRegex = "^.*ip(" + PWDHostnameRegex + ")(?:-?(" + PortRegex + "))?(?:\\..*)?$" + AliasPortGroupRegex = "^.*pwd" + AliasGroupRegex + "(?:-?(" + PortRegex + "))?\\..*$" +) + +var ( + NameFilter = regexp.MustCompile(PWDHostPortGroupRegex) + AliasFilter = regexp.MustCompile(AliasPortGroupRegex) +) + +var ( + PortNumber, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, HashKey, SSHKeyPath, L2RouterIP, CookieHashKey, CookieBlockKey string + UseLetsEncrypt, ExternalDindVolume, NoWindows bool + LetsEncryptCertsDir string + MaxLoadAvg float64 + ForceTLS bool + SecureCookie *securecookie.SecureCookie + AdminToken string +) + +// Unsafe enables a number of unsafe features when set. It is principally +// intended to be used in development. For example, it allows the caller to +// specify the Docker networks to join. +var Unsafe bool + +var PlaygroundDomain string + +var SegmentId string + +// TODO move this to a sync map so it can be updated on demand when the configuration for a playground changes +var Providers = map[string]map[string]*oauth2.Config{} + +func ParseFlags() { + flag.StringVar(&LetsEncryptCertsDir, "letsencrypt-certs-dir", "/certs", "Path where let's encrypt certs will be stored") + flag.BoolVar(&UseLetsEncrypt, "letsencrypt-enable", false, "Enabled let's encrypt tls certificates") + flag.BoolVar(&ForceTLS, "tls", false, "Use TLS to connect to docker daemons") + flag.StringVar(&PortNumber, "port", "3000", "Port number") + flag.StringVar(&SessionsFile, "save", "./pwd/sessions", "Tell where to store sessions file") + flag.StringVar(&PWDContainerName, "name", "pwd", "Container name used to run PWD (used to be able to connect it to the networks it creates)") + flag.StringVar(&L2ContainerName, "l2", "l2", "Container name used to run L2 Router") + flag.StringVar(&L2RouterIP, "l2-ip", "", "Host IP address for L2 router ping response") + flag.StringVar(&L2Subdomain, "l2-subdomain", "direct", "Subdomain to the L2 Router") + flag.StringVar(&HashKey, "hash_key", "salmonrosado", "Hash key to use for cookies") + flag.BoolVar(&NoWindows, "win-disable", false, "Disable windows instances") + flag.BoolVar(&ExternalDindVolume, "dind-external-volume", false, "Use external dind volume though XFS volume driver") + flag.Float64Var(&MaxLoadAvg, "maxload", 100, "Maximum allowed load average before failing ping requests") + flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use") + flag.StringVar(&CookieHashKey, "cookie-hash-key", "", "Hash key to use to validate cookies") + flag.StringVar(&CookieBlockKey, "cookie-block-key", "", "Block key to use to encrypt cookies") + + flag.StringVar(&PlaygroundDomain, "playground-domain", "localhost", "Domain to use for the playground") + flag.StringVar(&AdminToken, "admin-token", "", "Token to validate admin user for admin endpoints") + + flag.StringVar(&SegmentId, "segment-id", "", "Segment id to post metrics") + + flag.BoolVar(&Unsafe, "unsafe", os.Getenv("PWD_UNSAFE") == "true", "Operate in unsafe mode") + + flag.Parse() + + SecureCookie = securecookie.New([]byte(CookieHashKey), []byte(CookieBlockKey)) +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..664d7f2f03e243bb40585a8ec5eddf43c5da9405 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +version: '3.2' +services: + haproxy: + container_name: haproxy + image: haproxy + ports: + - "80:8080" + volumes: + - ./haproxy:/usr/local/etc/haproxy + + pwd: + # pwd daemon container always needs to be named this way + container_name: pwd + # use the latest golang image + image: golang + # go to the right place and starts the app + command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/; if [ -e /runbin/pwd ]; then /runbin/pwd -save /pwd/sessions -name l2; else go run api.go -save /pwd/sessions -name l2; fi' + environment: + - APPARMOR_PROFILE=docker-dind + volumes: + # since this app creates networks and launches containers, we need to talk to docker daemon + - /var/run/docker.sock:/var/run/docker.sock + # mount the box mounted shared folder to the container + - $PWD:/go/src + - sessions:/pwd + l2: + container_name: l2 + # use the latest golang image + image: golang + # go to the right place and starts the app + command: /bin/sh -c 'ssh-keygen -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key >/dev/null; cd /go/src/router/l2; if [ -e /runbin/l2 ]; then /runbin/l2 -ssh_key_path /etc/ssh/ssh_host_rsa_key -name l2 -save /pwd/networks; else go run l2.go -ssh_key_path /etc/ssh/ssh_host_rsa_key -name l2 -save /pwd/networks; fi' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - $PWD:/go/src + - networks:/pwd + ports: + - "8022:22" + - "8053:53" + - "443:443" +volumes: + sessions: + networks: diff --git a/docker/docker.go b/docker/docker.go new file mode 100644 index 0000000000000000000000000000000000000000..e36125d5d29722af15d3a8b949a926a68156dc6a --- /dev/null +++ b/docker/docker.go @@ -0,0 +1,546 @@ +package docker + +import ( + "archive/tar" + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "os" + "strconv" + "strings" + "time" + + "github.com/containerd/containerd/reference" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/client" + "github.com/play-with-docker/play-with-docker/config" +) + +const ( + Byte = 1 + Kilobyte = 1024 * Byte + Megabyte = 1024 * Kilobyte +) + +type DockerApi interface { + GetClient() *client.Client + + NetworkCreate(id string, opts types.NetworkCreate) error + NetworkConnect(container, network, ip string) (string, error) + NetworkInspect(id string) (types.NetworkResource, error) + NetworkDelete(id string) error + NetworkDisconnect(containerId, networkId string) error + + DaemonInfo() (types.Info, error) + DaemonHost() string + + GetSwarmPorts() ([]string, []uint16, error) + GetPorts() ([]uint16, error) + + ContainerStats(name string) (io.ReadCloser, error) + ContainerResize(name string, rows, cols uint) error + ContainerRename(old, new string) error + ContainerDelete(name string) error + ContainerCreate(opts CreateContainerOpts) error + ContainerIPs(id string) (map[string]string, error) + ExecAttach(instanceName string, command []string, out io.Writer) (int, error) + Exec(instanceName string, command []string) (int, error) + + CreateAttachConnection(name string) (net.Conn, error) + CopyToContainer(containerName, destination, fileName string, content io.Reader) error + CopyFromContainer(containerName, filePath string) (io.Reader, error) + SwarmInit(advertiseAddr string) (*SwarmTokens, error) + SwarmJoin(addr, token string) error + + ConfigCreate(name string, labels map[string]string, data []byte) error + ConfigDelete(name string) error +} + +type SwarmTokens struct { + Manager string + Worker string +} + +type docker struct { + c *client.Client +} + +func (d *docker) GetClient() *client.Client { + return d.c +} + +func (d *docker) ConfigCreate(name string, labels map[string]string, data []byte) error { + config := swarm.ConfigSpec{} + config.Name = name + config.Labels = labels + config.Data = data + _, err := d.c.ConfigCreate(context.Background(), config) + return err +} +func (d *docker) ConfigDelete(name string) error { + return d.c.ConfigRemove(context.Background(), name) +} + +func (d *docker) NetworkCreate(id string, opts types.NetworkCreate) error { + _, err := d.c.NetworkCreate(context.Background(), id, opts) + + if err != nil { + log.Printf("Starting session err [%s]\n", err) + + return err + } + + return nil +} + +func (d *docker) NetworkConnect(containerId, networkId, ip string) (string, error) { + settings := &network.EndpointSettings{} + if ip != "" { + settings.IPAddress = ip + } + err := d.c.NetworkConnect(context.Background(), networkId, containerId, settings) + + if err != nil && !strings.Contains(err.Error(), "already exists") { + log.Printf("Connection container to network err [%s]\n", err) + + return "", err + } + + // Obtain the IP of the PWD container in this network + container, err := d.c.ContainerInspect(context.Background(), containerId) + if err != nil { + return "", err + } + + n, found := container.NetworkSettings.Networks[networkId] + if !found { + return "", fmt.Errorf("Container [%s] connected to the network [%s] but couldn't obtain it's IP address", containerId, networkId) + } + + return n.IPAddress, nil +} + +func (d *docker) NetworkInspect(id string) (types.NetworkResource, error) { + return d.c.NetworkInspect(context.Background(), id, types.NetworkInspectOptions{}) +} + +func (d *docker) DaemonInfo() (types.Info, error) { + return d.c.Info(context.Background()) +} + +func (d *docker) DaemonHost() string { + return d.c.DaemonHost() +} + +func (d *docker) GetSwarmPorts() ([]string, []uint16, error) { + hosts := []string{} + ports := []uint16{} + + nodesIdx := map[string]string{} + nodes, nodesErr := d.c.NodeList(context.Background(), types.NodeListOptions{}) + if nodesErr != nil { + return nil, nil, nodesErr + } + for _, n := range nodes { + nodesIdx[n.ID] = n.Description.Hostname + hosts = append(hosts, n.Description.Hostname) + } + + services, err := d.c.ServiceList(context.Background(), types.ServiceListOptions{}) + if err != nil { + return nil, nil, err + } + for _, service := range services { + for _, p := range service.Endpoint.Ports { + ports = append(ports, uint16(p.PublishedPort)) + } + } + + return hosts, ports, nil +} + +func (d *docker) GetPorts() ([]uint16, error) { + opts := types.ContainerListOptions{} + containers, err := d.c.ContainerList(context.Background(), opts) + if err != nil { + return nil, err + } + + openPorts := []uint16{} + for _, c := range containers { + for _, p := range c.Ports { + // When port is not published on the host docker return public port as 0, so we need to avoid it + if p.PublicPort != 0 { + openPorts = append(openPorts, p.PublicPort) + } + } + } + + return openPorts, nil +} + +func (d *docker) ContainerStats(name string) (io.ReadCloser, error) { + stats, err := d.c.ContainerStats(context.Background(), name, false) + + return stats.Body, err +} + +func (d *docker) ContainerResize(name string, rows, cols uint) error { + return d.c.ContainerResize(context.Background(), name, types.ResizeOptions{Height: rows, Width: cols}) +} + +func (d *docker) ContainerRename(old, new string) error { + return d.c.ContainerRename(context.Background(), old, new) +} + +func (d *docker) CreateAttachConnection(name string) (net.Conn, error) { + ctx := context.Background() + + conf := types.ContainerAttachOptions{true, true, true, true, "ctrl-^,ctrl-^", true} + conn, err := d.c.ContainerAttach(ctx, name, conf) + if err != nil { + return nil, err + } + + return conn.Conn, nil +} + +func (d *docker) CopyToContainer(containerName, destination, fileName string, content io.Reader) error { + contents, err := ioutil.ReadAll(content) + if err != nil { + return err + } + var buf bytes.Buffer + t := tar.NewWriter(&buf) + if err := t.WriteHeader(&tar.Header{Name: fileName, Mode: 0600, Size: int64(len(contents)), ModTime: time.Now()}); err != nil { + return err + } + if _, err := t.Write(contents); err != nil { + return err + } + if err := t.Close(); err != nil { + return err + } + return d.c.CopyToContainer(context.Background(), containerName, destination, &buf, types.CopyToContainerOptions{AllowOverwriteDirWithFile: true, CopyUIDGID: true}) +} + +func (d *docker) CopyFromContainer(containerName, filePath string) (io.Reader, error) { + rc, stat, err := d.c.CopyFromContainer(context.Background(), containerName, filePath) + if err != nil { + return nil, err + } + if stat.Mode.IsDir() { + return nil, fmt.Errorf("Copying directories is not supported") + } + tr := tar.NewReader(rc) + // advance to the only possible file in the tar archive + tr.Next() + return tr, nil +} + +func (d *docker) ContainerDelete(name string) error { + err := d.c.ContainerRemove(context.Background(), name, types.ContainerRemoveOptions{Force: true, RemoveVolumes: true}) + d.c.VolumeRemove(context.Background(), name, true) + return err +} + +type CreateContainerOpts struct { + Image string + SessionId string + ContainerName string + Hostname string + ServerCert []byte + ServerKey []byte + CACert []byte + Privileged bool + HostFQDN string + Labels map[string]string + Networks []string + DindVolumeSize string + Envs []string +} + +func (d *docker) ContainerCreate(opts CreateContainerOpts) (err error) { + // Make sure directories are available for the new instance container + containerDir := "/opt/pwd" + containerCertDir := fmt.Sprintf("%s/certs", containerDir) + + env := append(opts.Envs, fmt.Sprintf("SESSION_ID=%s", opts.SessionId)) + + // Write certs to container cert dir + if len(opts.ServerCert) > 0 { + env = append(env, `DOCKER_TLSCERT=\/opt\/pwd\/certs\/cert.pem`) + } + if len(opts.ServerKey) > 0 { + env = append(env, `DOCKER_TLSKEY=\/opt\/pwd\/certs\/key.pem`) + } + if len(opts.CACert) > 0 { + // if ca cert is specified, verify that clients that connects present a certificate signed by the CA + env = append(env, `DOCKER_TLSCACERT=\/opt\/pwd\/certs\/ca.pem`) + } + if len(opts.ServerCert) > 0 || len(opts.ServerKey) > 0 || len(opts.CACert) > 0 { + // if any of the certs is specified, enable TLS + env = append(env, "DOCKER_TLSENABLE=true") + } else { + env = append(env, "DOCKER_TLSENABLE=false") + } + + h := &container.HostConfig{ + NetworkMode: container.NetworkMode(opts.SessionId), + Privileged: opts.Privileged, + AutoRemove: true, + LogConfig: container.LogConfig{Config: map[string]string{"max-size": "10m", "max-file": "1"}}, + } + + if os.Getenv("APPARMOR_PROFILE") != "" { + h.SecurityOpt = []string{fmt.Sprintf("apparmor=%s", os.Getenv("APPARMOR_PROFILE"))} + } + + if os.Getenv("STORAGE_SIZE") != "" { + // assing 10GB size FS for each container + h.StorageOpt = map[string]string{"size": os.Getenv("STORAGE_SIZE")} + } + + var pidsLimit = int64(1000) + if envLimit := os.Getenv("MAX_PROCESSES"); envLimit != "" { + if i, err := strconv.Atoi(envLimit); err == nil { + pidsLimit = int64(i) + } + } + h.Resources.PidsLimit = &pidsLimit + + if memLimit := os.Getenv("MAX_MEMORY_MB"); memLimit != "" { + if i, err := strconv.Atoi(memLimit); err == nil { + h.Resources.Memory = int64(i) * Megabyte + } + } + + t := true + h.Resources.OomKillDisable = &t + + env = append(env, fmt.Sprintf("PWD_HOST_FQDN=%s", opts.HostFQDN)) + cf := &container.Config{ + Hostname: opts.Hostname, + Image: opts.Image, + Tty: true, + OpenStdin: true, + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Env: env, + Labels: opts.Labels, + } + + networkConf := &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{opts.Networks[0]: &network.EndpointSettings{}}, + } + + if config.ExternalDindVolume { + _, err = d.c.VolumeCreate(context.Background(), volume.VolumeCreateBody{ + Driver: "xfsvol", + DriverOpts: map[string]string{ + "size": opts.DindVolumeSize, + }, + Name: opts.ContainerName, + }) + if err != nil { + return + } + h.Binds = []string{fmt.Sprintf("%s:/var/lib/docker", opts.ContainerName)} + + defer func() { + if err != nil { + d.c.VolumeRemove(context.Background(), opts.SessionId, true) + } + }() + } + + container, err := d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName) + + if err != nil { + //if client.IsErrImageNotFound(err) { + //log.Printf("Unable to find image '%s' locally\n", opts.Image) + //if err = d.pullImage(context.Background(), opts.Image); err != nil { + //return "", err + //} + //container, err = d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName) + //if err != nil { + //return "", err + //} + //} else { + return err + //} + } + + //connect remaining networks if there are any + if len(opts.Networks) > 1 { + for _, nid := range opts.Networks { + err = d.c.NetworkConnect(context.Background(), nid, container.ID, &network.EndpointSettings{}) + if err != nil { + return + } + } + } + + if err = d.copyIfSet(opts.ServerCert, "cert.pem", containerCertDir, opts.ContainerName); err != nil { + return + } + if err = d.copyIfSet(opts.ServerKey, "key.pem", containerCertDir, opts.ContainerName); err != nil { + return + } + if err = d.copyIfSet(opts.CACert, "ca.pem", containerCertDir, opts.ContainerName); err != nil { + return + } + + err = d.c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}) + if err != nil { + return + } + + return +} + +func (d *docker) ContainerIPs(id string) (map[string]string, error) { + cinfo, err := d.c.ContainerInspect(context.Background(), id) + if err != nil { + return nil, err + } + + ips := map[string]string{} + for networkId, conf := range cinfo.NetworkSettings.Networks { + ips[networkId] = conf.IPAddress + } + return ips, nil + +} + +func (d *docker) pullImage(ctx context.Context, image string) error { + _, err := reference.Parse(image) + if err != nil { + return err + } + + options := types.ImageCreateOptions{} + + responseBody, err := d.c.ImageCreate(ctx, image, options) + if err != nil { + return err + } + _, err = io.Copy(ioutil.Discard, responseBody) + + return err +} + +func (d *docker) copyIfSet(content []byte, fileName, path, containerName string) error { + if len(content) > 0 { + return d.CopyToContainer(containerName, path, fileName, bytes.NewReader(content)) + } + return nil +} + +func (d *docker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) { + e, err := d.c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command, AttachStdout: true, AttachStderr: true, Tty: true}) + if err != nil { + return 0, err + } + resp, err := d.c.ContainerExecAttach(context.Background(), e.ID, types.ExecStartCheck{ + Tty: true, + }) + if err != nil { + return 0, err + } + io.Copy(out, resp.Reader) + var ins types.ContainerExecInspect + for _ = range time.Tick(1 * time.Second) { + ins, err = d.c.ContainerExecInspect(context.Background(), e.ID) + if ins.Running { + continue + } + if err != nil { + return 0, err + } + break + } + return ins.ExitCode, nil + +} + +func (d *docker) Exec(instanceName string, command []string) (int, error) { + e, err := d.c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command}) + if err != nil { + return 0, err + } + err = d.c.ContainerExecStart(context.Background(), e.ID, types.ExecStartCheck{}) + if err != nil { + return 0, err + } + var ins types.ContainerExecInspect + for _ = range time.Tick(1 * time.Second) { + ins, err = d.c.ContainerExecInspect(context.Background(), e.ID) + if ins.Running { + continue + } + if err != nil { + return 0, err + } + break + } + return ins.ExitCode, nil +} + +func (d *docker) NetworkDisconnect(containerId, networkId string) error { + err := d.c.NetworkDisconnect(context.Background(), networkId, containerId, true) + + if err != nil { + log.Printf("Disconnection of container from network err [%s]\n", err) + + return err + } + + return nil +} + +func (d *docker) NetworkDelete(id string) error { + err := d.c.NetworkRemove(context.Background(), id) + + if err != nil { + return err + } + + return nil +} + +func (d *docker) SwarmInit(advertiseAddr string) (*SwarmTokens, error) { + req := swarm.InitRequest{AdvertiseAddr: advertiseAddr, ListenAddr: "0.0.0.0:2377"} + _, err := d.c.SwarmInit(context.Background(), req) + + if err != nil { + return nil, err + } + + swarmInfo, err := d.c.SwarmInspect(context.Background()) + if err != nil { + return nil, err + } + + return &SwarmTokens{ + Worker: swarmInfo.JoinTokens.Worker, + Manager: swarmInfo.JoinTokens.Manager, + }, nil +} +func (d *docker) SwarmJoin(addr, token string) error { + req := swarm.JoinRequest{RemoteAddrs: []string{addr}, JoinToken: token, ListenAddr: "0.0.0.0:2377", AdvertiseAddr: "eth0"} + return d.c.SwarmJoin(context.Background(), req) +} + +func NewDocker(c *client.Client) *docker { + return &docker{c: c} +} diff --git a/docker/factory.go b/docker/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..da50cd467bb413edba0b0a57e4035b65a432aa4d --- /dev/null +++ b/docker/factory.go @@ -0,0 +1,70 @@ +package docker + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "net/url" + "time" + + "github.com/docker/docker/api" + "github.com/docker/docker/client" + "github.com/docker/go-connections/tlsconfig" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/router" +) + +type FactoryApi interface { + GetForSession(session *types.Session) (DockerApi, error) + GetForInstance(instance *types.Instance) (DockerApi, error) +} + +func NewClient(instance *types.Instance, proxyHost string) (*client.Client, error) { + var host string + var durl string + + var tlsConfig *tls.Config + if (len(instance.Cert) > 0 && len(instance.Key) > 0) || instance.Tls { + host = router.EncodeHost(instance.SessionId, instance.RoutableIP, router.HostOpts{EncodedPort: 2376}) + tlsConfig = tlsconfig.ClientDefault() + tlsConfig.InsecureSkipVerify = true + tlsConfig.ServerName = host + if len(instance.Cert) > 0 && len(instance.Key) > 0 { + tlsCert, err := tls.X509KeyPair(instance.Cert, instance.Key) + if err != nil { + return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) + } + tlsConfig.Certificates = []tls.Certificate{tlsCert} + } + } else { + host = router.EncodeHost(instance.SessionId, instance.RoutableIP, router.HostOpts{EncodedPort: 2375}) + } + + transport := &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 1 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConnsPerHost: 5, + } + + if tlsConfig != nil { + transport.TLSClientConfig = tlsConfig + durl = fmt.Sprintf("https://%s", proxyHost) + } else { + transport.Proxy = http.ProxyURL(&url.URL{Host: proxyHost}) + durl = fmt.Sprintf("http://%s", host) + } + + cli := &http.Client{ + Transport: transport, + } + + dc, err := client.NewClient(durl, api.DefaultVersion, cli, nil) + if err != nil { + return nil, fmt.Errorf("Could not connect to DinD docker daemon: %v", err) + } + + return dc, nil +} diff --git a/docker/factory_mock.go b/docker/factory_mock.go new file mode 100644 index 0000000000000000000000000000000000000000..69c3c0789467a5787b63b4fb723d67966326514d --- /dev/null +++ b/docker/factory_mock.go @@ -0,0 +1,20 @@ +package docker + +import ( + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/mock" +) + +type FactoryMock struct { + mock.Mock +} + +func (m *FactoryMock) GetForSession(session *types.Session) (DockerApi, error) { + args := m.Called(session) + return args.Get(0).(DockerApi), args.Error(1) +} + +func (m *FactoryMock) GetForInstance(instance *types.Instance) (DockerApi, error) { + args := m.Called(instance) + return args.Get(0).(DockerApi), args.Error(1) +} diff --git a/docker/local_cached_factory.go b/docker/local_cached_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..39a25812c7555cd3d0428410e34facffabdd2f2b --- /dev/null +++ b/docker/local_cached_factory.go @@ -0,0 +1,113 @@ +package docker + +import ( + "context" + "fmt" + "log" + "sync" + "time" + + "github.com/docker/docker/client" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" +) + +type localCachedFactory struct { + rw sync.Mutex + irw sync.Mutex + sessionClient DockerApi + instanceClients map[string]*instanceEntry + storage storage.StorageApi +} + +type instanceEntry struct { + rw sync.Mutex + client DockerApi +} + +func (f *localCachedFactory) GetForSession(session *types.Session) (DockerApi, error) { + f.rw.Lock() + defer f.rw.Unlock() + + if f.sessionClient != nil { + if err := f.check(f.sessionClient.GetClient()); err == nil { + return f.sessionClient, nil + } else { + f.sessionClient.GetClient().Close() + } + } + + c, err := client.NewClientWithOpts() + if err != nil { + return nil, err + } + err = f.check(c) + if err != nil { + return nil, err + } + d := NewDocker(c) + f.sessionClient = d + return f.sessionClient, nil +} + +func (f *localCachedFactory) GetForInstance(instance *types.Instance) (DockerApi, error) { + key := instance.Name + + f.irw.Lock() + c, found := f.instanceClients[key] + if !found { + c := &instanceEntry{} + f.instanceClients[key] = c + } + c = f.instanceClients[key] + f.irw.Unlock() + + c.rw.Lock() + defer c.rw.Unlock() + + if c.client != nil { + if err := f.check(c.client.GetClient()); err == nil { + return c.client, nil + } else { + c.client.GetClient().Close() + } + } + + dc, err := NewClient(instance, "l2:443") + if err != nil { + return nil, err + } + err = f.check(dc) + if err != nil { + return nil, err + } + dockerClient := NewDocker(dc) + c.client = dockerClient + + return dockerClient, nil +} + +func (f *localCachedFactory) check(c *client.Client) error { + ok := false + for i := 0; i < 5; i++ { + _, err := c.Ping(context.Background()) + if err != nil { + log.Printf("Connection to [%s] has failed, maybe instance is not ready yet, sleeping and retrying in 1 second. Try #%d. Got: %v\n", c.DaemonHost(), i+1, err) + time.Sleep(time.Second) + continue + } + ok = true + break + } + if !ok { + return fmt.Errorf("Connection to docker daemon was not established.") + } + return nil +} + +func NewLocalCachedFactory(s storage.StorageApi) *localCachedFactory { + return &localCachedFactory{ + instanceClients: make(map[string]*instanceEntry), + storage: s, + } +} diff --git a/docker/mock.go b/docker/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..4f4c2c93bacf2a1876c8330eb1f0b42b4580b173 --- /dev/null +++ b/docker/mock.go @@ -0,0 +1,160 @@ +package docker + +import ( + "io" + "net" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/stretchr/testify/mock" +) + +type Mock struct { + mock.Mock +} + +func (m *Mock) GetClient() *client.Client { + args := m.Called() + return args.Get(0).(*client.Client) +} + +func (m *Mock) NetworkCreate(id string, opts types.NetworkCreate) error { + args := m.Called(id, opts) + return args.Error(0) +} + +func (m *Mock) NetworkConnect(container, network, ip string) (string, error) { + args := m.Called(container, network, ip) + return args.String(0), args.Error(1) +} + +func (m *Mock) NetworkInspect(id string) (types.NetworkResource, error) { + args := m.Called(id) + return args.Get(0).(types.NetworkResource), args.Error(1) +} + +func (m *Mock) DaemonInfo() (types.Info, error) { + args := m.Called() + return args.Get(0).(types.Info), args.Error(1) +} + +func (m *Mock) DaemonHost() string { + args := m.Called() + return args.String(0) +} + +func (m *Mock) GetSwarmPorts() ([]string, []uint16, error) { + args := m.Called() + return args.Get(0).([]string), args.Get(1).([]uint16), args.Error(2) +} + +func (m *Mock) GetPorts() ([]uint16, error) { + args := m.Called() + return args.Get(0).([]uint16), args.Error(1) +} +func (m *Mock) ContainerStats(name string) (io.ReadCloser, error) { + args := m.Called(name) + return args.Get(0).(io.ReadCloser), args.Error(1) +} +func (m *Mock) ContainerResize(name string, rows, cols uint) error { + args := m.Called(name, rows, cols) + return args.Error(0) +} +func (m *Mock) ContainerRename(old, new string) error { + args := m.Called(old, new) + return args.Error(0) +} +func (m *Mock) CreateAttachConnection(name string) (net.Conn, error) { + args := m.Called(name) + return args.Get(0).(net.Conn), args.Error(1) +} +func (m *Mock) CopyToContainer(containerName, destination, fileName string, content io.Reader) error { + args := m.Called(containerName, destination, fileName, content) + return args.Error(0) +} + +func (m *Mock) CopyFromContainer(containerName, filePath string) (io.Reader, error) { + args := m.Called(containerName, filePath) + return args.Get(0).(io.Reader), args.Error(1) +} +func (m *Mock) ContainerDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} +func (m *Mock) ContainerCreate(opts CreateContainerOpts) error { + args := m.Called(opts) + return args.Error(0) +} +func (m *Mock) ContainerIPs(id string) (map[string]string, error) { + args := m.Called(id) + return args.Get(0).(map[string]string), args.Error(1) +} + +func (m *Mock) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) { + args := m.Called(instanceName, command, out) + return args.Int(0), args.Error(1) +} +func (m *Mock) NetworkDisconnect(containerId, networkId string) error { + args := m.Called(containerId, networkId) + return args.Error(0) +} +func (m *Mock) NetworkDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} +func (m *Mock) Exec(instanceName string, command []string) (int, error) { + args := m.Called(instanceName, command) + return args.Int(0), args.Error(1) +} +func (m *Mock) SwarmInit(advertiseAddr string) (*SwarmTokens, error) { + args := m.Called(advertiseAddr) + return args.Get(0).(*SwarmTokens), args.Error(1) +} +func (m *Mock) SwarmJoin(addr, token string) error { + args := m.Called(addr, token) + return args.Error(0) +} +func (m *Mock) ConfigCreate(name string, labels map[string]string, data []byte) error { + args := m.Called(name, labels, data) + return args.Error(0) +} +func (m *Mock) ConfigDelete(name string) error { + args := m.Called(name) + return args.Error(0) +} + +type MockConn struct { +} + +func (m *MockConn) Read(b []byte) (n int, err error) { + return len(b), nil +} + +func (m *MockConn) Write(b []byte) (n int, err error) { + return len(b), nil +} + +func (m *MockConn) Close() error { + return nil +} + +func (m *MockConn) LocalAddr() net.Addr { + return &net.IPAddr{} +} + +func (m *MockConn) RemoteAddr() net.Addr { + return &net.IPAddr{} +} + +func (m *MockConn) SetDeadline(t time.Time) error { + return nil +} + +func (m *MockConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (m *MockConn) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/dockerfiles/dind/.editorconfig b/dockerfiles/dind/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..e39eb0e4d2d013503f0f02de893673861bd0b1b6 --- /dev/null +++ b/dockerfiles/dind/.editorconfig @@ -0,0 +1,14 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[{Makefile,*.go}] +indent_style = tab diff --git a/dockerfiles/dind/.gitconfig b/dockerfiles/dind/.gitconfig new file mode 100644 index 0000000000000000000000000000000000000000..87b8f9be00f890c66e82b81fe6617ed7732ec11a --- /dev/null +++ b/dockerfiles/dind/.gitconfig @@ -0,0 +1,2 @@ +[url "https://"] + insteadOf = git:// diff --git a/dockerfiles/dind/.inputrc b/dockerfiles/dind/.inputrc new file mode 100644 index 0000000000000000000000000000000000000000..6a5b035b1a092730381e34e8b8219c3407465b4f --- /dev/null +++ b/dockerfiles/dind/.inputrc @@ -0,0 +1,73 @@ +# /etc/inputrc - global inputrc for libreadline +# See readline(3readline) and `info rluserman' for more information. + +# Be 8 bit clean. +set input-meta on +set output-meta on + +# To allow the use of 8bit-characters like the german umlauts, uncomment +# the line below. However this makes the meta key not work as a meta key, +# which is annoying to those which don't need to type in 8-bit characters. + +# set convert-meta off + +# try to enable the application keypad when it is called. Some systems +# need this to enable the arrow keys. +# set enable-keypad on + +# see /usr/share/doc/bash/inputrc.arrows for other codes of arrow keys + +# do not bell on tab-completion +# set bell-style none +# set bell-style visible + +# some defaults / modifications for the emacs mode +$if mode=emacs + +# allow the use of the Home/End keys +"\e[1~": beginning-of-line +"\e[4~": end-of-line + +# allow the use of the Delete/Insert keys +"\e[3~": delete-char +"\e[2~": quoted-insert + +# mappings for "page up" and "page down" to step to the beginning/end +# of the history +# "\e[5~": beginning-of-history +# "\e[6~": end-of-history + +# alternate mappings for "page up" and "page down" to search the history +# "\e[5~": history-search-backward +# "\e[6~": history-search-forward + +# mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving +"\e[1;5C": forward-word +"\e[1;5D": backward-word +"\e[5C": forward-word +"\e[5D": backward-word +"\e\e[C": forward-word +"\e\e[D": backward-word + +$if term=rxvt +"\e[7~": beginning-of-line +"\e[8~": end-of-line +"\eOc": forward-word +"\eOd": backward-word +$endif + +# for non RH/Debian xterm, can't hurt for RH/Debian xterm +# "\eOH": beginning-of-line +# "\eOF": end-of-line + +# for freebsd console +# "\e[H": beginning-of-line +# "\e[F": end-of-line + +$endif + +# faster completion +set show-all-if-ambiguous on + +"\e[A": history-search-backward +"\e[B": history-search-forward diff --git a/dockerfiles/dind/.profile b/dockerfiles/dind/.profile new file mode 100644 index 0000000000000000000000000000000000000000..1e1c8d944058a6e1e57a0379c3dc8f46c9cc0ea9 --- /dev/null +++ b/dockerfiles/dind/.profile @@ -0,0 +1,6 @@ +export PS1='\e[1m\e[31m[\h] \e[32m($(docker-prompt)) \e[34m\u@$(hostname -i)\e[35m \w\e[0m\n$ ' +alias vi='vim' +export PATH=$PATH:/root/go/bin +export DOCKER_HOST="" +cat /etc/motd +echo $BASHPID > /var/run/cwd diff --git a/dockerfiles/dind/.vimrc b/dockerfiles/dind/.vimrc new file mode 100644 index 0000000000000000000000000000000000000000..591902057a78b5b8abf12074d468b96213e86d20 --- /dev/null +++ b/dockerfiles/dind/.vimrc @@ -0,0 +1,6 @@ +syntax on +set autoindent +set expandtab +set number +set shiftwidth=2 +set softtabstop=2 diff --git a/dockerfiles/dind/Dockerfile b/dockerfiles/dind/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..0c6cde85e4a7001ed59af2901c6fc5a847ca49c0 --- /dev/null +++ b/dockerfiles/dind/Dockerfile @@ -0,0 +1,69 @@ +ARG VERSION=docker:dind +FROM ${VERSION} + +RUN apk add --no-cache py-pip python3-dev libffi-dev openssl-dev git tmux apache2-utils vim build-base gettext-dev curl bash-completion bash util-linux jq openssh openssl tree \ + && ln -s /usr/local/bin/docker /usr/bin/docker + +ENV GOPATH /root/go +ENV IPTABLES_LEGACY /usr/local/sbin/.iptables-legacy/ +ENV PATH $IPTABLES_LEGACY:$GOPATH:$PATH + + +ENV DOCKER_TLS_CERTDIR="" +ENV DOCKER_CLI_EXPERIMENTAL=enabled + +# Install compose +ENV COMPOSE_VERSION=2.18.1 +RUN mkdir -p /usr/lib/docker/cli-plugins \ + && curl -LsS https://github.com/docker/compose/releases/download/v$COMPOSE_VERSION/docker-compose-linux-x86_64 -o /usr/lib/docker/cli-plugins/docker-compose \ + && chmod +x /usr/lib/docker/cli-plugins/docker-compose + + +# Install scout +ENV SCOUT_VERSION=1.0.9 +RUN wget -O /tmp/scout.tar.gz https://github.com/docker/scout-cli/releases/download/v1.0.9/docker-scout_1.0.9_linux_amd64.tar.gz \ + && tar -xvf /tmp/scout.tar.gz docker-scout -C /usr/local/bin \ + && chmod +x /usr/local/bin/docker-scout \ + && ln -s $(which docker-scout) /usr/lib/docker/cli-plugins \ + && rm /tmp/scout.tar.gz + + + +# Add bash completion and set bash as default shell +RUN curl -sS https://raw.githubusercontent.com/docker/cli/refs/heads/master/contrib/completion/bash/docker -o /etc/bash_completion.d/docker \ + && sed -i "s/ash/bash/" /etc/passwd + +# Replace modprobe with a no-op to get rid of spurious warnings +# (note: we can't just symlink to /bin/true because it might be busybox) +RUN rm /sbin/modprobe && echo '#!/bin/true' >/sbin/modprobe && chmod +x /sbin/modprobe + +# Install a nice vimrc file and prompt (by soulshake) +COPY ["docker-prompt", "sudo", "/usr/local/bin/"] +COPY [".vimrc", ".profile", ".inputrc", ".gitconfig", "./root/"] +COPY ["motd", "/etc/motd"] +COPY ["daemon.json", "/etc/docker/"] + + +# Move to our home +WORKDIR /root + +# Setup certs and ssh keys +RUN mkdir -p /var/run/pwd/certs && mkdir -p /var/run/pwd/uploads \ + && ssh-keygen -N "" -t ed25519 -f /etc/ssh/ssh_host_ed25519_key >/dev/null \ + && mkdir ~/.ssh && ssh-keygen -N "" -t ed25519 -f ~/.ssh/id_rsa \ + && cat ~/.ssh/id_rsa.pub > ~/.ssh/authorized_keys + +# Remove IPv6 alias for localhost and start docker in the background ... +CMD cat /etc/hosts >/etc/hosts.bak && \ + sed 's/^::1.*//' /etc/hosts.bak > /etc/hosts && \ + sed -i "s/\PWD_IP_ADDRESS/$PWD_IP_ADDRESS/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSENABLE/$DOCKER_TLSENABLE/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSCACERT/$DOCKER_TLSCACERT/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSCERT/$DOCKER_TLSCERT/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSKEY/$DOCKER_TLSKEY/" /etc/docker/daemon.json && \ + mount -t securityfs none /sys/kernel/security && \ + echo "root:root" | chpasswd &> /dev/null && \ + /usr/sbin/sshd -o PermitRootLogin=yes -o PrintMotd=no 2>/dev/null && \ + dockerd &>/docker.log & \ + while true ; do script -q -c "/bin/bash -l" /dev/null ; done +# ... and then put a shell in the foreground, restarting it if it exits diff --git a/dockerfiles/dind/Dockerfile.dind-ee b/dockerfiles/dind/Dockerfile.dind-ee new file mode 100644 index 0000000000000000000000000000000000000000..b7e7da05f2a13bb2abcb8ff3ef7345d296eac728 --- /dev/null +++ b/dockerfiles/dind/Dockerfile.dind-ee @@ -0,0 +1,54 @@ +ARG VERSION=franela/docker:ubuntu-19.03ee +#ARG VERSION=franela/docker:18.09.2-ee-dind + +FROM ${VERSION} + +RUN apt-get update \ + && apt-get install -y git tmux python-pip apache2-utils vim curl jq bash-completion screen tree zip \ + && rm -rf /var/lib/apt/lists/* + +# Add kubectl client +RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.11.7/bin/linux/amd64/kubectl \ + && chmod +x ./kubectl \ + && mv ./kubectl /usr/local/bin/kubectl + +ENV COMPOSE_VERSION=1.22.0 + +RUN pip install docker-compose==${COMPOSE_VERSION} +RUN curl -L https://github.com/docker/machine/releases/download/${MACHINE_VERSION}/docker-machine-Linux-x86_64 \ + -o /usr/bin/docker-machine && chmod +x /usr/bin/docker-machine + + +# Install a nice vimrc file and prompt (by soulshake) +COPY ["docker-prompt", "sudo", "ucp-beta.sh", "/usr/local/bin/"] +COPY [".vimrc",".profile", ".inputrc", ".gitconfig", "workshop_beta.lic", "ucp-config.toml", "./root/"] +COPY ["motd", "/etc/motd"] +COPY ["ee/daemon.json", "/etc/docker/"] +COPY ["ee/cert.pem", "ee/key.pem", "/opt/pwd/certs/"] +COPY ["ee/ucp-key.pem", "./root/key.pem"] +COPY ["ee/ucp-cert.pem", "./root/cert.pem"] + +# Move to our home +WORKDIR /root + +# Setup certs and uploads folders +RUN mkdir -p /opt/pwd/certs /opt/pwd/uploads + +VOLUME ["/var/lib/kubelet"] + +# Remove IPv6 alias for localhost and start docker in the background ... +CMD cat /etc/hosts >/etc/hosts.bak && \ + sed 's/^::1.*//' /etc/hosts.bak > /etc/hosts && \ + sed -i "s/\PWD_IP_ADDRESS/$PWD_IP_ADDRESS/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSENABLE/$DOCKER_TLSENABLE/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSCACERT/$DOCKER_TLSCACERT/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSCERT/$DOCKER_TLSCERT/" /etc/docker/daemon.json && \ + sed -i "s/\DOCKER_TLSKEY/$DOCKER_TLSKEY/" /etc/docker/daemon.json && \ + mount -t securityfs none /sys/kernel/security && \ + mount --make-rshared / && \ + #mount --make-rshared -t tmpfs tmpfs /run && \ + #mount --make-rshared /var/lib/kubelet && \ + #mount --make-rshared /var/lib/docker && \ + dockerd > /docker.log 2>&1 & \ + while true ; do script -q -c "/bin/bash -l" /dev/null ; done +# ... and then put a shell in the foreground, restarting it if it exits diff --git a/dockerfiles/dind/copy_certs.ps1 b/dockerfiles/dind/copy_certs.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..d1fcf892f8727a8ac4827ae44330a34167435863 --- /dev/null +++ b/dockerfiles/dind/copy_certs.ps1 @@ -0,0 +1,114 @@ +param ( + [Parameter(Mandatory = $true)] + [string] $Node, + [Parameter(Mandatory = $true)] + [string] $SessionId, + [Parameter(Mandatory = $true)] + [string] $FQDN +) + + +function GetDirectUrlFromIp ($ip) { + $ip_dash=$ip -replace "\.","-" + $url="https://ip${ip_dash}-${SessionId}.direct.${FQDN}" + return $url +} + +function WaitForUrl ($url) { + write-host $url + do { + try{ + invoke-webrequest -UseBasicParsing -uri $url | Out-Null + } catch {} + $status = $? + sleep 1 + } until($status) +} + +function GetNodeRoutableIp ($nodeName) { + $JQFilter='.instances[] | select (.hostname == \"{0}\") | .routable_ip' -f $nodeName + $rip = (invoke-webrequest -UseBasicParsing -uri "https://$FQDN/sessions/$SessionId").Content | jq -r $JQFilter + + IF([string]::IsNullOrEmpty($rip)) { + Write-Host "Could not fetch IP for node $nodeName" + exit 1 + } + return $rip +} + +function Set-UseUnsafeHeaderParsing +{ + param( + [Parameter(Mandatory,ParameterSetName='Enable')] + [switch]$Enable, + + [Parameter(Mandatory,ParameterSetName='Disable')] + [switch]$Disable + ) + + $ShouldEnable = $PSCmdlet.ParameterSetName -eq 'Enable' + + $netAssembly = [Reflection.Assembly]::GetAssembly([System.Net.Configuration.SettingsSection]) + + if($netAssembly) + { + $bindingFlags = [Reflection.BindingFlags] 'Static,GetProperty,NonPublic' + $settingsType = $netAssembly.GetType('System.Net.Configuration.SettingsSectionInternal') + + $instance = $settingsType.InvokeMember('Section', $bindingFlags, $null, $null, @()) + + if($instance) + { + $bindingFlags = 'NonPublic','Instance' + $useUnsafeHeaderParsingField = $settingsType.GetField('useUnsafeHeaderParsing', $bindingFlags) + + if($useUnsafeHeaderParsingField) + { + $useUnsafeHeaderParsingField.SetValue($instance, $ShouldEnable) + } + } + } +} + + +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' + +Set-UseUnsafeHeaderParsing -Enable + +Start-Transcript -path ("C:\{0}.log" -f $MyInvocation.MyCommand.Name) -append + +add-type @" + using System.Net; + using System.Security.Cryptography.X509Certificates; + + public class IDontCarePolicy : ICertificatePolicy { + public IDontCarePolicy() {} + public bool CheckValidationResult( + ServicePoint sPoint, X509Certificate cert, + WebRequest wRequest, int certProb) { + return true; + } + } +"@ + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +[System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy + + +$dtr_ip = GetNodeRoutableIp $Node +$dtr_url = GetDirectUrlFromIp $dtr_ip +$dtr_hostname = $dtr_url -replace "https://","" + +WaitForUrl "${dtr_url}/ca" + +invoke-webrequest -UseBasicParsing -uri "$dtr_url/ca" -o c:\ca.crt + +$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 c:\ca.crt +$store = new-object System.Security.Cryptography.X509Certificates.X509Store('Root','localmachine') +$store.Open('ReadWrite') +$store.Add($cert) +$store.Close() + +Stop-Transcript diff --git a/dockerfiles/dind/daemon.json b/dockerfiles/dind/daemon.json new file mode 100644 index 0000000000000000000000000000000000000000..8276eca30404b32b8296830f2f968d533b58ba9c --- /dev/null +++ b/dockerfiles/dind/daemon.json @@ -0,0 +1,17 @@ +{ + "experimental": true, + "debug": true, + "log-level": "info", + "registry-mirrors": ["https://mirror.gcr.io"], + "insecure-registries": [ + "127.0.0.1" + ], + "hosts": [ + "unix:///var/run/docker.sock", + "tcp://0.0.0.0:2375" + ], + "tls": DOCKER_TLSENABLE, + "tlscacert": "DOCKER_TLSCACERT", + "tlscert": "DOCKER_TLSCERT", + "tlskey": "DOCKER_TLSKEY" +} diff --git a/dockerfiles/dind/docker-prompt b/dockerfiles/dind/docker-prompt new file mode 100644 index 0000000000000000000000000000000000000000..3df79d2953132a3867d43daba361fc6d2cd35591 --- /dev/null +++ b/dockerfiles/dind/docker-prompt @@ -0,0 +1,22 @@ + +#!/bin/sh +case "$DOCKER_HOST" in +*:3376) + echo swarm + ;; +*:2376) + echo $DOCKER_MACHINE_NAME + ;; +*:2375) + echo $DOCKER_MACHINE_NAME + ;; +*:55555) + echo $DOCKER_MACHINE_NAME + ;; +"") + echo local + ;; +*) + echo unknown + ;; +esac diff --git a/dockerfiles/dind/ee/cert.pem b/dockerfiles/dind/ee/cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..e600f76f3a85f1c9d07e7732471bf46e0809bffc --- /dev/null +++ b/dockerfiles/dind/ee/cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9jCCAt6gAwIBAgIQSCiXatddwed3bL9M9bierjANBgkqhkiG9w0BAQsFADAO +MQwwCgYDVQQKEwNVQ1AwHhcNMTcwOTE1MjAzMzAwWhcNMjAwODMwMjAzMzAwWjAO +MQwwCgYDVQQKEwNVQ1AwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCq +prmPRweArtZQ6HHDeYCSC3WxQOy6hakc3VZa6JEldbEoVjOc7MqZNPvTIp8b/W8H +O10ibEGZ03vyeq10UsiFQiYmdhn1SqEilZnFSo892PSpGaN7VO325uUnIccJqc3O +0YOdvNCdp9roZZ/K7z9nuC37cLy6+Lq2oLr1WYAxncJHedUi3LQCC+2qEBIVL+md +9yE8amFrYbDhbNqmIcAJ2KmkqBPa0Pa+Qe1FxqQI5zJOT5rOJgF3JbWeqFpm0Zjx +CPTt0cPY4lyQ2U9lyMXJmS4+R0wekkZXywaU1mJi3JJIlMSBMWmWoTrx5mLVWOLv +u44hYerfOmN+ImXRWq4NAPLi4722/OLzCmFn81fdUHOFyxg2Tr23b6I6sMyUfLJ0 +lqS+thJ7N/tcQe3nTeQm9dcruDbJpjJQrQkjq9CFFsxNEXBT6EFMRp8oDutOAyHf +guVeqdH5kz6vprNiLfSTqqZSEeQokRkHTyxpZ4grBBCiocsAxm8yLNqhcg3w44CN +9G/3pylgu7xYSEXHYnnlxsk0MHxDFZ4NTo0UBuyIuozoePIS63GvsyBsBzKzO/RZ +NsnPm3klZ4QnT3dIe0eRtCyu/prRmEMD/zC20fRcAuiG7jyV9NB/9mbLeDjAAngW +1UhrWpAMiObQZN4h5+ofc0EXFHVvOWaqBmYXlNlEeQIDAQABo1AwTjAOBgNVHQ8B +Af8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB +/wQCMAAwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQsFAAOCAgEAFT1PFimD +KGg8fVjuUO9IXf12LA7c+M6Eltyz22Ffxgopl1eHi4xHEfU94ueUAmODrag7Rc4E +VmvrMFIsuFrX/xYjiu2gpHPOP2nQjNRAwKDU0gr2bZ+y98EBtlYO/aFMmYCxJr7B +6esyA7I/cwLTxaNoTh67VTdPhfDmuEshoQn7Mtop38suevU5YBMTmUl7cp8bVdib +j7UkTq0oRKmAchMAz3W0TgGw9ZKJzU6zEck/3Csz5RWlTI9HV7R7J8aGEIeHGf/i +G+tfg0T8h+rQPkyCic5DIYuQzZ/P9pfJkedZuQU/mu0U/0IsNdkv9NX/4RQazu/Q +OzQ71FOO2HR/S3hcLzS1Iy2zrHbARwji/Sr95gVE1Z4QCK2xSvyy9aqzHwRfc4SX +AzaJhkACCnY7VDK6WJW7jnfkYco+l0tczDkyPjE7h3wP35tCuAZAvGkcrIbBL4oR +8bnwYAOqiG0cPBmFDBYW7v19qIspw5XDjfMu4YEHon7pYdiKK0Brf0iL+Ep4b1oB +8uvAysbc2Z/gIj1AsfnwSnrzcvzO6H1oCye277cSn2Z/ebiBaQi+kR3mubX96aPy +bFc9Xb11/y0Y7kYmJ3ifHDJkWerpz5bWEm2KDq1qsFRH9zUMEVfJAXThITawqfuG +3UBYWv8RePLnRbbnPuSaO9slNCoKl3NLqyk= +-----END CERTIFICATE----- diff --git a/dockerfiles/dind/ee/daemon.json b/dockerfiles/dind/ee/daemon.json new file mode 100644 index 0000000000000000000000000000000000000000..bd4e5092627265798d5252600367fa9c47cc300f --- /dev/null +++ b/dockerfiles/dind/ee/daemon.json @@ -0,0 +1,10 @@ +{ + "experimental": true, + "debug": true, + "log-level": "info", + "insecure-registries": ["127.0.0.1"], + "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"], + "tls": true, + "tlscert": "/opt/pwd/certs/cert.pem", + "tlskey": "/opt/pwd/certs/key.pem" +} diff --git a/dockerfiles/dind/ee/key.pem b/dockerfiles/dind/ee/key.pem new file mode 100644 index 0000000000000000000000000000000000000000..dbae394893d9acc7c0c2b59734a4aa980c08636d --- /dev/null +++ b/dockerfiles/dind/ee/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJJwIBAAKCAgEAqqa5j0cHgK7WUOhxw3mAkgt1sUDsuoWpHN1WWuiRJXWxKFYz +nOzKmTT70yKfG/1vBztdImxBmdN78nqtdFLIhUImJnYZ9UqhIpWZxUqPPdj0qRmj +e1Tt9ublJyHHCanNztGDnbzQnafa6GWfyu8/Z7gt+3C8uvi6tqC69VmAMZ3CR3nV +Ity0AgvtqhASFS/pnfchPGpha2Gw4WzapiHACdippKgT2tD2vkHtRcakCOcyTk+a +ziYBdyW1nqhaZtGY8Qj07dHD2OJckNlPZcjFyZkuPkdMHpJGV8sGlNZiYtySSJTE +gTFplqE68eZi1Vji77uOIWHq3zpjfiJl0VquDQDy4uO9tvzi8wphZ/NX3VBzhcsY +Nk69t2+iOrDMlHyydJakvrYSezf7XEHt503kJvXXK7g2yaYyUK0JI6vQhRbMTRFw +U+hBTEafKA7rTgMh34LlXqnR+ZM+r6azYi30k6qmUhHkKJEZB08saWeIKwQQoqHL +AMZvMizaoXIN8OOAjfRv96cpYLu8WEhFx2J55cbJNDB8QxWeDU6NFAbsiLqM6Hjy +Eutxr7MgbAcyszv0WTbJz5t5JWeEJ093SHtHkbQsrv6a0ZhDA/8wttH0XALohu48 +lfTQf/Zmy3g4wAJ4FtVIa1qQDIjm0GTeIefqH3NBFxR1bzlmqgZmF5TZRHkCAwEA +AQKCAgBDSNmBFJBwvH7kB8JTQGThMIOHEAJGyMyVBPA3h9sy2eSv8s0G4pY/MhTY +ep4hext7znw6RlTXQfts79HUO4+0exBvucEiZfqCmFm44Fz6FcDhq6o5xpLM9t0D +QN4pgToUgadTWk8m2jgFyYvnh82IJ6Z5rUm8rrVvrJAKjO9uoLUpWXAf/sU6yVk7 +5Ho8wFdsYTRJjeg7XplPSIwtVMFTIIpC0cKCVEH1YikbiebDW+UJ23k+Lt4FDGk/ +1UFPqPSUlON9oWeG7DlzIzua9j6F7k+9Xn80zpfNpc9CgATq1e0XkRCpn8HyEkAb +gKsXU6SmwVyY7PKecXcpFIbwtMBK2zTG4VrmgsjwptK1S8lbqYftQeTxvNYdhjxA +gdkBG5qIBkLcr8m796V2fDtJ6wvsVi+yDh+H7T8/vZuB9iaHJ3L1v36WiTODLTFW +/OlgfimiBXuK8Z1EiB6+w522TdmhKOiWfjHdl7JSzsOla5i5cbcdeaD4AUzlmvGZ +RCBE9Cd7RWGmDxnWz4NWFepwSfnOOQI9W95QkcRgwH61Y2axcdio0xJpQnUXiKHH +rHhPTW0eDD7yoIqqKKK3evCOxpbJy6M/+fVqNZYWEfJ0cb7+Ska6aW3rUv8aeYFj +xzitqKuL/0nFKpeppAkvXvoZf/mM0QtG+lgUHgOngwweYrrkwQKCAQEA3JtXFZDQ +mIfkv0mAiwV5QbzQ63OxkO0MtPqSq50I8F6S+fIz+ILhxbMjcGq5dCbnJCFGJqn3 +7PXrT6nFXZ8j2/dcXmtxala2VAAq+GyA0TY/DQ6seTaKhsLq50vnzMXHT0pU8s/2 +4n7euf66lzQ1ByKrqXZCAyNajUXPoL37HFgFtCrEJlvi//K8x+tHr3QgF4Si8l31 +A1HLq2+KbppWXzc//knanstsCIxPvEelV0GZn3r5opiOczS30rYo87wKI9aCRgLZ +GEKrMwlNVwwhScJd4msEYMsXUUxzDcNr5oi+iQmEDJpBKd98+3/Sp9XWVXUbik9a +QfOvUcQMfDc1pwKCAQEAxgeiaYBb369Z6CW7rC3b3YnwOBJVK23PYcpN2DtnhRRI +ARZgZBhwYKxDQ3djXZCiPEVtwO4WO8fCcY0GUFP2aVWuaokGjk1gNFwN6F046OdY +WGETEe7AUCLuuwAv7Aqqug3Y6bxCtPGN3MNHT8qjTH99EMHx8L2+0UiIXnQreGmH +VL/HEnpfDDZK4nfrwxdJOSueGdyOlflUIpDgmScIbKvIsyKhB2UstFBsCuDzhfE/ +a0VWDnZHgZPA/JhyhRy5eL9QGOqsdnzSxgvEbOyCR5p2jtO9otFw9fxpxF7uA0Yq +EBye0gidmnF/FKDNK0iggtk34LTrDv2fz4tclXM43wKCAQBY79NC4XgHFjoVGBfX +dCR3aRy8346Fg9uslpxMzWlOJoKQe6GSHYnRvy+ZYZ1Ug16KBVQXwEwwXtA39JSZ +8s9tHaNCeYRmv4CQCuVH885XCcyPggvsbh2YyLoU91gDCPUaNThcD5VTqJw4VcZ5 +sNV0A/k6v29LfpRCAhP7lLvIqH/cK6WaZU71qrGK04K57FIHyTQ8C778UJyQh85C +WrxZdJe696FIhXAPXinDGQtCSzMYxWYgs+ox7d3x9/g4kuVvn0oz2XAWRMJqN+TT +JBPDfbWF02kXcKj84Jo9wTwd26Ec9BYlUobUz8G+TsDpYt8e4rBwqR8VGZ3jk+sI +pOVfAoIBAA78xO33KPzk6IkJUgrV7a32opeby5Zd2TQte3bCCDOqNUjfyKvKrbaj +UvPoNTz/lUe6eXQAkO41UCIH6lJqCFwwf+LQPA7JDF7qGKNdatE1sRn/PtI8n5Fx +E2BTw0y6AfHS2nfWJ7ZKEdKDdQI08+b2PyDljMoLkkWEl82OPTv/wJ5JZWegm1Dx +SvmY2d8KBCCvjGeoqaHwHM4A6P6uVZTj62yjUkyc+6Up8QNhwwyAFayosrqleQP1 +isWTRBeO9PqOgCFioWrWR511hog33iRNLGvi2pdYApSbZeXWyWy2Arj1cY+z1zm5 +HUUSZnTAKmW8yt3W03Nu/olWossszUECggEAD+dqDccmWF30yg82mxIMPb8pMV27 ++ciQssiibGmhFvPcIfzish9FunXqLG7q+4M4M+O4WQ9unuaTH+z9TU7w3Foo4Xdf +GePuwmZdpuYxClHAsNALuKWEJcjfFOdETLkAbk81+ghtyFblkPPI82wofs4K8OII +1KPPDKoxeXmKXVF1UmOJX1KFyMnEjv0+Z1GrHnNV4703cNTMpDybaGpHsE77Vqd0 +ToZY9VG9eDLzaB6n5emSyFGBG73WQFU4EbLKjEBxtthgu8J9b17x96eF1NGZsEl1 +wEJvZpg7v6wyHK5XcYpwLY19+0khtvXwA7KKEr+sHqzF6arIqhl5hDLDAQ== +-----END RSA PRIVATE KEY----- diff --git a/dockerfiles/dind/ee/ucp-cert.pem b/dockerfiles/dind/ee/ucp-cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..7e124b1180aff1188f846863cb15b3847effaabd --- /dev/null +++ b/dockerfiles/dind/ee/ucp-cert.pem @@ -0,0 +1,63 @@ +-----BEGIN CERTIFICATE----- +MIIGPDCCBSSgAwIBAgISA4MIK4JV9npV+QdQS7wVa48rMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODAzMzEyMTQ3MjZaFw0x +ODA2MjkyMTQ3MjZaMDQxMjAwBgNVBAMMKSouZGlyZWN0LmJldGEtaHlicmlkLnBs +YXktd2l0aC1kb2NrZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA6PQCi9Rqr7Ka1KXSGCfBQVzgPyx/hh+uST1dz7PDw2epghYyaqNByaQEVKNR +3ubPvOoASzhdJ1dZdyUzKUoU/jm8hgVK7HHdQDpFEX60az+r4Xo32R6WirG5+GXd +hU3M0yRzbu0zZx7eVZognP/HcXJDhuf16hiHKmCr6MYXV4JY9xLMxExZOTB4fpGA +Loiyvn2OEZAhREhiSX+6n4x7KJga8gYn/0f89o7up1DYQSwev+gQgRjTGlo1xrgu +Oztekc3ydvbhGv7aL7Uj/zqPcVvXnDfnioQV7kEDcz8gupFyV7gZKolR1G8IQJdm +TaYHguzFXF5Q3lKVWx19/CSZ8wIDAQABo4IDMDCCAywwDgYDVR0PAQH/BAQDAgWg +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0G +A1UdDgQWBBTVloZoUI5vKAN+D1PTgtYBgU184zAfBgNVHSMEGDAWgBSoSmpjBH3d +uubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYIKwYBBQUHMAGGImh0dHA6 +Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYIKwYBBQUHMAKGI2h0dHA6 +Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMDQGA1UdEQQtMCuCKSouZGly +ZWN0LmJldGEtaHlicmlkLnBsYXktd2l0aC1kb2NrZXIuY29tMIH+BgNVHSAEgfYw +gfMwCAYGZ4EMAQIBMIHmBgsrBgEEAYLfEwEBATCB1jAmBggrBgEFBQcCARYaaHR0 +cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwgasGCCsGAQUFBwICMIGeDIGbVGhpcyBD +ZXJ0aWZpY2F0ZSBtYXkgb25seSBiZSByZWxpZWQgdXBvbiBieSBSZWx5aW5nIFBh +cnRpZXMgYW5kIG9ubHkgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBDZXJ0aWZpY2F0 +ZSBQb2xpY3kgZm91bmQgYXQgaHR0cHM6Ly9sZXRzZW5jcnlwdC5vcmcvcmVwb3Np +dG9yeS8wggEDBgorBgEEAdZ5AgQCBIH0BIHxAO8AdQDbdK/uyynssf7KPnFtLOW5 +qrs294Rxg8ddnU83th+/ZAAAAWJ+PniYAAAEAwBGMEQCIDngZdWcYWY0fPfUGTqX +/Vt2qx+PRN5DN+m13TnA37e2AiBHIi5kMSxlvKNc3xzuJrvt/RKaj9xsBLmc8+uW +ckaEdAB2ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABYn4+eLUA +AAQDAEcwRQIhAMkf8SYdt1egjzBE6nzOrY+f4WMS/N6XWN+gFl0mQIkhAiBn9+GG +0XbLw33+WNJLUkau2ZdTo5kTw2qdUXdYpWJwrDANBgkqhkiG9w0BAQsFAAOCAQEA +TAl62gFi+2l/yLItjNIrXeWh2ICH/epjeWlmF+rAb7Sb4iz9U8fsNBdDBQh25xJo +6nLOlS2NG0hdUScylCYyGJZe6PeQvGO+qSLDamXf1DvXWvzbmQOCUkejgD7Uwbol +5huuCAKoW4SsiaMku0J3545MEQx4Q5cPetsPawaByY5sgr2GZJzgM7lvtzr4hKWg +x5QAns/bmcqe9LCJ2NLcgArliYu6dOHtS62kB7/Dz2DQRtCvpV553RaBe4k9Ruwl +0ndHvjEC5OWa5sW1hwow5W3PC7Db7s0zqpt63EITkhrUOqtqtkwOMYBAkFIIe1eR +T5fSFAdirKUOt5GnRJ40qw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/dockerfiles/dind/ee/ucp-key.pem b/dockerfiles/dind/ee/ucp-key.pem new file mode 100644 index 0000000000000000000000000000000000000000..e61bf3b72c59ab4e391d1556a5bffa1c621519d2 --- /dev/null +++ b/dockerfiles/dind/ee/ucp-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDo9AKL1GqvsprU +pdIYJ8FBXOA/LH+GH65JPV3Ps8PDZ6mCFjJqo0HJpARUo1He5s+86gBLOF0nV1l3 +JTMpShT+ObyGBUrscd1AOkURfrRrP6vhejfZHpaKsbn4Zd2FTczTJHNu7TNnHt5V +miCc/8dxckOG5/XqGIcqYKvoxhdXglj3EszETFk5MHh+kYAuiLK+fY4RkCFESGJJ +f7qfjHsomBryBif/R/z2ju6nUNhBLB6/6BCBGNMaWjXGuC47O16RzfJ29uEa/tov +tSP/Oo9xW9ecN+eKhBXuQQNzPyC6kXJXuBkqiVHUbwhAl2ZNpgeC7MVcXlDeUpVb +HX38JJnzAgMBAAECggEAVqm4bMa4bea3HRcXYu8fQS7JKhdm1cHhd9PBm6yXzpE5 +CXEyjmNv7RD8n3Qm2BLsA67WLyWn2iPv35hSQTETQETAcudzKSVvFx7WZRzLB/8m +9XofXsG3ZZ+avONAlwALjB1KaGEMN3fPZO8y5NVvIDBPGNggr1cyqbxPGAjh1Cav +Laqki0rdPfr3FhxTyPBdmBFDcaMLc77Yl/7rmQJRYWb1qe+g4SEG4xXmEYpcpSUz +zDJZAkY5XAO5cHU5EoKgKJedVBNxqAaRtaisO9yv+CKMqD83hAWhXqeK1bSphghs +2qIkzNe134ZNUBbmK2FDsAbiPMHNcMKuI4ljfb78iQKBgQD5oZ/uzaYTt6ZQQzKq +rQFA2DxSlBt4Ewae5n6JYzw0hIjRf7LvitZF9zKXcMkHP2QcL+5RiibyJ6ohGypa +jpDP+m5e0B5tS6gEgFzBnrXWbjnrDxUR5Qj0lKg3uuOXw8OdwNxn+MulKkIfGyTW +pCu7G1nh/kltwvN87s4cJycwnwKBgQDu5XUyIcok8nxcBwtxu3zFdtdNn+P4Yq1a +W2sUEUEJUDwcUZqksPIxQhG/SMEEtBqii+EJj3nAlaWItBgTE37mzKGyKv16ZiM1 +hr+Rlv5AURxER+Eo4JLFqULZKwMaDlXDrFdV2ulF+6SXWOqKrp4/6sPYxtxHmKfs +oBnXq/4yLQKBgCQFl5+NG2cC/EPevoP0fRbPXT0JVEFqdW0ek6ndoQVvDpM0myyH +202zUyCZTNj348lRfVFU3zPYV2t5kQ4KPolUePLDk3BwF2m24CusbE7qDv+FaKPx +ae5pOTD5jfgLbsHn36Y9N5240FvOve0fOZRBaSH8YLovBJXFnAZh+/y/AoGALZzQ +CJddAjruNZ/+tmNmykkLiL2riERG9waXZkh5E28nWvzVuvYx9+e2fcBFYkGFCF4O +xIWJaJTp+zTvl8zUIPsXMG524UTZGiI1N3YN63fRHtRekDB4tZbAtbg5qmLsSyT/ +s9vNSFhor6EBfyMiAfAwHpaxflYOUearqHslWK0CgYEAzi/B0azCOaDqzpp6RhAL +rhTRFfu2HR8wN8EJLOSbBbUnlSSJHdnHJBwyyXe3shD/rETLV8dHx+6/k47e1l2d +MUlsad/dOKQyL2pY7UodBzPJkIkmwknDnKzioGety8Tb98oUSTQ8oHfHMuRBOie9 +mq1MSTuZyZtsdSXnFhH3qNc= +-----END PRIVATE KEY----- diff --git a/dockerfiles/dind/motd b/dockerfiles/dind/motd new file mode 100644 index 0000000000000000000000000000000000000000..2c1701b41730b498ca8a9ed2063ee904baeef1b2 --- /dev/null +++ b/dockerfiles/dind/motd @@ -0,0 +1,8 @@ +############################################################### +# WARNING!!!! # +# This is a sandbox environment. Using personal credentials # +# is HIGHLY! discouraged. Any consequences of doing so are # +# completely the user's responsibilites. # +# # +# The PWD team. # +############################################################### diff --git a/dockerfiles/dind/ssh_config b/dockerfiles/dind/ssh_config new file mode 100644 index 0000000000000000000000000000000000000000..f30d239b63c035071dfae5327657bc23b66edbc4 --- /dev/null +++ b/dockerfiles/dind/ssh_config @@ -0,0 +1,2 @@ +Host * + StrictHostKeyChecking no diff --git a/dockerfiles/dind/sudo b/dockerfiles/dind/sudo new file mode 100644 index 0000000000000000000000000000000000000000..637614a061fbd623a499d1234f54741a4a7b323a --- /dev/null +++ b/dockerfiles/dind/sudo @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This is shim to help with the case were pasted commands from a readme assume you are not root. Since this isto be run by root, it should effectively be a dummy command that allows the parameters to pass through. + +exec "$@" diff --git a/dockerfiles/dind/ucp-beta.sh b/dockerfiles/dind/ucp-beta.sh new file mode 100644 index 0000000000000000000000000000000000000000..33b9c1b4f9aa0bfdc035e7e12375704dd6cb718c --- /dev/null +++ b/dockerfiles/dind/ucp-beta.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +set -e + +function wait_for_url { + # Wait for docker daemon to be ready + while ! curl -k -sS $1 > /dev/null; do + sleep 1; + done +} + +function deploy_ucp { + wait_for_url "https://localhost:2376" + + docker config create com.docker.ucp.config $HOME/ucp-config.toml + + docker run --rm -i --name ucp \ + -v /var/run/docker.sock:/var/run/docker.sock \ + docker/ucp:3.2.3 install --debug --force-insecure-tcp --skip-cloud-provider-check \ + --san *.direct.${PWD_HOST_FQDN} \ + --license $(cat $HOME/workshop_beta.lic) \ + --swarm-port 2375 \ + --existing-config \ + --admin-username admin \ + --admin-password admin1234 + + rm $HOME/workshop_beta.lic $HOME/ucp-config.toml + echo "Finished deploying UCP" +} + +function get_instance_ip { + ip -o -4 a s eth1 | awk '{print $4}' | cut -d '/' -f1 +} + +function get_node_routable_ip { + curl -sS https://${PWD_HOST_FQDN}/sessions/${SESSION_ID} | jq -r '.instances[] | select(.hostname == "'$1'") | .routable_ip' +} + +function get_direct_url_from_ip { + local ip_dash="${1//./-}" + local url="https://ip${ip_dash}-${SESSION_ID}.direct.${PWD_HOST_FQDN}" + echo $url +} + +function deploy_dtr { + if [ $# -lt 1 ]; then + echo "DTR node hostname" + return + fi + + + local dtr_ip=$(get_node_routable_ip $1) + local ucp_ip=$(get_instance_ip) + + local dtr_url=$(get_direct_url_from_ip $dtr_ip) + local ucp_url=$(get_direct_url_from_ip $ucp_ip) + + docker run -i --rm docker/dtr:2.7.3 install \ + --dtr-external-url $dtr_url \ + --ucp-node $1 \ + --ucp-username admin \ + --ucp-password admin1234 \ + --ucp-insecure-tls \ + --ucp-url $ucp_url +} + +function setup_dtr_certs { + if [ $# -lt 1 ]; then + echo "DTR node hostname is missing" + return + fi + + + local dtr_ip=$(get_node_routable_ip $1) + local dtr_url=$(get_direct_url_from_ip $dtr_ip) + local dtr_hostname="${dtr_url/https:\/\/}" + + wait_for_url "$dtr_url/ca" + + curl -kfsSL $dtr_url/ca -o /usr/local/share/ca-certificates/$dtr_hostname.crt + update-ca-certificates +} + + +case "$1" in + deploy) + deploy_ucp + deploy_dtr $2 + setup_dtr_certs $2 + ;; + setup-certs) + setup_dtr_certs $2 + ;; + *) + echo "Illegal option $1" + ;; +esac + diff --git a/dockerfiles/dind/ucp-config.toml b/dockerfiles/dind/ucp-config.toml new file mode 100644 index 0000000000000000000000000000000000000000..ba78e3509ebf844f821a521b07146cf6b8f1399a --- /dev/null +++ b/dockerfiles/dind/ucp-config.toml @@ -0,0 +1,2 @@ +[cluster_config] + custom_kubelet_flags = ["--http-check-frequency=20s", "--containerized=false"] diff --git a/dockerfiles/dind/ucp.sh b/dockerfiles/dind/ucp.sh new file mode 100644 index 0000000000000000000000000000000000000000..9a6ce417401f7bf514c269ad150d02de8d0c4a5b --- /dev/null +++ b/dockerfiles/dind/ucp.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +set -e + +function wait_for_url { + # Wait for docker daemon to be ready + while ! curl -k -sS $1 > /dev/null; do + sleep 1; + done +} + +function deploy_ucp { + wait_for_url "https://localhost:2376" + docker run --rm -i --name ucp \ + -v /var/run/docker.sock:/var/run/docker.sock \ + docker/ucp:3.2.3 install --debug --force-insecure-tcp --skip-cloud-provider-check \ + --san *.direct.${PWD_HOST_FQDN} \ + --license $(cat $HOME/workshop_beta.lic) \ + --swarm-port 2375 \ + --admin-username admin \ + --admin-password admin1234 + + rm $HOME/workshop_beta.lic + echo "Finished deploying UCP" +} + +function get_instance_ip { + ip -o -4 a s eth1 | awk '{print $4}' | cut -d '/' -f1 +} + +function get_node_routable_ip { + curl -sS https://${PWD_HOST_FQDN}/sessions/${SESSION_ID} | jq -r '.instances[] | select(.hostname == "'$1'") | .routable_ip' +} + +function get_direct_url_from_ip { + local ip_dash="${1//./-}" + local url="https://ip${ip_dash}-${SESSION_ID}.direct.${PWD_HOST_FQDN}" + echo $url +} + +function deploy_dtr { + if [ $# -lt 1 ]; then + echo "DTR node hostname" + return + fi + + + local dtr_ip=$(get_node_routable_ip $1) + local ucp_ip=$(get_instance_ip) + + local dtr_url=$(get_direct_url_from_ip $dtr_ip) + local ucp_url=$(get_direct_url_from_ip $ucp_ip) + + docker run -i --rm docker/dtr:2.7.3 install \ + --dtr-external-url $dtr_url \ + --ucp-node $1 \ + --ucp-username admin \ + --ucp-password admin1234 \ + --ucp-insecure-tls \ + --ucp-url $ucp_url +} + +function setup_dtr_certs { + if [ $# -lt 1 ]; then + echo "DTR node hostname is missing" + return + fi + + + local dtr_ip=$(get_node_routable_ip $1) + local dtr_url=$(get_direct_url_from_ip $dtr_ip) + local dtr_hostname="${dtr_url/https:\/\/}" + + wait_for_url "$dtr_url/ca" + + curl -kfsSL $dtr_url/ca -o /usr/local/share/ca-certificates/$dtr_hostname.crt + update-ca-certificates +} + + +case "$1" in + deploy) + deploy_ucp + deploy_dtr $2 + setup_dtr_certs $2 + ;; + setup-certs) + setup_dtr_certs $2 + ;; + *) + echo "Illegal option $1" + ;; +esac + diff --git a/dockerfiles/dind/workshop.lic b/dockerfiles/dind/workshop.lic new file mode 100644 index 0000000000000000000000000000000000000000..05a8892a9afeda079011394f2010b4a4774ff6e6 --- /dev/null +++ b/dockerfiles/dind/workshop.lic @@ -0,0 +1 @@ +{"key_id":"B3T_Uirjs-tpcGd4Tql8HL--kDo1iTOUaVUFNMhEXM1Z","private_key":"RbtCEoNZ4OBu-yIHNM1mGCJ6R_4SxF-ThghAd-I3b6_N","authorization":"ewogICAicGF5bG9hZCI6ICJleUpsZUhCcGNtRjBhVzl1SWpvaU1qQXhPUzB3TkMweU5GUXhPRG93TkRvek5Gb2lMQ0owYjJ0bGJpSTZJbU16U1VnMllWSjFWak00WjBWSVIwWXRVV1l0ZGxGM2MwMHdlR05vYnpoWE4xSklPRzFLYVRaT1VUUTlJaXdpYldGNFJXNW5hVzVsY3lJNk1UQXNJbk5qWVc1dWFXNW5SVzVoWW14bFpDSTZkSEoxWlN3aWJHbGpaVzV6WlZSNWNHVWlPaUpQWm1ac2FXNWxJaXdpZEdsbGNpSTZJbEJ5YjJSMVkzUnBiMjRpZlEiLAogICAic2lnbmF0dXJlcyI6IFsKICAgICAgewogICAgICAgICAiaGVhZGVyIjogewogICAgICAgICAgICAiandrIjogewogICAgICAgICAgICAgICAiZSI6ICJBUUFCIiwKICAgICAgICAgICAgICAgImtleUlEIjogIko3TEQ6NjdWUjpMNUhaOlU3QkE6Mk80Rzo0QUwzOk9GMk46SkhHQjpFRlRIOjVDVlE6TUZFTzpBRUlUIiwKICAgICAgICAgICAgICAgImtpZCI6ICJKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVCIsCiAgICAgICAgICAgICAgICJrdHkiOiAiUlNBIiwKICAgICAgICAgICAgICAgIm4iOiAieWRJeS1sVTdvN1BjZVktNC1zLUNRNU9FZ0N5RjhDeEljUUlXdUs4NHBJaVpjaVk2NzMweUNZbndMU0tUbHctVTZVQ19RUmVXUmlvTU5ORTVEczVUWUVYYkdHNm9sbTJxZFdiQndjQ2ctMlVVSF9PY0I5V3VQNmdSUEhwTUZNc3hEeld3dmF5OEpVdUhnWVVMVXBtMUl2LW1xN2xwNW5RX1J4clQwS1pSQVFUWUxFTUVmR3dtM2hNT19nZUxQUy1oZ0tQdElIbGtnNl9XY294VEdvS1A3OWRfd2FIWXhHTmw3V2hTbmVpQlN4YnBiUUFLazIxbGc3OThYYjd2WnlFQVRETXJSUjlNZUU2QWRqNUhKcFkzQ295UkFQQ21hS0dSQ0s0dW9aU29JdTBoRlZsS1VQeWJidzAwMEdPLXdhMktOOFV3Z0lJbTBpNUkxdVc5R2txNHpqQnk1emhncXVVWGJHOWJXUEFPWXJxNVFhODFEeEdjQmxKeUhZQXAtRERQRTlUR2c0elltWGpKbnhacUhFZHVHcWRldlo4WE1JMHVrZmtHSUkxNHdVT2lNSUlJclhsRWNCZl80Nkk4Z1FXRHp4eWNaZV9KR1gtTEF1YXlYcnlyVUZlaFZOVWRaVWw5d1hOYUpCLWthQ3F6NVF3YVI5M3NHdy1RU2Z0RDBOdkxlN0N5T0gtRTZ2ZzZTdF9OZVR2Z3Y4WW5oQ2lYSWxaOEhPZkl3TmU3dEVGX1VjejVPYlB5a20zdHlsck5VanQwVnlBbXR0YWNWSTJpR2loY1VQcm1rNGxWSVo3VkRfTFNXLWk3eW9TdXJ0cHNQWGNlMnBLRElvMzBsSkdoT18zS1VtbDJTVVpDcXpKMXlFbUtweXNINUhEVzljc0lGQ0EzZGVBamZaVXZON1UiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJhbGciOiAiUlMyNTYiCiAgICAgICAgIH0sCiAgICAgICAgICJzaWduYXR1cmUiOiAid2xrQUhLd0l1TUs5Y0N3YUdINVB1MW50dGNkLVk0SkNsRnpLeTZtcmJlTzR3eXFOenpwUi16TG4tMlhsYnJGZTdlczYtSklSREhmNzBGR3JRZl9MZEc3QVQ4bC1HRXVoUk1SaF8xVTlXd1BkOGdsWnNFem44VFUyeGtzU3lkWEw2WER3TUlqMHJUMFdpQm43T29YcEc0ZGJrUGgwLWxfY1VKQnphQzlwbEZ3ZXdmdF9Ocl80a0FpcUlNa3FJZHdQaU5XOVc4NERPUFdpZ2FrcTZTTnRtMVpqT2E1UG1ldHUydk1iUGpnTzFZM19tUVFsUldpakRwRUR1Rzl1dl9yNDFsN1I2LTdKQTB6SGpvdVVqdkxDREdIMUQ3eUxnR1RFMTlXN2FMRHI4ZE5FeXBjdi1vQzVmb3pqM19ISjUyWXVDS0RnazJzb3Y5YVFYOHhNTW5DWGU4Y3JIYjlEVG05eVcyd09FN0kxYVZLYXRKbjZrSGprM1FSWGVNbnRNQnJ6TGlzanBjZnlBYzdGNlc1YnBTSUtXaUQtd2o5QTRlY0FPbFNxc0NBS3lkaWxnR2lqQTNPY1dOZHhvV0NhV1MzaXFvakFBTE1JNHlsOFlpdG50ckVMVFNuUDFFS08wTGFaaTJxVURfU0lBSmFOUlRPTVIzblRqQUNwd1ZwYXAyU3lkOEZwc1pFVllTZFJVLWJVZDJybmN1ZHZfcC1XdFZpYWVsQ3BvTWstdURzWGhud2JyWFB6Y3dkVHVobmg0V2kxMmRTcjRUQ3ZMRktSMklCaklwam1VZWt4MFBTazlKUkNXc2R2bjY0dElCZnV6dVZSRkVkSVBidnBZd2pWOUZZc19VQWJvVE85a2E1OWZmNm1zOThHYXVTbE9sYnkwSWE0TlBxTTRKY2ZvSFUiLAogICAgICAgICAicHJvdGVjdGVkIjogImV5Sm1iM0p0WVhSTVpXNW5kR2dpT2pFM05Dd2labTl5YldGMFZHRnBiQ0k2SW1aUklpd2lkR2x0WlNJNklqSXdNVGd0TURVdE1UWlVNREU2TURNNk1qTmFJbjAiCiAgICAgIH0KICAgXQp9"} \ No newline at end of file diff --git a/dockerfiles/k8s/.bashrc b/dockerfiles/k8s/.bashrc new file mode 100644 index 0000000000000000000000000000000000000000..619998c253a315093c60e06dc8aef86217a55fb9 --- /dev/null +++ b/dockerfiles/k8s/.bashrc @@ -0,0 +1,3 @@ +export PS1="[\h \W]$ " +cat /etc/motd +echo $BASHPID > /var/run/cwd diff --git a/dockerfiles/k8s/Dockerfile b/dockerfiles/k8s/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..065a7541b9adcc39e9a517183dbda551df1d4783 --- /dev/null +++ b/dockerfiles/k8s/Dockerfile @@ -0,0 +1,44 @@ +FROM centos:7 + +COPY ./systemctl /usr/bin/systemctl +COPY ./kubernetes.repo /etc/yum.repos.d/ + + + +RUN yum install -y kubectl-1.27.2 kubeadm-1.27.2 kubelet-1.27.2 \ + #&& mv -f /etc/systemd/system/kubelet.service.d/10-kubeadm.conf /etc/systemd/system/kubelet.service \ + && yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo \ + && yum install -y docker-ce git bash-completion \ + && sed -i -e '4d;5d;8d' /lib/systemd/system/docker.service \ + && yum clean all + +RUN curl -Lf -o /usr/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 \ + && curl -Lf -o /usr/bin/docker-compose https://github.com/docker/compose/releases/download/1.21.0/docker-compose-$(uname -s)-$(uname -m) \ + && chmod +x /usr/bin/jq /usr/bin/docker-compose + + +VOLUME ["/var/lib/kubelet"] + +COPY ./kube* /etc/systemd/system/ +COPY ./wrapkubeadm.sh /usr/local/bin/kubeadm +COPY ./tokens.csv /etc/pki/tokens.csv +COPY ./daemon.json /etc/docker/ +COPY ./resolv.conf.override /etc/ +COPY ./docker.service /usr/lib/systemd/system/ +COPY ./.bashrc /root/ + +COPY motd /etc/motd + + +RUN echo 'source <(kubectl completion bash)' >>~/.bashrc \ + && kubectl completion bash >> /etc/bash_completion.d/kubectl + +RUN mkdir -p /root/.kube && ln -s /etc/kubernetes/admin.conf /root/.kube/config \ + && rm -f /etc/machine-id + +WORKDIR /root + +CMD mount --make-shared / \ + && systemctl start docker \ + && systemctl start kubelet \ + && while true; do script -q -c "/bin/bash -l" /dev/null; done diff --git a/dockerfiles/k8s/daemon.json b/dockerfiles/k8s/daemon.json new file mode 100644 index 0000000000000000000000000000000000000000..b65a922fc1f9535277fc10ea07a74331addd988a --- /dev/null +++ b/dockerfiles/k8s/daemon.json @@ -0,0 +1,14 @@ +{ + "experimental": true, + "debug": true, + "cri-containerd": true, + "log-level": "info", + "tls": false, + "insecure-registries": [ + "127.0.0.1" + ], + "hosts": [ + "unix:///var/run/docker.sock", + "tcp://0.0.0.0:2375" + ] +} diff --git a/dockerfiles/k8s/docker.service b/dockerfiles/k8s/docker.service new file mode 100644 index 0000000000000000000000000000000000000000..2a68fe43a90ce82eedf995cb618f991bdd6b4aff --- /dev/null +++ b/dockerfiles/k8s/docker.service @@ -0,0 +1,30 @@ +[Unit] +Description=Docker Application Container Engine +Documentation=https://docs.docker.com + +[Service] +# the default is not to use systemd for cgroups because the delegate issues still +# exists and systemd currently does not support the cgroup feature set required +# for containers run by docker +ExecStart=/usr/bin/dockerd +ExecReload=/bin/kill -s HUP $MAINPID +# Having non-zero Limit*s causes performance problems due to accounting overhead +# in the kernel. We recommend using cgroups to do container-local accounting. +LimitNOFILE=infinity +LimitNPROC=infinity +LimitCORE=infinity +# Uncomment TasksMax if your systemd version supports it. +# Only systemd 226 and above support this version. +#TasksMax=infinity +TimeoutStartSec=0 +# set delegate yes so that systemd does not reset the cgroups of docker containers +Delegate=yes +# kill only the docker process, not all processes in the cgroup +KillMode=process +# restart the docker process if it exits prematurely +Restart=on-failure +StartLimitBurst=3 +StartLimitInterval=60s + +[Install] +WantedBy=multi-user.target diff --git a/dockerfiles/k8s/kubelet.env b/dockerfiles/k8s/kubelet.env new file mode 100644 index 0000000000000000000000000000000000000000..093a47ee79c7aecbafd8bba291920974c82d1820 --- /dev/null +++ b/dockerfiles/k8s/kubelet.env @@ -0,0 +1,6 @@ +KUBELET_KUBECONFIG_ARGS="--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" +KUBELET_SYSTEM_PODS_ARGS="--pod-manifest-path=/etc/kubernetes/manifests" +KUBELET_DNS_ARGS="--cluster-dns=10.96.0.10 --cluster-domain=cluster.local" +KUBELET_AUTHZ_ARGS="--authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt" +KUBELET_CGROUP_ARGS="--cgroup-driver=cgroupfs" +KUBELET_EXTRA_ARGS="--fail-swap-on=false --resolv-conf=/etc/resolv.conf.override --container-runtime-endpoint /run/docker/containerd/containerd.sock " diff --git a/dockerfiles/k8s/kubelet.service b/dockerfiles/k8s/kubelet.service new file mode 100644 index 0000000000000000000000000000000000000000..c5e29428b60bdd6df3aaa0d30d8d590992d524e2 --- /dev/null +++ b/dockerfiles/k8s/kubelet.service @@ -0,0 +1,4 @@ +[Service] +Restart=always +EnvironmentFile=/etc/systemd/system/kubelet.env +ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS $KUBELET_CGROUP_ARGS $KUBELET_EXTRA_ARGS diff --git a/dockerfiles/k8s/kubernetes.repo b/dockerfiles/k8s/kubernetes.repo new file mode 100644 index 0000000000000000000000000000000000000000..89f4873d0f54183ba33d3ecf51568a80d21167c4 --- /dev/null +++ b/dockerfiles/k8s/kubernetes.repo @@ -0,0 +1,7 @@ +[kubernetes] +name=Kubernetes +baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/ +enabled=1 +gpgcheck=1 +gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key +exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni diff --git a/dockerfiles/k8s/motd b/dockerfiles/k8s/motd new file mode 100644 index 0000000000000000000000000000000000000000..348b9c7e1312a69705f2bae04d4c2ea44bcda0de --- /dev/null +++ b/dockerfiles/k8s/motd @@ -0,0 +1,28 @@ + + WARNING!!!! + + This is a sandbox environment. Using personal credentials + is HIGHLY! discouraged. Any consequences of doing so, are + completely the user's responsibilites. + + You can bootstrap a cluster as follows: + + 1. Initializes cluster master node: + + kubeadm init --apiserver-advertise-address $(hostname -i) --pod-network-cidr 10.5.0.0/16 + + + 2. Initialize cluster networking: + + kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml + + + 3. (Optional) Create an nginx deployment: + + kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/application/nginx-app.yaml + + + The PWK team. + + + diff --git a/dockerfiles/k8s/resolv.conf.override b/dockerfiles/k8s/resolv.conf.override new file mode 100644 index 0000000000000000000000000000000000000000..cae093a833dbfe99c7ade2c81fc9500f482e649f --- /dev/null +++ b/dockerfiles/k8s/resolv.conf.override @@ -0,0 +1 @@ +nameserver 8.8.8.8 diff --git a/dockerfiles/k8s/systemctl b/dockerfiles/k8s/systemctl new file mode 100644 index 0000000000000000000000000000000000000000..c1c8b188fb8eaa8639e58ae8b46fa2f114162da2 --- /dev/null +++ b/dockerfiles/k8s/systemctl @@ -0,0 +1,281 @@ +#!/bin/bash + +function get_unit_file(){ + + local UNIT=$1 + + for DIR in ${UNIT_PATHS[@]} ; do + if [ -f "${DIR}${UNIT}" ] ; then + echo "${DIR}${UNIT}" + break + fi + done + +} + +function read_option(){ + local OPTION="$1" + local UNIT_FILE="$2" + local UNIT_INSTANCE="$3" + + local UNIT=`basename $UNIT_FILE` + local UNIT_FULL=`echo $UNIT | sed "s/@/@$UNIT_INSTANCE/"` + + VALUE="$(grep '^'$OPTION'[= ]' "$UNIT_FILE" | cut -d '=' -f2- | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + VALUE="` + echo $VALUE | + sed -e "s/%[i]/$UNIT_INSTANCE/g" \ + -e "s/%[I]/\"$UNIT_INSTANCE\"/g" \ + -e "s/%[n]/$UNIT_FULL/g" \ + -e "s/%[N]/\"$UNIT_FULL\"/g" + `" + # TODO: Add more options from: + # https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers + + echo $VALUE +} + +function get_unit_wants() { + + local UNIT_FILE=$1 + local UNIT=`basename $UNIT_FILE` + + sort -u <<< `( + # Print wants from UNIT_PATHS + for DIR in ${UNIT_PATHS[@]} ; do + if [ -d "${DIR}${UNIT}.wants" ] ; then + ls -1 "${DIR}${UNIT}.wants/" | tr '\n' ' ' + fi + done + + # Print wants from unit-file + read_option Wants $UNIT_FILE + )` +} + +function action_start(){ + + # Find depended services + local UNIT_FILE=$1 + local UNIT_WANTS=(`get_unit_wants $1`) + local UNIT_INSTANCE=$2 + + # Start depended services + for UNIT in ${UNIT_WANTS[@]}; do + exec_action start $UNIT + done + + # Load options + local User=`read_option User $UNIT_FILE $UNIT_INSTANCE` + local Type=`read_option Type $UNIT_FILE $UNIT_INSTANCE` + local EnvironmentFile=`read_option EnvironmentFile $UNIT_FILE $UNIT_INSTANCE` + local ExecStartPre=(`read_option ExecStartPre $UNIT_FILE $UNIT_INSTANCE`) + local ExecStart=`read_option ExecStart $UNIT_FILE $UNIT_INSTANCE` + local ExecStartPost=(`read_option ExecStartPost $UNIT_FILE $UNIT_INSTANCE`) + local Restart=(`read_option Restart $UNIT_FILE $UNIT_INSTANCE`) + local RestartSec=(`read_option RestartSec $UNIT_FILE $UNIT_INSTANCE`) + RestartSec=${RestartSec:=5} + + [ -f "$EnvironmentFile" ] && source "$EnvironmentFile" + + # Start service + if [ -z $Type ] || [[ "${Type,,}" == *"simple"* ]] ; then + if [ "$Restart" == "always" ]; then + COMMAND='nohup bash -c "while true ; do '"$ExecStart"'; sleep $RestartSec; done" &>/dev/null &' + else + COMMAND='nohup '"$ExecStart"' >>/dev/null 2>&1 &' + fi + elif [[ "${Type,,}" == *"forking"* ]] || [[ "${Type,,}" == *"oneshot"* ]] ; then + COMMAND="$ExecStart" + else + >&2 echo "Unknown service type $Type" + fi + + #[ -z $User ] || COMMAND="su $User -c \"$COMMAND\"" + + while IFS=$'\n' read -a i; do + eval $i + done <<< "${ExecStartPre[@]}" + + eval "$COMMAND" + + while IFS=$'\n' read -a i; do + eval $i + done <<< "${ExecStartPost[@]}" +} + +function action_stop(){ + + # Find depended services + local UNIT_FILE=$1 + local UNIT_WANTS=(`get_unit_wants $1`) + local UNIT_INSTANCE=$2 + + # Load options + local User=`read_option User $UNIT_FILE $UNIT_INSTANCE` + local Type=`read_option Type $UNIT_FILE $UNIT_INSTANCE` + local EnvironmentFile=`read_option EnvironmentFile $UNIT_FILE $UNIT_INSTANCE` + local ExecStopPre=(`read_option ExecStartPre $UNIT_FILE $UNIT_INSTANCE`) + local ExecStop=`read_option ExecStop $UNIT_FILE $UNIT_INSTANCE` + local ExecStopPost=(`read_option ExecStartPost $UNIT_FILE $UNIT_INSTANCE`) + local ExecStart=`read_option ExecStart $UNIT_FILE $UNIT_INSTANCE` + + [ -f "$EnvironmentFile" ] && source "$EnvironmentFile" + + # Stop service + if [ -z $ExecStop ] ; then + COMMAND="kill -TERM \$(pgrep -f \"$ExecStart\")" + else + COMMAND="$ExecStop" + fi + + #[ -z $User ] || COMMAND="su $User -c \"$COMMAND\"" + + while IFS=$'\n' read -a i; do + eval $i + done <<< "${ExecStopPre[@]}" + + eval "$COMMAND" + + while IFS=$'\n' read -a i; do + eval $i + done <<< "${ExecStopPost[@]}" +} + +function action_restart(){ + local UNIT_FILE=$1 + local UNIT_INSTANCE=$2 + + action_stop $UNIT_FILE $UNIT_INSTANCE + action_start $UNIT_FILE $UNIT_INSTANCE +} + + +function action_enable(){ + + local UNIT_FILE=$1 + local UNIT=`basename $UNIT_FILE` + local UNIT_INSTANCE=$2 + local UNIT_FULL=`echo $UNIT | sed "s/@/@$UNIT_INSTANCE/"` + + local WantedBy=`read_option WantedBy $UNIT_FILE` + + if [ -z $WantedBy ] ; then + >&2 echo "Unit $UNIT have no WantedBy option." + exit 1 + fi + + local WANTEDBY_DIR="/etc/systemd/system/$WantedBy.wants" + + if [ ! -f "$WANTEDBY_DIR/$UNIT_FULL" ] ; then + mkdir -p $WANTEDBY_DIR + echo Created symlink from $WANTEDBY_DIR/$UNIT_FULL to $UNIT_FILE. + ln -s $WANTEDBY_DIR/$UNIT_FULL $UNIT_FILE + fi + +} + +function action_disable(){ + + local UNIT_FILE=$1 + local UNIT=`basename $UNIT_FILE` + local UNIT_INSTANCE=$2 + local UNIT_FULL=`echo $UNIT | sed "s/@/@$UNIT_INSTANCE/"` + + local WantedBy=`read_option WantedBy $UNIT_FILE` + + if [ -z $WantedBy ] ; then + >&2 echo "Unit $UNIT have no WantedBy option." + exit 1 + fi + + local WANTEDBY_DIR="/etc/systemd/system/$WantedBy.wants" + + if [ -f "$WANTEDBY_DIR/$UNIT_FULL" ] ; then + echo Removed $WANTEDBY_DIR/$UNIT_FULL. + rm -f $WANTEDBY_DIR/$UNIT_FULL. + rmdir --ignore-fail-on-non-empty $WANTEDBY_DIR + fi + +} + +function action_status(){ + + # Find depended services + local UNIT_FILE=$1 + local UNIT_WANTS=(`get_unit_wants $1`) + local UNIT_INSTANCE=$2 + + local ExecStart=`read_option ExecStart $UNIT_FILE $UNIT_INSTANCE` + + + COMMAND="pgrep -f \"$ExecStart\" &>/dev/null" + + + if eval "$COMMAND"; then + exit 0 + fi + + >&2 echo "Loaded: not-found" + exit 1 +} + +function action_is_enabled(){ + exit 0 +} + +function action_is_active(){ + local UNIT=`basename $1` + if systemctl status $UNIT ; then + >&2 echo "active" + exit 0 + fi + + exit 1 + +} + +function exec_action(){ + + local ACTION=$1 + local UNIT=$2 + + [[ $UNIT =~ '.' ]] || UNIT="$UNIT.service" + + if [[ $UNIT =~ '@' ]] ; then + local UNIT_INSTANCE=`echo $UNIT | cut -d'@' -f2- | cut -d. -f1` + local UNIT=`echo $UNIT | sed "s/$UNIT_INSTANCE//"` + fi + + UNIT_FILE=`get_unit_file $UNIT` + + if [ -z $UNIT_FILE ] ; then + >&2 echo "Failed to $ACTION $UNIT: Unit $UNIT not found." + exit 1 + else + case "$ACTION" in + start ) action_start $UNIT_FILE $UNIT_INSTANCE ;; + stop ) action_stop $UNIT_FILE $UNIT_INSTANCE ;; + restart ) action_restart $UNIT_FILE $UNIT_INSTANCE ;; + enable ) action_enable $UNIT_FILE $UNIT_INSTANCE ;; + disable ) action_disable $UNIT_FILE $UNIT_INSTANCE ;; + status ) action_status $UNIT_FILE $UNIT_INSTANCE ;; + is-enabled ) action_is_enabled $UNIT_FILE $UNIT_INSTANCE ;; + is-active ) action_is_active $UNIT_FILE $UNIT_INSTANCE ;; + * ) >&2 echo "Unknown operation $ACTION." ; exit 1 ;; + esac + fi +} + +ACTION="$1" +UNITS="${@:2}" +UNIT_PATHS=( + /etc/systemd/system/ + /usr/lib/systemd/system/ +) + + +for UNIT in ${UNITS[@]}; do + exec_action $ACTION $UNIT +done diff --git a/dockerfiles/k8s/tokens.csv b/dockerfiles/k8s/tokens.csv new file mode 100644 index 0000000000000000000000000000000000000000..e7a4f92751462413d4a231aab4589ff555d2fd06 --- /dev/null +++ b/dockerfiles/k8s/tokens.csv @@ -0,0 +1 @@ +31ada4fd-adec-460c-809a-9e56ceb75269,pwd,pwd,"system:admin,system:masters" diff --git a/dockerfiles/k8s/wrapkubeadm.sh b/dockerfiles/k8s/wrapkubeadm.sh new file mode 100644 index 0000000000000000000000000000000000000000..2a78e4491fb633165812bc1c2339ce238979a66f --- /dev/null +++ b/dockerfiles/k8s/wrapkubeadm.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# Copyright 2017 Mirantis +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o pipefail +set -o errtrace + +apiserver_static_pod="/etc/kubernetes/manifests/kube-apiserver" + +# jq filters follow. + +# TODO: think about more secure possibilities +apiserver_anonymous_auth='.spec.containers[0].command|=map(select(startswith("--token-auth-file")|not))+["--token-auth-file=/etc/pki/tokens.csv"]' + +# Sets etcd2 as backend +apiserver_etcd2_backend='.spec.containers[0].command|=map(select(startswith("--storage-backend")|not))+["--storage-backend=etcd2"]' + +# Make apiserver accept insecure connections on port 8080 +# TODO: don't use insecure port +#apiserver_insecure_bind_port='.spec.containers[0].command|=map(select(startswith("--insecure-port=")|not))+["--insecure-port=2375"]' + + +# Update kube-proxy CIDR, enable --masquerade-all and disable conntrack (see dind::frob-proxy below) +function dind::proxy-cidr-and-no-conntrack { + cluster_cidr="$(ip addr show docker0 | grep -w inet | awk '{ print $2; }')" + echo ".items[0].spec.template.spec.containers[0].command |= .+ [\"--cluster-cidr=${cluster_cidr}\", \"--masquerade-all\", \"--conntrack-max-per-core=0\"]" +} + + +# Adds route to defualt eth0 interface so 10.96.x.x can go through +function dind::add-route { + ip route add 10.96.0.0/16 dev eth0 +} + + + +function dind::join-filters { + local IFS="|" + echo "$*" +} + +function dind::frob-apiserver { + local -a filters=("${apiserver_anonymous_auth}") + + dind::frob-file "${apiserver_static_pod}" "${filters[@]}" +} + +function dind::frob-file { + local path_base="$1" + shift + local filter="$(dind::join-filters "$@")" + local status=0 + if [[ -f ${path_base}.yaml ]]; then + dind::yq "${filter}" "${path_base}.yaml" || status=$? + else + echo "${path_base}.json or ${path_base}.yaml not found" >&2 + return 1 + fi + if [[ ${status} -ne 0 ]]; then + echo "Failed to frob ${path_base}.yaml" >&2 + return 1 + fi +} + +function dind::yq { + local filter="$1" + local path="$2" + # We need to use a temp file here because if you feed an object to + # 'kubectl convert' via stdin, you'll get a List object because + # multiple input objects are implied + tmp="$(mktemp tmp-XXXXXXXXXX.json)" + kubectl convert -f "${path}" --local -o json 2>/dev/null | + jq "${filter}" > "${tmp}" + kubectl convert -f "${tmp}" --local -o yaml 2>/dev/null >"${path}" + rm -f "${tmp}" +} + +function dind::frob-proxy { + # Trying to change conntrack settings fails even in priveleged containers, + # so we need to avoid it. Here's sample error message from kube-proxy: + # I1010 21:53:00.525940 1 conntrack.go:57] Setting conntrack hashsize to 49152 + # Error: write /sys/module/nf_conntrack/parameters/hashsize: operation not supported + # write /sys/module/nf_conntrack/parameters/hashsize: operation not supported + # + # Recipe by @errordeveloper: + # https://github.com/kubernetes/kubernetes/pull/34522#issuecomment-253248985 + local force_apply=--force + if ! kubectl version --short >&/dev/null; then + # kubectl 1.4 doesn't have version --short and also it doesn't support apply --force + force_apply= + fi + KUBECONFIG=/etc/kubernetes/admin.conf kubectl -n kube-system get ds -l k8s-app=kube-proxy -o json | + jq "$(dind::join-filters "$(dind::proxy-cidr-and-no-conntrack)")" | KUBECONFIG=/etc/kubernetes/admin.conf kubectl apply ${force_apply} -f - + + KUBECONFIG=/etc/kubernetes/admin.conf kubectl -n kube-system delete pods --now -l "k8s-app=kube-proxy" +} + + +function dind::wait-for-apiserver { + echo -n "Waiting for api server to startup" + local url="https://localhost:6443/api" + local n=60 + while true; do + if curl -k -s "${url}" >&/dev/null; then + break + fi + if ((--n == 0)); then + echo "Error: timed out waiting for apiserver to become available" >&2 + fi + echo -n "." + sleep 0.5 + done + echo "" +} + +function dind::frob-cluster { + #dind::frob-apiserver + dind::wait-for-apiserver + dind::frob-proxy +} + +# Weave depends on /etc/machine-id being unique +if [[ ! -f /etc/machine-id ]]; then + rm -f /etc/machine-id + systemd-machine-id-setup +fi + +if [[ "$@" == "init"* || "$@" == "join"* ]]; then +# Call kubeadm with params and skip flag + /usr/bin/kubeadm "$@" --ignore-preflight-errors all --cri-socket /run/docker/containerd/containerd.sock +else +# Call kubeadm with params + /usr/bin/kubeadm "$@" +fi + +# Frob cluster +if [[ "$@" == "init"* && $? -eq 0 && ! "$@" == *"--help"* ]]; then + dind::frob-cluster +else + dind::add-route +fi + diff --git a/dockerfiles/pwm/.gitconfig b/dockerfiles/pwm/.gitconfig new file mode 100644 index 0000000000000000000000000000000000000000..87b8f9be00f890c66e82b81fe6617ed7732ec11a --- /dev/null +++ b/dockerfiles/pwm/.gitconfig @@ -0,0 +1,2 @@ +[url "https://"] + insteadOf = git:// diff --git a/dockerfiles/pwm/.inputrc b/dockerfiles/pwm/.inputrc new file mode 100644 index 0000000000000000000000000000000000000000..6a5b035b1a092730381e34e8b8219c3407465b4f --- /dev/null +++ b/dockerfiles/pwm/.inputrc @@ -0,0 +1,73 @@ +# /etc/inputrc - global inputrc for libreadline +# See readline(3readline) and `info rluserman' for more information. + +# Be 8 bit clean. +set input-meta on +set output-meta on + +# To allow the use of 8bit-characters like the german umlauts, uncomment +# the line below. However this makes the meta key not work as a meta key, +# which is annoying to those which don't need to type in 8-bit characters. + +# set convert-meta off + +# try to enable the application keypad when it is called. Some systems +# need this to enable the arrow keys. +# set enable-keypad on + +# see /usr/share/doc/bash/inputrc.arrows for other codes of arrow keys + +# do not bell on tab-completion +# set bell-style none +# set bell-style visible + +# some defaults / modifications for the emacs mode +$if mode=emacs + +# allow the use of the Home/End keys +"\e[1~": beginning-of-line +"\e[4~": end-of-line + +# allow the use of the Delete/Insert keys +"\e[3~": delete-char +"\e[2~": quoted-insert + +# mappings for "page up" and "page down" to step to the beginning/end +# of the history +# "\e[5~": beginning-of-history +# "\e[6~": end-of-history + +# alternate mappings for "page up" and "page down" to search the history +# "\e[5~": history-search-backward +# "\e[6~": history-search-forward + +# mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving +"\e[1;5C": forward-word +"\e[1;5D": backward-word +"\e[5C": forward-word +"\e[5D": backward-word +"\e\e[C": forward-word +"\e\e[D": backward-word + +$if term=rxvt +"\e[7~": beginning-of-line +"\e[8~": end-of-line +"\eOc": forward-word +"\eOd": backward-word +$endif + +# for non RH/Debian xterm, can't hurt for RH/Debian xterm +# "\eOH": beginning-of-line +# "\eOF": end-of-line + +# for freebsd console +# "\e[H": beginning-of-line +# "\e[F": end-of-line + +$endif + +# faster completion +set show-all-if-ambiguous on + +"\e[A": history-search-backward +"\e[B": history-search-forward diff --git a/dockerfiles/pwm/.profile b/dockerfiles/pwm/.profile new file mode 100644 index 0000000000000000000000000000000000000000..3269f8b7da88876ca71c00d3f1c1ffc06830894c --- /dev/null +++ b/dockerfiles/pwm/.profile @@ -0,0 +1,5 @@ +export PS1='\e[1m\e[31m[\h] \e[32m\e[34m\u@$(hostname -i)\e[35m \w\e[0m\n$ ' +alias vi='vim' +export PATH=$PATH:/root/go/bin +cat /etc/motd +echo $BASHPID > /var/run/cwd diff --git a/dockerfiles/pwm/.vimrc b/dockerfiles/pwm/.vimrc new file mode 100644 index 0000000000000000000000000000000000000000..591902057a78b5b8abf12074d468b96213e86d20 --- /dev/null +++ b/dockerfiles/pwm/.vimrc @@ -0,0 +1,6 @@ +syntax on +set autoindent +set expandtab +set number +set shiftwidth=2 +set softtabstop=2 diff --git a/dockerfiles/pwm/Dockerfile b/dockerfiles/pwm/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6e3450483b2600ba310ea7b06efeb8ae968dbc24 --- /dev/null +++ b/dockerfiles/pwm/Dockerfile @@ -0,0 +1,44 @@ +ARG VERSION=docker:stable-dind +FROM ${VERSION} + +RUN apk add --no-cache git tmux vim curl bash build-base qemu-img qemu-system-x86_64 + +ENV GOPATH /root/go +ENV PATH $PATH:$GOPATH + +# Use specific moby commit due to vendoring mismatch +ENV MOBY_COMMIT="d9d2a91780b34b92e669bbfa099f613bd9fad6bb" + +RUN mkdir /root/go && apk add --no-cache go \ + && go get -u -d github.com/moby/tool/cmd/moby && (cd $GOPATH/src/github.com/moby/tool/cmd/moby && git checkout $MOBY_COMMIT && go install) \ + && go get -u github.com/linuxkit/linuxkit/src/cmd/linuxkit \ + && rm -rf /root/go/pkg && rm -rf /root/go/src && rm -rf /usr/lib/go + + +# Add bash completion and set bash as default shell +RUN mkdir /etc/bash_completion.d \ + && curl https://raw.githubusercontent.com/docker/cli/master/contrib/completion/bash/docker -o /etc/bash_completion.d/docker \ + && sed -i "s/ash/bash/" /etc/passwd + + +# Replace modprobe with a no-op to get rid of spurious warnings +# (note: we can't just symlink to /bin/true because it might be busybox) +RUN rm /sbin/modprobe && echo '#!/bin/true' >/sbin/modprobe && chmod +x /sbin/modprobe + +# Install a nice vimrc file and prompt (by soulshake) +COPY ["sudo", "/usr/local/bin/"] +COPY [".vimrc", ".profile", ".inputrc", ".gitconfig", "./root/"] +COPY ["motd", "/etc/motd"] +COPY ["daemon.json", "/etc/docker/"] + +# Move to our home +WORKDIR /root + + +# Remove IPv6 alias for localhost and start docker in the background ... +CMD cat /etc/hosts >/etc/hosts.bak && \ + sed 's/^::1.*//' /etc/hosts.bak > /etc/hosts && \ + mount -t securityfs none /sys/kernel/security && \ + dockerd &>/docker.log & \ + while true ; do /bin/bash -l; done +# ... and then put a shell in the foreground, restarting it if it exits diff --git a/dockerfiles/pwm/daemon.json b/dockerfiles/pwm/daemon.json new file mode 100644 index 0000000000000000000000000000000000000000..2792f9397c5ec85dc63f08721dd25097149e8517 --- /dev/null +++ b/dockerfiles/pwm/daemon.json @@ -0,0 +1,7 @@ +{ + "experimental": true, + "debug": true, + "log-level": "info", + "insecure-registries": ["127.0.0.1"], + "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"] +} diff --git a/dockerfiles/pwm/motd b/dockerfiles/pwm/motd new file mode 100644 index 0000000000000000000000000000000000000000..2c1701b41730b498ca8a9ed2063ee904baeef1b2 --- /dev/null +++ b/dockerfiles/pwm/motd @@ -0,0 +1,8 @@ +############################################################### +# WARNING!!!! # +# This is a sandbox environment. Using personal credentials # +# is HIGHLY! discouraged. Any consequences of doing so are # +# completely the user's responsibilites. # +# # +# The PWD team. # +############################################################### diff --git a/dockerfiles/pwm/sudo b/dockerfiles/pwm/sudo new file mode 100644 index 0000000000000000000000000000000000000000..637614a061fbd623a499d1234f54741a4a7b323a --- /dev/null +++ b/dockerfiles/pwm/sudo @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This is shim to help with the case were pasted commands from a readme assume you are not root. Since this isto be run by root, it should effectively be a dummy command that allows the parameters to pass through. + +exec "$@" diff --git a/event/event.go b/event/event.go new file mode 100644 index 0000000000000000000000000000000000000000..6c56b42cd40487e9c60ac9da217e4590b60f4227 --- /dev/null +++ b/event/event.go @@ -0,0 +1,28 @@ +package event + +type EventType string + +func (e EventType) String() string { + return string(e) +} + +var ( + INSTANCE_VIEWPORT_RESIZE = EventType("instance viewport resize") + INSTANCE_DELETE = EventType("instance delete") + INSTANCE_NEW = EventType("instance new") + INSTANCE_STATS = EventType("instance stats") + SESSION_NEW = EventType("session new") + SESSION_END = EventType("session end") + SESSION_READY = EventType("session ready") + SESSION_BUILDER_OUT = EventType("session builder out") + PLAYGROUND_NEW = EventType("playground_new") +) + +type Handler func(id string, args ...interface{}) +type AnyHandler func(eventType EventType, id string, args ...interface{}) + +type EventApi interface { + Emit(name EventType, id string, args ...interface{}) + On(name EventType, handler Handler) + OnAny(handler AnyHandler) +} diff --git a/event/local_broker.go b/event/local_broker.go new file mode 100644 index 0000000000000000000000000000000000000000..2f33292278bc4f02783458909f6bd423cd984b17 --- /dev/null +++ b/event/local_broker.go @@ -0,0 +1,47 @@ +package event + +import "sync" + +type localBroker struct { + sync.Mutex + + handlers map[EventType][]Handler + anyHandlers []AnyHandler +} + +func NewLocalBroker() *localBroker { + return &localBroker{handlers: map[EventType][]Handler{}, anyHandlers: []AnyHandler{}} +} + +func (b *localBroker) On(name EventType, handler Handler) { + b.Lock() + defer b.Unlock() + + if b.handlers[name] == nil { + b.handlers[name] = []Handler{} + } + b.handlers[name] = append(b.handlers[name], handler) +} + +func (b *localBroker) OnAny(handler AnyHandler) { + b.Lock() + defer b.Unlock() + + b.anyHandlers = append(b.anyHandlers, handler) +} + +func (b *localBroker) Emit(name EventType, sessionId string, args ...interface{}) { + go func() { + b.Lock() + defer b.Unlock() + + for _, handler := range b.anyHandlers { + handler(name, sessionId, args...) + } + if b.handlers[name] != nil { + for _, handler := range b.handlers[name] { + handler(sessionId, args...) + } + } + }() +} diff --git a/event/local_broker_test.go b/event/local_broker_test.go new file mode 100644 index 0000000000000000000000000000000000000000..90939c8f4b568b160c8972e7118bb7d2f4e383c1 --- /dev/null +++ b/event/local_broker_test.go @@ -0,0 +1,60 @@ +package event + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocalBroker_On(t *testing.T) { + broker := NewLocalBroker() + + called := 0 + receivedSessionId := "" + receivedArgs := []interface{}{} + + wg := sync.WaitGroup{} + wg.Add(1) + + broker.On(INSTANCE_NEW, func(sessionId string, args ...interface{}) { + called++ + receivedSessionId = sessionId + receivedArgs = args + wg.Done() + }) + broker.Emit(SESSION_READY, "1") + broker.Emit(INSTANCE_NEW, "2", "foo", "bar") + + wg.Wait() + + assert.Equal(t, 1, called) + assert.Equal(t, "2", receivedSessionId) + assert.Equal(t, []interface{}{"foo", "bar"}, receivedArgs) +} + +func TestLocalBroker_OnAny(t *testing.T) { + broker := NewLocalBroker() + + var receivedEvent EventType + receivedSessionId := "" + receivedArgs := []interface{}{} + + wg := sync.WaitGroup{} + wg.Add(1) + + broker.OnAny(func(eventType EventType, sessionId string, args ...interface{}) { + receivedSessionId = sessionId + receivedArgs = args + receivedEvent = eventType + wg.Done() + }) + broker.Emit(SESSION_READY, "1") + + wg.Wait() + + var expectedArgs []interface{} + assert.Equal(t, SESSION_READY, receivedEvent) + assert.Equal(t, "1", receivedSessionId) + assert.Equal(t, expectedArgs, receivedArgs) +} diff --git a/event/mock.go b/event/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..136fb5d0992876536b65fb21f2bc71610c029e11 --- /dev/null +++ b/event/mock.go @@ -0,0 +1,19 @@ +package event + +import "github.com/stretchr/testify/mock" + +type Mock struct { + M mock.Mock +} + +func (m *Mock) Emit(name EventType, sessionId string, args ...interface{}) { + m.M.Called(name, sessionId, args) +} + +func (m *Mock) On(name EventType, handler Handler) { + m.M.Called(name, handler) +} + +func (m *Mock) OnAny(handler AnyHandler) { + m.M.Called(handler) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..d57cbaf9489d6d3b5087257c26a1bad6ccd892ee --- /dev/null +++ b/go.mod @@ -0,0 +1,68 @@ +module github.com/play-with-docker/play-with-docker + +go 1.16 + +require ( + cloud.google.com/go v0.58.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.4.5 // indirect + github.com/PuerkitoBio/purell v1.1.0 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/aws/aws-sdk-go v1.12.15 + github.com/containerd/containerd v1.0.0-beta.2 + github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible // indirect + github.com/docker/docker v1.4.2-0.20200309214505-aa6a9891b09c + github.com/docker/go-connections v0.3.0 + github.com/docker/go-units v0.3.2 + github.com/emicklei/go-restful v2.4.0+incompatible // indirect + github.com/emicklei/go-restful-swagger12 v0.0.0-20170208215640-dcef7f557305 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-ini/ini v1.30.0 // indirect + github.com/go-openapi/jsonpointer v0.0.0-20170102174223-779f45308c19 // indirect + github.com/go-openapi/jsonreference v0.0.0-20161105162150-36d33bfe519e // indirect + github.com/go-openapi/spec v0.0.0-20171105074921-a4fa9574c7aa // indirect + github.com/go-openapi/swag v0.0.0-20171111214437-cf0bdb963811 // indirect + github.com/google/go-github v13.0.1-0.20171014143926-a021c14a5f19+incompatible + github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect + github.com/googleapis/gnostic v0.1.0 // indirect + github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f // indirect + github.com/gorilla/handlers v1.3.0 + github.com/gorilla/mux v1.5.0 + github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a + github.com/gorilla/websocket v1.2.0 + github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f // indirect + github.com/hashicorp/golang-lru v0.5.1 + github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b + github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect + github.com/juju/ratelimit v1.0.1 // indirect + github.com/mailru/easyjson v0.0.0-20171120080333-32fa128f234d // indirect + github.com/miekg/dns v0.0.0-20171019064225-822ae18e7187 + github.com/morikuni/aec v1.0.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/opencontainers/image-spec v1.0.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/prometheus/client_golang v1.7.1 + github.com/rs/xid v0.0.0-20170604230408-02dd45c33376 + github.com/satori/go.uuid v1.1.1-0.20170321230731-5bf94b69c6b6 + github.com/shirou/gopsutil v2.16.13-0.20170924065440-6e221c482653+incompatible + github.com/smartystreets/goconvey v1.6.4 // indirect + github.com/spf13/pflag v1.0.0 // indirect + github.com/stretchr/testify v1.4.0 + github.com/urfave/negroni v0.2.0 + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.3.6 + google.golang.org/api v0.26.0 + google.golang.org/genproto v0.0.0-20200611194920-44ba362f84c1 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/inf.v0 v0.9.0 // indirect + gotest.tools v2.2.0+incompatible // indirect + k8s.io/api v0.0.0-20171027084545-218912509d74 // indirect + k8s.io/apimachinery v0.0.0-20171027084411-18a564baac72 + k8s.io/client-go v5.0.1+incompatible + k8s.io/kube-openapi v0.0.0-20171101183504-39a7bf85c140 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..e6f4e392bdd6c8398a7dbdca680551aa645d8ab0 --- /dev/null +++ b/go.sum @@ -0,0 +1,545 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.58.0 h1:vtAfVc723K3xKq1BQydk/FyCldnaNFhGhpJxaJzgRMQ= +cloud.google.com/go v0.58.0/go.mod h1:W+9FnSUw6nhVwXlFcp1eL+krq5+HQUJeUogSeJZZiWg= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.4.5 h1:U2XsGR5dBg1yzwSEJoP2dE2/aAXpmad+CNG2hE9Pd5k= +github.com/Microsoft/go-winio v0.4.5/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aws/aws-sdk-go v1.12.15 h1:ywQy1Yg2WBLKAW2uj95nHisn1Islid2zkIUqEwm0m7A= +github.com/aws/aws-sdk-go v1.12.15/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/containerd v1.0.0-beta.2 h1:TOLkeQ/oZ5D2IL7JEp3cJxLXl+DqDYvxn4AjVZy7RMU= +github.com/containerd/containerd v1.0.0-beta.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible h1:357nGVUC8gSpeSc2Axup8HfrfTLLUfWfCsCUhiQSKIg= +github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200309214505-aa6a9891b09c h1:zviRyz1SWO8+WVJbi9/jlJCkrsZ54r/lTRbgtcaQhLs= +github.com/docker/docker v1.4.2-0.20200309214505-aa6a9891b09c/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= +github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.2 h1:Kjm80apys7gTtfVmCvVY8gwu10uofaFSrmAKOVrtueE= +github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emicklei/go-restful v2.4.0+incompatible h1:p9u+CKd2OEI+kUmFLDwuf0LtmBtDhcok4UjQDs0rDDk= +github.com/emicklei/go-restful v2.4.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful-swagger12 v0.0.0-20170208215640-dcef7f557305 h1:2vAWk0wMCWb/pYiyat2rRZp5I5ZM+efPlagySNZ3JeM= +github.com/emicklei/go-restful-swagger12 v0.0.0-20170208215640-dcef7f557305/go.mod h1:qr0VowGBT4CS4Q8vFF8BSeKz34PuqKGxs/L0IAQA9DQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.30.0 h1:bcFeUQUA+99t1cZPXmtc7HpGv2KTlZGIFeBDWQh2DRw= +github.com/go-ini/ini v1.30.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-openapi/jsonpointer v0.0.0-20170102174223-779f45308c19 h1:UmnefiS/Yrdfl15NXUA9T51lyQf72tCvWHfOiRLd1+g= +github.com/go-openapi/jsonpointer v0.0.0-20170102174223-779f45308c19/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20161105162150-36d33bfe519e h1:gbNUNGpVJLxaXBxI7iCHZdg3PwgLOJ9lPQGVINRrC9E= +github.com/go-openapi/jsonreference v0.0.0-20161105162150-36d33bfe519e/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20171105074921-a4fa9574c7aa h1:qePsbAVdhUehfvnxXb76IUtOaCE7RhDSH4ToubTWww4= +github.com/go-openapi/spec v0.0.0-20171105074921-a4fa9574c7aa/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20171111214437-cf0bdb963811 h1:r38dWW65/T34YOCwzGTAoDxP16l+OpDJl/n07P+pvUo= +github.com/go-openapi/swag v0.0.0-20171111214437-cf0bdb963811/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v13.0.1-0.20171014143926-a021c14a5f19+incompatible h1:BqvW/QNZtzSStnaHJevBVYCPhqBSrhLgj8SeEUr9iR4= +github.com/google/go-github v13.0.1-0.20171014143926-a021c14a5f19+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI= +github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.5.0 h1:mq8bRov+5x+pZNR/uAHyUEgovR9gLgYFwDQIeuYi9TM= +github.com/gorilla/mux v1.5.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a h1:YH0IojQwndMQdeRWdw1aPT8bkbiWaYR3WD+Zf5e09DU= +github.com/gorilla/securecookie v0.0.0-20160422134519-667fe4e3466a/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f h1:kOkUP6rcVVqC+KlKKENKtgfFfJyDySYhqL9srXooghY= +github.com/gregjones/httpcache v0.0.0-20171119193500-2bcd89a1743f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE= +github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20171120080333-32fa128f234d h1:bM4HYnlVXPgUKmzl7o3drEaVfOk+sTBiADAQOWjU+8I= +github.com/mailru/easyjson v0.0.0-20171120080333-32fa128f234d/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v0.0.0-20171019064225-822ae18e7187 h1:gF0xdz8uynTvRxheFyq4UxAXc0bDxiB3QpSIGmfA5xk= +github.com/miekg/dns v0.0.0-20171019064225-822ae18e7187/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.0 h1:jcw3cCH887bLKETGYpv8afogdYchbShR0eH6oD9d5PQ= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v0.0.0-20170604230408-02dd45c33376 h1:pisBoZ1sLLFc+g7EZflpvatXVqmQKv8EjPP8/radknQ= +github.com/rs/xid v0.0.0-20170604230408-02dd45c33376/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/satori/go.uuid v1.1.1-0.20170321230731-5bf94b69c6b6 h1:oZag5hylqWwZrDdj/laMwWQnXaeWBQf66qm4PGQI6Wc= +github.com/satori/go.uuid v1.1.1-0.20170321230731-5bf94b69c6b6/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shirou/gopsutil v2.16.13-0.20170924065440-6e221c482653+incompatible h1:gh3Hq7V5Zb8X2xk4T9FbHVi1m2Yo+oEIyjjJUiezVoY= +github.com/shirou/gopsutil v2.16.13-0.20170924065440-6e221c482653+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/pflag v1.0.0 h1:oaPbdDe/x0UncahuwiPxW1GYJyilRAdsPnq3e1yaPcI= +github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/negroni v0.2.0 h1:cadBY8/+9L/dTagBqV7N0l/SJiB4Wg+os5QdmaFY5Wg= +github.com/urfave/negroni v0.2.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08= +google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200608115520-7c474a2e3482/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200611194920-44ba362f84c1 h1:rRk0Nk0YJuu8qID4s2qgHG/WOMKXFafQ0/4vD/6/EuM= +google.golang.org/genproto v0.0.0-20200611194920-44ba362f84c1/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.0.0-20171027084545-218912509d74 h1:CYism0UbF96TF8s8sYrKagsJn3oqR456q/ER/cELIuA= +k8s.io/api v0.0.0-20171027084545-218912509d74/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apimachinery v0.0.0-20171027084411-18a564baac72 h1:pPbfmsjOvePqojmf5AhR0o7bxZTCxE0nkDakanV50CM= +k8s.io/apimachinery v0.0.0-20171027084411-18a564baac72/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/client-go v5.0.1+incompatible h1:IPZ0cnux5ui8+X8r1HdeFPXucpQ4HyJQigjo1clq1QM= +k8s.io/client-go v5.0.1+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/kube-openapi v0.0.0-20171101183504-39a7bf85c140 h1:j1Zez+Xb4OWvCdROqeq8sP2ACi/qWV1tj/imP0/8a0k= +k8s.io/kube-openapi v0.0.0-20171101183504-39a7bf85c140/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/handlers/bootstrap.go b/handlers/bootstrap.go new file mode 100644 index 0000000000000000000000000000000000000000..48e7162b422e2313a0d8285c824d92822abfcb5b --- /dev/null +++ b/handlers/bootstrap.go @@ -0,0 +1,287 @@ +package handlers + +import ( + "bytes" + "context" + "crypto/tls" + "embed" + "fmt" + "html/template" + "io/fs" + "io/ioutil" + "log" + "net/http" + "path" + "strings" + "time" + + "golang.org/x/crypto/acme/autocert" + "golang.org/x/oauth2" + + gh "github.com/gorilla/handlers" + "github.com/gorilla/mux" + lru "github.com/hashicorp/golang-lru" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/urfave/negroni" + oauth2Github "golang.org/x/oauth2/github" + oauth2Google "golang.org/x/oauth2/google" + "google.golang.org/api/people/v1" +) + +var ( + core pwd.PWDApi + e event.EventApi + landings = map[string][]byte{} +) + +//go:embed www/* +var embeddedFiles embed.FS + +var staticFiles fs.FS + +var latencyHistogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "pwd_handlers_duration_ms", + Help: "How long it took to process a specific handler, in a specific host", + Buckets: []float64{300, 1200, 5000}, +}, []string{"action"}) + +type HandlerExtender func(h *mux.Router) + +func init() { + prometheus.MustRegister(latencyHistogramVec) + staticFiles, _ = fs.Sub(embeddedFiles, "www") +} + +func Bootstrap(c pwd.PWDApi, ev event.EventApi) { + core = c + e = ev +} + +func Register(extend HandlerExtender) { + initPlaygrounds() + + r := mux.NewRouter() + corsRouter := mux.NewRouter() + + corsHandler := gh.CORS(gh.AllowCredentials(), gh.AllowedHeaders([]string{"x-requested-with", "content-type"}), gh.AllowedMethods([]string{"GET", "POST", "HEAD", "DELETE"}), gh.AllowedOriginValidator(func(origin string) bool { + if strings.HasSuffix(origin, ".play-with-docker.com") || + strings.HasSuffix(origin, ".play-with-kubernetes.com") || + strings.HasSuffix(origin, ".docker.com") || + strings.HasSuffix(origin, ".play-with-go.dev") { + return true + } + return false + }), gh.AllowedOrigins([]string{})) + + // Specific routes + r.HandleFunc("/ping", Ping).Methods("GET") + corsRouter.HandleFunc("/instances/images", GetInstanceImages).Methods("GET") + corsRouter.HandleFunc("/sessions/{sessionId}", GetSession).Methods("GET") + corsRouter.HandleFunc("/sessions/{sessionId}/close", CloseSession).Methods("POST") + corsRouter.HandleFunc("/sessions/{sessionId}", CloseSession).Methods("DELETE") + corsRouter.HandleFunc("/sessions/{sessionId}/setup", SessionSetup).Methods("POST") + corsRouter.HandleFunc("/sessions/{sessionId}/instances", NewInstance).Methods("POST") + corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/uploads", FileUpload).Methods("POST") + corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", DeleteInstance).Methods("DELETE") + corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/exec", Exec).Methods("POST") + corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/fstree", fsTree).Methods("GET") + corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/file", file).Methods("GET") + + r.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/editor", func(rw http.ResponseWriter, r *http.Request) { + serveAsset(rw, r, "editor.html") + }) + + r.HandleFunc("/ooc", func(rw http.ResponseWriter, r *http.Request) { + serveAsset(rw, r, "ooc.html") + }).Methods("GET") + r.HandleFunc("/503", func(rw http.ResponseWriter, r *http.Request) { + serveAsset(rw, r, "503.html") + }).Methods("GET") + r.HandleFunc("/p/{sessionId}", Home).Methods("GET") + r.PathPrefix("/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + serveAsset(rw, r, r.URL.Path[1:]) + }) + r.HandleFunc("/robots.txt", func(rw http.ResponseWriter, r *http.Request) { + serveAsset(rw, r, "robots.txt") + }) + + corsRouter.HandleFunc("/sessions/{sessionId}/ws/", WSH) + r.Handle("/metrics", promhttp.Handler()) + + // Generic routes + r.HandleFunc("/", Landing).Methods("GET") + + corsRouter.HandleFunc("/users/me", LoggedInUser).Methods("GET") + r.HandleFunc("/users/{userId:.{3,}}", GetUser).Methods("GET") + r.HandleFunc("/oauth/providers", ListProviders).Methods("GET") + r.HandleFunc("/oauth/providers/{provider}/login", Login).Methods("GET") + r.HandleFunc("/oauth/providers/{provider}/callback", LoginCallback).Methods("GET") + r.HandleFunc("/playgrounds", NewPlayground).Methods("PUT") + r.HandleFunc("/playgrounds", ListPlaygrounds).Methods("GET") + r.HandleFunc("/my/playground", GetCurrentPlayground).Methods("GET") + + corsRouter.HandleFunc("/", NewSession).Methods("POST") + + if extend != nil { + extend(corsRouter) + } + + n := negroni.Classic() + + r.PathPrefix("/").Handler(negroni.New(negroni.Wrap(corsHandler(corsRouter)))) + n.UseHandler(r) + + httpServer := http.Server{ + Addr: "0.0.0.0:" + config.PortNumber, + Handler: n, + IdleTimeout: 30 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + } + + if config.UseLetsEncrypt { + domainCache, err := lru.New(5000) + if err != nil { + log.Fatalf("Could not start domain cache. Got: %v", err) + } + certManager := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: func(ctx context.Context, host string) error { + if _, found := domainCache.Get(host); !found { + if playground := core.PlaygroundFindByDomain(host); playground == nil { + return fmt.Errorf("Playground for domain %s was not found", host) + } + domainCache.Add(host, true) + } + return nil + }, + Cache: autocert.DirCache(config.LetsEncryptCertsDir), + } + + httpServer.TLSConfig = &tls.Config{ + GetCertificate: certManager.GetCertificate, + } + + go func() { + rr := mux.NewRouter() + rr.HandleFunc("/ping", Ping).Methods("GET") + rr.Handle("/metrics", promhttp.Handler()) + rr.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { + target := fmt.Sprintf("https://%s%s", r.Host, r.URL.Path) + if len(r.URL.RawQuery) > 0 { + target += "?" + r.URL.RawQuery + } + http.Redirect(rw, r, target, http.StatusMovedPermanently) + }) + nr := negroni.Classic() + nr.UseHandler(rr) + log.Println("Starting redirect server") + redirectServer := http.Server{ + Addr: "0.0.0.0:3001", + Handler: certManager.HTTPHandler(nr), + IdleTimeout: 30 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + } + log.Fatal(redirectServer.ListenAndServe()) + }() + + log.Println("Listening on port " + config.PortNumber) + log.Fatal(httpServer.ListenAndServeTLS("", "")) + } else { + log.Println("Listening on port " + config.PortNumber) + log.Fatal(httpServer.ListenAndServe()) + } +} + +func serveAsset(w http.ResponseWriter, r *http.Request, name string) { + a, err := fs.ReadFile(staticFiles, name) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + http.ServeContent(w, r, name, time.Time{}, bytes.NewReader(a)) +} + +func initPlaygrounds() { + pgs, err := core.PlaygroundList() + if err != nil { + log.Fatal("Error getting playgrounds for initialization") + } + + for _, p := range pgs { + initAssets(p) + initOauthProviders(p) + } +} + +func initAssets(p *types.Playground) { + if p.AssetsDir == "" { + p.AssetsDir = "default" + } + + lpath := path.Join(p.AssetsDir, "landing.html") + landing, err := fs.ReadFile(staticFiles, lpath) + if err != nil { + log.Printf("Could not load %v: %v", lpath, err) + return + } + + var b bytes.Buffer + t := template.New("landing.html").Delims("[[", "]]") + t, err = t.Parse(string(landing)) + if err != nil { + log.Fatalf("Error parsing template %v", err) + } + if err := t.Execute(&b, struct{ SegmentId string }{config.SegmentId}); err != nil { + log.Fatalf("Error executing template %v", err) + } + landingBytes, err := ioutil.ReadAll(&b) + if err != nil { + log.Fatalf("Error reading template bytes %v", err) + } + landings[p.Id] = landingBytes +} + +func initOauthProviders(p *types.Playground) { + config.Providers[p.Id] = map[string]*oauth2.Config{} + + if p.GithubClientID != "" && p.GithubClientSecret != "" { + conf := &oauth2.Config{ + ClientID: p.GithubClientID, + ClientSecret: p.GithubClientSecret, + Scopes: []string{"user:email"}, + Endpoint: oauth2Github.Endpoint, + } + + config.Providers[p.Id]["github"] = conf + } + if p.GoogleClientID != "" && p.GoogleClientSecret != "" { + conf := &oauth2.Config{ + ClientID: p.GoogleClientID, + ClientSecret: p.GoogleClientSecret, + Scopes: []string{people.UserinfoEmailScope, people.UserinfoProfileScope}, + Endpoint: oauth2Google.Endpoint, + } + + config.Providers[p.Id]["google"] = conf + } + if p.DockerClientID != "" && p.DockerClientSecret != "" { + + endpoint := getDockerEndpoint(p) + conf := &oauth2.Config{ + ClientID: p.DockerClientID, + ClientSecret: p.DockerClientSecret, + Scopes: []string{"openid", "full_access:account"}, + Endpoint: oauth2.Endpoint{ + AuthURL: fmt.Sprintf("https://%s/authorize/", endpoint), + TokenURL: fmt.Sprintf("https://%s/oauth/token", endpoint), + }, + } + + config.Providers[p.Id]["docker"] = conf + } +} diff --git a/handlers/close_session.go b/handlers/close_session.go new file mode 100644 index 0000000000000000000000000000000000000000..22753b181cbd9378e831230f511aba381a824777 --- /dev/null +++ b/handlers/close_session.go @@ -0,0 +1,30 @@ +package handlers + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" +) + +func CloseSession(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + + session, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + rw.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + if err := core.SessionClose(session); err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + +} diff --git a/handlers/cookie_id.go b/handlers/cookie_id.go new file mode 100644 index 0000000000000000000000000000000000000000..a7ab66181f26b52959e5f15ae614c3d2bd9f5d3e --- /dev/null +++ b/handlers/cookie_id.go @@ -0,0 +1,44 @@ +package handlers + +import ( + "net/http" + + "github.com/play-with-docker/play-with-docker/config" +) + +type CookieID struct { + Id string `json:"id"` + UserName string `json:"user_name"` + UserAvatar string `json:"user_avatar"` + ProviderId string `json:"provider_id"` +} + +func (c *CookieID) SetCookie(rw http.ResponseWriter, host string) error { + if encoded, err := config.SecureCookie.Encode("id", c); err == nil { + cookie := &http.Cookie{ + Name: "id", + Value: encoded, + Domain: host, + Path: "/", + SameSite: http.SameSiteDefaultMode, + Secure: false, + HttpOnly: true, + } + http.SetCookie(rw, cookie) + } else { + return err + } + return nil +} +func ReadCookie(r *http.Request) (*CookieID, error) { + if cookie, err := r.Cookie("id"); err == nil { + value := &CookieID{} + if err = config.SecureCookie.Decode("id", cookie.Value, &value); err == nil { + return value, nil + } else { + return nil, err + } + } else { + return nil, err + } +} diff --git a/handlers/delete_instance.go b/handlers/delete_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..8a7d3975349cd5d9ae9893bfb6dae28d25c06ec8 --- /dev/null +++ b/handlers/delete_instance.go @@ -0,0 +1,30 @@ +package handlers + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" +) + +func DeleteInstance(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + instanceName := vars["instanceName"] + + s, err := core.SessionGet(sessionId) + if s != nil { + i := core.InstanceGet(s, instanceName) + err := core.InstanceDelete(s, i) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + } else if err == storage.NotFoundError { + rw.WriteHeader(http.StatusInternalServerError) + return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/handlers/exec.go b/handlers/exec.go new file mode 100644 index 0000000000000000000000000000000000000000..55eb939f57748a0688f2c3709fbc75fdd2098553 --- /dev/null +++ b/handlers/exec.go @@ -0,0 +1,51 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" +) + +type execRequest struct { + Cmd []string `json:"command"` +} + +type execResponse struct { + ExitCode int `json:"status_code"` +} + +func Exec(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + instanceName := vars["instanceName"] + + var er execRequest + err := json.NewDecoder(req.Body).Decode(&er) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + return + } + + s, _ := core.SessionGet(sessionId) + if s == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + i := core.InstanceGet(s, instanceName) + if i == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + code, err := core.InstanceExec(i, er.Cmd) + + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + json.NewEncoder(rw).Encode(execResponse{code}) +} diff --git a/handlers/file_instance.go b/handlers/file_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..cd03baf0fd31b88b06854c38ed62d7155ca10942 --- /dev/null +++ b/handlers/file_instance.go @@ -0,0 +1,54 @@ +package handlers + +import ( + "encoding/base64" + "io" + "log" + "net/http" + + "github.com/gorilla/mux" +) + +func file(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + instanceName := vars["instanceName"] + + query := req.URL.Query() + + path := query.Get("path") + + if path == "" { + rw.WriteHeader(http.StatusBadRequest) + return + } + + s, _ := core.SessionGet(sessionId) + if s == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + i := core.InstanceGet(s, instanceName) + if i == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + instanceFile, err := core.InstanceFile(i, path) + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + encoder := base64.NewEncoder(base64.StdEncoding, rw) + + if _, err = io.Copy(encoder, instanceFile); err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + encoder.Close() +} diff --git a/handlers/file_upload.go b/handlers/file_upload.go new file mode 100644 index 0000000000000000000000000000000000000000..52b54012feb6dba3781b588ae48f4ae7e007d192 --- /dev/null +++ b/handlers/file_upload.go @@ -0,0 +1,80 @@ +package handlers + +import ( + "io" + "log" + "net/http" + "path/filepath" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" +) + +func FileUpload(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + instanceName := vars["instanceName"] + + s, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + rw.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + i := core.InstanceGet(s, instanceName) + + // Path to upload the file to + path := req.URL.Query().Get("path") + + // allow up to 32 MB which is the default + + // has a url query parameter, ignore body + if url := req.URL.Query().Get("url"); url != "" { + + _, fileName := filepath.Split(url) + + err := core.InstanceUploadFromUrl(i, fileName, path, req.URL.Query().Get("url")) + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + rw.WriteHeader(http.StatusOK) + return + } else { + red, err := req.MultipartReader() + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusBadRequest) + return + } + + for { + p, err := red.NextPart() + if err == io.EOF { + break + } + if err != nil { + log.Println(err) + continue + } + + if p.FileName() == "" { + continue + } + err = core.InstanceUploadFromReader(i, p.FileName(), path, p) + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + log.Printf("Uploaded [%s] to [%s]\n", p.FileName(), i.Name) + } + rw.WriteHeader(http.StatusOK) + return + } + +} diff --git a/handlers/fstree_instance.go b/handlers/fstree_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..9e1b17591d4e7d14ae539474be1941c55607986d --- /dev/null +++ b/handlers/fstree_instance.go @@ -0,0 +1,42 @@ +package handlers + +import ( + "io" + "log" + "net/http" + + "github.com/gorilla/mux" +) + +func fsTree(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + instanceName := vars["instanceName"] + + s, _ := core.SessionGet(sessionId) + if s == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + i := core.InstanceGet(s, instanceName) + if i == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + tree, err := core.InstanceFSTree(i) + + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + rw.Header().Set("content-type", "application/json") + if _, err = io.Copy(rw, tree); err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/handlers/get_instance_images.go b/handlers/get_instance_images.go new file mode 100644 index 0000000000000000000000000000000000000000..a2e42cd3bd79930ab352269131bc98a3d612de6b --- /dev/null +++ b/handlers/get_instance_images.go @@ -0,0 +1,17 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" +) + +func GetInstanceImages(rw http.ResponseWriter, req *http.Request) { + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusBadRequest) + return + } + json.NewEncoder(rw).Encode(playground.AvailableDinDInstanceImages) +} diff --git a/handlers/get_session.go b/handlers/get_session.go new file mode 100644 index 0000000000000000000000000000000000000000..66ce321d735a34d6d0981ceaae054f9b729a30b0 --- /dev/null +++ b/handlers/get_session.go @@ -0,0 +1,43 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" +) + +type SessionInfo struct { + *types.Session + Instances map[string]*types.Instance `json:"instances"` +} + +func GetSession(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + + session, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + rw.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + instances, err := core.InstanceFindBySession(session) + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + is := map[string]*types.Instance{} + for _, i := range instances { + is[i.Name] = i + } + + json.NewEncoder(rw).Encode(SessionInfo{session, is}) +} diff --git a/handlers/home.go b/handlers/home.go new file mode 100644 index 0000000000000000000000000000000000000000..64a6d7c14b55795ec3020ab731138982b548579f --- /dev/null +++ b/handlers/home.go @@ -0,0 +1,60 @@ +package handlers + +import ( + "io/fs" + "log" + "net/http" + "path/filepath" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" +) + +func Home(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sessionId := vars["sessionId"] + + s, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + // Session doesn't exist (can happen if closing the sessions an reloading the page, or similar). + w.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if s.Stack != "" { + go core.SessionDeployStack(s) + } + + playground := core.PlaygroundGet(s.PlaygroundId) + if playground == nil { + log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id) + w.WriteHeader(http.StatusBadRequest) + return + } + + index, err := fs.ReadFile(staticFiles, filepath.Join(playground.AssetsDir, "/index.html")) + if err != nil { + index, err = fs.ReadFile(staticFiles, "default/index.html") + } + + if err != nil { + w.WriteHeader(http.StatusFound) + return + + } + w.Write(index) +} + +func Landing(rw http.ResponseWriter, req *http.Request) { + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusNotFound) + return + } + + rw.Write(landings[playground.Id]) + +} diff --git a/handlers/login.go b/handlers/login.go new file mode 100644 index 0000000000000000000000000000000000000000..d2a3862519cd3d3b42361947b32ca0f06426db82 --- /dev/null +++ b/handlers/login.go @@ -0,0 +1,263 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strconv" + "strings" + + "golang.org/x/oauth2" + + "github.com/google/go-github/github" + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/pwd/types" + uuid "github.com/satori/go.uuid" + "google.golang.org/api/option" + "google.golang.org/api/people/v1" +) + +func LoggedInUser(rw http.ResponseWriter, req *http.Request) { + cookie, err := ReadCookie(req) + if err != nil { + log.Println("Cannot read cookie") + rw.WriteHeader(http.StatusUnauthorized) + return + } + + user, err := core.UserGet(cookie.Id) + if err != nil { + log.Printf("Couldn't get user with id %s. Got: %v\n", cookie.Id, err) + rw.WriteHeader(http.StatusUnauthorized) + return + } + json.NewEncoder(rw).Encode(user) +} + +func ListProviders(rw http.ResponseWriter, req *http.Request) { + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusBadRequest) + return + } + + providers := []string{} + for name := range config.Providers[playground.Id] { + providers = append(providers, name) + } + json.NewEncoder(rw).Encode(providers) +} + +func Login(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + providerName := vars["provider"] + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusBadRequest) + return + } + + provider, found := config.Providers[playground.Id][providerName] + if !found { + log.Printf("Could not find provider %s\n", providerName) + rw.WriteHeader(http.StatusNotFound) + return + } + + loginRequest, err := core.UserNewLoginRequest(providerName) + if err != nil { + log.Printf("Could not start a new user login request for provider %s. Got: %v\n", providerName, err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + if playground.AuthRedirectBase != "" { + provider.RedirectURL = fmt.Sprintf("%s/oauth/providers/%s/callback", playground.AuthRedirectBase, providerName) + } else { + scheme := "http" + if req.TLS != nil { + scheme = "https" + } + host := "localhost" + if req.Host != "" { + host = req.Host + } + provider.RedirectURL = fmt.Sprintf("%s://%s/oauth/providers/%s/callback", scheme, host, providerName) + } + + url := provider.AuthCodeURL(loginRequest.Id, oauth2.SetAuthURLParam("nonce", uuid.NewV4().String())) + + http.Redirect(rw, req, url, http.StatusFound) +} + +func LoginCallback(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + providerName := vars["provider"] + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusBadRequest) + return + } + + provider, found := config.Providers[playground.Id][providerName] + if !found { + log.Printf("Could not find provider %s\n", providerName) + rw.WriteHeader(http.StatusNotFound) + return + } + + query := req.URL.Query() + + code := query.Get("code") + loginRequestId := query.Get("state") + + loginRequest, err := core.UserGetLoginRequest(loginRequestId) + if err != nil { + log.Printf("Could not get login request %s for provider %s. Got: %v\n", loginRequestId, providerName, err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + ctx := req.Context() + tok, err := provider.Exchange(ctx, code) + if err != nil { + log.Printf("Could not exchage code for access token for provider %s. Got: %v\n", providerName, err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + user := &types.User{Provider: providerName} + if providerName == "github" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: tok.AccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + u, _, err := client.Users.Get(ctx, "") + if err != nil { + log.Printf("Could not get user from github. Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + user.ProviderUserId = strconv.Itoa(u.GetID()) + user.Name = u.GetName() + user.Avatar = u.GetAvatarURL() + user.Email = u.GetEmail() + } else if providerName == "google" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: tok.AccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + p, err := people.NewService(ctx, option.WithHTTPClient(tc)) + if err != nil { + log.Printf("Could not initialize people service . Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + person, err := p.People.Get("people/me").PersonFields("emailAddresses,names").Do() + if err != nil { + log.Printf("Could not initialize people service . Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + user.Email = person.EmailAddresses[0].Value + user.Name = person.Names[0].GivenName + user.ProviderUserId = person.ResourceName + + } else if providerName == "docker" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: tok.AccessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + endpoint := getDockerEndpoint(playground) + resp, err := tc.Get(fmt.Sprintf("https://%s/userinfo", endpoint)) + if err != nil { + log.Printf("Could not get user from docker. Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + userInfo := map[string]interface{}{} + if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { + log.Printf("Could not decode user info. Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + user.ProviderUserId = strings.Split(userInfo["sub"].(string), "|")[1] + user.Name = userInfo["https://hub.docker.com"].(map[string]interface{})["username"].(string) + user.Email = userInfo["https://hub.docker.com"].(map[string]interface{})["email"].(string) + // Since DockerID doesn't return a user avatar, we try with twitter through avatars.io + // Worst case we get a generic avatar + user.Avatar = fmt.Sprintf("https://avatars.io/twitter/%s", user.Name) + } + + user, err = core.UserLogin(loginRequest, user) + if err != nil { + log.Printf("Could not login user. Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + cookieData := CookieID{Id: user.Id, UserName: user.Name, UserAvatar: user.Avatar, ProviderId: user.ProviderUserId} + + host := "localhost" + if req.Host != "" { + // we get the parent domain so cookie is set + // in all subdomain and siblings + host = getParentDomain(req.Host) + } + + if err := cookieData.SetCookie(rw, host); err != nil { + log.Printf("Could not encode cookie. Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + r, _ := playground.Extras.GetString("LoginRedirect") + + fmt.Fprintf(rw, ` + + + + + + +`, r) +} + +// getParentDomain returns the parent domain (if available) +// of the currend domain +func getParentDomain(host string) string { + levels := strings.Split(host, ".") + if len(levels) > 2 { + return strings.Join(levels[1:], ".") + } + return host +} + +func getDockerEndpoint(p *types.Playground) string { + if len(p.DockerHost) > 0 { + return p.DockerHost + } + return "login.docker.com" +} diff --git a/handlers/new_instance.go b/handlers/new_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..e2335f8a04c89b0f6261c9d794ed795f2089625c --- /dev/null +++ b/handlers/new_instance.go @@ -0,0 +1,78 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +func NewInstance(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + + body := types.InstanceConfig{PlaygroundFQDN: req.Host, DindVolumeSize: "5G"} + + json.NewDecoder(req.Body).Decode(&body) + + s, err := core.SessionGet(sessionId) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + playground := core.PlaygroundGet(s.PlaygroundId) + if playground == nil { + log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id) + rw.WriteHeader(http.StatusBadRequest) + return + } + + if body.Type == "windows" && !playground.AllowWindowsInstances { + rw.WriteHeader(http.StatusUnauthorized) + return + } + + instances, err := core.InstanceFindBySession(s) + + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + if playground.MaxInstances > 0 && len(instances) >= playground.MaxInstances { + log.Println(err) + rw.WriteHeader(http.StatusConflict) + return + } + + if len(playground.DindVolumeSize) > 0 { + body.DindVolumeSize = playground.DindVolumeSize + } + + // TODO I don't like how this is implemented here. NewInstance + // should be a function that's in the Playground struct. + if playground.Privileged { + body.Privileged = true + } + + i, err := core.InstanceNew(s, body) + if err != nil { + if provisioner.OutOfCapacity(err) { + rw.WriteHeader(http.StatusServiceUnavailable) + fmt.Fprintln(rw, `{"error": "out_of_capacity"}`) + return + } + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + //TODO: Set a status error + } else { + json.NewEncoder(rw).Encode(i) + } +} diff --git a/handlers/new_session.go b/handlers/new_session.go new file mode 100644 index 0000000000000000000000000000000000000000..59738bb20321f6c63d31433cf5648878995c8015 --- /dev/null +++ b/handlers/new_session.go @@ -0,0 +1,125 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "path" + "strings" + "time" + + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type NewSessionResponse struct { + SessionId string `json:"session_id"` + Hostname string `json:"hostname"` +} + +func NewSession(rw http.ResponseWriter, req *http.Request) { + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusBadRequest) + return + } + + req.ParseForm() + + userId := "" + if len(config.Providers[playground.Id]) > 0 { + cookie, err := ReadCookie(req) + if err != nil { + // User it not a human + rw.WriteHeader(http.StatusForbidden) + return + } + userId = cookie.Id + } + + reqDur := req.Form.Get("session-duration") + stack := req.Form.Get("stack") + stackName := req.Form.Get("stack_name") + imageName := req.Form.Get("image_name") + + if stack != "" { + stack = formatStack(stack) + if ok, err := stackExists(stack); err != nil { + log.Printf("Error retrieving stack: %s", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } else if !ok { + log.Printf("Stack [%s] could not be found", stack) + rw.WriteHeader(http.StatusBadRequest) + return + } + + } + + var duration time.Duration + if reqDur != "" { + d, err := time.ParseDuration(reqDur) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + return + } + if d > playground.DefaultSessionDuration { + log.Printf("Specified session duration was %s but maximum allowed by this playground is %s\n", d.String(), playground.DefaultSessionDuration.String()) + rw.WriteHeader(http.StatusBadRequest) + return + } + duration = d + } else { + duration = playground.DefaultSessionDuration + } + + sConfig := types.SessionConfig{Playground: playground, UserId: userId, Duration: duration, Stack: stack, StackName: stackName, ImageName: imageName} + s, err := core.SessionNew(context.Background(), sConfig) + if err != nil { + if provisioner.OutOfCapacity(err) { + http.Redirect(rw, req, "/ooc", http.StatusFound) + return + } + log.Printf("%#v \n", err) + http.Redirect(rw, req, "/500", http.StatusInternalServerError) + return + //TODO: Return some error code + } else { + hostname := req.Host + // If request is not a form, return sessionId in the body + if req.Header.Get("X-Requested-With") == "XMLHttpRequest" { + resp := NewSessionResponse{SessionId: s.Id, Hostname: hostname} + rw.Header().Set("Content-Type", "application/json") + json.NewEncoder(rw).Encode(resp) + return + } + + http.Redirect(rw, req, fmt.Sprintf("/p/%s", s.Id), http.StatusFound) + } +} + +func formatStack(stack string) string { + if !strings.HasSuffix(stack, ".yml") { + // If it doesn't end with ".yml", assume it hasn't been specified, then default to "stack.yml" + stack = path.Join(stack, "stack.yml") + } + if strings.HasPrefix(stack, "/") { + // The host is anonymous, then use our own stack repo. + stack = fmt.Sprintf("%s%s", "https://raw.githubusercontent.com/play-with-docker/stacks/master", stack) + } + return stack +} + +func stackExists(stack string) (bool, error) { + resp, err := http.Head(stack) + if err != nil { + return false, err + } + defer resp.Body.Close() + + return resp.StatusCode == 200, nil +} diff --git a/handlers/ping.go b/handlers/ping.go new file mode 100644 index 0000000000000000000000000000000000000000..5f25e4cdf27a21cfd250b0fffdea42dcd48e0dc6 --- /dev/null +++ b/handlers/ping.go @@ -0,0 +1,43 @@ +package handlers + +import ( + "context" + "log" + "net/http" + "time" + + "github.com/docker/docker/client" + "github.com/play-with-docker/play-with-docker/config" + "github.com/shirou/gopsutil/load" +) + +func Ping(rw http.ResponseWriter, req *http.Request) { + defer latencyHistogramVec.WithLabelValues("ping").Observe(float64(time.Since(time.Now()).Nanoseconds()) / 1000000) + // Get system load average of the last 5 minutes and compare it against a threashold. + + c, err := client.NewClientWithOpts() + + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if _, err := c.Info(ctx); err != nil && err == context.DeadlineExceeded { + log.Printf("Docker info took to long to respond\n") + rw.WriteHeader(http.StatusGatewayTimeout) + return + } + + a, err := load.Avg() + if err != nil { + log.Println("Cannot get system load average!", err) + } else { + if a.Load5 > config.MaxLoadAvg { + log.Printf("System load average is too high [%f]\n", a.Load5) + rw.WriteHeader(http.StatusInsufficientStorage) + } + } +} diff --git a/handlers/playground.go b/handlers/playground.go new file mode 100644 index 0000000000000000000000000000000000000000..2f58923b63e1f381600349cd1bc2d4e9b2de1af8 --- /dev/null +++ b/handlers/playground.go @@ -0,0 +1,94 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +func NewPlayground(rw http.ResponseWriter, req *http.Request) { + if !ValidateToken(req) { + rw.WriteHeader(http.StatusForbidden) + return + } + + var playground types.Playground + + err := json.NewDecoder(req.Body).Decode(&playground) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(rw, "Error creating playground. Got: %v", err) + return + } + + newPlayground, err := core.PlaygroundNew(playground) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(rw, "Error creating playground. Got: %v", err) + return + } + + json.NewEncoder(rw).Encode(newPlayground) +} + +func ListPlaygrounds(rw http.ResponseWriter, req *http.Request) { + if !ValidateToken(req) { + rw.WriteHeader(http.StatusForbidden) + return + } + + playgrounds, err := core.PlaygroundList() + if err != nil { + log.Printf("Error listing playgrounds. Got: %v\n", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + json.NewEncoder(rw).Encode(playgrounds) +} + +type PlaygroundConfigurationResponse struct { + Id string `json:"id"` + Domain string `json:"domain"` + DefaultDinDInstanceImage string `json:"default_dind_instance_image"` + AvailableDinDInstanceImages []string `json:"available_dind_instance_images"` + AllowWindowsInstances bool `json:"allow_windows_instances"` + DefaultSessionDuration time.Duration `json:"default_session_duration"` + DindVolumeSize string `json:"dind_volume_size"` +} + +func GetCurrentPlayground(rw http.ResponseWriter, req *http.Request) { + playground := core.PlaygroundFindByDomain(req.Host) + if playground == nil { + log.Printf("Playground for domain %s was not found!", req.Host) + rw.WriteHeader(http.StatusBadRequest) + return + } + json.NewEncoder(rw).Encode(PlaygroundConfigurationResponse{ + Id: playground.Id, + Domain: playground.Domain, + DefaultDinDInstanceImage: playground.DefaultDinDInstanceImage, + AvailableDinDInstanceImages: playground.AvailableDinDInstanceImages, + AllowWindowsInstances: playground.AllowWindowsInstances, + DefaultSessionDuration: playground.DefaultSessionDuration, + DindVolumeSize: playground.DindVolumeSize, + }) +} + +func ValidateToken(req *http.Request) bool { + _, password, ok := req.BasicAuth() + if !ok { + return false + } + + if password != config.AdminToken { + return false + } + + return true +} diff --git a/handlers/session_setup.go b/handlers/session_setup.go new file mode 100644 index 0000000000000000000000000000000000000000..04d05214d4c6668a2a556e4cd8135fc11dcbb9d1 --- /dev/null +++ b/handlers/session_setup.go @@ -0,0 +1,50 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/pwd" +) + +func SessionSetup(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + sessionId := vars["sessionId"] + + body := pwd.SessionSetupConf{PlaygroundFQDN: req.Host, DindVolumeSize: "5G"} + + json.NewDecoder(req.Body).Decode(&body) + + s, err := core.SessionGet(sessionId) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + + playground := core.PlaygroundGet(s.PlaygroundId) + if playground == nil { + log.Printf("Playground with id %s for session %s was not found!", s.PlaygroundId, s.Id) + rw.WriteHeader(http.StatusBadRequest) + return + } + + if len(playground.DindVolumeSize) > 0 { + body.DindVolumeSize = playground.DindVolumeSize + } + + body.Privileged = playground.Privileged + err = core.SessionSetup(s, body) + if err != nil { + if pwd.SessionNotEmpty(err) { + log.Println("Cannot setup a session that contains instances") + rw.WriteHeader(http.StatusConflict) + rw.Write([]byte("Cannot setup a session that contains instances")) + return + } + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/handlers/terms.go b/handlers/terms.go new file mode 100644 index 0000000000000000000000000000000000000000..ca72d4709cc7fa80b9ae4b12251992349ea33ba3 --- /dev/null +++ b/handlers/terms.go @@ -0,0 +1,240 @@ +package handlers + +import ( + "log" + "net" + "sync" + "time" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + + "golang.org/x/text/encoding" +) + +type terminal struct { + conn net.Conn + write chan []byte + instance *types.Instance +} + +func (t *terminal) Go(ch chan info, ech chan *types.Instance) { + go func() { + for d := range t.write { + _, err := t.conn.Write(d) + if err != nil { + ech <- t.instance + return + } + } + }() + go func() { + encoder := encoding.Replacement.NewEncoder() + buf := make([]byte, 1024) + for { + n, err := t.conn.Read(buf) + if err != nil { + ech <- t.instance + return + } + b, err := encoder.Bytes(buf[:n]) + if err != nil { + ech <- t.instance + return + } + ch <- info{name: t.instance.Name, data: b} + } + }() +} + +type info struct { + name string + data []byte +} + +type state struct { + name string + status string +} + +type manager struct { + session *types.Session + sendCh chan info + receiveCh chan info + stateCh chan state + terminals map[string]*terminal + errorCh chan *types.Instance + instances map[string]*types.Instance + sync.Mutex +} + +func (m *manager) Send(name string, data []byte) { + m.sendCh <- info{name: name, data: data} +} +func (m *manager) Receive(cb func(name string, data []byte)) { + for i := range m.receiveCh { + cb(i.name, i.data) + } +} +func (m *manager) Status(cb func(name, status string)) { + for s := range m.stateCh { + cb(s.name, s.status) + } +} + +func (m *manager) connect(instance *types.Instance) error { + if !m.trackingInstance(instance) { + return nil + } + + return m.connectTerminal(instance) +} + +func (m *manager) connectTerminal(instance *types.Instance) error { + m.Lock() + defer m.Unlock() + + conn, err := core.InstanceGetTerminal(instance) + if err != nil { + return err + } + chw := make(chan []byte, 10) + t := terminal{conn: conn, write: chw, instance: instance} + m.terminals[instance.Name] = &t + t.Go(m.receiveCh, m.errorCh) + m.stateCh <- state{name: instance.Name, status: "connect"} + + return nil +} + +func (m *manager) disconnectTerminal(instance *types.Instance) { + m.Lock() + defer m.Unlock() + + t := m.terminals[instance.Name] + if t != nil { + if t.write != nil { + close(t.write) + } + if t.conn != nil { + t.conn.Close() + } + delete(m.terminals, instance.Name) + } +} + +func (m *manager) getTerminal(instanceName string) *terminal { + return m.terminals[instanceName] +} + +func (m *manager) trackInstance(instance *types.Instance) { + m.Lock() + defer m.Unlock() + + m.instances[instance.Name] = instance + +} +func (m *manager) untrackInstance(instance *types.Instance) { + m.Lock() + defer m.Unlock() + + delete(m.instances, instance.Name) +} +func (m *manager) trackingInstance(instance *types.Instance) bool { + m.Lock() + defer m.Unlock() + _, found := m.instances[instance.Name] + + return found +} + +func (m *manager) disconnect(instance *types.Instance) { + if !m.trackingInstance(instance) { + return + } + + m.disconnectTerminal(instance) + m.untrackInstance(instance) +} + +func (m *manager) process() { + for { + select { + case i := <-m.sendCh: + t := m.getTerminal(i.name) + if t != nil { + t.write <- i.data + } + case instance := <-m.errorCh: + // check if it still exists before reconnecting + i := core.InstanceGet(&types.Session{Id: instance.SessionId}, instance.Name) + if i == nil { + log.Println("Instance doesn't exist anymore. Won't reconnect") + continue + } + m.stateCh <- state{name: instance.Name, status: "reconnect"} + time.AfterFunc(time.Second, func() { + m.connect(instance) + }) + } + } +} +func (m *manager) Close() { + for _, i := range m.instances { + m.disconnect(i) + } +} + +func (m *manager) Start() error { + instances, err := core.InstanceFindBySession(m.session) + if err != nil { + return err + } + for _, i := range instances { + m.instances[i.Name] = i + m.connect(i) + } + go m.process() + return nil +} + +func NewManager(s *types.Session) (*manager, error) { + m := &manager{ + session: s, + sendCh: make(chan info, 10), + receiveCh: make(chan info, 10), + stateCh: make(chan state, 10), + terminals: make(map[string]*terminal), + errorCh: make(chan *types.Instance, 10), + instances: make(map[string]*types.Instance), + } + + e.On(event.INSTANCE_NEW, func(sessionId string, args ...interface{}) { + if sessionId != s.Id { + return + } + + // There is a new instance in a session we are tracking. We should track it's terminal + instanceName := args[0].(string) + instance := core.InstanceGet(s, instanceName) + if instance == nil { + log.Printf("Instance [%s] was not found in session [%s]\n", instanceName, sessionId) + return + } + m.trackInstance(instance) + m.connect(instance) + }) + + e.On(event.INSTANCE_DELETE, func(sessionId string, args ...interface{}) { + if sessionId != s.Id { + return + } + + // There is a new instance in a session we are tracking. We should track it's terminal + instanceName := args[0].(string) + instance := &types.Instance{Name: instanceName} + m.disconnect(instance) + }) + + return m, nil +} diff --git a/handlers/user.go b/handlers/user.go new file mode 100644 index 0000000000000000000000000000000000000000..577adc997fe9cd2253421bb0448025df29e9eb82 --- /dev/null +++ b/handlers/user.go @@ -0,0 +1,36 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" +) + +type PublicUserInfo struct { + Id string `json:"id"` + Avatar string `json:"avatar"` + Name string `json:"name"` +} + +func GetUser(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + userId := vars["userId"] + + u, err := core.UserGet(userId) + if err != nil { + if storage.NotFound(err) { + log.Printf("User with id %s was not found\n", userId) + rw.WriteHeader(http.StatusNotFound) + return + } + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + pui := PublicUserInfo{Id: u.Id, Avatar: u.Avatar, Name: u.Name} + json.NewEncoder(rw).Encode(pui) +} diff --git a/handlers/ws.go b/handlers/ws.go new file mode 100644 index 0000000000000000000000000000000000000000..fbf850af573493d4477062aa50e499308ce77ed6 --- /dev/null +++ b/handlers/ws.go @@ -0,0 +1,210 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "sync" + + "github.com/gorilla/mux" + "github.com/gorilla/websocket" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/satori/go.uuid" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, +} + +type message struct { + Name string `json:"name"` + Args []interface{} `json:"args"` +} + +type socket struct { + c *websocket.Conn + mx sync.Mutex + listeners map[string][]func(args ...interface{}) + r *http.Request + id string + closed bool +} + +func newSocket(r *http.Request, c *websocket.Conn) *socket { + return &socket{ + c: c, + listeners: map[string][]func(args ...interface{}){}, + r: r, + id: uuid.NewV4().String(), + } +} + +func (s *socket) Id() string { + return s.id +} + +func (s *socket) Request() *http.Request { + return s.r +} + +func (s *socket) Close() { + s.closed = true + s.onMessage(message{Name: "close"}) +} + +func (s *socket) process() { + defer s.Close() + for { + mt, m, err := s.c.ReadMessage() + if err != nil { + log.Printf("Error reading message from websocket. Got: %v\n", err) + break + } + if mt != websocket.TextMessage { + log.Printf("Received websocket message, but it is not a text message.\n") + continue + } + go func() { + var msg message + if err := json.Unmarshal(m, &msg); err != nil { + log.Printf("Cannot unmarshal message received from websocket. Got: %v\n", err) + return + } + s.onMessage(msg) + }() + } +} + +func (s *socket) onMessage(msg message) { + s.mx.Lock() + defer s.mx.Unlock() + + cbs, found := s.listeners[msg.Name] + if !found { + return + } + for _, cb := range cbs { + go cb(msg.Args...) + } +} + +func (s *socket) Emit(ev string, args ...interface{}) { + s.mx.Lock() + defer s.mx.Unlock() + + if s.closed { + return + } + + m := message{Name: ev, Args: args} + b, err := json.Marshal(m) + if err != nil { + log.Printf("Cannot marshal event to json. Got: %v\n", err) + return + } + if err := s.c.WriteMessage(websocket.TextMessage, b); err != nil { + log.Printf("Cannot write event to websocket connection. Got: %v\n", err) + s.Close() + return + } +} + +func (s *socket) On(ev string, cb func(args ...interface{})) { + s.mx.Lock() + defer s.mx.Unlock() + listeners, found := s.listeners[ev] + if !found { + listeners = []func(args ...interface{}){} + } + listeners = append(listeners, cb) + s.listeners[ev] = listeners +} + +func WSH(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + + s := newSocket(r, c) + ws(s) + s.process() +} + +func ws(so *socket) { + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered from ", r) + } + }() + vars := mux.Vars(so.Request()) + + sessionId := vars["sessionId"] + + session, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + log.Printf("Session with id [%s] does not exist!\n", sessionId) + return + } + + client := core.ClientNew(so.Id(), session) + if client == nil { + log.Printf("ERROR: Client was not created for session id %s and socket id %s\n", session.Id, so.Id()) + } + + m, err := NewManager(session) + if err != nil { + log.Printf("Error creating terminal manager. Got: %v", err) + return + } + + go m.Receive(func(name string, data []byte) { + so.Emit("instance terminal out", name, string(data)) + }) + go m.Status(func(name, status string) { + so.Emit("instance terminal status", name, status) + }) + + err = m.Start() + if err != nil { + log.Println(err) + return + } + + so.On("session close", func(args ...interface{}) { + m.Close() + core.SessionClose(session) + }) + + so.On("instance terminal in", func(args ...interface{}) { + if len(args) == 2 && args[0] != nil && args[1] != nil { + name := args[0].(string) + data := args[1].(string) + m.Send(name, []byte(data)) + } + }) + + so.On("instance viewport resize", func(args ...interface{}) { + if len(args) == 2 && args[0] != nil && args[1] != nil { + // User resized his viewport + cols := args[0].(float64) + rows := args[1].(float64) + core.ClientResizeViewPort(client, uint(cols), uint(rows)) + } + }) + + so.On("close", func(args ...interface{}) { + m.Close() + core.ClientClose(client) + }) + + e.OnAny(func(eventType event.EventType, sessionId string, args ...interface{}) { + if session.Id == sessionId { + so.Emit(eventType.String(), args...) + } + }) +} diff --git a/handlers/www/503.html b/handlers/www/503.html new file mode 100644 index 0000000000000000000000000000000000000000..843f965400368fad5bd97f2bf78da83fe5d084ea --- /dev/null +++ b/handlers/www/503.html @@ -0,0 +1,23 @@ + + + + Docker Playground + + + + + + + + +
+ An error has occurred. If you have some time, please report it. Thanks! +
+ + diff --git a/handlers/www/assets/app.js b/handlers/www/assets/app.js new file mode 100644 index 0000000000000000000000000000000000000000..f1e9201db58495497a3179e87545c97ba6e610de --- /dev/null +++ b/handlers/www/assets/app.js @@ -0,0 +1,928 @@ +(function() { + 'use strict'; + + var app = angular.module('DockerPlay', ['ngMaterial', 'ngFileUpload', 'ngclipboard']); + + // Automatically redirects user to a new session when bypassing captcha. + // Controller keeps code/logic separate from the HTML + app.controller("BypassController", ['$scope', '$log', '$http', '$location', '$timeout', function($scope, $log, $http, $location, $timeout) { + setTimeout(function() { + document.getElementById("welcomeFormBypass").submit(); + }, 500); + }]); + + function SessionBuilderModalController($mdDialog, $scope) { + $scope.createBuilderTerminal(); + + $scope.closeSessionBuilder = function() { + $mdDialog.cancel(); + } + } + + app.controller('PlayController', ['$scope', '$rootScope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', 'SessionService', 'Upload', function($scope, $rootScope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService, SessionService, Upload) { + $scope.sessionId = SessionService.getCurrentSessionId(); + $rootScope.instances = []; + $scope.idx = {}; + $scope.host = window.location.host; + $scope.idxByHostname = {}; + $rootScope.selectedInstance = null; + $scope.isAlive = true; + $scope.ttl = '--:--:--'; + $scope.connected = false; + $scope.type = {windows: false}; + $scope.isInstanceBeingCreated = false; + $scope.newInstanceBtnText = '+ Add new instance'; + $scope.deleteInstanceBtnText = 'Delete'; + $scope.isInstanceBeingDeleted = false; + $scope.uploadProgress = 0; + + $scope.uploadFiles = function (files, invalidFiles) { + let total = files.length; + let uploadFile = function() { + let file = files.shift(); + if (!file){ + $scope.uploadMessage = ""; + $scope.uploadProgress = 0; + return + } + $scope.uploadMessage = "Uploading file(s) " + (total - files.length) + "/"+ total + " : " + file.name; + let upload = Upload.upload({url: '/sessions/' + $scope.sessionId + '/instances/' + $rootScope.selectedInstance.name + '/uploads', data: {file: file}, method: 'POST'}) + .then(function(){}, function(){}, function(evt) { + $scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total); + }); + + // process next file + upload.finally(uploadFile); + } + + uploadFile(); + } + + var selectedKeyboardShortcuts = KeyboardShortcutService.getCurrentShortcuts(); + + $scope.resizeHandler = null; + + angular.element($window).bind('resize', function() { + if ($rootScope.selectedInstance) { + if (!$scope.resizeHandler) { + $scope.resizeHandler = setTimeout(function() { + $scope.resizeHandler = null + $scope.resize($scope.selectedInstance.term.proposeGeometry()); + }, 1000); + } + } + }); + + $scope.$on("settings:shortcutsSelected", function(e, preset) { + selectedKeyboardShortcuts = preset; + }); + + + $scope.showAlert = function(title, content, parent, cb) { + $mdDialog.show( + $mdDialog.alert() + .parent(angular.element(document.querySelector(parent || '#popupContainer'))) + .clickOutsideToClose(true) + .title(title) + .textContent(content) + .ok('Got it!') + ).finally(function() { + if (cb) { + cb(); + } + }); + } + + $scope.resize = function(geometry) { + $scope.socket.emit('instance viewport resize', geometry.cols, geometry.rows); + } + + KeyboardShortcutService.setResizeFunc($scope.resize); + + $scope.closeSession = function() { + // Remove alert before closing browser tab + window.onbeforeunload = null; + $scope.socket.emit('session close'); + } + + $scope.upsertInstance = function(info) { + var i = info; + if (!$scope.idx[i.name]) { + $rootScope.instances.push(i); + i.buffer = ''; + $scope.idx[i.name] = i; + $scope.idxByHostname[i.hostname] = i; + } else { + $scope.idx[i.name] = Object.assign($scope.idx[i.name], info); + } + + return $scope.idx[i.name]; + } + + $scope.newInstance = function() { + updateNewInstanceBtnState(true); + var instanceType = $scope.type.windows ? 'windows': 'linux'; + $http({ + method: 'POST', + url: '/sessions/' + $scope.sessionId + '/instances', + data : { ImageName : InstanceService.getDesiredImage(), type: instanceType } + }).then(function(response) { + $scope.upsertInstance(response.data); + }, function(response) { + if (response.status == 409) { + $scope.showAlert('Max instances reached', 'Maximum number of instances reached') + } else if (response.status == 503 && response.data.error == 'out_of_capacity') { + $scope.showAlert('Out Of Capacity', 'We are really sorry. But we are currently out of capacity and cannot create new instances. Please try again later.') + } + }).finally(function() { + updateNewInstanceBtnState(false); + }); + } + + $scope.setSessionState = function(state) { + $scope.ready = state; + + if (!state) { + $mdDialog.show({ + onComplete: function(){SessionBuilderModalController($mdDialog, $scope)}, + contentElement: '#builderDialog', + parent: angular.element(document.body), + clickOutsideToClose: false, + scope: $scope, + preserveScope: true + }); + } + } + + $scope.loadPlaygroundConf = function() { + $http({ + method: 'GET', + url: '/my/playground', + }).then(function(response) { + $scope.playground = response.data; + }); + + } + $scope.getSession = function(sessionId) { + $http({ + method: 'GET', + url: '/sessions/' + $scope.sessionId, + }).then(function(response) { + $scope.setSessionState(response.data.ready); + + if (response.data.created_at) { + $scope.expiresAt = moment(response.data.expires_at); + setInterval(function() { + $scope.ttl = moment.utc($scope.expiresAt.diff(moment())).format('HH:mm:ss'); + $scope.$apply(); + }, 1000); + } + + var i = response.data; + for (var k in i.instances) { + var instance = i.instances[k]; + $rootScope.instances.push(instance); + $scope.idx[instance.name] = instance; + $scope.idxByHostname[instance.hostname] = instance; + } + + var base = ''; + if (window.location.protocol == 'http:') { + base = 'ws://'; + } else { + base = 'wss://'; + } + base += window.location.host; + if (window.location.port) { + base += ':' + window.location.port; + } + + var socket = new ReconnectingWebSocket(base + '/sessions/' + sessionId + '/ws/', null, {reconnectInterval: 1000}); + socket.listeners = {}; + + socket.on = function(name, cb) { + if (!socket.listeners[name]) { + socket.listeners[name] = []; + } + socket.listeners[name].push(cb); + } + + socket.emit = function() { + var name = arguments[0] + var args = []; + for (var i = 1; i < arguments.length; i++) { + args.push(arguments[i]); + } + socket.send(JSON.stringify({name: name, args: args})); + } + + socket.addEventListener('open', function (event) { + $scope.connected = true; + for (var i in $rootScope.instances) { + var instance = $rootScope.instances[i]; + if (instance.term) { + instance.term.setOption('disableStdin', false); + } + } + }); + socket.addEventListener('close', function (event) { + $scope.connected = false; + for (var i in $rootScope.instances) { + var instance = $rootScope.instances[i]; + if (instance.term) { + instance.term.setOption('disableStdin', true); + } + } + }); + socket.addEventListener('message', function (event) { + var m = JSON.parse(event.data); + var ls = socket.listeners[m.name]; + if (ls) { + for (var i=0; i 0) { + // if no instance has been passed, select the first. + $scope.showInstance($rootScope.instances[0]); + } + }, function(response) { + if (response.status == 404) { + document.write('session not found'); + return + } + }); + } + + $scope.openPort = function(instance) { + var port = prompt('What port would you like to open?'); + if (!port) return; + + var url = $scope.getProxyUrl(instance, port); + window.open(url, '_blank'); + } + + $scope.getProxyUrl = function(instance, port) { + var url = 'http://' + instance.proxy_host + '-' + port + '.direct.' + $scope.host; + + return url; + } + + $scope.showInstance = function(instance) { + $rootScope.selectedInstance = instance; + $location.hash(instance.name); + if (!instance.term) { + $timeout(function() { + createTerminal(instance); + TerminalService.setFontSize(TerminalService.getFontSize()); + instance.term.focus(); + $timeout(function() { + }, 0, false); + }, 0, false); + return + } + instance.term.focus(); + } + + $scope.removeInstance = function(name) { + if ($scope.idx[name]) { + var handler = $scope.idx[name].terminalBufferInterval; + clearInterval(handler); + } + if ($scope.idx[name]) { + delete $scope.idx[name]; + $rootScope.instances = $rootScope.instances.filter(function(i) { + return i.name != name; + }); + if ($rootScope.instances.length) { + $scope.showInstance($rootScope.instances[0]); + } + } + } + + $scope.deleteInstance = function(instance) { + updateDeleteInstanceBtnState(true); + $http({ + method: 'DELETE', + url: '/sessions/' + $scope.sessionId + '/instances/' + instance.name, + }).then(function(response) { + $scope.removeInstance(instance.name); + }, function(response) { + console.log('error', response); + }).finally(function() { + updateDeleteInstanceBtnState(false); + }); + }; + + $scope.openEditor = function(instance) { + var w = window.screen.availWidth * 45 / 100; + var h = window.screen.availHeight * 45 / 100; + $window.open('/sessions/' + instance.session_id + '/instances/'+instance.name+'/editor', 'editor', + 'width='+w+',height='+h+',resizable,scrollbars=yes,status=1'); + }; + + $scope.loadPlaygroundConf(); + $scope.getSession($scope.sessionId); + + $scope.createBuilderTerminal = function() { + var builderTerminalContainer = document.getElementById('builder-terminal'); + let term = new Terminal({ + cursorBlink: false + }); + + term.open(builderTerminalContainer); + $scope.builderTerminal = term; + } + function createTerminal(instance, cb) { + if (instance.term) { + return instance.term; + } + + var terminalContainer = document.getElementById('terminal-' + instance.name); + + var term = new Terminal({ + cursorBlink: false, + screenReaderMode: true + }); + + term.open(terminalContainer); + + + const handleCopy = (e) => { + // Ctrl + Alt + C + if (e.ctrlKey && e.altKey && (e.keyCode == 67)) { + document.execCommand('copy'); + return false; + } + }; + + term.attachCustomKeyEventHandler(function(e) { + // handleCopy(e); + if (selectedKeyboardShortcuts == null) return; + + var presets = selectedKeyboardShortcuts.presets + .filter(function(preset) { return preset.keyCode == e.keyCode }) + .filter(function(preset) { return (preset.metaKey == undefined && !e.metaKey) || preset.metaKey == e.metaKey }) + .filter(function(preset) { return (preset.ctrlKey == undefined && !e.ctrlKey) || preset.ctrlKey == e.ctrlKey }) + .filter(function(preset) { return (preset.altKey == undefined && !e.altKey) || preset.altKey == e.altKey }) + .forEach(function(preset) { preset.action({ terminal : term, e })}); + }); + + // Set geometry during the next tick, to avoid race conditions. + + setTimeout(function() { + $scope.resize(term.proposeGeometry()); + }, 0); + + instance.terminalBuffer = ''; + instance.terminalBufferInterval = setInterval(function() { + if (instance.terminalBuffer.length > 0) { + $scope.socket.emit('instance terminal in', instance.name, instance.terminalBuffer); + instance.terminalBuffer = ''; + } + }, 70); + term.on('data', function(d) { + instance.terminalBuffer += d; + }); + + instance.term = term; + + if (cb) { + cb(); + } + } + + function updateNewInstanceBtnState(isInstanceBeingCreated) { + if (isInstanceBeingCreated === true) { + $scope.newInstanceBtnText = '+ Creating...'; + $scope.isInstanceBeingCreated = true; + } else { + $scope.newInstanceBtnText = '+ Add new instance'; + $scope.isInstanceBeingCreated = false; + } + } + + function updateDeleteInstanceBtnState(isInstanceBeingDeleted) { + if (isInstanceBeingDeleted === true) { + $scope.deleteInstanceBtnText = 'Deleting...'; + $scope.isInstanceBeingDeleted = true; + } else { + $scope.deleteInstanceBtnText = 'Delete'; + $scope.isInstanceBeingDeleted = false; + } + } + }]) + .config(['$mdIconProvider', '$locationProvider', '$mdThemingProvider', function($mdIconProvider, $locationProvider, $mdThemingProvider) { + $locationProvider.html5Mode({enabled: true, requireBase: false}); + $mdIconProvider.defaultIconSet('../assets/social-icons.svg', 24); + $mdThemingProvider.theme('kube') + .primaryPalette('grey') + .accentPalette('grey'); + }]) + .component('settingsIcon', { + template : "settings", + controller : function($mdDialog) { + var $ctrl = this; + $ctrl.onClick = function() { + $mdDialog.show({ + controller : function() {}, + template : "", + parent: angular.element(document.body), + clickOutsideToClose : true + }) + } + } + }) + .component('templatesIcon', { + template : "build", + controller : function($mdDialog) { + var $ctrl = this; + $ctrl.onClick = function() { + $mdDialog.show({ + controller : function() {}, + template : "", + parent: angular.element(document.body), + clickOutsideToClose : true + }) + } + } + }) + .component("templatesDialog", { + templateUrl : "templates-modal.html", + controller : function($mdDialog, $scope, SessionService) { + var $ctrl = this; + $scope.building = false; + $scope.templates = SessionService.getAvailableTemplates(); + $ctrl.close = function() { + $mdDialog.cancel(); + } + $ctrl.setupSession = function(setup) { + $scope.building = true; + SessionService.setup(setup, function(err) { + $scope.building = false; + if (err) { + $scope.errorMessage = err; + return; + } + $ctrl.close(); + }); + } + } + }) + .component("settingsDialog", { + templateUrl : "settings-modal.html", + controller : function($mdDialog, KeyboardShortcutService, $rootScope, InstanceService, TerminalService) { + var $ctrl = this; + $ctrl.$onInit = function() { + $ctrl.keyboardShortcutPresets = KeyboardShortcutService.getAvailablePresets(); + $ctrl.selectedShortcutPreset = KeyboardShortcutService.getCurrentShortcuts(); + $ctrl.instanceImages = InstanceService.getAvailableImages(); + $ctrl.selectedInstanceImage = InstanceService.getDesiredImage(); + $ctrl.terminalFontSizes = TerminalService.getFontSizes(); + }; + + $ctrl.currentShortcutConfig = function(value) { + if (value !== undefined) { + value = JSON.parse(value); + KeyboardShortcutService.setCurrentShortcuts(value); + $ctrl.selectedShortcutPreset = angular.copy(KeyboardShortcutService.getCurrentShortcuts()); + $rootScope.$broadcast('settings:shortcutsSelected', $ctrl.selectedShortcutPreset); + } + return JSON.stringify(KeyboardShortcutService.getCurrentShortcuts()); + }; + + $ctrl.currentDesiredInstanceImage = function(value) { + if (value !== undefined) { + InstanceService.setDesiredImage(value); + } + return InstanceService.getDesiredImage(value); + }; + $ctrl.currentTerminalFontSize = function(value) { + if (value !== undefined) { + // set font size + TerminalService.setFontSize(value); + return; + } + + return TerminalService.getFontSize(); + } + + $ctrl.close = function() { + $mdDialog.cancel(); + } + } + }) + .service("SessionService", function($http) { + var templates = [ + { + title: '3 Managers and 2 Workers', + icon: '/assets/swarm.png', + setup: { + instances: [ + {hostname: 'manager1', is_swarm_manager: true}, + {hostname: 'manager2', is_swarm_manager: true}, + {hostname: 'manager3', is_swarm_manager: true}, + {hostname: 'worker1', is_swarm_worker: true}, + {hostname: 'worker2', is_swarm_worker: true} + ] + } + }, + { + title: '5 Managers and no workers', + icon: '/assets/swarm.png', + setup: { + instances: [ + {hostname: 'manager1', is_swarm_manager: true}, + {hostname: 'manager2', is_swarm_manager: true}, + {hostname: 'manager3', is_swarm_manager: true}, + {hostname: 'manager4', is_swarm_manager: true}, + {hostname: 'manager5', is_swarm_manager: true} + ] + } + }, + { + title: '1 Manager and 1 Worker', + icon: '/assets/swarm.png', + setup: { + instances: [ + {hostname: 'manager1', is_swarm_manager: true}, + {hostname: 'worker1', is_swarm_worker: true} + ] + } + } + ]; + + return { + getAvailableTemplates: getAvailableTemplates, + getCurrentSessionId: getCurrentSessionId, + setup: setup, + }; + + function getCurrentSessionId() { + return window.location.pathname.replace('/p/', ''); + } + function getAvailableTemplates() { + return templates; + } + function setup(plan, cb) { + return $http + .post("/sessions/" + getCurrentSessionId() + "/setup", plan) + .then(function(response) { + if (cb) cb(); + }, function(response) { + if (cb) cb(response.data); + }); + } + }) + .service("InstanceService", function($http) { + var instanceImages = []; + _prepopulateAvailableImages(); + + return { + getAvailableImages : getAvailableImages, + setDesiredImage : setDesiredImage, + getDesiredImage : getDesiredImage, + }; + + function getAvailableImages() { + return instanceImages; + } + + function getDesiredImage() { + var image = localStorage.getItem("settings.desiredImage"); + if (image == null) + return instanceImages[0]; + return image; + } + + function setDesiredImage(image) { + if (image === null) + localStorage.removeItem("settings.desiredImage"); + else + localStorage.setItem("settings.desiredImage", image); + } + + function _prepopulateAvailableImages() { + return $http + .get("/instances/images") + .then(function(response) { + instanceImages = response.data; + }); + } + + }) + .run(function(InstanceService) { /* forcing pre-populating for now */ }) + .service("KeyboardShortcutService", ['TerminalService', function(TerminalService) { + var resizeFunc; + + return { + getAvailablePresets : getAvailablePresets, + getCurrentShortcuts : getCurrentShortcuts, + setCurrentShortcuts : setCurrentShortcuts, + setResizeFunc : setResizeFunc + }; + + function setResizeFunc(f) { + resizeFunc = f; + } + + function getAvailablePresets() { + return [ + { + name : "None", + presets : [ + { + description : "Toggle terminal fullscreen", command : "Alt+enter", altKey : true, keyCode : 13, action : function(context) { TerminalService.toggleFullScreen(context.terminal, resizeFunc); } + }, + { + description: "Increase Font Size", + command: "Ctrl++", + ctrlKey : true, + keyCode: 187, + action: function(context) { + TerminalService.increaseFontSize(); + context.e.preventDefault() + } + }, + { + description: "Decrease Font Size", + command: "Ctrl+-", + ctrlKey: true, + keyCode: 189, + action: function(context) { + context.e.preventDefault() + TerminalService.decreaseFontSize(); + } + } + ] + }, + { + name : "Mac OSX", + presets : [ + { description : "Clear terminal", command : "Cmd+K", metaKey : true, keyCode : 75, action : function(context) { context.terminal.clear(); }}, + { description : "Toggle terminal fullscreen", command : "Alt+enter", altKey : true, keyCode : 13, action : function(context) { TerminalService.toggleFullScreen(context.terminal, resizeFunc); }}, + { + description: "Increase Font Size", + command: "Cmd++", + metaKey : true, + keyCode: 187, + action: function(context) { + TerminalService.increaseFontSize(); + context.e.preventDefault() + } + }, + { + description: "Decrease Font Size", + command: "Cmd+-", + metaKey: true, + keyCode: 189, + action: function(context) { + context.e.preventDefault() + TerminalService.decreaseFontSize(); + } + } + ] + } + ] + } + + function getCurrentShortcuts() { + var shortcuts = localStorage.getItem("shortcut-preset-name"); + if (shortcuts == null) { + shortcuts = getDefaultShortcutPrefixName(); + if (shortcuts == null) + return null; + } + + var preset = getAvailablePresets() + .filter(function(preset) { return preset.name == shortcuts; }); + if (preset.length == 0) + console.error("Unable to find preset with name '" + shortcuts + "'"); + return preset[0]; + return (shortcuts == null) ? null : JSON.parse(shortcuts); + } + + function setCurrentShortcuts(config) { + localStorage.setItem("shortcut-preset-name", config.name); + } + + function getDefaultShortcutPrefixName() { + if (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) + return "Mac OSX"; + return "None"; + } + }]) + .service('TerminalService', ['$window', '$rootScope', function($window, $rootScope) { + var fullscreen; + var fontSize = getFontSize(); + return { + getFontSizes : getFontSizes, + setFontSize : setFontSize, + getFontSize : getFontSize, + increaseFontSize : increaseFontSize, + decreaseFontSize : decreaseFontSize, + toggleFullScreen : toggleFullScreen + }; + function getFontSizes() { + var terminalFontSizes = []; + for (var i=3; i<40; i++) { + terminalFontSizes.push(i+'px'); + } + return terminalFontSizes; + }; + function getFontSize() { + if($rootScope.selectedInstance){ + return $rootScope.selectedInstance.term.getOption("fontSize") + "px" + }else{ + return $(".terminal").css("font-size") + } + } + function setFontSize(value) { + const { term }= $rootScope.selectedInstance; + fontSize = value; + var size = parseInt(value); + term.setOption("fontSize", size) + term.resize(1,1) + term.fit() + } + function increaseFontSize() { + var sizes = getFontSizes(); + var size = getFontSize(); + var i = sizes.indexOf(size); + if (i == -1) { + return; + } + if (i+1 > sizes.length) { + return; + } + setFontSize(sizes[i+1]); + } + function decreaseFontSize() { + var sizes = getFontSizes(); + var size = getFontSize(); + var i = sizes.indexOf(size); + if (i == -1) { + return; + } + if (i-1 < 0) { + return; + } + setFontSize(sizes[i-1]); + } + function toggleFullScreen(terminal, resize) { + if(fullscreen) { + terminal.toggleFullScreen(); + terminal.containerElement.append(terminal.element) + setTimeout(()=>{ + terminal.resize(1,1); + terminal.fit(); + terminal.focus(); + },100) + fullscreen = null; + } else { + // save the current parent + terminal.containerElement = $(terminal.element).parent() + $("body").append(terminal.element) + fullscreen = terminal.proposeGeometry(); + terminal.toggleFullScreen(); + terminal.fit(); + terminal.focus(); + } + } + }]); +})(); diff --git a/handlers/www/assets/attach.js b/handlers/www/assets/attach.js new file mode 100644 index 0000000000000000000000000000000000000000..2ef1f7378c97bc69aa05d36537934c82e46343d5 --- /dev/null +++ b/handlers/www/assets/attach.js @@ -0,0 +1,134 @@ +/* + * Implements the attach method, that + * attaches the terminal to a WebSocket stream. + * + * The bidirectional argument indicates, whether the terminal should + * send data to the socket as well and is true, by default. + */ + +(function (attach) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = attach(require('../../src/xterm')); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../src/xterm'], attach); + } else { + /* + * Plain browser environment + */ + attach(window.Terminal); + } +})(function (Xterm) { + 'use strict'; + + /** + * This module provides methods for attaching a terminal to a WebSocket + * stream. + * + * @module xterm/addons/attach/attach + */ + var exports = {}; + + /** + * Attaches the given terminal to the given socket. + * + * @param {Xterm} term - The terminal to be attached to the given socket. + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + exports.attach = function (term, socket, bidirectional, buffered) { + bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; + term.socket = socket; + + term._flushBuffer = function () { + term.write(term._attachSocketBuffer); + term._attachSocketBuffer = null; + clearTimeout(term._attachSocketBufferTimer); + term._attachSocketBufferTimer = null; + }; + + term._pushToBuffer = function (data) { + if (term._attachSocketBuffer) { + term._attachSocketBuffer += data; + } else { + term._attachSocketBuffer = data; + setTimeout(term._flushBuffer, 10); + } + }; + + term._getMessage = function (ev) { + if (buffered) { + term._pushToBuffer(ev.data); + } else { + term.write(ev.data); + } + }; + + term._sendData = function (data) { + socket.send(data); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + + socket.addEventListener('close', term.detach.bind(term, socket)); + socket.addEventListener('error', term.detach.bind(term, socket)); + }; + + /** + * Detaches the given terminal from the given socket + * + * @param {Xterm} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + exports.detach = function (term, socket) { + term.off('data', term._sendData); + + socket = (typeof socket == 'undefined') ? term.socket : socket; + + if (socket) { + socket.removeEventListener('message', term._getMessage); + } + + delete term.socket; + }; + + /** + * Attaches the current terminal to the given socket + * + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + Xterm.prototype.attach = function (socket, bidirectional, buffered) { + return exports.attach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + Xterm.prototype.detach = function (socket) { + return exports.detach(this, socket); + }; + + return exports; +}); diff --git a/handlers/www/assets/button.png b/handlers/www/assets/button.png new file mode 100644 index 0000000000000000000000000000000000000000..dcea54d0fe4a6e4bd6d16138099979087a7c9ee9 Binary files /dev/null and b/handlers/www/assets/button.png differ diff --git a/handlers/www/assets/editor.css b/handlers/www/assets/editor.css new file mode 100644 index 0000000000000000000000000000000000000000..2f8a4c7a682aa9476e571dfc80c8889d9df226ad --- /dev/null +++ b/handlers/www/assets/editor.css @@ -0,0 +1,21 @@ +.alert-top { + position: absolute; + top: 0; + right: 0; + width:100px; + display:none; + text-align: center; + padding: 3px; + height: 30px; + margin-bottom: 0px; +} + +.alert-newfile { + text-align: center; + padding: 3px; + font-size: 15px; +} + +.col-md-3 { + overflow-x: auto; +} diff --git a/handlers/www/assets/full_horizontal.svg b/handlers/www/assets/full_horizontal.svg new file mode 100644 index 0000000000000000000000000000000000000000..d2201403176b4e90ae5dc90a94cb6cf9e9ece56d --- /dev/null +++ b/handlers/www/assets/full_horizontal.svg @@ -0,0 +1,72 @@ + + + + full_horizontal + Created with Sketch. + + background + + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/handlers/www/assets/landing.css b/handlers/www/assets/landing.css new file mode 100644 index 0000000000000000000000000000000000000000..09f6da21dfc600a6f47678b2bca8d65db6ec1449 --- /dev/null +++ b/handlers/www/assets/landing.css @@ -0,0 +1,82 @@ +/* Space out content a bit */ +body { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +/* Everything but the jumbotron gets side spacing for mobile first views */ +.header, +.marketing, +.footer { + padding-right: 1rem; + padding-left: 1rem; +} + +/* Custom page header */ +.header { + padding-bottom: 1rem; + border-bottom: .05rem solid #e5e5e5; +} +/* Make the masthead heading the same height as the navigation */ +.header h3 { + margin-top: 0; + margin-bottom: 0; + line-height: 3rem; +} + +/* Custom page footer */ +.footer { + padding-top: 1.5rem; + color: #777; + border-top: .05rem solid #e5e5e5; +} + +/* Customize container */ +@media (min-width: 48em) { + .container { + max-width: 46rem; + } +} +.container-narrow > hr { + margin: 2rem 0; +} + +/* Main marketing message and sign up button */ +.jumbotron { + text-align: center; + border-bottom: .05rem solid #e5e5e5; +} +.jumbotron .btn { + padding: .75rem 1.5rem; + font-size: 1.5rem; +} +.btn.dropdown-toggle, .dropdown-menu a { + cursor: pointer; +} + +/* Supporting marketing content */ +.marketing { + margin: 3rem 0; +} +.marketing p + h4 { + margin-top: 1.5rem; +} + +/* Responsive: Portrait tablets and up */ +@media screen and (min-width: 48em) { + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 2rem; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } +} diff --git a/handlers/www/assets/package-lock.json b/handlers/www/assets/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..77fbf9d50e14508f172789e45286b1d07575f1e6 --- /dev/null +++ b/handlers/www/assets/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "xterm": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-3.14.5.tgz", + "integrity": "sha512-DVmQ8jlEtL+WbBKUZuMxHMBgK/yeIZwkXB81bH+MGaKKnJGYwA+770hzhXPfwEIokK9On9YIFPRleVp/5G7z9g==" + } + } +} diff --git a/handlers/www/assets/setup-xterm.js b/handlers/www/assets/setup-xterm.js new file mode 100644 index 0000000000000000000000000000000000000000..b884826d3ca10a28d6038519bd4186ac5eaff7ee --- /dev/null +++ b/handlers/www/assets/setup-xterm.js @@ -0,0 +1,3 @@ +Terminal.applyAddon(fit); +Terminal.applyAddon(fullscreen); + diff --git a/handlers/www/assets/social-icons.svg b/handlers/www/assets/social-icons.svg new file mode 100644 index 0000000000000000000000000000000000000000..3b39255310d5b1b4d1a2cfb0b2987837908ed2ff --- /dev/null +++ b/handlers/www/assets/social-icons.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/handlers/www/assets/style.css b/handlers/www/assets/style.css new file mode 100644 index 0000000000000000000000000000000000000000..b76538146ad73bc41848b8b3e615a7d33ef3a06e --- /dev/null +++ b/handlers/www/assets/style.css @@ -0,0 +1,99 @@ +@import url('https://fonts.googleapis.com/css?family=Rationale'); + +.selected button { + background-color: rgba(158,158,158,0.2); +} + +.terminal-container { + background-color: #000; + padding: 0; + display: flex; + align-items: stretch; + justify-content: stretch; + flex: 1; +} + +.terminal-instance{ + width: 100%; +} + +.clock { + font-family: 'Rationale', sans-serif; + font-size: 3.0em; + color: #1da4eb; + text-align: center; +} + +.welcome { + background-color: #e7e7e7; +} + +.welcome > div { + text-align: center; +} + +.welcome > div > img { + max-width: 100%; +} + +.g-recaptcha div { + margin-left: auto; + margin-right: auto; + margin-bottom: auto; + margin-top: 50px; +} + +.uploadStatus .bottom-block { + display: block; + position: relative; + background-color: rgba(255, 235, 169, 0.25); + height: 30px; + width: 100%; +} + +.uploadStatus .bottom-block > span { + display: inline-block; + padding: 8px; + font-size: 0.9em; +} + +.uploadStatus { + display: block; + position: relative; + width: 100%; + border: 2px solid #aad1f9; + transition: opacity 0.1s linear; + border-top: 0px; +} + +.disconnected { + background-color: #FDF4B6; +} +md-input-container { + margin-bottom: 0; +} +md-input-container .md-errors-spacer { + height: 0; + min-height: 0; +} + +.stats { + min-height: 230px; +} + +::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; +} +::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0,0,0,.5); + -webkit-box-shadow: 0 0 1px rgba(255,255,255,.5); +} +.md-mini { + min-width: 24px; +} + +.dragover { + opacity: 0.5; +} diff --git a/handlers/www/assets/swarm.png b/handlers/www/assets/swarm.png new file mode 100644 index 0000000000000000000000000000000000000000..3e7414f7cde8be52c2a720b14d6abc927420747a --- /dev/null +++ b/handlers/www/assets/swarm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66de55a585a7f42fc78b3f84e7144e94ad07a0b7fcd9aaf66b3a142f89a1c02f +size 123625 diff --git a/handlers/www/assets/xterm/addons/attach/attach.js b/handlers/www/assets/xterm/addons/attach/attach.js new file mode 100644 index 0000000000000000000000000000000000000000..23a9030b3db980d9d2606bdf277a96ee505ce81f --- /dev/null +++ b/handlers/www/assets/xterm/addons/attach/attach.js @@ -0,0 +1,106 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.attach = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterm;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n // TODO: This should be typed but there seem to be issues importing the type\n let myTextDecoder: any;\n\n addonTerminal.__getMessage = function(ev: MessageEvent): void {\n let str: string;\n\n if (typeof ev.data === 'object') {\n if (!myTextDecoder) {\n myTextDecoder = new TextDecoder();\n }\n if (ev.data instanceof ArrayBuffer) {\n str = myTextDecoder.decode(ev.data);\n displayData(str);\n } else {\n const fileReader = new FileReader();\n\n fileReader.addEventListener('load', () => {\n str = myTextDecoder.decode(fileReader.result);\n displayData(str);\n });\n fileReader.readAsArrayBuffer(ev.data);\n }\n } else if (typeof ev.data === 'string') {\n displayData(ev.data);\n } else {\n throw Error(`Cannot handle \"${typeof ev.data}\" websocket message.`);\n }\n };\n\n /**\n * Push data to buffer or write it in the terminal.\n * This is used as a callback for FileReader.onload.\n *\n * @param str String decoded by FileReader.\n * @param data The data of the EventMessage.\n */\n function displayData(str?: string, data?: string): void {\n if (buffered) {\n addonTerminal.__pushToBuffer(str || data);\n } else {\n addonTerminal.write(str || data);\n }\n }\n\n addonTerminal.__sendData = (data: string) => {\n if (socket.readyState !== 1) {\n return;\n }\n socket.send(data);\n };\n\n addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));\n\n if (bidirectional) {\n addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData);\n addonTerminal._core.register(addonTerminal.__dataListener);\n }\n\n addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket)));\n addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket)));\n}\n\nfunction addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable {\n socket.addEventListener(type, handler);\n return {\n dispose: () => {\n if (!handler) {\n // Already disposed\n return;\n }\n socket.removeEventListener(type, handler);\n handler = null;\n }\n };\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function detach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.__dataListener.dispose();\n addonTerminal.__dataListener = undefined;\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n attach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).detach = function (socket: WebSocket): void {\n detach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADmBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/EA;AAiFA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAZA;AAeA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/addons/fit/fit.js b/handlers/www/assets/xterm/addons/fit/fit.js new file mode 100644 index 0000000000000000000000000000000000000000..880ae025bc46870da20ea769d8e18a8b43fd99e1 --- /dev/null +++ b/handlers/www/assets/xterm/addons/fit/fit.js @@ -0,0 +1,51 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterm)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (term)._core._renderCoordinator.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (term)._core._renderCoordinator.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (term)._core._renderCoordinator.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/addons/fullscreen/fullscreen.css b/handlers/www/assets/xterm/addons/fullscreen/fullscreen.css new file mode 100644 index 0000000000000000000000000000000000000000..60e8c5114c170aad3aca4e1aa11e4544e64053d4 --- /dev/null +++ b/handlers/www/assets/xterm/addons/fullscreen/fullscreen.css @@ -0,0 +1,10 @@ +.xterm.fullscreen { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: auto; + height: auto; + z-index: 255; +} diff --git a/handlers/www/assets/xterm/addons/fullscreen/fullscreen.js b/handlers/www/assets/xterm/addons/fullscreen/fullscreen.js new file mode 100644 index 0000000000000000000000000000000000000000..b568be8840bbababa5a6470641930cee846f37b7 --- /dev/null +++ b/handlers/www/assets/xterm/addons/fullscreen/fullscreen.js @@ -0,0 +1,29 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i void;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ?\n term.element.classList.remove : term.element.classList.add;\n } else if (!fullscreen) {\n fn = term.element.classList.remove;\n } else {\n fn = term.element.classList.add;\n }\n\n fn = fn.bind(term.element.classList);\n fn('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADYA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/addons/search/search.js b/handlers/www/assets/xterm/addons/search/search.js new file mode 100644 index 0000000000000000000000000000000000000000..d632b071b7c1fa6d060b6261e663e67d9ead3208 --- /dev/null +++ b/handlers/www/assets/xterm/addons/search/search.js @@ -0,0 +1,262 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i?'; +var LINES_CACHE_TIME_TO_LIVE = 15 * 1000; +var SearchHelper = (function () { + function SearchHelper(_terminal) { + this._terminal = _terminal; + this._linesCache = null; + this._linesCacheTimeoutId = 0; + this._destroyLinesCache = this._destroyLinesCache.bind(this); + } + SearchHelper.prototype.findNext = function (term, searchOptions) { + var incremental = searchOptions.incremental; + var result; + if (!term || term.length === 0) { + this._terminal.clearSelection(); + return false; + } + var startCol = 0; + var startRow = this._terminal.buffer.viewportY; + if (this._terminal.hasSelection()) { + var currentSelection = this._terminal.getSelectionPosition(); + startRow = incremental ? currentSelection.startRow : currentSelection.endRow; + startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; + } + this._initLinesCache(); + var findingRow = startRow; + var cumulativeCols = startCol; + while (this._terminal.buffer.getLine(findingRow).isWrapped) { + findingRow--; + cumulativeCols += this._terminal.cols; + } + result = this._findInLine(term, findingRow, cumulativeCols, searchOptions); + if (!result) { + for (var y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) { + result = this._findInLine(term, y, 0, searchOptions); + if (result) { + break; + } + } + } + if (!result) { + for (var y = 0; y < findingRow; y++) { + result = this._findInLine(term, y, 0, searchOptions); + if (result) { + break; + } + } + } + return this._selectResult(result); + }; + SearchHelper.prototype.findPrevious = function (term, searchOptions) { + var result; + if (!term || term.length === 0) { + this._terminal.clearSelection(); + return false; + } + var isReverseSearch = true; + var startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1; + var startCol = this._terminal.cols; + if (this._terminal.hasSelection()) { + var currentSelection = this._terminal.getSelectionPosition(); + startRow = currentSelection.startRow; + startCol = currentSelection.startColumn; + } + this._initLinesCache(); + result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch); + if (!result) { + var cumulativeCols = this._terminal.cols; + if (this._terminal.buffer.getLine(startRow).isWrapped) { + cumulativeCols += startCol; + } + for (var y = startRow - 1; y >= 0; y--) { + result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); + if (result) { + break; + } + if (this._terminal.buffer.getLine(y).isWrapped) { + cumulativeCols += this._terminal.cols; + } + else { + cumulativeCols = this._terminal.cols; + } + } + } + if (!result) { + var searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1; + var cumulativeCols = this._terminal.cols; + for (var y = searchFrom; y >= startRow; y--) { + result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch); + if (result) { + break; + } + if (this._terminal.buffer.getLine(y).isWrapped) { + cumulativeCols += this._terminal.cols; + } + else { + cumulativeCols = this._terminal.cols; + } + } + } + return this._selectResult(result); + }; + SearchHelper.prototype._initLinesCache = function () { + var _this = this; + if (!this._linesCache) { + this._linesCache = new Array(this._terminal.buffer.length); + this._cursorMoveListener = this._terminal.onCursorMove(function () { return _this._destroyLinesCache(); }); + this._resizeListener = this._terminal.onResize(function () { return _this._destroyLinesCache(); }); + } + window.clearTimeout(this._linesCacheTimeoutId); + this._linesCacheTimeoutId = window.setTimeout(function () { return _this._destroyLinesCache(); }, LINES_CACHE_TIME_TO_LIVE); + }; + SearchHelper.prototype._destroyLinesCache = function () { + this._linesCache = null; + if (this._cursorMoveListener) { + this._cursorMoveListener.dispose(); + this._cursorMoveListener = undefined; + } + if (this._resizeListener) { + this._resizeListener.dispose(); + this._resizeListener = undefined; + } + if (this._linesCacheTimeoutId) { + window.clearTimeout(this._linesCacheTimeoutId); + this._linesCacheTimeoutId = 0; + } + }; + SearchHelper.prototype._isWholeWord = function (searchIndex, line, term) { + return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) && + (((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1))); + }; + SearchHelper.prototype._findInLine = function (term, row, col, searchOptions, isReverseSearch) { + if (searchOptions === void 0) { searchOptions = {}; } + if (isReverseSearch === void 0) { isReverseSearch = false; } + if (this._terminal.buffer.getLine(row).isWrapped) { + return; + } + var stringLine = this._linesCache ? this._linesCache[row] : void 0; + if (stringLine === void 0) { + stringLine = this.translateBufferLineToStringWithWrap(row, true); + if (this._linesCache) { + this._linesCache[row] = stringLine; + } + } + var searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase(); + var searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase(); + var resultIndex = -1; + if (searchOptions.regex) { + var searchRegex = RegExp(searchTerm, 'g'); + var foundTerm = void 0; + if (isReverseSearch) { + while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) { + resultIndex = searchRegex.lastIndex - foundTerm[0].length; + term = foundTerm[0]; + searchRegex.lastIndex -= (term.length - 1); + } + } + else { + foundTerm = searchRegex.exec(searchStringLine.slice(col)); + if (foundTerm && foundTerm[0].length > 0) { + resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length); + term = foundTerm[0]; + } + } + } + else { + if (isReverseSearch) { + if (col - searchTerm.length >= 0) { + resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length); + } + } + else { + resultIndex = searchStringLine.indexOf(searchTerm, col); + } + } + if (resultIndex >= 0) { + if (resultIndex >= this._terminal.cols) { + row += Math.floor(resultIndex / this._terminal.cols); + resultIndex = resultIndex % this._terminal.cols; + } + if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) { + return; + } + var line = this._terminal.buffer.getLine(row); + for (var i = 0; i < resultIndex; i++) { + var cell = line.getCell(i); + var char = cell.char; + if (char.length > 1) { + resultIndex -= char.length - 1; + } + var charWidth = cell.width; + if (charWidth === 0) { + resultIndex++; + } + } + return { + term: term, + col: resultIndex, + row: row + }; + } + }; + SearchHelper.prototype.translateBufferLineToStringWithWrap = function (lineIndex, trimRight) { + var lineString = ''; + var lineWrapsToNext; + do { + var nextLine = this._terminal.buffer.getLine(lineIndex + 1); + lineWrapsToNext = nextLine ? nextLine.isWrapped : false; + lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols); + lineIndex++; + } while (lineWrapsToNext); + return lineString; + }; + SearchHelper.prototype._selectResult = function (result) { + if (!result) { + this._terminal.clearSelection(); + return false; + } + this._terminal.select(result.col, result.row, result.term.length); + this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY); + return true; + }; + return SearchHelper; +}()); +exports.SearchHelper = SearchHelper; + +},{}],2:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SearchHelper_1 = require("./SearchHelper"); +function findNext(terminal, term, searchOptions) { + if (searchOptions === void 0) { searchOptions = {}; } + var addonTerminal = terminal; + if (!addonTerminal.__searchHelper) { + addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal); + } + return addonTerminal.__searchHelper.findNext(term, searchOptions); +} +exports.findNext = findNext; +function findPrevious(terminal, term, searchOptions) { + var addonTerminal = terminal; + if (!addonTerminal.__searchHelper) { + addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal); + } + return addonTerminal.__searchHelper.findPrevious(term, searchOptions); +} +exports.findPrevious = findPrevious; +function apply(terminalConstructor) { + terminalConstructor.prototype.findNext = function (term, searchOptions) { + return findNext(this, term, searchOptions); + }; + terminalConstructor.prototype.findPrevious = function (term, searchOptions) { + return findPrevious(this, term, searchOptions); + }; +} +exports.apply = apply; + +},{"./SearchHelper":1}]},{},[2])(2) +}); +//# sourceMappingURL=search.js.map diff --git a/handlers/www/assets/xterm/addons/search/search.js.map b/handlers/www/assets/xterm/addons/search/search.js.map new file mode 100644 index 0000000000000000000000000000000000000000..33a7b37ebef76189504efc5cdca9beb2330223ab --- /dev/null +++ b/handlers/www/assets/xterm/addons/search/search.js.map @@ -0,0 +1 @@ +{"version":3,"file":"search.js","sources":["../../../src/addons/search/search.ts","../../../src/addons/search/SearchHelper.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { SearchHelper } from './SearchHelper';\nimport { Terminal } from 'xterm';\nimport { ISearchAddonTerminal, ISearchOptions } from './Interfaces';\n\n/**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term The search term.\n * @param searchOptions Search options\n * @return Whether a result was found.\n */\nexport function findNext(terminal: Terminal, term: string, searchOptions: ISearchOptions = {}): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findNext(term, searchOptions);\n}\n\n/**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term The search term.\n * @param searchOptions Search options\n * @return Whether a result was found.\n */\nexport function findPrevious(terminal: Terminal, term: string, searchOptions: ISearchOptions): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findPrevious(term, searchOptions);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).findNext = function(term: string, searchOptions: ISearchOptions): boolean {\n return findNext(this, term, searchOptions);\n };\n\n (terminalConstructor.prototype).findPrevious = function(term: string, searchOptions: ISearchOptions): boolean {\n return findPrevious(this, term, searchOptions);\n };\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';\nimport { IDisposable } from 'xterm';\n\nconst NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:\"\\',./<>?';\nconst LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs\n\n/**\n * A class that knows how to search the terminal and how to display the results.\n */\nexport class SearchHelper implements ISearchHelper {\n /**\n * translateBufferLineToStringWithWrap is a fairly expensive call.\n * We memoize the calls into an array that has a time based ttl.\n * _linesCache is also invalidated when the terminal cursor moves.\n */\n private _linesCache: string[] = null;\n private _linesCacheTimeoutId = 0;\n private _cursorMoveListener: IDisposable | undefined;\n private _resizeListener: IDisposable | undefined;\n\n constructor(private _terminal: ISearchAddonTerminal) {\n this._destroyLinesCache = this._destroyLinesCache.bind(this);\n }\n\n /**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term The search term.\n * @param searchOptions Search options.\n * @return Whether a result was found.\n */\n public findNext(term: string, searchOptions?: ISearchOptions): boolean {\n const {incremental} = searchOptions;\n let result: ISearchResult;\n\n if (!term || term.length === 0) {\n this._terminal.clearSelection();\n return false;\n }\n\n let startCol: number = 0;\n let startRow = this._terminal.buffer.viewportY;\n\n if (this._terminal.hasSelection()) {\n // Start from the selection end if there is a selection\n // For incremental search, use existing row\n const currentSelection = this._terminal.getSelectionPosition();\n startRow = incremental ? currentSelection.startRow : currentSelection.endRow;\n startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;\n }\n\n this._initLinesCache();\n\n // A row that has isWrapped = false\n let findingRow = startRow;\n // index of beginning column that _findInLine need to scan.\n let cumulativeCols = startCol;\n // If startRow is wrapped row, scan for unwrapped row above.\n // So we can start matching on wrapped line from long unwrapped line.\n while (this._terminal.buffer.getLine(findingRow).isWrapped) {\n findingRow--;\n cumulativeCols += this._terminal.cols;\n }\n\n // Search startRow\n result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);\n\n // Search from startRow + 1 to end\n if (!result) {\n\n for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {\n\n // If the current line is wrapped line, increase index of column to ignore the previous scan\n // Otherwise, reset beginning column index to zero with set new unwrapped line index\n result = this._findInLine(term, y, 0, searchOptions);\n if (result) {\n break;\n }\n }\n }\n\n // Search from the top to the startRow (search the whole startRow again in\n // case startCol > 0)\n if (!result) {\n for (let y = 0; y < findingRow; y++) {\n result = this._findInLine(term, y, 0, searchOptions);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term The search term.\n * @param searchOptions Search options.\n * @return Whether a result was found.\n */\n public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {\n let result: ISearchResult;\n\n if (!term || term.length === 0) {\n this._terminal.clearSelection();\n return false;\n }\n\n const isReverseSearch = true;\n let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1;\n let startCol = this._terminal.cols;\n\n if (this._terminal.hasSelection()) {\n // Start from the selection start if there is a selection\n const currentSelection = this._terminal.getSelectionPosition();\n startRow = currentSelection.startRow;\n startCol = currentSelection.startColumn;\n }\n\n this._initLinesCache();\n\n // Search startRow\n result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);\n\n // Search from startRow - 1 to top\n if (!result) {\n // If the line is wrapped line, increase number of columns that is needed to be scanned\n // Se we can scan on wrapped line from unwrapped line\n let cumulativeCols = this._terminal.cols;\n if (this._terminal.buffer.getLine(startRow).isWrapped) {\n cumulativeCols += startCol;\n }\n for (let y = startRow - 1; y >= 0; y--) {\n result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);\n if (result) {\n break;\n }\n // If the current line is wrapped line, increase scanning range,\n // preparing for scanning on unwrapped line\n if (this._terminal.buffer.getLine(y).isWrapped) {\n cumulativeCols += this._terminal.cols;\n } else {\n cumulativeCols = this._terminal.cols;\n }\n }\n }\n\n // Search from the bottom to startRow (search the whole startRow again in\n // case startCol > 0)\n if (!result) {\n const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1;\n let cumulativeCols = this._terminal.cols;\n for (let y = searchFrom; y >= startRow; y--) {\n result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);\n if (result) {\n break;\n }\n if (this._terminal.buffer.getLine(y).isWrapped) {\n cumulativeCols += this._terminal.cols;\n } else {\n cumulativeCols = this._terminal.cols;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Sets up a line cache with a ttl\n */\n private _initLinesCache(): void {\n if (!this._linesCache) {\n this._linesCache = new Array(this._terminal.buffer.length);\n this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache());\n this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache());\n }\n\n window.clearTimeout(this._linesCacheTimeoutId);\n this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), LINES_CACHE_TIME_TO_LIVE);\n }\n\n private _destroyLinesCache(): void {\n this._linesCache = null;\n if (this._cursorMoveListener) {\n this._cursorMoveListener.dispose();\n this._cursorMoveListener = undefined;\n }\n if (this._resizeListener) {\n this._resizeListener.dispose();\n this._resizeListener = undefined;\n }\n if (this._linesCacheTimeoutId) {\n window.clearTimeout(this._linesCacheTimeoutId);\n this._linesCacheTimeoutId = 0;\n }\n }\n\n /**\n * A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.\n * @param searchIndex starting indext of the potential whole word substring\n * @param line entire string in which the potential whole word was found\n * @param term the substring that starts at searchIndex\n */\n private _isWholeWord(searchIndex: number, line: string, term: string): boolean {\n return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&\n (((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));\n }\n\n /**\n * Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain\n * subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that\n * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the\n * text starts on is searched.\n * @param term The search term.\n * @param row The line to start the search from.\n * @param col The column to start the search from.\n * @param searchOptions Search options.\n * @return The search result if it was found.\n */\n protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult {\n\n // Ignore wrapped lines, only consider on unwrapped line (first row of command string).\n if (this._terminal.buffer.getLine(row).isWrapped) {\n return;\n }\n let stringLine = this._linesCache ? this._linesCache[row] : void 0;\n if (stringLine === void 0) {\n stringLine = this.translateBufferLineToStringWithWrap(row, true);\n if (this._linesCache) {\n this._linesCache[row] = stringLine;\n }\n }\n\n const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();\n const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();\n\n let resultIndex = -1;\n if (searchOptions.regex) {\n const searchRegex = RegExp(searchTerm, 'g');\n let foundTerm: RegExpExecArray;\n if (isReverseSearch) {\n // This loop will get the resultIndex of the _last_ regex match in the range 0..col\n while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {\n resultIndex = searchRegex.lastIndex - foundTerm[0].length;\n term = foundTerm[0];\n searchRegex.lastIndex -= (term.length - 1);\n }\n } else {\n foundTerm = searchRegex.exec(searchStringLine.slice(col));\n if (foundTerm && foundTerm[0].length > 0) {\n resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);\n term = foundTerm[0];\n }\n }\n } else {\n if (isReverseSearch) {\n if (col - searchTerm.length >= 0) {\n resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);\n }\n } else {\n resultIndex = searchStringLine.indexOf(searchTerm, col);\n }\n }\n\n if (resultIndex >= 0) {\n // Adjust the row number and search index if needed since a \"line\" of text can span multiple rows\n if (resultIndex >= this._terminal.cols) {\n row += Math.floor(resultIndex / this._terminal.cols);\n resultIndex = resultIndex % this._terminal.cols;\n }\n if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {\n return;\n }\n\n const line = this._terminal.buffer.getLine(row);\n\n for (let i = 0; i < resultIndex; i++) {\n const cell = line.getCell(i);\n // Adjust the searchIndex to normalize emoji into single chars\n const char = cell.char;\n if (char.length > 1) {\n resultIndex -= char.length - 1;\n }\n // Adjust the searchIndex for empty characters following wide unicode\n // chars (eg. CJK)\n const charWidth = cell.width;\n if (charWidth === 0) {\n resultIndex++;\n }\n }\n return {\n term,\n col: resultIndex,\n row\n };\n }\n }\n /**\n * Translates a buffer line to a string, including subsequent lines if they are wraps.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n */\n public translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string {\n let lineString = '';\n let lineWrapsToNext: boolean;\n\n do {\n const nextLine = this._terminal.buffer.getLine(lineIndex + 1);\n lineWrapsToNext = nextLine ? nextLine.isWrapped : false;\n lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols);\n lineIndex++;\n } while (lineWrapsToNext);\n\n return lineString;\n }\n\n /**\n * Selects and scrolls to a result.\n * @param result The result to select.\n * @return Whethera result was selected.\n */\n private _selectResult(result: ISearchResult): boolean {\n if (!result) {\n this._terminal.clearSelection();\n return false;\n }\n this._terminal.select(result.col, result.row, result.term.length);\n this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY);\n return true;\n }\n}\n",null],"names":[],"mappings":"AEAA;;;ADQA;AACA;AAKA;AAWA;AAAA;AALA;AACA;AAKA;AACA;AASA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AAGA;AAGA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AASA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AACA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAaA;AAAA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAzUa;;;;;ADTb;AAWA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/addons/terminado/terminado.js b/handlers/www/assets/xterm/addons/terminado/terminado.js new file mode 100644 index 0000000000000000000000000000000000000000..362246f40c75bc1dd847b849e6dc314813d7a960 --- /dev/null +++ b/handlers/www/assets/xterm/addons/terminado/terminado.js @@ -0,0 +1,70 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.terminado = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iterm;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData));\n }\n addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize));\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.__dataListener.dispose();\n addonTerminal.__dataListener = undefined;\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADoBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/addons/webLinks/webLinks.js b/handlers/www/assets/xterm/addons/webLinks/webLinks.js new file mode 100644 index 0000000000000000000000000000000000000000..2176e8755c82e15b32218809f4036706dfc34ec3 --- /dev/null +++ b/handlers/www/assets/xterm/addons/webLinks/webLinks.js @@ -0,0 +1,42 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/addons/zmodem/zmodem.js b/handlers/www/assets/xterm/addons/zmodem/zmodem.js new file mode 100644 index 0000000000000000000000000000000000000000..70a7ff77312ddcf6150acac184c6dfa1d0f76878 --- /dev/null +++ b/handlers/www/assets/xterm/addons/zmodem/zmodem.js @@ -0,0 +1,45 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.zmodem = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i, )` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.\n *\n * You’ll need to provide logic to handle uploads and downloads.\n * See zmodem.js’s documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, you’ll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem: any;\n\nexport interface IZmodemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nfunction zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {\n const term = this;\n const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets));\n\n let zsentry: any;\n\n function shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (term).emit('zmodemRetract'),\n on_detect: (detection: any) => (term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.js’s demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.js’s demo, ultimately we\n // should reject anything that isn’t binary.\n if (typeof evt.data === 'string') {\n if (shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADoCA;AAMA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AALA;"} \ No newline at end of file diff --git a/handlers/www/assets/xterm/xterm.css b/handlers/www/assets/xterm/xterm.css new file mode 100644 index 0000000000000000000000000000000000000000..b3d8d4f210a3812e56fc8cf72701d279ee66c429 --- /dev/null +++ b/handlers/www/assets/xterm/xterm.css @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * @license MIT + * + * 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 limitation 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 this permission 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. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */ + +/** + * Default styles for xterm.js + */ + +.xterm { + font-feature-settings: "liga" 0; + position: relative; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} + +.xterm.focus, +.xterm:focus { + outline: none; +} + +.xterm .xterm-helpers { + position: absolute; + top: 0; + /** + * The z-index of the helpers must be higher than the canvases in order for + * IMEs to appear on top. + */ + z-index: 5; +} + +.xterm .xterm-helper-textarea { + /* + * HACK: to fix IE's blinking cursor + * Move textarea out of the screen to the far left, so that the cursor is not visible. + */ + position: absolute; + opacity: 0; + left: -9999em; + top: 0; + width: 0; + height: 0; + z-index: -5; + /** Prevent wrapping so the IME appears against the textarea at the correct position */ + white-space: nowrap; + overflow: hidden; + resize: none; +} + +.xterm .composition-view { + /* TODO: Composition position got messed up somewhere */ + background: #000; + color: #FFF; + display: none; + position: absolute; + white-space: nowrap; + z-index: 1; +} + +.xterm .composition-view.active { + display: block; +} + +.xterm .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; + overflow-y: scroll; + cursor: default; + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; +} + +.xterm .xterm-screen { + position: relative; +} + +.xterm .xterm-screen canvas { + position: absolute; + left: 0; + top: 0; +} + +.xterm .xterm-scroll-area { + visibility: hidden; +} + +.xterm-char-measure-element { + display: inline-block; + visibility: hidden; + position: absolute; + top: 0; + left: -9999em; + line-height: normal; +} + +.xterm { + cursor: text; +} + +.xterm.enable-mouse-events { + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + cursor: default; +} + +.xterm.xterm-cursor-pointer { + cursor: pointer; +} + +.xterm.column-select.focus { + /* Column selection mode */ + cursor: crosshair; +} + +.xterm .xterm-accessibility, +.xterm .xterm-message { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 10; + color: transparent; +} + +.xterm .live-region { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +.xterm-dim { + opacity: 0.5; +} + +.xterm-underline { + text-decoration: underline; +} diff --git a/handlers/www/assets/xterm/xterm.js b/handlers/www/assets/xterm/xterm.js new file mode 100644 index 0000000000000000000000000000000000000000..7b7f5ad941163b5e29260ee6859a61a9bac1c1d2 --- /dev/null +++ b/handlers/www/assets/xterm/xterm.js @@ -0,0 +1,10707 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i rows) { + this._rowContainer.removeChild(this._rowElements.pop()); + } + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); + this._refreshRowsDimensions(); + }; + AccessibilityManager.prototype._createAccessibilityTreeNode = function () { + var element = document.createElement('div'); + element.setAttribute('role', 'listitem'); + element.tabIndex = -1; + this._refreshRowDimensions(element); + return element; + }; + AccessibilityManager.prototype._onTab = function (spaceCount) { + for (var i = 0; i < spaceCount; i++) { + this._onChar(' '); + } + }; + AccessibilityManager.prototype._onChar = function (char) { + var _this = this; + if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { + if (this._charsToConsume.length > 0) { + var shiftedChar = this._charsToConsume.shift(); + if (shiftedChar !== char) { + this._charsToAnnounce += char; + } + } + else { + this._charsToAnnounce += char; + } + if (char === '\n') { + this._liveRegionLineCount++; + if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) { + this._liveRegion.textContent += Strings.tooMuchOutput; + } + } + if (Platform_1.isMac) { + if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { + setTimeout(function () { + _this._accessibilityTreeRoot.appendChild(_this._liveRegion); + }, 0); + } + } + } + }; + AccessibilityManager.prototype._clearLiveRegion = function () { + this._liveRegion.textContent = ''; + this._liveRegionLineCount = 0; + if (Platform_1.isMac) { + if (this._liveRegion.parentNode) { + this._accessibilityTreeRoot.removeChild(this._liveRegion); + } + } + }; + AccessibilityManager.prototype._onKey = function (keyChar) { + this._clearLiveRegion(); + this._charsToConsume.push(keyChar); + }; + AccessibilityManager.prototype._refreshRows = function (start, end) { + this._renderRowsDebouncer.refresh(start, end, this._terminal.rows); + }; + AccessibilityManager.prototype._renderRows = function (start, end) { + var buffer = this._terminal.buffer; + var setSize = buffer.lines.length.toString(); + for (var i = start; i <= end; i++) { + var lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); + var posInSet = (buffer.ydisp + i + 1).toString(); + var element = this._rowElements[i]; + if (element) { + element.textContent = lineData.length === 0 ? Strings.blankLine : lineData; + element.setAttribute('aria-posinset', posInSet); + element.setAttribute('aria-setsize', setSize); + } + } + this._announceCharacters(); + }; + AccessibilityManager.prototype._refreshRowsDimensions = function () { + if (!this._dimensions.actualCellHeight) { + return; + } + if (this._rowElements.length !== this._terminal.rows) { + this._onResize(this._terminal.rows); + } + for (var i = 0; i < this._terminal.rows; i++) { + this._refreshRowDimensions(this._rowElements[i]); + } + }; + AccessibilityManager.prototype.setDimensions = function (dimensions) { + this._dimensions = dimensions; + this._refreshRowsDimensions(); + }; + AccessibilityManager.prototype._refreshRowDimensions = function (element) { + element.style.height = this._dimensions.actualCellHeight + "px"; + }; + AccessibilityManager.prototype._announceCharacters = function () { + if (this._charsToAnnounce.length === 0) { + return; + } + this._liveRegion.textContent += this._charsToAnnounce; + this._charsToAnnounce = ''; + }; + return AccessibilityManager; +}(Lifecycle_2.Disposable)); +exports.AccessibilityManager = AccessibilityManager; + +},{"./Strings":16,"./common/Lifecycle":24,"./common/Platform":25,"./ui/Lifecycle":59,"./ui/RenderDebouncer":60,"./ui/ScreenDprMonitor":61}],2:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CircularList_1 = require("./common/CircularList"); +var BufferLine_1 = require("./core/buffer/BufferLine"); +var BufferReflow_1 = require("./core/buffer/BufferReflow"); +var Marker_1 = require("./core/buffer/Marker"); +exports.MAX_BUFFER_SIZE = 4294967295; +var Buffer = (function () { + function Buffer(_terminal, _hasScrollback) { + this._terminal = _terminal; + this._hasScrollback = _hasScrollback; + this.savedCurAttrData = BufferLine_1.DEFAULT_ATTR_DATA.clone(); + this.markers = []; + this._nullCell = BufferLine_1.CellData.fromCharData([0, BufferLine_1.NULL_CELL_CHAR, BufferLine_1.NULL_CELL_WIDTH, BufferLine_1.NULL_CELL_CODE]); + this._whitespaceCell = BufferLine_1.CellData.fromCharData([0, BufferLine_1.WHITESPACE_CELL_CHAR, BufferLine_1.WHITESPACE_CELL_WIDTH, BufferLine_1.WHITESPACE_CELL_CODE]); + this._cols = this._terminal.cols; + this._rows = this._terminal.rows; + this.clear(); + } + Buffer.prototype.getNullCell = function (attr) { + if (attr) { + this._nullCell.fg = attr.fg; + this._nullCell.bg = attr.bg; + } + else { + this._nullCell.fg = 0; + this._nullCell.bg = 0; + } + return this._nullCell; + }; + Buffer.prototype.getWhitespaceCell = function (attr) { + if (attr) { + this._whitespaceCell.fg = attr.fg; + this._whitespaceCell.bg = attr.bg; + } + else { + this._whitespaceCell.fg = 0; + this._whitespaceCell.bg = 0; + } + return this._whitespaceCell; + }; + Buffer.prototype.getBlankLine = function (attr, isWrapped) { + return new BufferLine_1.BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped); + }; + Object.defineProperty(Buffer.prototype, "hasScrollback", { + get: function () { + return this._hasScrollback && this.lines.maxLength > this._rows; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Buffer.prototype, "isCursorInViewport", { + get: function () { + var absoluteY = this.ybase + this.y; + var relativeY = absoluteY - this.ydisp; + return (relativeY >= 0 && relativeY < this._rows); + }, + enumerable: true, + configurable: true + }); + Buffer.prototype._getCorrectBufferLength = function (rows) { + if (!this._hasScrollback) { + return rows; + } + var correctBufferLength = rows + this._terminal.options.scrollback; + return correctBufferLength > exports.MAX_BUFFER_SIZE ? exports.MAX_BUFFER_SIZE : correctBufferLength; + }; + Buffer.prototype.fillViewportRows = function (fillAttr) { + if (this.lines.length === 0) { + if (fillAttr === undefined) { + fillAttr = BufferLine_1.DEFAULT_ATTR_DATA; + } + var i = this._rows; + while (i--) { + this.lines.push(this.getBlankLine(fillAttr)); + } + } + }; + Buffer.prototype.clear = function () { + this.ydisp = 0; + this.ybase = 0; + this.y = 0; + this.x = 0; + this.lines = new CircularList_1.CircularList(this._getCorrectBufferLength(this._rows)); + this.scrollTop = 0; + this.scrollBottom = this._rows - 1; + this.setupTabStops(); + }; + Buffer.prototype.resize = function (newCols, newRows) { + var nullCell = this.getNullCell(BufferLine_1.DEFAULT_ATTR_DATA); + var newMaxLength = this._getCorrectBufferLength(newRows); + if (newMaxLength > this.lines.maxLength) { + this.lines.maxLength = newMaxLength; + } + if (this.lines.length > 0) { + if (this._cols < newCols) { + for (var i = 0; i < this.lines.length; i++) { + this.lines.get(i).resize(newCols, nullCell); + } + } + var addToY = 0; + if (this._rows < newRows) { + for (var y = this._rows; y < newRows; y++) { + if (this.lines.length < newRows + this.ybase) { + if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) { + this.ybase--; + addToY++; + if (this.ydisp > 0) { + this.ydisp--; + } + } + else { + this.lines.push(new BufferLine_1.BufferLine(newCols, nullCell)); + } + } + } + } + else { + for (var y = this._rows; y > newRows; y--) { + if (this.lines.length > newRows + this.ybase) { + if (this.lines.length > this.ybase + this.y + 1) { + this.lines.pop(); + } + else { + this.ybase++; + this.ydisp++; + } + } + } + } + if (newMaxLength < this.lines.maxLength) { + var amountToTrim = this.lines.length - newMaxLength; + if (amountToTrim > 0) { + this.lines.trimStart(amountToTrim); + this.ybase = Math.max(this.ybase - amountToTrim, 0); + this.ydisp = Math.max(this.ydisp - amountToTrim, 0); + } + this.lines.maxLength = newMaxLength; + } + this.x = Math.min(this.x, newCols - 1); + this.y = Math.min(this.y, newRows - 1); + if (addToY) { + this.y += addToY; + } + this.savedY = Math.min(this.savedY, newRows - 1); + this.savedX = Math.min(this.savedX, newCols - 1); + this.scrollTop = 0; + } + this.scrollBottom = newRows - 1; + if (this._isReflowEnabled) { + this._reflow(newCols, newRows); + if (this._cols > newCols) { + for (var i = 0; i < this.lines.length; i++) { + this.lines.get(i).resize(newCols, nullCell); + } + } + } + this._cols = newCols; + this._rows = newRows; + }; + Object.defineProperty(Buffer.prototype, "_isReflowEnabled", { + get: function () { + return this._hasScrollback && !this._terminal.options.windowsMode; + }, + enumerable: true, + configurable: true + }); + Buffer.prototype._reflow = function (newCols, newRows) { + if (this._cols === newCols) { + return; + } + if (newCols > this._cols) { + this._reflowLarger(newCols, newRows); + } + else { + this._reflowSmaller(newCols, newRows); + } + }; + Buffer.prototype._reflowLarger = function (newCols, newRows) { + var toRemove = BufferReflow_1.reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(BufferLine_1.DEFAULT_ATTR_DATA)); + if (toRemove.length > 0) { + var newLayoutResult = BufferReflow_1.reflowLargerCreateNewLayout(this.lines, toRemove); + BufferReflow_1.reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout); + this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved); + } + }; + Buffer.prototype._reflowLargerAdjustViewport = function (newCols, newRows, countRemoved) { + var nullCell = this.getNullCell(BufferLine_1.DEFAULT_ATTR_DATA); + var viewportAdjustments = countRemoved; + while (viewportAdjustments-- > 0) { + if (this.ybase === 0) { + if (this.y > 0) { + this.y--; + } + if (this.lines.length < newRows) { + this.lines.push(new BufferLine_1.BufferLine(newCols, nullCell)); + } + } + else { + if (this.ydisp === this.ybase) { + this.ydisp--; + } + this.ybase--; + } + } + }; + Buffer.prototype._reflowSmaller = function (newCols, newRows) { + var nullCell = this.getNullCell(BufferLine_1.DEFAULT_ATTR_DATA); + var toInsert = []; + var countToInsert = 0; + for (var y = this.lines.length - 1; y >= 0; y--) { + var nextLine = this.lines.get(y); + if (!nextLine || !nextLine.isWrapped && nextLine.getTrimmedLength() <= newCols) { + continue; + } + var wrappedLines = [nextLine]; + while (nextLine.isWrapped && y > 0) { + nextLine = this.lines.get(--y); + wrappedLines.unshift(nextLine); + } + var absoluteY = this.ybase + this.y; + if (absoluteY >= y && absoluteY < y + wrappedLines.length) { + continue; + } + var lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength(); + var destLineLengths = BufferReflow_1.reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols); + var linesToAdd = destLineLengths.length - wrappedLines.length; + var trimmedLines = void 0; + if (this.ybase === 0 && this.y !== this.lines.length - 1) { + trimmedLines = Math.max(0, this.y - this.lines.maxLength + linesToAdd); + } + else { + trimmedLines = Math.max(0, this.lines.length - this.lines.maxLength + linesToAdd); + } + var newLines = []; + for (var i = 0; i < linesToAdd; i++) { + var newLine = this.getBlankLine(BufferLine_1.DEFAULT_ATTR_DATA, true); + newLines.push(newLine); + } + if (newLines.length > 0) { + toInsert.push({ + start: y + wrappedLines.length + countToInsert, + newLines: newLines + }); + countToInsert += newLines.length; + } + wrappedLines.push.apply(wrappedLines, newLines); + var destLineIndex = destLineLengths.length - 1; + var destCol = destLineLengths[destLineIndex]; + if (destCol === 0) { + destLineIndex--; + destCol = destLineLengths[destLineIndex]; + } + var srcLineIndex = wrappedLines.length - linesToAdd - 1; + var srcCol = lastLineLength; + while (srcLineIndex >= 0) { + var cellsToCopy = Math.min(srcCol, destCol); + wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy, true); + destCol -= cellsToCopy; + if (destCol === 0) { + destLineIndex--; + destCol = destLineLengths[destLineIndex]; + } + srcCol -= cellsToCopy; + if (srcCol === 0) { + srcLineIndex--; + var wrappedLinesIndex = Math.max(srcLineIndex, 0); + srcCol = BufferReflow_1.getWrappedLineTrimmedLength(wrappedLines, wrappedLinesIndex, this._cols); + } + } + for (var i = 0; i < wrappedLines.length; i++) { + if (destLineLengths[i] < newCols) { + wrappedLines[i].setCell(destLineLengths[i], nullCell); + } + } + var viewportAdjustments = linesToAdd - trimmedLines; + while (viewportAdjustments-- > 0) { + if (this.ybase === 0) { + if (this.y < newRows - 1) { + this.y++; + this.lines.pop(); + } + else { + this.ybase++; + this.ydisp++; + } + } + else { + if (this.ybase < Math.min(this.lines.maxLength, this.lines.length + countToInsert) - newRows) { + if (this.ybase === this.ydisp) { + this.ydisp++; + } + this.ybase++; + } + } + } + } + if (toInsert.length > 0) { + var insertEvents = []; + var originalLines = []; + for (var i = 0; i < this.lines.length; i++) { + originalLines.push(this.lines.get(i)); + } + var originalLinesLength = this.lines.length; + var originalLineIndex = originalLinesLength - 1; + var nextToInsertIndex = 0; + var nextToInsert = toInsert[nextToInsertIndex]; + this.lines.length = Math.min(this.lines.maxLength, this.lines.length + countToInsert); + var countInsertedSoFar = 0; + for (var i = Math.min(this.lines.maxLength - 1, originalLinesLength + countToInsert - 1); i >= 0; i--) { + if (nextToInsert && nextToInsert.start > originalLineIndex + countInsertedSoFar) { + for (var nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) { + this.lines.set(i--, nextToInsert.newLines[nextI]); + } + i++; + insertEvents.push({ + index: originalLineIndex + 1, + amount: nextToInsert.newLines.length + }); + countInsertedSoFar += nextToInsert.newLines.length; + nextToInsert = toInsert[++nextToInsertIndex]; + } + else { + this.lines.set(i, originalLines[originalLineIndex--]); + } + } + var insertCountEmitted = 0; + for (var i = insertEvents.length - 1; i >= 0; i--) { + insertEvents[i].index += insertCountEmitted; + this.lines.onInsertEmitter.fire(insertEvents[i]); + insertCountEmitted += insertEvents[i].amount; + } + var amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); + if (amountToTrim > 0) { + this.lines.onTrimEmitter.fire(amountToTrim); + } + } + }; + Buffer.prototype.stringIndexToBufferIndex = function (lineIndex, stringIndex, trimRight) { + if (trimRight === void 0) { trimRight = false; } + while (stringIndex) { + var line = this.lines.get(lineIndex); + if (!line) { + return [-1, -1]; + } + var length_1 = (trimRight) ? line.getTrimmedLength() : line.length; + for (var i = 0; i < length_1; ++i) { + if (line.get(i)[BufferLine_1.CHAR_DATA_WIDTH_INDEX]) { + stringIndex -= line.get(i)[BufferLine_1.CHAR_DATA_CHAR_INDEX].length || 1; + } + if (stringIndex < 0) { + return [lineIndex, i]; + } + } + lineIndex++; + } + return [lineIndex, 0]; + }; + Buffer.prototype.translateBufferLineToString = function (lineIndex, trimRight, startCol, endCol) { + if (startCol === void 0) { startCol = 0; } + var line = this.lines.get(lineIndex); + if (!line) { + return ''; + } + return line.translateToString(trimRight, startCol, endCol); + }; + Buffer.prototype.getWrappedRangeForLine = function (y) { + var first = y; + var last = y; + while (first > 0 && this.lines.get(first).isWrapped) { + first--; + } + while (last + 1 < this.lines.length && this.lines.get(last + 1).isWrapped) { + last++; + } + return { first: first, last: last }; + }; + Buffer.prototype.setupTabStops = function (i) { + if (i !== null && i !== undefined) { + if (!this.tabs[i]) { + i = this.prevStop(i); + } + } + else { + this.tabs = {}; + i = 0; + } + for (; i < this._cols; i += this._terminal.options.tabStopWidth) { + this.tabs[i] = true; + } + }; + Buffer.prototype.prevStop = function (x) { + if (x === null || x === undefined) { + x = this.x; + } + while (!this.tabs[--x] && x > 0) + ; + return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x; + }; + Buffer.prototype.nextStop = function (x) { + if (x === null || x === undefined) { + x = this.x; + } + while (!this.tabs[++x] && x < this._cols) + ; + return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x; + }; + Buffer.prototype.addMarker = function (y) { + var _this = this; + var marker = new Marker_1.Marker(y); + this.markers.push(marker); + marker.register(this.lines.onTrim(function (amount) { + marker.line -= amount; + if (marker.line < 0) { + marker.dispose(); + } + })); + marker.register(this.lines.onInsert(function (event) { + if (marker.line >= event.index) { + marker.line += event.amount; + } + })); + marker.register(this.lines.onDelete(function (event) { + if (marker.line >= event.index && marker.line < event.index + event.amount) { + marker.dispose(); + } + if (marker.line > event.index) { + marker.line -= event.amount; + } + })); + marker.register(marker.onDispose(function () { return _this._removeMarker(marker); })); + return marker; + }; + Buffer.prototype._removeMarker = function (marker) { + this.markers.splice(this.markers.indexOf(marker), 1); + }; + Buffer.prototype.iterator = function (trimRight, startIndex, endIndex, startOverscan, endOverscan) { + return new BufferStringIterator(this, trimRight, startIndex, endIndex, startOverscan, endOverscan); + }; + return Buffer; +}()); +exports.Buffer = Buffer; +var BufferStringIterator = (function () { + function BufferStringIterator(_buffer, _trimRight, _startIndex, _endIndex, _startOverscan, _endOverscan) { + if (_startIndex === void 0) { _startIndex = 0; } + if (_endIndex === void 0) { _endIndex = _buffer.lines.length; } + if (_startOverscan === void 0) { _startOverscan = 0; } + if (_endOverscan === void 0) { _endOverscan = 0; } + this._buffer = _buffer; + this._trimRight = _trimRight; + this._startIndex = _startIndex; + this._endIndex = _endIndex; + this._startOverscan = _startOverscan; + this._endOverscan = _endOverscan; + if (this._startIndex < 0) { + this._startIndex = 0; + } + if (this._endIndex > this._buffer.lines.length) { + this._endIndex = this._buffer.lines.length; + } + this._current = this._startIndex; + } + BufferStringIterator.prototype.hasNext = function () { + return this._current < this._endIndex; + }; + BufferStringIterator.prototype.next = function () { + var range = this._buffer.getWrappedRangeForLine(this._current); + if (range.first < this._startIndex - this._startOverscan) { + range.first = this._startIndex - this._startOverscan; + } + if (range.last > this._endIndex + this._endOverscan) { + range.last = this._endIndex + this._endOverscan; + } + range.first = Math.max(range.first, 0); + range.last = Math.min(range.last, this._buffer.lines.length); + var result = ''; + for (var i = range.first; i <= range.last; ++i) { + result += this._buffer.translateBufferLineToString(i, this._trimRight); + } + this._current = range.last + 1; + return { range: range, content: result }; + }; + return BufferStringIterator; +}()); +exports.BufferStringIterator = BufferStringIterator; + +},{"./common/CircularList":20,"./core/buffer/BufferLine":29,"./core/buffer/BufferReflow":30,"./core/buffer/Marker":31}],3:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Buffer_1 = require("./Buffer"); +var EventEmitter2_1 = require("./common/EventEmitter2"); +var BufferSet = (function () { + function BufferSet(_terminal) { + this._terminal = _terminal; + this._onBufferActivate = new EventEmitter2_1.EventEmitter2(); + this._normal = new Buffer_1.Buffer(this._terminal, true); + this._normal.fillViewportRows(); + this._alt = new Buffer_1.Buffer(this._terminal, false); + this._activeBuffer = this._normal; + this.setupTabStops(); + } + Object.defineProperty(BufferSet.prototype, "onBufferActivate", { + get: function () { return this._onBufferActivate.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferSet.prototype, "alt", { + get: function () { + return this._alt; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferSet.prototype, "active", { + get: function () { + return this._activeBuffer; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferSet.prototype, "normal", { + get: function () { + return this._normal; + }, + enumerable: true, + configurable: true + }); + BufferSet.prototype.activateNormalBuffer = function () { + if (this._activeBuffer === this._normal) { + return; + } + this._normal.x = this._alt.x; + this._normal.y = this._alt.y; + this._alt.clear(); + this._activeBuffer = this._normal; + this._onBufferActivate.fire({ + activeBuffer: this._normal, + inactiveBuffer: this._alt + }); + }; + BufferSet.prototype.activateAltBuffer = function (fillAttr) { + if (this._activeBuffer === this._alt) { + return; + } + this._alt.fillViewportRows(fillAttr); + this._alt.x = this._normal.x; + this._alt.y = this._normal.y; + this._activeBuffer = this._alt; + this._onBufferActivate.fire({ + activeBuffer: this._alt, + inactiveBuffer: this._normal + }); + }; + BufferSet.prototype.resize = function (newCols, newRows) { + this._normal.resize(newCols, newRows); + this._alt.resize(newCols, newRows); + }; + BufferSet.prototype.setupTabStops = function (i) { + this._normal.setupTabStops(i); + this._alt.setupTabStops(i); + }; + return BufferSet; +}()); +exports.BufferSet = BufferSet; + +},{"./Buffer":2,"./common/EventEmitter2":23}],4:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter2_1 = require("./common/EventEmitter2"); +var CharMeasure = (function () { + function CharMeasure(document, parentElement) { + this._onCharSizeChanged = new EventEmitter2_1.EventEmitter2(); + this._document = document; + this._parentElement = parentElement; + this._measureElement = this._document.createElement('span'); + this._measureElement.classList.add('xterm-char-measure-element'); + this._measureElement.textContent = 'W'; + this._measureElement.setAttribute('aria-hidden', 'true'); + this._parentElement.appendChild(this._measureElement); + } + Object.defineProperty(CharMeasure.prototype, "onCharSizeChanged", { + get: function () { return this._onCharSizeChanged.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CharMeasure.prototype, "width", { + get: function () { + return this._width; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CharMeasure.prototype, "height", { + get: function () { + return this._height; + }, + enumerable: true, + configurable: true + }); + CharMeasure.prototype.measure = function (options) { + this._measureElement.style.fontFamily = options.fontFamily; + this._measureElement.style.fontSize = options.fontSize + "px"; + var geometry = this._measureElement.getBoundingClientRect(); + if (geometry.width === 0 || geometry.height === 0) { + return; + } + var adjustedHeight = Math.ceil(geometry.height); + if (this._width !== geometry.width || this._height !== adjustedHeight) { + this._width = geometry.width; + this._height = adjustedHeight; + this._onCharSizeChanged.fire(); + } + }; + return CharMeasure; +}()); +exports.CharMeasure = CharMeasure; + +},{"./common/EventEmitter2":23}],5:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var TypedArrayUtils_1 = require("./common/TypedArrayUtils"); +exports.wcwidth = (function (opts) { + var COMBINING_BMP = [ + [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489], + [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], + [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603], + [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670], + [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED], + [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A], + [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902], + [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D], + [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981], + [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD], + [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C], + [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D], + [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC], + [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], + [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C], + [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D], + [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0], + [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48], + [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC], + [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], + [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D], + [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], + [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E], + [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC], + [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35], + [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E], + [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97], + [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030], + [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039], + [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F], + [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753], + [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD], + [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD], + [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922], + [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B], + [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34], + [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42], + [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF], + [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063], + [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F], + [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B], + [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F], + [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB] + ]; + var COMBINING_HIGH = [ + [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], + [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169], + [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD], + [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F], + [0xE0100, 0xE01EF] + ]; + function bisearch(ucs, data) { + var min = 0; + var max = data.length - 1; + var mid; + if (ucs < data[0][0] || ucs > data[max][1]) { + return false; + } + while (max >= min) { + mid = (min + max) >> 1; + if (ucs > data[mid][1]) { + min = mid + 1; + } + else if (ucs < data[mid][0]) { + max = mid - 1; + } + else { + return true; + } + } + return false; + } + function wcwidthHigh(ucs) { + if (bisearch(ucs, COMBINING_HIGH)) { + return 0; + } + if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) { + return 2; + } + return 1; + } + var control = opts.control | 0; + var table = new Uint8Array(65536); + TypedArrayUtils_1.fill(table, 1); + table[0] = opts.nul; + TypedArrayUtils_1.fill(table, opts.control, 1, 32); + TypedArrayUtils_1.fill(table, opts.control, 0x7f, 0xa0); + TypedArrayUtils_1.fill(table, 2, 0x1100, 0x1160); + table[0x2329] = 2; + table[0x232a] = 2; + TypedArrayUtils_1.fill(table, 2, 0x2e80, 0xa4d0); + table[0x303f] = 1; + TypedArrayUtils_1.fill(table, 2, 0xac00, 0xd7a4); + TypedArrayUtils_1.fill(table, 2, 0xf900, 0xfb00); + TypedArrayUtils_1.fill(table, 2, 0xfe10, 0xfe1a); + TypedArrayUtils_1.fill(table, 2, 0xfe30, 0xfe70); + TypedArrayUtils_1.fill(table, 2, 0xff00, 0xff61); + TypedArrayUtils_1.fill(table, 2, 0xffe0, 0xffe7); + for (var r = 0; r < COMBINING_BMP.length; ++r) { + TypedArrayUtils_1.fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1); + } + return function (num) { + if (num < 32) { + return control | 0; + } + if (num < 127) { + return 1; + } + if (num < 65536) { + return table[num]; + } + return wcwidthHigh(num); + }; +})({ nul: 0, control: 0 }); +function getStringCellWidth(s) { + var result = 0; + var length = s.length; + for (var i = 0; i < length; ++i) { + var code = s.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + if (++i >= length) { + return result + exports.wcwidth(code); + } + var second = s.charCodeAt(i); + if (0xDC00 <= second && second <= 0xDFFF) { + code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + else { + result += exports.wcwidth(second); + } + } + result += exports.wcwidth(code); + } + return result; +} +exports.getStringCellWidth = getStringCellWidth; + +},{"./common/TypedArrayUtils":26}],6:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function prepareTextForTerminal(text) { + return text.replace(/\r?\n/g, '\r'); +} +exports.prepareTextForTerminal = prepareTextForTerminal; +function bracketTextForPaste(text, bracketedPasteMode) { + if (bracketedPasteMode) { + return '\x1b[200~' + text + '\x1b[201~'; + } + return text; +} +exports.bracketTextForPaste = bracketTextForPaste; +function copyHandler(ev, term, selectionManager) { + if (term.browser.isMSIE) { + window.clipboardData.setData('Text', selectionManager.selectionText); + } + else { + ev.clipboardData.setData('text/plain', selectionManager.selectionText); + } + ev.preventDefault(); +} +exports.copyHandler = copyHandler; +function pasteHandler(ev, term) { + ev.stopPropagation(); + var text; + var dispatchPaste = function (text) { + text = prepareTextForTerminal(text); + text = bracketTextForPaste(text, term.bracketedPasteMode); + term.handler(text); + term.textarea.value = ''; + term.emit('paste', text); + term.cancel(ev); + }; + if (term.browser.isMSIE) { + if (window.clipboardData) { + text = window.clipboardData.getData('Text'); + dispatchPaste(text); + } + } + else { + if (ev.clipboardData) { + text = ev.clipboardData.getData('text/plain'); + dispatchPaste(text); + } + } +} +exports.pasteHandler = pasteHandler; +function moveTextAreaUnderMouseCursor(ev, term) { + var pos = term.screenElement.getBoundingClientRect(); + var left = ev.clientX - pos.left - 10; + var top = ev.clientY - pos.top - 10; + term.textarea.style.position = 'absolute'; + term.textarea.style.width = '20px'; + term.textarea.style.height = '20px'; + term.textarea.style.left = left + "px"; + term.textarea.style.top = top + "px"; + term.textarea.style.zIndex = '1000'; + term.textarea.focus(); + setTimeout(function () { + term.textarea.style.position = null; + term.textarea.style.width = null; + term.textarea.style.height = null; + term.textarea.style.left = null; + term.textarea.style.top = null; + term.textarea.style.zIndex = null; + }, 200); +} +exports.moveTextAreaUnderMouseCursor = moveTextAreaUnderMouseCursor; +function rightClickHandler(ev, term, selectionManager, shouldSelectWord) { + moveTextAreaUnderMouseCursor(ev, term); + if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) { + selectionManager.selectWordAtCursor(ev); + } + term.textarea.value = selectionManager.selectionText; + term.textarea.select(); +} +exports.rightClickHandler = rightClickHandler; + +},{}],7:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CompositionHelper = (function () { + function CompositionHelper(_textarea, _compositionView, _terminal) { + this._textarea = _textarea; + this._compositionView = _compositionView; + this._terminal = _terminal; + this._isComposing = false; + this._isSendingComposition = false; + this._compositionPosition = { start: null, end: null }; + } + CompositionHelper.prototype.compositionstart = function () { + this._isComposing = true; + this._compositionPosition.start = this._textarea.value.length; + this._compositionView.textContent = ''; + this._compositionView.classList.add('active'); + }; + CompositionHelper.prototype.compositionupdate = function (ev) { + var _this = this; + this._compositionView.textContent = ev.data; + this.updateCompositionElements(); + setTimeout(function () { + _this._compositionPosition.end = _this._textarea.value.length; + }, 0); + }; + CompositionHelper.prototype.compositionend = function () { + this._finalizeComposition(true); + }; + CompositionHelper.prototype.keydown = function (ev) { + if (this._isComposing || this._isSendingComposition) { + if (ev.keyCode === 229) { + return false; + } + else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) { + return false; + } + this._finalizeComposition(false); + } + if (ev.keyCode === 229) { + this._handleAnyTextareaChanges(); + return false; + } + return true; + }; + CompositionHelper.prototype._finalizeComposition = function (waitForPropagation) { + var _this = this; + this._compositionView.classList.remove('active'); + this._isComposing = false; + this._clearTextareaPosition(); + if (!waitForPropagation) { + this._isSendingComposition = false; + var input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end); + this._terminal.handler(input); + } + else { + var currentCompositionPosition_1 = { + start: this._compositionPosition.start, + end: this._compositionPosition.end + }; + this._isSendingComposition = true; + setTimeout(function () { + if (_this._isSendingComposition) { + _this._isSendingComposition = false; + var input = void 0; + if (_this._isComposing) { + input = _this._textarea.value.substring(currentCompositionPosition_1.start, currentCompositionPosition_1.end); + } + else { + input = _this._textarea.value.substring(currentCompositionPosition_1.start); + } + _this._terminal.handler(input); + } + }, 0); + } + }; + CompositionHelper.prototype._handleAnyTextareaChanges = function () { + var _this = this; + var oldValue = this._textarea.value; + setTimeout(function () { + if (!_this._isComposing) { + var newValue = _this._textarea.value; + var diff = newValue.replace(oldValue, ''); + if (diff.length > 0) { + _this._terminal.handler(diff); + } + } + }, 0); + }; + CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) { + var _this = this; + if (!this._isComposing) { + return; + } + if (this._terminal.buffer.isCursorInViewport) { + var cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight); + var cursorTop = this._terminal.buffer.y * cellHeight; + var cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width; + this._compositionView.style.left = cursorLeft + 'px'; + this._compositionView.style.top = cursorTop + 'px'; + this._compositionView.style.height = cellHeight + 'px'; + this._compositionView.style.lineHeight = cellHeight + 'px'; + this._compositionView.style.fontFamily = this._terminal.options.fontFamily; + this._compositionView.style.fontSize = this._terminal.options.fontSize + 'px'; + var compositionViewBounds = this._compositionView.getBoundingClientRect(); + this._textarea.style.left = cursorLeft + 'px'; + this._textarea.style.top = cursorTop + 'px'; + this._textarea.style.width = compositionViewBounds.width + 'px'; + this._textarea.style.height = compositionViewBounds.height + 'px'; + this._textarea.style.lineHeight = compositionViewBounds.height + 'px'; + } + if (!dontRecurse) { + setTimeout(function () { return _this.updateCompositionElements(true); }, 0); + } + }; + CompositionHelper.prototype._clearTextareaPosition = function () { + this._textarea.style.left = ''; + this._textarea.style.top = ''; + }; + return CompositionHelper; +}()); +exports.CompositionHelper = CompositionHelper; + +},{}],8:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./common/Lifecycle"); +var TextDecoder_1 = require("./core/input/TextDecoder"); +function r(low, high) { + var c = high - low; + var arr = new Array(c); + while (c--) { + arr[c] = --high; + } + return arr; +} +var TransitionTable = (function () { + function TransitionTable(length) { + this.table = (typeof Uint8Array === 'undefined') + ? new Array(length) + : new Uint8Array(length); + } + TransitionTable.prototype.add = function (code, state, action, next) { + this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next); + }; + TransitionTable.prototype.addMany = function (codes, state, action, next) { + for (var i = 0; i < codes.length; i++) { + this.add(codes[i], state, action, next); + } + }; + return TransitionTable; +}()); +exports.TransitionTable = TransitionTable; +var PRINTABLES = r(0x20, 0x7f); +var EXECUTABLES = r(0x00, 0x18); +EXECUTABLES.push(0x19); +EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20)); +var NON_ASCII_PRINTABLE = 0xA0; +exports.VT500_TRANSITION_TABLE = (function () { + var table = new TransitionTable(4095); + var states = r(0, 13 + 1); + var state; + for (state in states) { + for (var code = 0; code <= NON_ASCII_PRINTABLE; ++code) { + table.add(code, state, 1, 0); + } + } + table.addMany(PRINTABLES, 0, 2, 0); + for (state in states) { + table.addMany([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); + table.addMany(r(0x80, 0x90), state, 3, 0); + table.addMany(r(0x90, 0x98), state, 3, 0); + table.add(0x9c, state, 0, 0); + table.add(0x1b, state, 11, 1); + table.add(0x9d, state, 4, 8); + table.addMany([0x98, 0x9e, 0x9f], state, 0, 7); + table.add(0x9b, state, 11, 3); + table.add(0x90, state, 11, 9); + } + table.addMany(EXECUTABLES, 0, 3, 0); + table.addMany(EXECUTABLES, 1, 3, 1); + table.add(0x7f, 1, 0, 1); + table.addMany(EXECUTABLES, 8, 0, 8); + table.addMany(EXECUTABLES, 3, 3, 3); + table.add(0x7f, 3, 0, 3); + table.addMany(EXECUTABLES, 4, 3, 4); + table.add(0x7f, 4, 0, 4); + table.addMany(EXECUTABLES, 6, 3, 6); + table.addMany(EXECUTABLES, 5, 3, 5); + table.add(0x7f, 5, 0, 5); + table.addMany(EXECUTABLES, 2, 3, 2); + table.add(0x7f, 2, 0, 2); + table.add(0x5d, 1, 4, 8); + table.addMany(PRINTABLES, 8, 5, 8); + table.add(0x7f, 8, 5, 8); + table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); + table.addMany(r(0x1c, 0x20), 8, 0, 8); + table.addMany([0x58, 0x5e, 0x5f], 1, 0, 7); + table.addMany(PRINTABLES, 7, 0, 7); + table.addMany(EXECUTABLES, 7, 0, 7); + table.add(0x9c, 7, 0, 0); + table.add(0x7f, 7, 0, 7); + table.add(0x5b, 1, 11, 3); + table.addMany(r(0x40, 0x7f), 3, 7, 0); + table.addMany(r(0x30, 0x3a), 3, 8, 4); + table.add(0x3b, 3, 8, 4); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); + table.addMany(r(0x30, 0x3a), 4, 8, 4); + table.add(0x3b, 4, 8, 4); + table.addMany(r(0x40, 0x7f), 4, 7, 0); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); + table.addMany(r(0x20, 0x40), 6, 0, 6); + table.add(0x7f, 6, 0, 6); + table.addMany(r(0x40, 0x7f), 6, 0, 0); + table.add(0x3a, 3, 0, 6); + table.addMany(r(0x20, 0x30), 3, 9, 5); + table.addMany(r(0x20, 0x30), 5, 9, 5); + table.addMany(r(0x30, 0x40), 5, 0, 6); + table.addMany(r(0x40, 0x7f), 5, 7, 0); + table.addMany(r(0x20, 0x30), 4, 9, 5); + table.addMany(r(0x20, 0x30), 1, 9, 2); + table.addMany(r(0x20, 0x30), 2, 9, 2); + table.addMany(r(0x30, 0x7f), 2, 10, 0); + table.addMany(r(0x30, 0x50), 1, 10, 0); + table.addMany(r(0x51, 0x58), 1, 10, 0); + table.addMany([0x59, 0x5a, 0x5c], 1, 10, 0); + table.addMany(r(0x60, 0x7f), 1, 10, 0); + table.add(0x50, 1, 11, 9); + table.addMany(EXECUTABLES, 9, 0, 9); + table.add(0x7f, 9, 0, 9); + table.addMany(r(0x1c, 0x20), 9, 0, 9); + table.addMany(r(0x20, 0x30), 9, 9, 12); + table.add(0x3a, 9, 0, 11); + table.addMany(r(0x30, 0x3a), 9, 8, 10); + table.add(0x3b, 9, 8, 10); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); + table.addMany(EXECUTABLES, 11, 0, 11); + table.addMany(r(0x20, 0x80), 11, 0, 11); + table.addMany(r(0x1c, 0x20), 11, 0, 11); + table.addMany(EXECUTABLES, 10, 0, 10); + table.add(0x7f, 10, 0, 10); + table.addMany(r(0x1c, 0x20), 10, 0, 10); + table.addMany(r(0x30, 0x3a), 10, 8, 10); + table.add(0x3b, 10, 8, 10); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); + table.addMany(r(0x20, 0x30), 10, 9, 12); + table.addMany(EXECUTABLES, 12, 0, 12); + table.add(0x7f, 12, 0, 12); + table.addMany(r(0x1c, 0x20), 12, 0, 12); + table.addMany(r(0x20, 0x30), 12, 9, 12); + table.addMany(r(0x30, 0x40), 12, 0, 11); + table.addMany(r(0x40, 0x7f), 12, 12, 13); + table.addMany(r(0x40, 0x7f), 10, 12, 13); + table.addMany(r(0x40, 0x7f), 9, 12, 13); + table.addMany(EXECUTABLES, 13, 13, 13); + table.addMany(PRINTABLES, 13, 13, 13); + table.add(0x7f, 13, 0, 13); + table.addMany([0x1b, 0x9c], 13, 14, 0); + table.add(NON_ASCII_PRINTABLE, 8, 5, 8); + return table; +})(); +var DcsDummy = (function () { + function DcsDummy() { + } + DcsDummy.prototype.hook = function (collect, params, flag) { }; + DcsDummy.prototype.put = function (data, start, end) { }; + DcsDummy.prototype.unhook = function () { }; + return DcsDummy; +}()); +var EscapeSequenceParser = (function (_super) { + __extends(EscapeSequenceParser, _super); + function EscapeSequenceParser(TRANSITIONS) { + if (TRANSITIONS === void 0) { TRANSITIONS = exports.VT500_TRANSITION_TABLE; } + var _this = _super.call(this) || this; + _this.TRANSITIONS = TRANSITIONS; + _this.initialState = 0; + _this.currentState = _this.initialState; + _this._osc = ''; + _this._params = [0]; + _this._collect = ''; + _this._printHandlerFb = function (data, start, end) { }; + _this._executeHandlerFb = function (code) { }; + _this._csiHandlerFb = function (collect, params, flag) { }; + _this._escHandlerFb = function (collect, flag) { }; + _this._oscHandlerFb = function (identifier, data) { }; + _this._dcsHandlerFb = new DcsDummy(); + _this._errorHandlerFb = function (state) { return state; }; + _this._printHandler = _this._printHandlerFb; + _this._executeHandlers = Object.create(null); + _this._csiHandlers = Object.create(null); + _this._escHandlers = Object.create(null); + _this._oscHandlers = Object.create(null); + _this._dcsHandlers = Object.create(null); + _this._activeDcsHandler = null; + _this._errorHandler = _this._errorHandlerFb; + _this.setEscHandler('\\', function () { }); + return _this; + } + EscapeSequenceParser.prototype.dispose = function () { + this._printHandlerFb = null; + this._executeHandlerFb = null; + this._csiHandlerFb = null; + this._escHandlerFb = null; + this._oscHandlerFb = null; + this._dcsHandlerFb = null; + this._errorHandlerFb = null; + this._printHandler = null; + this._executeHandlers = null; + this._escHandlers = null; + this._csiHandlers = null; + this._oscHandlers = null; + this._dcsHandlers = null; + this._activeDcsHandler = null; + this._errorHandler = null; + }; + EscapeSequenceParser.prototype.setPrintHandler = function (callback) { + this._printHandler = callback; + }; + EscapeSequenceParser.prototype.clearPrintHandler = function () { + this._printHandler = this._printHandlerFb; + }; + EscapeSequenceParser.prototype.setExecuteHandler = function (flag, callback) { + this._executeHandlers[flag.charCodeAt(0)] = callback; + }; + EscapeSequenceParser.prototype.clearExecuteHandler = function (flag) { + if (this._executeHandlers[flag.charCodeAt(0)]) + delete this._executeHandlers[flag.charCodeAt(0)]; + }; + EscapeSequenceParser.prototype.setExecuteHandlerFallback = function (callback) { + this._executeHandlerFb = callback; + }; + EscapeSequenceParser.prototype.addCsiHandler = function (flag, callback) { + var index = flag.charCodeAt(0); + if (this._csiHandlers[index] === undefined) { + this._csiHandlers[index] = []; + } + var handlerList = this._csiHandlers[index]; + handlerList.push(callback); + return { + dispose: function () { + var handlerIndex = handlerList.indexOf(callback); + if (handlerIndex !== -1) { + handlerList.splice(handlerIndex, 1); + } + } + }; + }; + EscapeSequenceParser.prototype.setCsiHandler = function (flag, callback) { + this._csiHandlers[flag.charCodeAt(0)] = [callback]; + }; + EscapeSequenceParser.prototype.clearCsiHandler = function (flag) { + if (this._csiHandlers[flag.charCodeAt(0)]) + delete this._csiHandlers[flag.charCodeAt(0)]; + }; + EscapeSequenceParser.prototype.setCsiHandlerFallback = function (callback) { + this._csiHandlerFb = callback; + }; + EscapeSequenceParser.prototype.setEscHandler = function (collectAndFlag, callback) { + this._escHandlers[collectAndFlag] = callback; + }; + EscapeSequenceParser.prototype.clearEscHandler = function (collectAndFlag) { + if (this._escHandlers[collectAndFlag]) + delete this._escHandlers[collectAndFlag]; + }; + EscapeSequenceParser.prototype.setEscHandlerFallback = function (callback) { + this._escHandlerFb = callback; + }; + EscapeSequenceParser.prototype.addOscHandler = function (ident, callback) { + if (this._oscHandlers[ident] === undefined) { + this._oscHandlers[ident] = []; + } + var handlerList = this._oscHandlers[ident]; + handlerList.push(callback); + return { + dispose: function () { + var handlerIndex = handlerList.indexOf(callback); + if (handlerIndex !== -1) { + handlerList.splice(handlerIndex, 1); + } + } + }; + }; + EscapeSequenceParser.prototype.setOscHandler = function (ident, callback) { + this._oscHandlers[ident] = [callback]; + }; + EscapeSequenceParser.prototype.clearOscHandler = function (ident) { + if (this._oscHandlers[ident]) + delete this._oscHandlers[ident]; + }; + EscapeSequenceParser.prototype.setOscHandlerFallback = function (callback) { + this._oscHandlerFb = callback; + }; + EscapeSequenceParser.prototype.setDcsHandler = function (collectAndFlag, handler) { + this._dcsHandlers[collectAndFlag] = handler; + }; + EscapeSequenceParser.prototype.clearDcsHandler = function (collectAndFlag) { + if (this._dcsHandlers[collectAndFlag]) + delete this._dcsHandlers[collectAndFlag]; + }; + EscapeSequenceParser.prototype.setDcsHandlerFallback = function (handler) { + this._dcsHandlerFb = handler; + }; + EscapeSequenceParser.prototype.setErrorHandler = function (callback) { + this._errorHandler = callback; + }; + EscapeSequenceParser.prototype.clearErrorHandler = function () { + this._errorHandler = this._errorHandlerFb; + }; + EscapeSequenceParser.prototype.reset = function () { + this.currentState = this.initialState; + this._osc = ''; + this._params = [0]; + this._collect = ''; + this._activeDcsHandler = null; + }; + EscapeSequenceParser.prototype.parse = function (data, length) { + var code = 0; + var transition = 0; + var error = false; + var currentState = this.currentState; + var print = -1; + var dcs = -1; + var osc = this._osc; + var collect = this._collect; + var params = this._params; + var table = this.TRANSITIONS.table; + var dcsHandler = this._activeDcsHandler; + var callback = null; + for (var i = 0; i < length; ++i) { + code = data[i]; + if (currentState === 0 && code > 0x1f && code < 0x80) { + print = (~print) ? print : i; + do + i++; + while (i < length && data[i] > 0x1f && data[i] < 0x80); + i--; + continue; + } + if (currentState === 4 && (code > 0x2f && code < 0x39)) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; + } + transition = table[currentState << 8 | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)]; + switch (transition >> 4) { + case 2: + print = (~print) ? print : i; + break; + case 3: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + callback = this._executeHandlers[code]; + if (callback) + callback(); + else + this._executeHandlerFb(code); + break; + case 0: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + else if (~dcs) { + dcsHandler.put(data, dcs, i); + dcs = -1; + } + break; + case 1: + if (code > 0x9f) { + switch (currentState) { + case 0: + print = (~print) ? print : i; + break; + case 6: + transition |= 6; + break; + case 11: + transition |= 11; + break; + case 13: + dcs = (~dcs) ? dcs : i; + transition |= 13; + break; + default: + error = true; + } + } + else { + error = true; + } + if (error) { + var inject = this._errorHandler({ + position: i, + code: code, + currentState: currentState, + print: print, + dcs: dcs, + osc: osc, + collect: collect, + params: params, + abort: false + }); + if (inject.abort) + return; + error = false; + } + break; + case 7: + var handlers = this._csiHandlers[code]; + var j = handlers ? handlers.length - 1 : -1; + for (; j >= 0; j--) { + if (handlers[j](params, collect) !== false) { + break; + } + } + if (j < 0) { + this._csiHandlerFb(collect, params, code); + } + break; + case 8: + if (code === 0x3b) + params.push(0); + else + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + break; + case 9: + collect += String.fromCharCode(code); + break; + case 10: + callback = this._escHandlers[collect + String.fromCharCode(code)]; + if (callback) + callback(collect, code); + else + this._escHandlerFb(collect, code); + break; + case 11: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + case 12: + dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)]; + if (!dcsHandler) + dcsHandler = this._dcsHandlerFb; + dcsHandler.hook(collect, params, code); + break; + case 13: + dcs = (~dcs) ? dcs : i; + break; + case 14: + if (dcsHandler) { + if (~dcs) + dcsHandler.put(data, dcs, i); + dcsHandler.unhook(); + dcsHandler = null; + } + if (code === 0x1b) + transition |= 1; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + case 4: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + osc = ''; + break; + case 5: + for (var j_1 = i + 1;; j_1++) { + if (j_1 >= length + || (code = data[j_1]) < 0x20 + || (code > 0x7f && code <= 0x9f)) { + osc += TextDecoder_1.utf32ToString(data, i, j_1); + i = j_1 - 1; + break; + } + } + break; + case 6: + if (osc && code !== 0x18 && code !== 0x1a) { + var idx = osc.indexOf(';'); + if (idx === -1) { + this._oscHandlerFb(-1, osc); + } + else { + var identifier = parseInt(osc.substring(0, idx)); + var content = osc.substring(idx + 1); + var handlers_1 = this._oscHandlers[identifier]; + var j_2 = handlers_1 ? handlers_1.length - 1 : -1; + for (; j_2 >= 0; j_2--) { + if (handlers_1[j_2](content) !== false) { + break; + } + } + if (j_2 < 0) { + this._oscHandlerFb(identifier, content); + } + } + } + if (code === 0x1b) + transition |= 1; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + } + currentState = transition & 15; + } + if (currentState === 0 && ~print) { + this._printHandler(data, print, length); + } + else if (currentState === 13 && ~dcs && dcsHandler) { + dcsHandler.put(data, dcs, length); + } + this._osc = osc; + this._collect = collect; + this._params = params; + this._activeDcsHandler = dcsHandler; + this.currentState = currentState; + }; + return EscapeSequenceParser; +}(Lifecycle_1.Disposable)); +exports.EscapeSequenceParser = EscapeSequenceParser; + +},{"./common/Lifecycle":24,"./core/input/TextDecoder":34}],9:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("./common/data/EscapeSequences"); +var Charsets_1 = require("./core/data/Charsets"); +var CharWidth_1 = require("./CharWidth"); +var EscapeSequenceParser_1 = require("./EscapeSequenceParser"); +var Lifecycle_1 = require("./common/Lifecycle"); +var TypedArrayUtils_1 = require("./common/TypedArrayUtils"); +var TextDecoder_1 = require("./core/input/TextDecoder"); +var BufferLine_1 = require("./core/buffer/BufferLine"); +var EventEmitter2_1 = require("./common/EventEmitter2"); +var GLEVEL = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; +var DECRQSS = (function () { + function DECRQSS(_terminal) { + this._terminal = _terminal; + this._data = new Uint32Array(0); + } + DECRQSS.prototype.hook = function (collect, params, flag) { + this._data = new Uint32Array(0); + }; + DECRQSS.prototype.put = function (data, start, end) { + this._data = TypedArrayUtils_1.concat(this._data, data.subarray(start, end)); + }; + DECRQSS.prototype.unhook = function () { + var data = TextDecoder_1.utf32ToString(this._data); + this._data = new Uint32Array(0); + switch (data) { + case '"q': + return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r0\"q" + EscapeSequences_1.C0.ESC + "\\"); + case '"p': + return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r61\"p" + EscapeSequences_1.C0.ESC + "\\"); + case 'r': + var pt = '' + (this._terminal.buffer.scrollTop + 1) + + ';' + (this._terminal.buffer.scrollBottom + 1) + 'r'; + return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r" + pt + EscapeSequences_1.C0.ESC + "\\"); + case 'm': + return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r0m" + EscapeSequences_1.C0.ESC + "\\"); + case ' q': + var STYLES = { 'block': 2, 'underline': 4, 'bar': 6 }; + var style = STYLES[this._terminal.getOption('cursorStyle')]; + style -= this._terminal.getOption('cursorBlink'); + return this._terminal.handler(EscapeSequences_1.C0.ESC + "P1$r" + style + " q" + EscapeSequences_1.C0.ESC + "\\"); + default: + this._terminal.error('Unknown DCS $q %s', data); + this._terminal.handler(EscapeSequences_1.C0.ESC + "P0$r" + EscapeSequences_1.C0.ESC + "\\"); + } + }; + return DECRQSS; +}()); +var InputHandler = (function (_super) { + __extends(InputHandler, _super); + function InputHandler(_terminal, _parser) { + if (_parser === void 0) { _parser = new EscapeSequenceParser_1.EscapeSequenceParser(); } + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._parser = _parser; + _this._parseBuffer = new Uint32Array(4096); + _this._stringDecoder = new TextDecoder_1.StringToUtf32(); + _this._utf8Decoder = new TextDecoder_1.Utf8ToUtf32(); + _this._workCell = new BufferLine_1.CellData(); + _this._onCursorMove = new EventEmitter2_1.EventEmitter2(); + _this._onData = new EventEmitter2_1.EventEmitter2(); + _this._onLineFeed = new EventEmitter2_1.EventEmitter2(); + _this._onScroll = new EventEmitter2_1.EventEmitter2(); + _this.register(_this._parser); + _this._parser.setCsiHandlerFallback(function (collect, params, flag) { + _this._terminal.error('Unknown CSI code: ', { collect: collect, params: params, flag: String.fromCharCode(flag) }); + }); + _this._parser.setEscHandlerFallback(function (collect, flag) { + _this._terminal.error('Unknown ESC code: ', { collect: collect, flag: String.fromCharCode(flag) }); + }); + _this._parser.setExecuteHandlerFallback(function (code) { + _this._terminal.error('Unknown EXECUTE code: ', { code: code }); + }); + _this._parser.setOscHandlerFallback(function (identifier, data) { + _this._terminal.error('Unknown OSC code: ', { identifier: identifier, data: data }); + }); + _this._parser.setPrintHandler(function (data, start, end) { return _this.print(data, start, end); }); + _this._parser.setCsiHandler('@', function (params, collect) { return _this.insertChars(params); }); + _this._parser.setCsiHandler('A', function (params, collect) { return _this.cursorUp(params); }); + _this._parser.setCsiHandler('B', function (params, collect) { return _this.cursorDown(params); }); + _this._parser.setCsiHandler('C', function (params, collect) { return _this.cursorForward(params); }); + _this._parser.setCsiHandler('D', function (params, collect) { return _this.cursorBackward(params); }); + _this._parser.setCsiHandler('E', function (params, collect) { return _this.cursorNextLine(params); }); + _this._parser.setCsiHandler('F', function (params, collect) { return _this.cursorPrecedingLine(params); }); + _this._parser.setCsiHandler('G', function (params, collect) { return _this.cursorCharAbsolute(params); }); + _this._parser.setCsiHandler('H', function (params, collect) { return _this.cursorPosition(params); }); + _this._parser.setCsiHandler('I', function (params, collect) { return _this.cursorForwardTab(params); }); + _this._parser.setCsiHandler('J', function (params, collect) { return _this.eraseInDisplay(params); }); + _this._parser.setCsiHandler('K', function (params, collect) { return _this.eraseInLine(params); }); + _this._parser.setCsiHandler('L', function (params, collect) { return _this.insertLines(params); }); + _this._parser.setCsiHandler('M', function (params, collect) { return _this.deleteLines(params); }); + _this._parser.setCsiHandler('P', function (params, collect) { return _this.deleteChars(params); }); + _this._parser.setCsiHandler('S', function (params, collect) { return _this.scrollUp(params); }); + _this._parser.setCsiHandler('T', function (params, collect) { return _this.scrollDown(params, collect); }); + _this._parser.setCsiHandler('X', function (params, collect) { return _this.eraseChars(params); }); + _this._parser.setCsiHandler('Z', function (params, collect) { return _this.cursorBackwardTab(params); }); + _this._parser.setCsiHandler('`', function (params, collect) { return _this.charPosAbsolute(params); }); + _this._parser.setCsiHandler('a', function (params, collect) { return _this.hPositionRelative(params); }); + _this._parser.setCsiHandler('b', function (params, collect) { return _this.repeatPrecedingCharacter(params); }); + _this._parser.setCsiHandler('c', function (params, collect) { return _this.sendDeviceAttributes(params, collect); }); + _this._parser.setCsiHandler('d', function (params, collect) { return _this.linePosAbsolute(params); }); + _this._parser.setCsiHandler('e', function (params, collect) { return _this.vPositionRelative(params); }); + _this._parser.setCsiHandler('f', function (params, collect) { return _this.hVPosition(params); }); + _this._parser.setCsiHandler('g', function (params, collect) { return _this.tabClear(params); }); + _this._parser.setCsiHandler('h', function (params, collect) { return _this.setMode(params, collect); }); + _this._parser.setCsiHandler('l', function (params, collect) { return _this.resetMode(params, collect); }); + _this._parser.setCsiHandler('m', function (params, collect) { return _this.charAttributes(params); }); + _this._parser.setCsiHandler('n', function (params, collect) { return _this.deviceStatus(params, collect); }); + _this._parser.setCsiHandler('p', function (params, collect) { return _this.softReset(params, collect); }); + _this._parser.setCsiHandler('q', function (params, collect) { return _this.setCursorStyle(params, collect); }); + _this._parser.setCsiHandler('r', function (params, collect) { return _this.setScrollRegion(params, collect); }); + _this._parser.setCsiHandler('s', function (params, collect) { return _this.saveCursor(params); }); + _this._parser.setCsiHandler('u', function (params, collect) { return _this.restoreCursor(params); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.BEL, function () { return _this.bell(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.LF, function () { return _this.lineFeed(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.VT, function () { return _this.lineFeed(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.FF, function () { return _this.lineFeed(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.CR, function () { return _this.carriageReturn(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.BS, function () { return _this.backspace(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.HT, function () { return _this.tab(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.SO, function () { return _this.shiftOut(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C0.SI, function () { return _this.shiftIn(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C1.IND, function () { return _this.index(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C1.NEL, function () { return _this.nextLine(); }); + _this._parser.setExecuteHandler(EscapeSequences_1.C1.HTS, function () { return _this.tabSet(); }); + _this._parser.setOscHandler(0, function (data) { return _this.setTitle(data); }); + _this._parser.setOscHandler(2, function (data) { return _this.setTitle(data); }); + _this._parser.setEscHandler('7', function () { return _this.saveCursor([]); }); + _this._parser.setEscHandler('8', function () { return _this.restoreCursor([]); }); + _this._parser.setEscHandler('D', function () { return _this.index(); }); + _this._parser.setEscHandler('E', function () { return _this.nextLine(); }); + _this._parser.setEscHandler('H', function () { return _this.tabSet(); }); + _this._parser.setEscHandler('M', function () { return _this.reverseIndex(); }); + _this._parser.setEscHandler('=', function () { return _this.keypadApplicationMode(); }); + _this._parser.setEscHandler('>', function () { return _this.keypadNumericMode(); }); + _this._parser.setEscHandler('c', function () { return _this.reset(); }); + _this._parser.setEscHandler('n', function () { return _this.setgLevel(2); }); + _this._parser.setEscHandler('o', function () { return _this.setgLevel(3); }); + _this._parser.setEscHandler('|', function () { return _this.setgLevel(3); }); + _this._parser.setEscHandler('}', function () { return _this.setgLevel(2); }); + _this._parser.setEscHandler('~', function () { return _this.setgLevel(1); }); + _this._parser.setEscHandler('%@', function () { return _this.selectDefaultCharset(); }); + _this._parser.setEscHandler('%G', function () { return _this.selectDefaultCharset(); }); + var _loop_1 = function (flag) { + this_1._parser.setEscHandler('(' + flag, function () { return _this.selectCharset('(' + flag); }); + this_1._parser.setEscHandler(')' + flag, function () { return _this.selectCharset(')' + flag); }); + this_1._parser.setEscHandler('*' + flag, function () { return _this.selectCharset('*' + flag); }); + this_1._parser.setEscHandler('+' + flag, function () { return _this.selectCharset('+' + flag); }); + this_1._parser.setEscHandler('-' + flag, function () { return _this.selectCharset('-' + flag); }); + this_1._parser.setEscHandler('.' + flag, function () { return _this.selectCharset('.' + flag); }); + this_1._parser.setEscHandler('/' + flag, function () { return _this.selectCharset('/' + flag); }); + }; + var this_1 = this; + for (var flag in Charsets_1.CHARSETS) { + _loop_1(flag); + } + _this._parser.setErrorHandler(function (state) { + _this._terminal.error('Parsing error: ', state); + return state; + }); + _this._parser.setDcsHandler('$q', new DECRQSS(_this._terminal)); + return _this; + } + Object.defineProperty(InputHandler.prototype, "onCursorMove", { + get: function () { return this._onCursorMove.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(InputHandler.prototype, "onData", { + get: function () { return this._onData.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(InputHandler.prototype, "onLineFeed", { + get: function () { return this._onLineFeed.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(InputHandler.prototype, "onScroll", { + get: function () { return this._onScroll.event; }, + enumerable: true, + configurable: true + }); + InputHandler.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._terminal = null; + }; + InputHandler.prototype.parse = function (data) { + if (!this._terminal) { + return; + } + var buffer = this._terminal.buffer; + var cursorStartX = buffer.x; + var cursorStartY = buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + if (this._parseBuffer.length < data.length) { + this._parseBuffer = new Uint32Array(data.length); + } + this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer)); + buffer = this._terminal.buffer; + if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { + this._onCursorMove.fire(); + } + }; + InputHandler.prototype.parseUtf8 = function (data) { + if (!this._terminal) { + return; + } + var buffer = this._terminal.buffer; + var cursorStartX = buffer.x; + var cursorStartY = buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + if (this._parseBuffer.length < data.length) { + this._parseBuffer = new Uint32Array(data.length); + } + this._parser.parse(this._parseBuffer, this._utf8Decoder.decode(data, this._parseBuffer)); + buffer = this._terminal.buffer; + if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + }; + InputHandler.prototype.print = function (data, start, end) { + var code; + var chWidth; + var buffer = this._terminal.buffer; + var charset = this._terminal.charset; + var screenReaderMode = this._terminal.options.screenReaderMode; + var cols = this._terminal.cols; + var wraparoundMode = this._terminal.wraparoundMode; + var insertMode = this._terminal.insertMode; + var curAttr = this._terminal.curAttrData; + var bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + this._terminal.updateRange(buffer.y); + for (var pos = start; pos < end; ++pos) { + code = data[pos]; + chWidth = CharWidth_1.wcwidth(code); + if (code < 127 && charset) { + var ch = charset[String.fromCharCode(code)]; + if (ch) { + code = ch.charCodeAt(0); + } + } + if (screenReaderMode) { + this._terminal.emit('a11y.char', TextDecoder_1.stringFromCodePoint(code)); + } + if (!chWidth && buffer.x) { + if (!bufferRow.getWidth(buffer.x - 1)) { + bufferRow.addCodepointToCell(buffer.x - 2, code); + } + else { + bufferRow.addCodepointToCell(buffer.x - 1, code); + } + continue; + } + if (buffer.x + chWidth - 1 >= cols) { + if (wraparoundMode) { + buffer.x = 0; + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; + this._terminal.scroll(true); + } + else { + buffer.lines.get(buffer.y).isWrapped = true; + } + bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + } + else { + if (chWidth === 2) { + continue; + } + } + } + if (insertMode) { + bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr)); + if (bufferRow.getWidth(cols - 1) === 2) { + bufferRow.setCellFromCodePoint(cols - 1, BufferLine_1.NULL_CELL_CODE, BufferLine_1.NULL_CELL_WIDTH, curAttr.fg, curAttr.bg); + } + } + bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg); + if (chWidth > 0) { + while (--chWidth) { + bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg); + } + } + } + this._terminal.updateRange(buffer.y); + }; + InputHandler.prototype.addCsiHandler = function (flag, callback) { + return this._parser.addCsiHandler(flag, callback); + }; + InputHandler.prototype.addOscHandler = function (ident, callback) { + return this._parser.addOscHandler(ident, callback); + }; + InputHandler.prototype.bell = function () { + this._terminal.bell(); + }; + InputHandler.prototype.lineFeed = function () { + var buffer = this._terminal.buffer; + if (this._terminal.options.convertEol) { + buffer.x = 0; + } + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; + this._terminal.scroll(); + } + if (buffer.x >= this._terminal.cols) { + buffer.x--; + } + this._onLineFeed.fire(); + }; + InputHandler.prototype.carriageReturn = function () { + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.backspace = function () { + if (this._terminal.buffer.x > 0) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.tab = function () { + var originalX = this._terminal.buffer.x; + this._terminal.buffer.x = this._terminal.buffer.nextStop(); + if (this._terminal.options.screenReaderMode) { + this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX); + } + }; + InputHandler.prototype.shiftOut = function () { + this._terminal.setgLevel(1); + }; + InputHandler.prototype.shiftIn = function () { + this._terminal.setgLevel(0); + }; + InputHandler.prototype.insertChars = function (params) { + this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells(this._terminal.buffer.x, params[0] || 1, this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())); + this._terminal.updateRange(this._terminal.buffer.y); + }; + InputHandler.prototype.cursorUp = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y -= param; + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } + }; + InputHandler.prototype.cursorDown = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.cursorForward = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x += param; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.cursorBackward = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + this._terminal.buffer.x -= param; + if (this._terminal.buffer.x < 0) { + this._terminal.buffer.x = 0; + } + }; + InputHandler.prototype.cursorNextLine = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.cursorPrecedingLine = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y -= param; + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } + this._terminal.buffer.x = 0; + }; + InputHandler.prototype.cursorCharAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x = param - 1; + }; + InputHandler.prototype.cursorPosition = function (params) { + var col; + var row = params[0] - 1; + if (params.length >= 2) { + col = params[1] - 1; + } + else { + col = 0; + } + if (row < 0) { + row = 0; + } + else if (row >= this._terminal.rows) { + row = this._terminal.rows - 1; + } + if (col < 0) { + col = 0; + } + else if (col >= this._terminal.cols) { + col = this._terminal.cols - 1; + } + this._terminal.buffer.x = col; + this._terminal.buffer.y = row; + }; + InputHandler.prototype.cursorForwardTab = function (params) { + var param = params[0] || 1; + while (param--) { + this._terminal.buffer.x = this._terminal.buffer.nextStop(); + } + }; + InputHandler.prototype._eraseInBufferLine = function (y, start, end, clearWrap) { + if (clearWrap === void 0) { clearWrap = false; } + var line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); + line.replaceCells(start, end, this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())); + if (clearWrap) { + line.isWrapped = false; + } + }; + InputHandler.prototype._resetBufferLine = function (y) { + this._eraseInBufferLine(y, 0, this._terminal.cols, true); + }; + InputHandler.prototype.eraseInDisplay = function (params) { + var j; + switch (params[0]) { + case 0: + j = this._terminal.buffer.y; + this._terminal.updateRange(j); + this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols, this._terminal.buffer.x === 0); + for (; j < this._terminal.rows; j++) { + this._resetBufferLine(j); + } + this._terminal.updateRange(j); + break; + case 1: + j = this._terminal.buffer.y; + this._terminal.updateRange(j); + this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1, true); + if (this._terminal.buffer.x + 1 >= this._terminal.cols) { + this._terminal.buffer.lines.get(j + 1).isWrapped = false; + } + while (j--) { + this._resetBufferLine(j); + } + this._terminal.updateRange(0); + break; + case 2: + j = this._terminal.rows; + this._terminal.updateRange(j - 1); + while (j--) { + this._resetBufferLine(j); + } + this._terminal.updateRange(0); + break; + case 3: + var scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows; + if (scrollBackSize > 0) { + this._terminal.buffer.lines.trimStart(scrollBackSize); + this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); + this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); + this._onScroll.fire(0); + } + break; + } + }; + InputHandler.prototype.eraseInLine = function (params) { + switch (params[0]) { + case 0: + this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols); + break; + case 1: + this._eraseInBufferLine(this._terminal.buffer.y, 0, this._terminal.buffer.x + 1); + break; + case 2: + this._eraseInBufferLine(this._terminal.buffer.y, 0, this._terminal.cols); + break; + } + this._terminal.updateRange(this._terminal.buffer.y); + }; + InputHandler.prototype.insertLines = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom; + var scrollBottomAbsolute = this._terminal.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1; + while (param--) { + buffer.lines.splice(scrollBottomAbsolute - 1, 1); + buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); + } + this._terminal.updateRange(buffer.y); + this._terminal.updateRange(buffer.scrollBottom); + }; + InputHandler.prototype.deleteLines = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + var buffer = this._terminal.buffer; + var row = buffer.y + buffer.ybase; + var j; + j = this._terminal.rows - 1 - buffer.scrollBottom; + j = this._terminal.rows - 1 + buffer.ybase - j; + while (param--) { + buffer.lines.splice(row, 1); + buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttrData())); + } + this._terminal.updateRange(buffer.y); + this._terminal.updateRange(buffer.scrollBottom); + }; + InputHandler.prototype.deleteChars = function (params) { + this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells(this._terminal.buffer.x, params[0] || 1, this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())); + this._terminal.updateRange(this._terminal.buffer.y); + }; + InputHandler.prototype.scrollUp = function (params) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + while (param--) { + buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(BufferLine_1.DEFAULT_ATTR_DATA)); + } + this._terminal.updateRange(buffer.scrollTop); + this._terminal.updateRange(buffer.scrollBottom); + }; + InputHandler.prototype.scrollDown = function (params, collect) { + if (params.length < 2 && !collect) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + while (param--) { + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); + buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(BufferLine_1.DEFAULT_ATTR_DATA)); + } + this._terminal.updateRange(buffer.scrollTop); + this._terminal.updateRange(buffer.scrollBottom); + } + }; + InputHandler.prototype.eraseChars = function (params) { + this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells(this._terminal.buffer.x, this._terminal.buffer.x + (params[0] || 1), this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())); + }; + InputHandler.prototype.cursorBackwardTab = function (params) { + var param = params[0] || 1; + var buffer = this._terminal.buffer; + while (param--) { + buffer.x = buffer.prevStop(); + } + }; + InputHandler.prototype.charPosAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x = param - 1; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.hPositionRelative = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.x += param; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.repeatPrecedingCharacter = function (params) { + var buffer = this._terminal.buffer; + var line = buffer.lines.get(buffer.ybase + buffer.y); + line.loadCell(buffer.x - 1, this._workCell); + line.replaceCells(buffer.x, buffer.x + (params[0] || 1), (this._workCell.content !== undefined) ? this._workCell : buffer.getNullCell(BufferLine_1.DEFAULT_ATTR_DATA)); + }; + InputHandler.prototype.sendDeviceAttributes = function (params, collect) { + if (params[0] > 0) { + return; + } + if (!collect) { + if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + this._terminal.handler(EscapeSequences_1.C0.ESC + '[?1;2c'); + } + else if (this._terminal.is('linux')) { + this._terminal.handler(EscapeSequences_1.C0.ESC + '[?6c'); + } + } + else if (collect === '>') { + if (this._terminal.is('xterm')) { + this._terminal.handler(EscapeSequences_1.C0.ESC + '[>0;276;0c'); + } + else if (this._terminal.is('rxvt-unicode')) { + this._terminal.handler(EscapeSequences_1.C0.ESC + '[>85;95;0c'); + } + else if (this._terminal.is('linux')) { + this._terminal.handler(params[0] + 'c'); + } + else if (this._terminal.is('screen')) { + this._terminal.handler(EscapeSequences_1.C0.ESC + '[>83;40003;0c'); + } + } + }; + InputHandler.prototype.linePosAbsolute = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y = param - 1; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + }; + InputHandler.prototype.vPositionRelative = function (params) { + var param = params[0]; + if (param < 1) { + param = 1; + } + this._terminal.buffer.y += param; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x--; + } + }; + InputHandler.prototype.hVPosition = function (params) { + if (params[0] < 1) + params[0] = 1; + if (params[1] < 1) + params[1] = 1; + this._terminal.buffer.y = params[0] - 1; + if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + this._terminal.buffer.x = params[1] - 1; + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + }; + InputHandler.prototype.tabClear = function (params) { + var param = params[0]; + if (param <= 0) { + delete this._terminal.buffer.tabs[this._terminal.buffer.x]; + } + else if (param === 3) { + this._terminal.buffer.tabs = {}; + } + }; + InputHandler.prototype.setMode = function (params, collect) { + if (params.length > 1) { + for (var i = 0; i < params.length; i++) { + this.setMode([params[i]]); + } + return; + } + if (!collect) { + switch (params[0]) { + case 4: + this._terminal.insertMode = true; + break; + case 20: + break; + } + } + else if (collect === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = true; + break; + case 2: + this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(1, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(2, Charsets_1.DEFAULT_CHARSET); + this._terminal.setgCharset(3, Charsets_1.DEFAULT_CHARSET); + break; + case 3: + this._terminal.savedCols = this._terminal.cols; + this._terminal.resize(132, this._terminal.rows); + break; + case 6: + this._terminal.originMode = true; + break; + case 7: + this._terminal.wraparoundMode = true; + break; + case 12: + break; + case 66: + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + break; + case 9: + case 1000: + case 1002: + case 1003: + this._terminal.x10Mouse = params[0] === 9; + this._terminal.vt200Mouse = params[0] === 1000; + this._terminal.normalMouse = params[0] > 1000; + this._terminal.mouseEvents = true; + if (this._terminal.element) { + this._terminal.element.classList.add('enable-mouse-events'); + } + if (this._terminal.selectionManager) { + this._terminal.selectionManager.disable(); + } + this._terminal.log('Binding to mouse events.'); + break; + case 1004: + this._terminal.sendFocus = true; + break; + case 1005: + this._terminal.utfMouse = true; + break; + case 1006: + this._terminal.sgrMouse = true; + break; + case 1015: + this._terminal.urxvtMouse = true; + break; + case 25: + this._terminal.cursorHidden = false; + break; + case 1048: + this.saveCursor(params); + break; + case 1049: + this.saveCursor(params); + case 47: + case 1047: + this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttrData()); + this._terminal.refresh(0, this._terminal.rows - 1); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + this._terminal.showCursor(); + break; + case 2004: + this._terminal.bracketedPasteMode = true; + break; + } + } + }; + InputHandler.prototype.resetMode = function (params, collect) { + if (params.length > 1) { + for (var i = 0; i < params.length; i++) { + this.resetMode([params[i]]); + } + return; + } + if (!collect) { + switch (params[0]) { + case 4: + this._terminal.insertMode = false; + break; + case 20: + break; + } + } + else if (collect === '?') { + switch (params[0]) { + case 1: + this._terminal.applicationCursor = false; + break; + case 3: + if (this._terminal.cols === 132 && this._terminal.savedCols) { + this._terminal.resize(this._terminal.savedCols, this._terminal.rows); + } + delete this._terminal.savedCols; + break; + case 6: + this._terminal.originMode = false; + break; + case 7: + this._terminal.wraparoundMode = false; + break; + case 12: + break; + case 66: + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + break; + case 9: + case 1000: + case 1002: + case 1003: + this._terminal.x10Mouse = false; + this._terminal.vt200Mouse = false; + this._terminal.normalMouse = false; + this._terminal.mouseEvents = false; + if (this._terminal.element) { + this._terminal.element.classList.remove('enable-mouse-events'); + } + if (this._terminal.selectionManager) { + this._terminal.selectionManager.enable(); + } + break; + case 1004: + this._terminal.sendFocus = false; + break; + case 1005: + this._terminal.utfMouse = false; + break; + case 1006: + this._terminal.sgrMouse = false; + break; + case 1015: + this._terminal.urxvtMouse = false; + break; + case 25: + this._terminal.cursorHidden = true; + break; + case 1048: + this.restoreCursor(params); + break; + case 1049: + case 47: + case 1047: + this._terminal.buffers.activateNormalBuffer(); + if (params[0] === 1049) { + this.restoreCursor(params); + } + this._terminal.refresh(0, this._terminal.rows - 1); + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + this._terminal.showCursor(); + break; + case 2004: + this._terminal.bracketedPasteMode = false; + break; + } + } + }; + InputHandler.prototype.charAttributes = function (params) { + if (params.length === 1 && params[0] === 0) { + this._terminal.curAttrData.fg = BufferLine_1.DEFAULT_ATTR_DATA.fg; + this._terminal.curAttrData.bg = BufferLine_1.DEFAULT_ATTR_DATA.bg; + return; + } + var l = params.length; + var p; + var attr = this._terminal.curAttrData; + for (var i = 0; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + attr.fg &= ~(50331648 | 255); + attr.fg |= 16777216 | (p - 30); + } + else if (p >= 40 && p <= 47) { + attr.bg &= ~(50331648 | 255); + attr.bg |= 16777216 | (p - 40); + } + else if (p >= 90 && p <= 97) { + attr.fg &= ~(50331648 | 255); + attr.fg |= 16777216 | (p - 90) | 8; + } + else if (p >= 100 && p <= 107) { + attr.bg &= ~(50331648 | 255); + attr.bg |= 16777216 | (p - 100) | 8; + } + else if (p === 0) { + attr.fg = BufferLine_1.DEFAULT_ATTR_DATA.fg; + attr.bg = BufferLine_1.DEFAULT_ATTR_DATA.bg; + } + else if (p === 1) { + attr.fg |= 134217728; + } + else if (p === 3) { + attr.bg |= 67108864; + } + else if (p === 4) { + attr.fg |= 268435456; + } + else if (p === 5) { + attr.fg |= 536870912; + } + else if (p === 7) { + attr.fg |= 67108864; + } + else if (p === 8) { + attr.fg |= 1073741824; + } + else if (p === 2) { + attr.bg |= 134217728; + } + else if (p === 22) { + attr.fg &= ~134217728; + attr.bg &= ~134217728; + } + else if (p === 23) { + attr.bg &= ~67108864; + } + else if (p === 24) { + attr.fg &= ~268435456; + } + else if (p === 25) { + attr.fg &= ~536870912; + } + else if (p === 27) { + attr.fg &= ~67108864; + } + else if (p === 28) { + attr.fg &= ~1073741824; + } + else if (p === 39) { + attr.fg &= ~(50331648 | 16777215); + attr.fg |= BufferLine_1.DEFAULT_ATTR_DATA.fg & (255 | 16777215); + } + else if (p === 49) { + attr.bg &= ~(50331648 | 16777215); + attr.bg |= BufferLine_1.DEFAULT_ATTR_DATA.bg & (255 | 16777215); + } + else if (p === 38) { + if (params[i + 1] === 2) { + i += 2; + attr.fg |= 50331648; + attr.fg &= ~16777215; + attr.fg |= BufferLine_1.AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]); + i += 2; + } + else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + attr.fg &= ~(50331648 | 255); + attr.fg |= 33554432 | p; + } + } + else if (p === 48) { + if (params[i + 1] === 2) { + i += 2; + attr.bg |= 50331648; + attr.bg &= ~16777215; + attr.bg |= BufferLine_1.AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]); + i += 2; + } + else if (params[i + 1] === 5) { + i += 2; + p = params[i] & 0xff; + attr.bg &= ~(50331648 | 255); + attr.bg |= 33554432 | p; + } + } + else if (p === 100) { + attr.fg &= ~(50331648 | 16777215); + attr.fg |= BufferLine_1.DEFAULT_ATTR_DATA.fg & (255 | 16777215); + attr.bg &= ~(50331648 | 16777215); + attr.bg |= BufferLine_1.DEFAULT_ATTR_DATA.bg & (255 | 16777215); + } + else { + this._terminal.error('Unknown SGR attribute: %d.', p); + } + } + }; + InputHandler.prototype.deviceStatus = function (params, collect) { + if (!collect) { + switch (params[0]) { + case 5: + this._onData.fire(EscapeSequences_1.C0.ESC + "[0n"); + break; + case 6: + var y = this._terminal.buffer.y + 1; + var x = this._terminal.buffer.x + 1; + this._onData.fire(EscapeSequences_1.C0.ESC + "[" + y + ";" + x + "R"); + break; + } + } + else if (collect === '?') { + switch (params[0]) { + case 6: + var y = this._terminal.buffer.y + 1; + var x = this._terminal.buffer.x + 1; + this._onData.fire(EscapeSequences_1.C0.ESC + "[?" + y + ";" + x + "R"); + break; + case 15: + break; + case 25: + break; + case 26: + break; + case 53: + break; + } + } + }; + InputHandler.prototype.softReset = function (params, collect) { + if (collect === '!') { + this._terminal.cursorHidden = false; + this._terminal.insertMode = false; + this._terminal.originMode = false; + this._terminal.wraparoundMode = true; + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + this._terminal.applicationCursor = false; + this._terminal.buffer.scrollTop = 0; + this._terminal.buffer.scrollBottom = this._terminal.rows - 1; + this._terminal.curAttrData = BufferLine_1.DEFAULT_ATTR_DATA.clone(); + this._terminal.buffer.x = this._terminal.buffer.y = 0; + this._terminal.charset = null; + this._terminal.glevel = 0; + this._terminal.charsets = [null]; + } + }; + InputHandler.prototype.setCursorStyle = function (params, collect) { + if (collect === ' ') { + var param = params[0] < 1 ? 1 : params[0]; + switch (param) { + case 1: + case 2: + this._terminal.setOption('cursorStyle', 'block'); + break; + case 3: + case 4: + this._terminal.setOption('cursorStyle', 'underline'); + break; + case 5: + case 6: + this._terminal.setOption('cursorStyle', 'bar'); + break; + } + var isBlinking = param % 2 === 1; + this._terminal.setOption('cursorBlink', isBlinking); + } + }; + InputHandler.prototype.setScrollRegion = function (params, collect) { + if (collect) { + return; + } + this._terminal.buffer.scrollTop = (params[0] || 1) - 1; + this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; + this._terminal.buffer.x = 0; + this._terminal.buffer.y = 0; + }; + InputHandler.prototype.saveCursor = function (params) { + this._terminal.buffer.savedX = this._terminal.buffer.x; + this._terminal.buffer.savedY = this._terminal.buffer.y; + this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; + this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg; + }; + InputHandler.prototype.restoreCursor = function (params) { + this._terminal.buffer.x = this._terminal.buffer.savedX || 0; + this._terminal.buffer.y = this._terminal.buffer.savedY || 0; + this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg; + this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg; + }; + InputHandler.prototype.setTitle = function (data) { + this._terminal.handleTitle(data); + }; + InputHandler.prototype.nextLine = function () { + this._terminal.buffer.x = 0; + this.index(); + }; + InputHandler.prototype.keypadApplicationMode = function () { + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }; + InputHandler.prototype.keypadNumericMode = function () { + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }; + InputHandler.prototype.selectDefaultCharset = function () { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET); + }; + InputHandler.prototype.selectCharset = function (collectAndFlag) { + if (collectAndFlag.length !== 2) { + this.selectDefaultCharset(); + return; + } + if (collectAndFlag[0] === '/') { + return; + } + this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], Charsets_1.CHARSETS[collectAndFlag[1]] || Charsets_1.DEFAULT_CHARSET); + return; + }; + InputHandler.prototype.index = function () { + this._terminal.index(); + }; + InputHandler.prototype.tabSet = function () { + this._terminal.tabSet(); + }; + InputHandler.prototype.reverseIndex = function () { + this._terminal.reverseIndex(); + }; + InputHandler.prototype.reset = function () { + this._parser.reset(); + this._terminal.reset(); + }; + InputHandler.prototype.setgLevel = function (level) { + this._terminal.setgLevel(level); + }; + return InputHandler; +}(Lifecycle_1.Disposable)); +exports.InputHandler = InputHandler; + +},{"./CharWidth":5,"./EscapeSequenceParser":8,"./common/EventEmitter2":23,"./common/Lifecycle":24,"./common/TypedArrayUtils":26,"./common/data/EscapeSequences":28,"./core/buffer/BufferLine":29,"./core/data/Charsets":32,"./core/input/TextDecoder":34}],10:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseZoneManager_1 = require("./MouseZoneManager"); +var CharWidth_1 = require("./CharWidth"); +var EventEmitter2_1 = require("./common/EventEmitter2"); +var Linkifier = (function () { + function Linkifier(_terminal) { + this._terminal = _terminal; + this._linkMatchers = []; + this._nextLinkMatcherId = 0; + this._onLinkHover = new EventEmitter2_1.EventEmitter2(); + this._onLinkLeave = new EventEmitter2_1.EventEmitter2(); + this._onLinkTooltip = new EventEmitter2_1.EventEmitter2(); + this._rowsToLinkify = { + start: null, + end: null + }; + } + Object.defineProperty(Linkifier.prototype, "onLinkHover", { + get: function () { return this._onLinkHover.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Linkifier.prototype, "onLinkLeave", { + get: function () { return this._onLinkLeave.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Linkifier.prototype, "onLinkTooltip", { + get: function () { return this._onLinkTooltip.event; }, + enumerable: true, + configurable: true + }); + Linkifier.prototype.attachToDom = function (mouseZoneManager) { + this._mouseZoneManager = mouseZoneManager; + }; + Linkifier.prototype.linkifyRows = function (start, end) { + var _this = this; + if (!this._mouseZoneManager) { + return; + } + if (this._rowsToLinkify.start === null) { + this._rowsToLinkify.start = start; + this._rowsToLinkify.end = end; + } + else { + this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start); + this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end); + } + this._mouseZoneManager.clearAll(start, end); + if (this._rowsTimeoutId) { + clearTimeout(this._rowsTimeoutId); + } + this._rowsTimeoutId = setTimeout(function () { return _this._linkifyRows(); }, Linkifier.TIME_BEFORE_LINKIFY); + }; + Linkifier.prototype._linkifyRows = function () { + this._rowsTimeoutId = null; + var buffer = this._terminal.buffer; + var absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start; + if (absoluteRowIndexStart >= buffer.lines.length) { + return; + } + var absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._terminal.rows) + 1; + var overscanLineLimit = Math.ceil(Linkifier.OVERSCAN_CHAR_LIMIT / this._terminal.cols); + var iterator = this._terminal.buffer.iterator(false, absoluteRowIndexStart, absoluteRowIndexEnd, overscanLineLimit, overscanLineLimit); + while (iterator.hasNext()) { + var lineData = iterator.next(); + for (var i = 0; i < this._linkMatchers.length; i++) { + this._doLinkifyRow(lineData.range.first, lineData.content, this._linkMatchers[i]); + } + } + this._rowsToLinkify.start = null; + this._rowsToLinkify.end = null; + }; + Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) { + if (options === void 0) { options = {}; } + if (!handler) { + throw new Error('handler must be defined'); + } + var matcher = { + id: this._nextLinkMatcherId++, + regex: regex, + handler: handler, + matchIndex: options.matchIndex, + validationCallback: options.validationCallback, + hoverTooltipCallback: options.tooltipCallback, + hoverLeaveCallback: options.leaveCallback, + willLinkActivate: options.willLinkActivate, + priority: options.priority || 0 + }; + this._addLinkMatcherToList(matcher); + return matcher.id; + }; + Linkifier.prototype._addLinkMatcherToList = function (matcher) { + if (this._linkMatchers.length === 0) { + this._linkMatchers.push(matcher); + return; + } + for (var i = this._linkMatchers.length - 1; i >= 0; i--) { + if (matcher.priority <= this._linkMatchers[i].priority) { + this._linkMatchers.splice(i + 1, 0, matcher); + return; + } + } + this._linkMatchers.splice(0, 0, matcher); + }; + Linkifier.prototype.deregisterLinkMatcher = function (matcherId) { + for (var i = 0; i < this._linkMatchers.length; i++) { + if (this._linkMatchers[i].id === matcherId) { + this._linkMatchers.splice(i, 1); + return true; + } + } + return false; + }; + Linkifier.prototype._doLinkifyRow = function (rowIndex, text, matcher) { + var _this = this; + var rex = new RegExp(matcher.regex.source, matcher.regex.flags + 'g'); + var match; + var stringIndex = -1; + var _loop_1 = function () { + var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; + if (!uri) { + if (this_1._terminal.debug) { + console.log({ match: match, matcher: matcher }); + throw new Error('match found without corresponding matchIndex'); + } + return "break"; + } + stringIndex = text.indexOf(uri, stringIndex + 1); + rex.lastIndex = stringIndex + uri.length; + if (stringIndex < 0) { + return "break"; + } + var bufferIndex = this_1._terminal.buffer.stringIndexToBufferIndex(rowIndex, stringIndex); + if (bufferIndex[0] < 0) { + return "break"; + } + var line = this_1._terminal.buffer.lines.get(bufferIndex[0]); + var attr = line.getFg(bufferIndex[1]); + var fg; + if (attr) { + fg = (attr >> 9) & 0x1ff; + } + if (matcher.validationCallback) { + matcher.validationCallback(uri, function (isValid) { + if (_this._rowsTimeoutId) { + return; + } + if (isValid) { + _this._addLink(bufferIndex[1], bufferIndex[0] - _this._terminal.buffer.ydisp, uri, matcher, fg); + } + }); + } + else { + this_1._addLink(bufferIndex[1], bufferIndex[0] - this_1._terminal.buffer.ydisp, uri, matcher, fg); + } + }; + var this_1 = this; + while ((match = rex.exec(text)) !== null) { + var state_1 = _loop_1(); + if (state_1 === "break") + break; + } + }; + Linkifier.prototype._addLink = function (x, y, uri, matcher, fg) { + var _this = this; + var width = CharWidth_1.getStringCellWidth(uri); + var x1 = x % this._terminal.cols; + var y1 = y + Math.floor(x / this._terminal.cols); + var x2 = (x1 + width) % this._terminal.cols; + var y2 = y1 + Math.floor((x1 + width) / this._terminal.cols); + if (x2 === 0) { + x2 = this._terminal.cols; + y2--; + } + this._mouseZoneManager.add(new MouseZoneManager_1.MouseZone(x1 + 1, y1 + 1, x2 + 1, y2 + 1, function (e) { + if (matcher.handler) { + return matcher.handler(e, uri); + } + window.open(uri, '_blank'); + }, function () { + _this._onLinkHover.fire(_this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + _this._terminal.element.classList.add('xterm-cursor-pointer'); + }, function (e) { + _this._onLinkTooltip.fire(_this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + if (matcher.hoverTooltipCallback) { + matcher.hoverTooltipCallback(e, uri); + } + }, function () { + _this._onLinkLeave.fire(_this._createLinkHoverEvent(x1, y1, x2, y2, fg)); + _this._terminal.element.classList.remove('xterm-cursor-pointer'); + if (matcher.hoverLeaveCallback) { + matcher.hoverLeaveCallback(); + } + }, function (e) { + if (matcher.willLinkActivate) { + return matcher.willLinkActivate(e, uri); + } + return true; + })); + }; + Linkifier.prototype._createLinkHoverEvent = function (x1, y1, x2, y2, fg) { + return { x1: x1, y1: y1, x2: x2, y2: y2, cols: this._terminal.cols, fg: fg }; + }; + Linkifier.TIME_BEFORE_LINKIFY = 200; + Linkifier.OVERSCAN_CHAR_LIMIT = 2000; + return Linkifier; +}()); +exports.Linkifier = Linkifier; + +},{"./CharWidth":5,"./MouseZoneManager":12,"./common/EventEmitter2":23}],11:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseHelper = (function () { + function MouseHelper(_renderCoordinator) { + this._renderCoordinator = _renderCoordinator; + } + MouseHelper.getCoordsRelativeToElement = function (event, element) { + var rect = element.getBoundingClientRect(); + return [event.clientX - rect.left, event.clientY - rect.top]; + }; + MouseHelper.prototype.getCoords = function (event, element, charMeasure, colCount, rowCount, isSelection) { + if (!charMeasure.width || !charMeasure.height) { + return null; + } + var coords = MouseHelper.getCoordsRelativeToElement(event, element); + if (!coords) { + return null; + } + coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderCoordinator.dimensions.actualCellWidth / 2 : 0)) / this._renderCoordinator.dimensions.actualCellWidth); + coords[1] = Math.ceil(coords[1] / this._renderCoordinator.dimensions.actualCellHeight); + coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0)); + coords[1] = Math.min(Math.max(coords[1], 1), rowCount); + return coords; + }; + MouseHelper.prototype.getRawByteCoords = function (event, element, charMeasure, colCount, rowCount) { + var coords = this.getCoords(event, element, charMeasure, colCount, rowCount); + var x = coords[0]; + var y = coords[1]; + x += 32; + y += 32; + return { x: x, y: y }; + }; + return MouseHelper; +}()); +exports.MouseHelper = MouseHelper; + +},{}],12:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./common/Lifecycle"); +var Lifecycle_2 = require("./ui/Lifecycle"); +var HOVER_DURATION = 500; +var MouseZoneManager = (function (_super) { + __extends(MouseZoneManager, _super); + function MouseZoneManager(_terminal) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._zones = []; + _this._areZonesActive = false; + _this._tooltipTimeout = null; + _this._currentZone = null; + _this._lastHoverCoords = [null, null]; + _this.register(Lifecycle_2.addDisposableDomListener(_this._terminal.element, 'mousedown', function (e) { return _this._onMouseDown(e); })); + _this._mouseMoveListener = function (e) { return _this._onMouseMove(e); }; + _this._mouseLeaveListener = function (e) { return _this._onMouseLeave(e); }; + _this._clickListener = function (e) { return _this._onClick(e); }; + return _this; + } + MouseZoneManager.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._deactivate(); + }; + MouseZoneManager.prototype.add = function (zone) { + this._zones.push(zone); + if (this._zones.length === 1) { + this._activate(); + } + }; + MouseZoneManager.prototype.clearAll = function (start, end) { + if (this._zones.length === 0) { + return; + } + if (!end) { + start = 0; + end = this._terminal.rows - 1; + } + for (var i = 0; i < this._zones.length; i++) { + var zone = this._zones[i]; + if ((zone.y1 > start && zone.y1 <= end + 1) || + (zone.y2 > start && zone.y2 <= end + 1) || + (zone.y1 < start && zone.y2 > end + 1)) { + if (this._currentZone && this._currentZone === zone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + } + this._zones.splice(i--, 1); + } + } + if (this._zones.length === 0) { + this._deactivate(); + } + }; + MouseZoneManager.prototype._activate = function () { + if (!this._areZonesActive) { + this._areZonesActive = true; + this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener); + this._terminal.element.addEventListener('click', this._clickListener); + } + }; + MouseZoneManager.prototype._deactivate = function () { + if (this._areZonesActive) { + this._areZonesActive = false; + this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener); + this._terminal.element.removeEventListener('click', this._clickListener); + } + }; + MouseZoneManager.prototype._onMouseMove = function (e) { + if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) { + this._onHover(e); + this._lastHoverCoords = [e.pageX, e.pageY]; + } + }; + MouseZoneManager.prototype._onHover = function (e) { + var _this = this; + var zone = this._findZoneEventAt(e); + if (zone === this._currentZone) { + return; + } + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + if (!zone) { + return; + } + this._currentZone = zone; + if (zone.hoverCallback) { + zone.hoverCallback(e); + } + this._tooltipTimeout = setTimeout(function () { return _this._onTooltip(e); }, HOVER_DURATION); + }; + MouseZoneManager.prototype._onTooltip = function (e) { + this._tooltipTimeout = null; + var zone = this._findZoneEventAt(e); + if (zone && zone.tooltipCallback) { + zone.tooltipCallback(e); + } + }; + MouseZoneManager.prototype._onMouseDown = function (e) { + this._initialSelectionLength = this._terminal.getSelection().length; + if (!this._areZonesActive) { + return; + } + var zone = this._findZoneEventAt(e); + if (zone) { + if (zone.willLinkActivate(e)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + }; + MouseZoneManager.prototype._onMouseLeave = function (e) { + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + }; + MouseZoneManager.prototype._onClick = function (e) { + var zone = this._findZoneEventAt(e); + var currentSelectionLength = this._terminal.getSelection().length; + if (zone && currentSelectionLength === this._initialSelectionLength) { + zone.clickCallback(e); + e.preventDefault(); + e.stopImmediatePropagation(); + } + }; + MouseZoneManager.prototype._findZoneEventAt = function (e) { + var coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows); + if (!coords) { + return null; + } + var x = coords[0]; + var y = coords[1]; + for (var i = 0; i < this._zones.length; i++) { + var zone = this._zones[i]; + if (zone.y1 === zone.y2) { + if (y === zone.y1 && x >= zone.x1 && x < zone.x2) { + return zone; + } + } + else { + if ((y === zone.y1 && x >= zone.x1) || + (y === zone.y2 && x < zone.x2) || + (y > zone.y1 && y < zone.y2)) { + return zone; + } + } + } + return null; + }; + return MouseZoneManager; +}(Lifecycle_1.Disposable)); +exports.MouseZoneManager = MouseZoneManager; +var MouseZone = (function () { + function MouseZone(x1, y1, x2, y2, clickCallback, hoverCallback, tooltipCallback, leaveCallback, willLinkActivate) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.clickCallback = clickCallback; + this.hoverCallback = hoverCallback; + this.tooltipCallback = tooltipCallback; + this.leaveCallback = leaveCallback; + this.willLinkActivate = willLinkActivate; + } + return MouseZone; +}()); +exports.MouseZone = MouseZone; + +},{"./common/Lifecycle":24,"./ui/Lifecycle":59}],13:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var MouseHelper_1 = require("./MouseHelper"); +var Browser = require("./common/Platform"); +var SelectionModel_1 = require("./SelectionModel"); +var AltClickHandler_1 = require("./handlers/AltClickHandler"); +var BufferLine_1 = require("./core/buffer/BufferLine"); +var EventEmitter2_1 = require("./common/EventEmitter2"); +var DRAG_SCROLL_MAX_THRESHOLD = 50; +var DRAG_SCROLL_MAX_SPEED = 15; +var DRAG_SCROLL_INTERVAL = 50; +var ALT_CLICK_MOVE_CURSOR_TIME = 500; +var WORD_SEPARATORS = ' ()[]{}\'"'; +var NON_BREAKING_SPACE_CHAR = String.fromCharCode(160); +var ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g'); +var SelectionManager = (function () { + function SelectionManager(_terminal, _charMeasure) { + this._terminal = _terminal; + this._charMeasure = _charMeasure; + this._enabled = true; + this._workCell = new BufferLine_1.CellData(); + this._onLinuxMouseSelection = new EventEmitter2_1.EventEmitter2(); + this._onRedrawRequest = new EventEmitter2_1.EventEmitter2(); + this._onSelectionChange = new EventEmitter2_1.EventEmitter2(); + this._initListeners(); + this.enable(); + this._model = new SelectionModel_1.SelectionModel(_terminal); + this._activeSelectionMode = 0; + } + Object.defineProperty(SelectionManager.prototype, "onLinuxMouseSelection", { + get: function () { return this._onLinuxMouseSelection.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "onRedrawRequest", { + get: function () { return this._onRedrawRequest.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "onSelectionChange", { + get: function () { return this._onSelectionChange.event; }, + enumerable: true, + configurable: true + }); + SelectionManager.prototype.dispose = function () { + this._removeMouseDownListeners(); + }; + Object.defineProperty(SelectionManager.prototype, "_buffer", { + get: function () { + return this._terminal.buffers.active; + }, + enumerable: true, + configurable: true + }); + SelectionManager.prototype._initListeners = function () { + var _this = this; + this._mouseMoveListener = function (event) { return _this._onMouseMove(event); }; + this._mouseUpListener = function (event) { return _this._onMouseUp(event); }; + this.initBuffersListeners(); + }; + SelectionManager.prototype.initBuffersListeners = function () { + var _this = this; + this._trimListener = this._terminal.buffer.lines.onTrim(function (amount) { return _this._onTrim(amount); }); + this._terminal.buffers.onBufferActivate(function (e) { return _this._onBufferActivate(e); }); + }; + SelectionManager.prototype.disable = function () { + this.clearSelection(); + this._enabled = false; + }; + SelectionManager.prototype.enable = function () { + this._enabled = true; + }; + Object.defineProperty(SelectionManager.prototype, "selectionStart", { + get: function () { return this._model.finalSelectionStart; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "selectionEnd", { + get: function () { return this._model.finalSelectionEnd; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "hasSelection", { + get: function () { + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return false; + } + return start[0] !== end[0] || start[1] !== end[1]; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionManager.prototype, "selectionText", { + get: function () { + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return ''; + } + var result = []; + if (this._activeSelectionMode === 3) { + if (start[0] === end[0]) { + return ''; + } + for (var i = start[1]; i <= end[1]; i++) { + var lineText = this._buffer.translateBufferLineToString(i, true, start[0], end[0]); + result.push(lineText); + } + } + else { + var startRowEndCol = start[1] === end[1] ? end[0] : undefined; + result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol)); + for (var i = start[1] + 1; i <= end[1] - 1; i++) { + var bufferLine = this._buffer.lines.get(i); + var lineText = this._buffer.translateBufferLineToString(i, true); + if (bufferLine.isWrapped) { + result[result.length - 1] += lineText; + } + else { + result.push(lineText); + } + } + if (start[1] !== end[1]) { + var bufferLine = this._buffer.lines.get(end[1]); + var lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); + if (bufferLine.isWrapped) { + result[result.length - 1] += lineText; + } + else { + result.push(lineText); + } + } + } + var formattedResult = result.map(function (line) { + return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' '); + }).join(Browser.isMSWindows ? '\r\n' : '\n'); + return formattedResult; + }, + enumerable: true, + configurable: true + }); + SelectionManager.prototype.clearSelection = function () { + this._model.clearSelection(); + this._removeMouseDownListeners(); + this.refresh(); + this._onSelectionChange.fire(); + }; + SelectionManager.prototype.refresh = function (isLinuxMouseSelection) { + var _this = this; + if (!this._refreshAnimationFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(function () { return _this._refresh(); }); + } + if (Browser.isLinux && isLinuxMouseSelection) { + var selectionText = this.selectionText; + if (selectionText.length) { + this._onLinuxMouseSelection.fire(this.selectionText); + } + } + }; + SelectionManager.prototype._refresh = function () { + this._refreshAnimationFrame = null; + this._onRedrawRequest.fire({ + start: this._model.finalSelectionStart, + end: this._model.finalSelectionEnd, + columnSelectMode: this._activeSelectionMode === 3 + }); + }; + SelectionManager.prototype.isClickInSelection = function (event) { + var coords = this._getMouseBufferCoords(event); + var start = this._model.finalSelectionStart; + var end = this._model.finalSelectionEnd; + if (!start || !end) { + return false; + } + return this._areCoordsInSelection(coords, start, end); + }; + SelectionManager.prototype._areCoordsInSelection = function (coords, start, end) { + return (coords[1] > start[1] && coords[1] < end[1]) || + (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) || + (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) || + (start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]); + }; + SelectionManager.prototype.selectWordAtCursor = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._selectWordAt(coords, false); + this._model.selectionEnd = null; + this.refresh(true); + } + }; + SelectionManager.prototype.selectAll = function () { + this._model.isSelectAllActive = true; + this.refresh(); + this._onSelectionChange.fire(); + }; + SelectionManager.prototype.selectLines = function (start, end) { + this._model.clearSelection(); + start = Math.max(start, 0); + end = Math.min(end, this._terminal.buffer.lines.length - 1); + this._model.selectionStart = [0, start]; + this._model.selectionEnd = [this._terminal.cols, end]; + this.refresh(); + this._onSelectionChange.fire(); + }; + SelectionManager.prototype._onTrim = function (amount) { + var needsRefresh = this._model.onTrim(amount); + if (needsRefresh) { + this.refresh(); + } + }; + SelectionManager.prototype._getMouseBufferCoords = function (event) { + var coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.cols, this._terminal.rows, true); + if (!coords) { + return null; + } + coords[0]--; + coords[1]--; + coords[1] += this._terminal.buffer.ydisp; + return coords; + }; + SelectionManager.prototype._getMouseEventScrollAmount = function (event) { + var offset = MouseHelper_1.MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; + var terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight); + if (offset >= 0 && offset <= terminalHeight) { + return 0; + } + if (offset > terminalHeight) { + offset -= terminalHeight; + } + offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD); + offset /= DRAG_SCROLL_MAX_THRESHOLD; + return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1)); + }; + SelectionManager.prototype.shouldForceSelection = function (event) { + if (Browser.isMac) { + return event.altKey && this._terminal.options.macOptionClickForcesSelection; + } + return event.shiftKey; + }; + SelectionManager.prototype.onMouseDown = function (event) { + this._mouseDownTimeStamp = event.timeStamp; + if (event.button === 2 && this.hasSelection) { + return; + } + if (event.button !== 0) { + return; + } + if (!this._enabled) { + if (!this.shouldForceSelection(event)) { + return; + } + event.stopPropagation(); + } + event.preventDefault(); + this._dragScrollAmount = 0; + if (this._enabled && event.shiftKey) { + this._onIncrementalClick(event); + } + else { + if (event.detail === 1) { + this._onSingleClick(event); + } + else if (event.detail === 2) { + this._onDoubleClick(event); + } + else if (event.detail === 3) { + this._onTripleClick(event); + } + } + this._addMouseDownListeners(); + this.refresh(true); + }; + SelectionManager.prototype._addMouseDownListeners = function () { + var _this = this; + this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener); + this._dragScrollIntervalTimer = setInterval(function () { return _this._dragScroll(); }, DRAG_SCROLL_INTERVAL); + }; + SelectionManager.prototype._removeMouseDownListeners = function () { + if (this._terminal.element.ownerDocument) { + this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener); + } + clearInterval(this._dragScrollIntervalTimer); + this._dragScrollIntervalTimer = null; + }; + SelectionManager.prototype._onIncrementalClick = function (event) { + if (this._model.selectionStart) { + this._model.selectionEnd = this._getMouseBufferCoords(event); + } + }; + SelectionManager.prototype._onSingleClick = function (event) { + this._model.selectionStartLength = 0; + this._model.isSelectAllActive = false; + this._activeSelectionMode = this.shouldColumnSelect(event) ? 3 : 0; + this._model.selectionStart = this._getMouseBufferCoords(event); + if (!this._model.selectionStart) { + return; + } + this._model.selectionEnd = null; + var line = this._buffer.lines.get(this._model.selectionStart[1]); + if (!line) { + return; + } + if (line.length >= this._model.selectionStart[0]) { + return; + } + if (line.hasWidth(this._model.selectionStart[0]) === 0) { + this._model.selectionStart[0]++; + } + }; + SelectionManager.prototype._onDoubleClick = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._activeSelectionMode = 1; + this._selectWordAt(coords, true); + } + }; + SelectionManager.prototype._onTripleClick = function (event) { + var coords = this._getMouseBufferCoords(event); + if (coords) { + this._activeSelectionMode = 2; + this._selectLineAt(coords[1]); + } + }; + SelectionManager.prototype.shouldColumnSelect = function (event) { + return event.altKey && !(Browser.isMac && this._terminal.options.macOptionClickForcesSelection); + }; + SelectionManager.prototype._onMouseMove = function (event) { + event.stopImmediatePropagation(); + var previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null; + this._model.selectionEnd = this._getMouseBufferCoords(event); + if (!this._model.selectionEnd) { + this.refresh(true); + return; + } + if (this._activeSelectionMode === 2) { + if (this._model.selectionEnd[1] < this._model.selectionStart[1]) { + this._model.selectionEnd[0] = 0; + } + else { + this._model.selectionEnd[0] = this._terminal.cols; + } + } + else if (this._activeSelectionMode === 1) { + this._selectToWordAt(this._model.selectionEnd); + } + this._dragScrollAmount = this._getMouseEventScrollAmount(event); + if (this._activeSelectionMode !== 3) { + if (this._dragScrollAmount > 0) { + this._model.selectionEnd[0] = this._terminal.cols; + } + else if (this._dragScrollAmount < 0) { + this._model.selectionEnd[0] = 0; + } + } + if (this._model.selectionEnd[1] < this._buffer.lines.length) { + if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) { + this._model.selectionEnd[0]++; + } + } + if (!previousSelectionEnd || + previousSelectionEnd[0] !== this._model.selectionEnd[0] || + previousSelectionEnd[1] !== this._model.selectionEnd[1]) { + this.refresh(true); + } + }; + SelectionManager.prototype._dragScroll = function () { + if (this._dragScrollAmount) { + this._terminal.scrollLines(this._dragScrollAmount, false); + if (this._dragScrollAmount > 0) { + if (this._activeSelectionMode !== 3) { + this._model.selectionEnd[0] = this._terminal.cols; + } + this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1); + } + else { + if (this._activeSelectionMode !== 3) { + this._model.selectionEnd[0] = 0; + } + this._model.selectionEnd[1] = this._terminal.buffer.ydisp; + } + this.refresh(); + } + }; + SelectionManager.prototype._onMouseUp = function (event) { + var timeElapsed = event.timeStamp - this._mouseDownTimeStamp; + this._removeMouseDownListeners(); + if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) { + (new AltClickHandler_1.AltClickHandler(event, this._terminal)).move(); + } + else if (this.hasSelection) { + this._onSelectionChange.fire(); + } + }; + SelectionManager.prototype._onBufferActivate = function (e) { + var _this = this; + this.clearSelection(); + if (this._trimListener) { + this._trimListener.dispose(); + } + this._trimListener = e.activeBuffer.lines.onTrim(function (amount) { return _this._onTrim(amount); }); + }; + SelectionManager.prototype._convertViewportColToCharacterIndex = function (bufferLine, coords) { + var charIndex = coords[0]; + for (var i = 0; coords[0] >= i; i++) { + var length_1 = bufferLine.loadCell(i, this._workCell).getChars().length; + if (this._workCell.getWidth() === 0) { + charIndex--; + } + else if (length_1 > 1 && coords[0] !== i) { + charIndex += length_1 - 1; + } + } + return charIndex; + }; + SelectionManager.prototype.setSelection = function (col, row, length) { + this._model.clearSelection(); + this._removeMouseDownListeners(); + this._model.selectionStart = [col, row]; + this._model.selectionStartLength = length; + this.refresh(); + }; + SelectionManager.prototype._getWordAt = function (coords, allowWhitespaceOnlySelection, followWrappedLinesAbove, followWrappedLinesBelow) { + if (followWrappedLinesAbove === void 0) { followWrappedLinesAbove = true; } + if (followWrappedLinesBelow === void 0) { followWrappedLinesBelow = true; } + if (coords[0] >= this._terminal.cols) { + return null; + } + var bufferLine = this._buffer.lines.get(coords[1]); + if (!bufferLine) { + return null; + } + var line = this._buffer.translateBufferLineToString(coords[1], false); + var startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); + var endIndex = startIndex; + var charOffset = coords[0] - startIndex; + var leftWideCharCount = 0; + var rightWideCharCount = 0; + var leftLongCharOffset = 0; + var rightLongCharOffset = 0; + if (line.charAt(startIndex) === ' ') { + while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') { + startIndex--; + } + while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') { + endIndex++; + } + } + else { + var startCol = coords[0]; + var endCol = coords[0]; + if (bufferLine.getWidth(startCol) === 0) { + leftWideCharCount++; + startCol--; + } + if (bufferLine.getWidth(endCol) === 2) { + rightWideCharCount++; + endCol++; + } + var length_2 = bufferLine.getString(endCol).length; + if (length_2 > 1) { + rightLongCharOffset += length_2 - 1; + endIndex += length_2 - 1; + } + while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) { + bufferLine.loadCell(startCol - 1, this._workCell); + var length_3 = this._workCell.getChars().length; + if (this._workCell.getWidth() === 0) { + leftWideCharCount++; + startCol--; + } + else if (length_3 > 1) { + leftLongCharOffset += length_3 - 1; + startIndex -= length_3 - 1; + } + startIndex--; + startCol--; + } + while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) { + bufferLine.loadCell(endCol + 1, this._workCell); + var length_4 = this._workCell.getChars().length; + if (this._workCell.getWidth() === 2) { + rightWideCharCount++; + endCol++; + } + else if (length_4 > 1) { + rightLongCharOffset += length_4 - 1; + endIndex += length_4 - 1; + } + endIndex++; + endCol++; + } + } + endIndex++; + var start = startIndex + + charOffset + - leftWideCharCount + + leftLongCharOffset; + var length = Math.min(this._terminal.cols, endIndex + - startIndex + + leftWideCharCount + + rightWideCharCount + - leftLongCharOffset + - rightLongCharOffset); + if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') { + return null; + } + if (followWrappedLinesAbove) { + if (start === 0 && bufferLine.getCodePoint(0) !== 32) { + var previousBufferLine = this._buffer.lines.get(coords[1] - 1); + if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32) { + var previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); + if (previousLineWordPosition) { + var offset = this._terminal.cols - previousLineWordPosition.start; + start -= offset; + length += offset; + } + } + } + } + if (followWrappedLinesBelow) { + if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32) { + var nextBufferLine = this._buffer.lines.get(coords[1] + 1); + if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32) { + var nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); + if (nextLineWordPosition) { + length += nextLineWordPosition.length; + } + } + } + } + return { start: start, length: length }; + }; + SelectionManager.prototype._selectWordAt = function (coords, allowWhitespaceOnlySelection) { + var wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection); + if (wordPosition) { + while (wordPosition.start < 0) { + wordPosition.start += this._terminal.cols; + coords[1]--; + } + this._model.selectionStart = [wordPosition.start, coords[1]]; + this._model.selectionStartLength = wordPosition.length; + } + }; + SelectionManager.prototype._selectToWordAt = function (coords) { + var wordPosition = this._getWordAt(coords, true); + if (wordPosition) { + var endRow = coords[1]; + while (wordPosition.start < 0) { + wordPosition.start += this._terminal.cols; + endRow--; + } + if (!this._model.areSelectionValuesReversed()) { + while (wordPosition.start + wordPosition.length > this._terminal.cols) { + wordPosition.length -= this._terminal.cols; + endRow++; + } + } + this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow]; + } + }; + SelectionManager.prototype._isCharWordSeparator = function (cell) { + if (cell.getWidth() === 0) { + return false; + } + return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0; + }; + SelectionManager.prototype._selectLineAt = function (line) { + var wrappedRange = this._buffer.getWrappedRangeForLine(line); + this._model.selectionStart = [0, wrappedRange.first]; + this._model.selectionEnd = [this._terminal.cols, wrappedRange.last]; + this._model.selectionStartLength = 0; + }; + return SelectionManager; +}()); +exports.SelectionManager = SelectionManager; + +},{"./MouseHelper":11,"./SelectionModel":14,"./common/EventEmitter2":23,"./common/Platform":25,"./core/buffer/BufferLine":29,"./handlers/AltClickHandler":35}],14:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SelectionModel = (function () { + function SelectionModel(_terminal) { + this._terminal = _terminal; + this.clearSelection(); + } + SelectionModel.prototype.clearSelection = function () { + this.selectionStart = null; + this.selectionEnd = null; + this.isSelectAllActive = false; + this.selectionStartLength = 0; + }; + Object.defineProperty(SelectionModel.prototype, "finalSelectionStart", { + get: function () { + if (this.isSelectAllActive) { + return [0, 0]; + } + if (!this.selectionEnd || !this.selectionStart) { + return this.selectionStart; + } + return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SelectionModel.prototype, "finalSelectionEnd", { + get: function () { + if (this.isSelectAllActive) { + return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1]; + } + if (!this.selectionStart) { + return null; + } + if (!this.selectionEnd || this.areSelectionValuesReversed()) { + var startPlusLength = this.selectionStart[0] + this.selectionStartLength; + if (startPlusLength > this._terminal.cols) { + return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)]; + } + return [startPlusLength, this.selectionStart[1]]; + } + if (this.selectionStartLength) { + if (this.selectionEnd[1] === this.selectionStart[1]) { + return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; + } + } + return this.selectionEnd; + }, + enumerable: true, + configurable: true + }); + SelectionModel.prototype.areSelectionValuesReversed = function () { + var start = this.selectionStart; + var end = this.selectionEnd; + if (!start || !end) { + return false; + } + return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]); + }; + SelectionModel.prototype.onTrim = function (amount) { + if (this.selectionStart) { + this.selectionStart[1] -= amount; + } + if (this.selectionEnd) { + this.selectionEnd[1] -= amount; + } + if (this.selectionEnd && this.selectionEnd[1] < 0) { + this.clearSelection(); + return true; + } + if (this.selectionStart && this.selectionStart[1] < 0) { + this.selectionStart[1] = 0; + } + return false; + }; + return SelectionModel; +}()); +exports.SelectionModel = SelectionModel; + +},{}],15:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; +var SoundManager = (function () { + function SoundManager(_terminal) { + this._terminal = _terminal; + } + Object.defineProperty(SoundManager, "audioContext", { + get: function () { + if (!SoundManager._audioContext) { + var audioContextCtor = window.AudioContext || window.webkitAudioContext; + if (!audioContextCtor) { + console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); + return null; + } + SoundManager._audioContext = new audioContextCtor(); + } + return SoundManager._audioContext; + }, + enumerable: true, + configurable: true + }); + SoundManager.prototype.playBellSound = function () { + var ctx = SoundManager.audioContext; + if (!ctx) { + return; + } + var bellAudioSource = ctx.createBufferSource(); + ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), function (buffer) { + bellAudioSource.buffer = buffer; + bellAudioSource.connect(ctx.destination); + bellAudioSource.start(0); + }); + }; + SoundManager.prototype._base64ToArrayBuffer = function (base64) { + var binaryString = window.atob(base64); + var len = binaryString.length; + var bytes = new Uint8Array(len); + for (var i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; + }; + SoundManager.prototype._removeMimeType = function (dataURI) { + var splitUri = dataURI.split(','); + return splitUri[1]; + }; + return SoundManager; +}()); +exports.SoundManager = SoundManager; + +},{}],16:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.blankLine = 'Blank line'; +exports.promptLabel = 'Terminal input'; +exports.tooMuchOutput = 'Too much output to announce, navigate to rows manually to read'; + +},{}],17:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BufferSet_1 = require("./BufferSet"); +var Buffer_1 = require("./Buffer"); +var CompositionHelper_1 = require("./CompositionHelper"); +var EventEmitter_1 = require("./common/EventEmitter"); +var Viewport_1 = require("./Viewport"); +var Clipboard_1 = require("./Clipboard"); +var EscapeSequences_1 = require("./common/data/EscapeSequences"); +var InputHandler_1 = require("./InputHandler"); +var Renderer_1 = require("./renderer/Renderer"); +var Linkifier_1 = require("./Linkifier"); +var SelectionManager_1 = require("./SelectionManager"); +var CharMeasure_1 = require("./CharMeasure"); +var Browser = require("./common/Platform"); +var Lifecycle_1 = require("./ui/Lifecycle"); +var Strings = require("./Strings"); +var MouseHelper_1 = require("./MouseHelper"); +var SoundManager_1 = require("./SoundManager"); +var MouseZoneManager_1 = require("./MouseZoneManager"); +var AccessibilityManager_1 = require("./AccessibilityManager"); +var CharAtlasCache_1 = require("./renderer/atlas/CharAtlasCache"); +var DomRenderer_1 = require("./renderer/dom/DomRenderer"); +var Keyboard_1 = require("./core/input/Keyboard"); +var Clone_1 = require("./common/Clone"); +var EventEmitter2_1 = require("./common/EventEmitter2"); +var BufferLine_1 = require("./core/buffer/BufferLine"); +var WindowsMode_1 = require("./WindowsMode"); +var ColorManager_1 = require("./ui/ColorManager"); +var RenderCoordinator_1 = require("./renderer/RenderCoordinator"); +var document = (typeof window !== 'undefined') ? window.document : null; +var WRITE_BUFFER_PAUSE_THRESHOLD = 5; +var WRITE_TIMEOUT_MS = 12; +var WRITE_BUFFER_LENGTH_THRESHOLD = 50; +var MINIMUM_COLS = 2; +var MINIMUM_ROWS = 1; +var CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; +var DEFAULT_OPTIONS = { + cols: 80, + rows: 24, + convertEol: false, + termName: 'xterm', + cursorBlink: false, + cursorStyle: 'block', + bellSound: SoundManager_1.DEFAULT_BELL_SOUND, + bellStyle: 'none', + drawBoldTextInBrightColors: true, + enableBold: true, + experimentalCharAtlas: 'static', + fontFamily: 'courier-new, courier, monospace', + fontSize: 15, + fontWeight: 'normal', + fontWeightBold: 'bold', + lineHeight: 1.0, + letterSpacing: 0, + scrollback: 1000, + screenKeys: false, + screenReaderMode: false, + debug: false, + macOptionIsMeta: false, + macOptionClickForcesSelection: false, + cancelEvents: false, + disableStdin: false, + useFlowControl: false, + allowTransparency: false, + tabStopWidth: 8, + theme: undefined, + rightClickSelectsWord: Browser.isMac, + rendererType: 'canvas', + windowsMode: false +}; +var Terminal = (function (_super) { + __extends(Terminal, _super); + function Terminal(options) { + if (options === void 0) { options = {}; } + var _this = _super.call(this) || this; + _this.browser = Browser; + _this._blankLine = null; + _this._onCursorMove = new EventEmitter2_1.EventEmitter2(); + _this._onData = new EventEmitter2_1.EventEmitter2(); + _this._onKey = new EventEmitter2_1.EventEmitter2(); + _this._onLineFeed = new EventEmitter2_1.EventEmitter2(); + _this._onRender = new EventEmitter2_1.EventEmitter2(); + _this._onResize = new EventEmitter2_1.EventEmitter2(); + _this._onScroll = new EventEmitter2_1.EventEmitter2(); + _this._onSelectionChange = new EventEmitter2_1.EventEmitter2(); + _this._onTitleChange = new EventEmitter2_1.EventEmitter2(); + _this.options = Clone_1.clone(options); + _this._setup(); + _this.onCursorMove(function () { return _this.emit('cursormove'); }); + _this.onData(function (e) { return _this.emit('data', e); }); + _this.onKey(function (e) { return _this.emit('key', e.key, e.domEvent); }); + _this.onLineFeed(function () { return _this.emit('linefeed'); }); + _this.onRender(function (e) { return _this.emit('refresh', e); }); + _this.onResize(function (e) { return _this.emit('resize', e); }); + _this.onSelectionChange(function () { return _this.emit('selection'); }); + _this.onScroll(function (e) { return _this.emit('scroll', e); }); + _this.onTitleChange(function (e) { return _this.emit('title', e); }); + return _this; + } + Object.defineProperty(Terminal.prototype, "onCursorMove", { + get: function () { return this._onCursorMove.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onData", { + get: function () { return this._onData.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onKey", { + get: function () { return this._onKey.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onLineFeed", { + get: function () { return this._onLineFeed.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onRender", { + get: function () { return this._onRender.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onResize", { + get: function () { return this._onResize.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onScroll", { + get: function () { return this._onScroll.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onSelectionChange", { + get: function () { return this._onSelectionChange.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onTitleChange", { + get: function () { return this._onTitleChange.event; }, + enumerable: true, + configurable: true + }); + Terminal.prototype.dispose = function () { + _super.prototype.dispose.call(this); + if (this._windowsMode) { + this._windowsMode.dispose(); + this._windowsMode = undefined; + } + this._customKeyEventHandler = null; + CharAtlasCache_1.removeTerminalFromCache(this); + this.handler = function () { }; + this.write = function () { }; + if (this.element && this.element.parentNode) { + this.element.parentNode.removeChild(this.element); + } + }; + Terminal.prototype.destroy = function () { + this.dispose(); + }; + Terminal.prototype._setup = function () { + var _this = this; + Object.keys(DEFAULT_OPTIONS).forEach(function (key) { + if (_this.options[key] === null || _this.options[key] === undefined) { + _this.options[key] = DEFAULT_OPTIONS[key]; + } + }); + this._parent = document ? document.body : null; + this.cols = Math.max(this.options.cols, MINIMUM_COLS); + this.rows = Math.max(this.options.rows, MINIMUM_ROWS); + if (this.options.handler) { + this.onData(this.options.handler); + } + this.cursorState = 0; + this.cursorHidden = false; + this._customKeyEventHandler = null; + this.applicationKeypad = false; + this.applicationCursor = false; + this.originMode = false; + this.insertMode = false; + this.wraparoundMode = true; + this.bracketedPasteMode = false; + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + this.curAttrData = BufferLine_1.DEFAULT_ATTR_DATA.clone(); + this._eraseAttrData = BufferLine_1.DEFAULT_ATTR_DATA.clone(); + this.params = []; + this.currentParam = 0; + this.writeBuffer = []; + this.writeBufferUtf8 = []; + this._writeInProgress = false; + this._xoffSentToCatchUp = false; + this._userScrolling = false; + this._inputHandler = new InputHandler_1.InputHandler(this); + this._inputHandler.onCursorMove(function () { return _this._onCursorMove.fire(); }); + this._inputHandler.onLineFeed(function () { return _this._onLineFeed.fire(); }); + this._inputHandler.onData(function (e) { return _this._onData.fire(e); }); + this.register(this._inputHandler); + this.selectionManager = this.selectionManager || null; + this.linkifier = this.linkifier || new Linkifier_1.Linkifier(this); + this._mouseZoneManager = this._mouseZoneManager || null; + this.soundManager = this.soundManager || new SoundManager_1.SoundManager(this); + this.buffers = new BufferSet_1.BufferSet(this); + if (this.selectionManager) { + this.selectionManager.clearSelection(); + this.selectionManager.initBuffersListeners(); + } + if (this.options.windowsMode) { + this._windowsMode = WindowsMode_1.applyWindowsMode(this); + } + }; + Object.defineProperty(Terminal.prototype, "buffer", { + get: function () { + return this.buffers.active; + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.eraseAttrData = function () { + this._eraseAttrData.bg &= ~(50331648 | 0xFFFFFF); + this._eraseAttrData.bg |= this.curAttrData.bg & ~0xFC000000; + return this._eraseAttrData; + }; + Terminal.prototype.focus = function () { + if (this.textarea) { + this.textarea.focus({ preventScroll: true }); + } + }; + Object.defineProperty(Terminal.prototype, "isFocused", { + get: function () { + return document.activeElement === this.textarea && document.hasFocus(); + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.getOption = function (key) { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + return this.options[key]; + }; + Terminal.prototype.setOption = function (key, value) { + if (!(key in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + key + '"'); + } + if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) { + console.error("Option \"" + key + "\" can only be set in the constructor"); + } + if (this.options[key] === value) { + return; + } + switch (key) { + case 'bellStyle': + if (!value) { + value = 'none'; + } + break; + case 'cursorStyle': + if (!value) { + value = 'block'; + } + break; + case 'fontWeight': + if (!value) { + value = 'normal'; + } + break; + case 'fontWeightBold': + if (!value) { + value = 'bold'; + } + break; + case 'lineHeight': + if (value < 1) { + console.warn(key + " cannot be less than 1, value: " + value); + return; + } + case 'rendererType': + if (!value) { + value = 'canvas'; + } + break; + case 'tabStopWidth': + if (value < 1) { + console.warn(key + " cannot be less than 1, value: " + value); + return; + } + break; + case 'theme': + this._setTheme(value); + break; + case 'scrollback': + value = Math.min(value, Buffer_1.MAX_BUFFER_SIZE); + if (value < 0) { + console.warn(key + " cannot be less than 0, value: " + value); + return; + } + if (this.options[key] !== value) { + var newBufferLength = this.rows + value; + if (this.buffer.lines.length > newBufferLength) { + var amountToTrim = this.buffer.lines.length - newBufferLength; + var needsRefresh = (this.buffer.ydisp - amountToTrim < 0); + this.buffer.lines.trimStart(amountToTrim); + this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0); + this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0); + if (needsRefresh) { + this.refresh(0, this.rows - 1); + } + } + } + break; + } + this.options[key] = value; + switch (key) { + case 'fontFamily': + case 'fontSize': + if (this._renderCoordinator) { + this._renderCoordinator.clear(); + this.charMeasure.measure(this.options); + } + break; + case 'drawBoldTextInBrightColors': + case 'experimentalCharAtlas': + case 'enableBold': + case 'letterSpacing': + case 'lineHeight': + case 'fontWeight': + case 'fontWeightBold': + if (this._renderCoordinator) { + this._renderCoordinator.clear(); + this._renderCoordinator.onResize(this.cols, this.rows); + this.refresh(0, this.rows - 1); + } + break; + case 'rendererType': + if (this._renderCoordinator) { + this._renderCoordinator.setRenderer(this._createRenderer()); + } + break; + case 'scrollback': + this.buffers.resize(this.cols, this.rows); + if (this.viewport) { + this.viewport.syncScrollArea(); + } + break; + case 'screenReaderMode': + if (value) { + if (!this._accessibilityManager && this._renderCoordinator) { + this._accessibilityManager = new AccessibilityManager_1.AccessibilityManager(this, this._renderCoordinator.dimensions); + } + } + else { + if (this._accessibilityManager) { + this._accessibilityManager.dispose(); + this._accessibilityManager = null; + } + } + break; + case 'tabStopWidth': + this.buffers.setupTabStops(); + break; + case 'windowsMode': + if (value) { + if (!this._windowsMode) { + this._windowsMode = WindowsMode_1.applyWindowsMode(this); + } + } + else { + if (this._windowsMode) { + this._windowsMode.dispose(); + this._windowsMode = undefined; + } + } + break; + } + if (this._renderCoordinator) { + this._renderCoordinator.onOptionsChanged(); + } + }; + Terminal.prototype._onTextAreaFocus = function (ev) { + if (this.sendFocus) { + this.handler(EscapeSequences_1.C0.ESC + '[I'); + } + this.updateCursorStyle(ev); + this.element.classList.add('focus'); + this.showCursor(); + this.emit('focus'); + }; + Terminal.prototype.blur = function () { + return this.textarea.blur(); + }; + Terminal.prototype._onTextAreaBlur = function () { + this.textarea.value = ''; + this.refresh(this.buffer.y, this.buffer.y); + if (this.sendFocus) { + this.handler(EscapeSequences_1.C0.ESC + '[O'); + } + this.element.classList.remove('focus'); + this.emit('blur'); + }; + Terminal.prototype._initGlobal = function () { + var _this = this; + this._bindKeys(); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'copy', function (event) { + if (!_this.hasSelection()) { + return; + } + Clipboard_1.copyHandler(event, _this, _this.selectionManager); + })); + var pasteHandlerWrapper = function (event) { return Clipboard_1.pasteHandler(event, _this); }; + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); + if (Browser.isFirefox) { + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'mousedown', function (event) { + if (event.button === 2) { + Clipboard_1.rightClickHandler(event, _this, _this.selectionManager, _this.options.rightClickSelectsWord); + } + })); + } + else { + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'contextmenu', function (event) { + Clipboard_1.rightClickHandler(event, _this, _this.selectionManager, _this.options.rightClickSelectsWord); + })); + } + if (Browser.isLinux) { + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'auxclick', function (event) { + if (event.button === 1) { + Clipboard_1.moveTextAreaUnderMouseCursor(event, _this); + } + })); + } + }; + Terminal.prototype._bindKeys = function () { + var _this = this; + var self = this; + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'keydown', function (ev) { + if (document.activeElement !== this) { + return; + } + self._keyDown(ev); + }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'keypress', function (ev) { + if (document.activeElement !== this) { + return; + } + self._keyPress(ev); + }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'keyup', function (ev) { + if (!wasModifierKeyOnlyEvent(ev)) { + _this.focus(); + } + self._keyUp(ev); + }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'keydown', function (ev) { return _this._keyDown(ev); }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'keypress', function (ev) { return _this._keyPress(ev); }, true)); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'compositionstart', function () { return _this._compositionHelper.compositionstart(); })); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'compositionupdate', function (e) { return _this._compositionHelper.compositionupdate(e); })); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'compositionend', function () { return _this._compositionHelper.compositionend(); })); + this.register(this.onRender(function () { return _this._compositionHelper.updateCompositionElements(); })); + this.register(this.onRender(function (e) { return _this._queueLinkification(e.start, e.end); })); + }; + Terminal.prototype.open = function (parent) { + var _this = this; + this._parent = parent || this._parent; + if (!this._parent) { + throw new Error('Terminal requires a parent element.'); + } + this._context = this._parent.ownerDocument.defaultView; + this._document = this._parent.ownerDocument; + this.element = this._document.createElement('div'); + this.element.dir = 'ltr'; + this.element.classList.add('terminal'); + this.element.classList.add('xterm'); + this.element.setAttribute('tabindex', '0'); + this._parent.appendChild(this.element); + var fragment = document.createDocumentFragment(); + this._viewportElement = document.createElement('div'); + this._viewportElement.classList.add('xterm-viewport'); + fragment.appendChild(this._viewportElement); + this._viewportScrollArea = document.createElement('div'); + this._viewportScrollArea.classList.add('xterm-scroll-area'); + this._viewportElement.appendChild(this._viewportScrollArea); + this.screenElement = document.createElement('div'); + this.screenElement.classList.add('xterm-screen'); + this._helperContainer = document.createElement('div'); + this._helperContainer.classList.add('xterm-helpers'); + this.screenElement.appendChild(this._helperContainer); + fragment.appendChild(this.screenElement); + this._mouseZoneManager = new MouseZoneManager_1.MouseZoneManager(this); + this.register(this._mouseZoneManager); + this.register(this.onScroll(function () { return _this._mouseZoneManager.clearAll(); })); + this.linkifier.attachToDom(this._mouseZoneManager); + this.textarea = document.createElement('textarea'); + this.textarea.classList.add('xterm-helper-textarea'); + this.textarea.setAttribute('aria-label', Strings.promptLabel); + this.textarea.setAttribute('aria-multiline', 'false'); + this.textarea.setAttribute('autocorrect', 'off'); + this.textarea.setAttribute('autocapitalize', 'off'); + this.textarea.setAttribute('spellcheck', 'false'); + this.textarea.tabIndex = 0; + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'focus', function (ev) { return _this._onTextAreaFocus(ev); })); + this.register(Lifecycle_1.addDisposableDomListener(this.textarea, 'blur', function () { return _this._onTextAreaBlur(); })); + this._helperContainer.appendChild(this.textarea); + this._compositionView = document.createElement('div'); + this._compositionView.classList.add('composition-view'); + this._compositionHelper = new CompositionHelper_1.CompositionHelper(this.textarea, this._compositionView, this); + this._helperContainer.appendChild(this._compositionView); + this.charMeasure = new CharMeasure_1.CharMeasure(document, this._helperContainer); + this.element.appendChild(fragment); + this._theme = this.options.theme; + this._colorManager = new ColorManager_1.ColorManager(document, this.options.allowTransparency); + this._colorManager.setTheme(this._theme); + var renderer = this._createRenderer(); + this._renderCoordinator = new RenderCoordinator_1.RenderCoordinator(renderer, this.rows, this.screenElement); + this._renderCoordinator.onRender(function (e) { return _this._onRender.fire(e); }); + this.onResize(function (e) { return _this._renderCoordinator.resize(e.cols, e.rows); }); + this.viewport = new Viewport_1.Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure, this._renderCoordinator.dimensions); + this.viewport.onThemeChange(this._colorManager.colors); + this.register(this.viewport); + this.register(this.onCursorMove(function () { return _this._renderCoordinator.onCursorMove(); })); + this.register(this.onResize(function () { return _this._renderCoordinator.onResize(_this.cols, _this.rows); })); + this.register(this.addDisposableListener('blur', function () { return _this._renderCoordinator.onBlur(); })); + this.register(this.addDisposableListener('focus', function () { return _this._renderCoordinator.onFocus(); })); + this.register(this.charMeasure.onCharSizeChanged(function () { return _this._renderCoordinator.onCharSizeChanged(); })); + this.register(this._renderCoordinator.onDimensionsChange(function () { return _this.viewport.syncScrollArea(); })); + this.selectionManager = new SelectionManager_1.SelectionManager(this, this.charMeasure); + this.register(this.selectionManager.onSelectionChange(function () { return _this._onSelectionChange.fire(); })); + this.register(Lifecycle_1.addDisposableDomListener(this.element, 'mousedown', function (e) { return _this.selectionManager.onMouseDown(e); })); + this.register(this.selectionManager.onRedrawRequest(function (e) { return _this._renderCoordinator.onSelectionChanged(e.start, e.end, e.columnSelectMode); })); + this.register(this.selectionManager.onLinuxMouseSelection(function (text) { + _this.textarea.value = text; + _this.textarea.focus(); + _this.textarea.select(); + })); + this.register(this.onScroll(function () { + _this.viewport.syncScrollArea(); + _this.selectionManager.refresh(); + })); + this.register(Lifecycle_1.addDisposableDomListener(this._viewportElement, 'scroll', function () { return _this.selectionManager.refresh(); })); + this.mouseHelper = new MouseHelper_1.MouseHelper(this._renderCoordinator); + this.element.classList.toggle('enable-mouse-events', this.mouseEvents); + if (this.mouseEvents) { + this.selectionManager.disable(); + } + else { + this.selectionManager.enable(); + } + if (this.options.screenReaderMode) { + this._accessibilityManager = new AccessibilityManager_1.AccessibilityManager(this, this._renderCoordinator.dimensions); + this._accessibilityManager.register(this._renderCoordinator.onDimensionsChange(function (e) { return _this._accessibilityManager.setDimensions(e); })); + } + this.charMeasure.measure(this.options); + this.refresh(0, this.rows - 1); + this._initGlobal(); + this.bindMouse(); + }; + Terminal.prototype._createRenderer = function () { + switch (this.options.rendererType) { + case 'canvas': + return new Renderer_1.Renderer(this, this._colorManager.colors); + break; + case 'dom': + return new DomRenderer_1.DomRenderer(this, this._colorManager.colors); + break; + default: throw new Error("Unrecognized rendererType \"" + this.options.rendererType + "\""); + } + }; + Terminal.prototype._setTheme = function (theme) { + this._theme = theme; + if (this._colorManager) { + this._colorManager.setTheme(theme); + } + if (this._renderCoordinator) { + this._renderCoordinator.setColors(this._colorManager.colors); + } + if (this.viewport) { + this.viewport.onThemeChange(this._colorManager.colors); + } + }; + Terminal.prototype.bindMouse = function () { + var _this = this; + var el = this.element; + var self = this; + var pressed = 32; + function sendButton(ev) { + var button; + var pos; + button = getButton(ev); + pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows); + if (!pos) + return; + sendEvent(button, pos); + switch (ev.overrideType || ev.type) { + case 'mousedown': + pressed = button; + break; + case 'mouseup': + pressed = 32; + break; + case 'wheel': + break; + } + } + function sendMove(ev) { + var button = pressed; + var pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows); + if (!pos) + return; + button += 32; + sendEvent(button, pos); + } + function encode(data, ch) { + if (!self.utfMouse) { + if (ch === 255) { + data.push(0); + return; + } + if (ch > 127) + ch = 127; + data.push(ch); + } + else { + if (ch > 2047) { + data.push(2047); + return; + } + data.push(ch); + } + } + function sendEvent(button, pos) { + if (self._vt300Mouse) { + button &= 3; + pos.x -= 32; + pos.y -= 32; + var data_1 = EscapeSequences_1.C0.ESC + '[24'; + if (button === 0) + data_1 += '1'; + else if (button === 1) + data_1 += '3'; + else if (button === 2) + data_1 += '5'; + else if (button === 3) + return; + else + data_1 += '0'; + data_1 += '~[' + pos.x + ',' + pos.y + ']\r'; + self.handler(data_1); + return; + } + if (self._decLocator) { + button &= 3; + pos.x -= 32; + pos.y -= 32; + if (button === 0) + button = 2; + else if (button === 1) + button = 4; + else if (button === 2) + button = 6; + else if (button === 3) + button = 3; + self.handler(EscapeSequences_1.C0.ESC + '[' + + button + + ';' + + (button === 3 ? 4 : 0) + + ';' + + pos.y + + ';' + + pos.x + + ';' + + pos.page || 0 + + '&w'); + return; + } + if (self.urxvtMouse) { + pos.x -= 32; + pos.y -= 32; + pos.x++; + pos.y++; + self.handler(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.handler(EscapeSequences_1.C0.ESC + '[<' + + (((button & 3) === 3 ? button & ~3 : button) - 32) + + ';' + + pos.x + + ';' + + pos.y + + ((button & 3) === 3 ? 'm' : 'M')); + return; + } + var data = []; + encode(data, button); + encode(data, pos.x); + encode(data, pos.y); + self.handler(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data)); + } + function getButton(ev) { + var button; + var shift; + var meta; + var ctrl; + var mod; + switch (ev.overrideType || ev.type) { + case 'mousedown': + button = ev.button !== null && ev.button !== undefined + ? +ev.button + : ev.which !== null && ev.which !== undefined + ? ev.which - 1 + : null; + if (Browser.isMSIE) { + button = button === 1 ? 0 : button === 4 ? 1 : button; + } + break; + case 'mouseup': + button = 3; + break; + case 'DOMMouseScroll': + button = ev.detail < 0 + ? 64 + : 65; + break; + case 'wheel': + button = ev.deltaY < 0 + ? 64 + : 65; + break; + } + shift = ev.shiftKey ? 4 : 0; + meta = ev.metaKey ? 8 : 0; + ctrl = ev.ctrlKey ? 16 : 0; + mod = shift | meta | ctrl; + if (self.vt200Mouse) { + mod &= ctrl; + } + else if (!self.normalMouse) { + mod = 0; + } + button = (32 + (mod << 2)) + button; + return button; + } + this.register(Lifecycle_1.addDisposableDomListener(el, 'mousedown', function (ev) { + ev.preventDefault(); + _this.focus(); + if (!_this.mouseEvents || _this.selectionManager.shouldForceSelection(ev)) { + return; + } + sendButton(ev); + if (_this.vt200Mouse) { + ev.overrideType = 'mouseup'; + sendButton(ev); + return _this.cancel(ev); + } + var moveHandler; + if (_this.normalMouse) { + moveHandler = function (event) { + if (!_this.normalMouse) { + return; + } + sendMove(event); + }; + _this._document.addEventListener('mousemove', moveHandler); + } + var handler = function (ev) { + if (_this.normalMouse && !_this.x10Mouse) { + sendButton(ev); + } + if (moveHandler) { + _this._document.removeEventListener('mousemove', moveHandler); + moveHandler = null; + } + _this._document.removeEventListener('mouseup', handler); + return _this.cancel(ev); + }; + _this._document.addEventListener('mouseup', handler); + return _this.cancel(ev); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'wheel', function (ev) { + if (!_this.mouseEvents) { + if (!_this.buffer.hasScrollback) { + var amount = _this.viewport.getLinesScrolled(ev); + if (amount === 0) { + return; + } + var sequence = EscapeSequences_1.C0.ESC + (_this.applicationCursor ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B'); + var data = ''; + for (var i = 0; i < Math.abs(amount); i++) { + data += sequence; + } + _this.handler(data); + } + return; + } + if (_this.x10Mouse || _this._vt300Mouse || _this._decLocator) + return; + sendButton(ev); + ev.preventDefault(); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'wheel', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onWheel(ev); + return _this.cancel(ev); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'touchstart', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onTouchStart(ev); + return _this.cancel(ev); + })); + this.register(Lifecycle_1.addDisposableDomListener(el, 'touchmove', function (ev) { + if (_this.mouseEvents) + return; + _this.viewport.onTouchMove(ev); + return _this.cancel(ev); + })); + }; + Terminal.prototype.refresh = function (start, end) { + if (this._renderCoordinator) { + this._renderCoordinator.refreshRows(start, end); + } + }; + Terminal.prototype._queueLinkification = function (start, end) { + if (this.linkifier) { + this.linkifier.linkifyRows(start, end); + } + }; + Terminal.prototype.updateCursorStyle = function (ev) { + if (this.selectionManager && this.selectionManager.shouldColumnSelect(ev)) { + this.element.classList.add('column-select'); + } + else { + this.element.classList.remove('column-select'); + } + }; + Terminal.prototype.showCursor = function () { + if (!this.cursorState) { + this.cursorState = 1; + this.refresh(this.buffer.y, this.buffer.y); + } + }; + Terminal.prototype.scroll = function (isWrapped) { + if (isWrapped === void 0) { isWrapped = false; } + var newLine; + newLine = this._blankLine; + var eraseAttr = this.eraseAttrData(); + if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) { + newLine = this.buffer.getBlankLine(eraseAttr, isWrapped); + this._blankLine = newLine; + } + newLine.isWrapped = isWrapped; + var topRow = this.buffer.ybase + this.buffer.scrollTop; + var bottomRow = this.buffer.ybase + this.buffer.scrollBottom; + if (this.buffer.scrollTop === 0) { + var willBufferBeTrimmed = this.buffer.lines.isFull; + if (bottomRow === this.buffer.lines.length - 1) { + if (willBufferBeTrimmed) { + this.buffer.lines.recycle().copyFrom(newLine); + } + else { + this.buffer.lines.push(newLine.clone()); + } + } + else { + this.buffer.lines.splice(bottomRow + 1, 0, newLine.clone()); + } + if (!willBufferBeTrimmed) { + this.buffer.ybase++; + if (!this._userScrolling) { + this.buffer.ydisp++; + } + } + else { + if (this._userScrolling) { + this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); + } + } + } + else { + var scrollRegionHeight = bottomRow - topRow + 1; + this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); + this.buffer.lines.set(bottomRow, newLine.clone()); + } + if (!this._userScrolling) { + this.buffer.ydisp = this.buffer.ybase; + } + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + this._onScroll.fire(this.buffer.ydisp); + }; + Terminal.prototype.scrollLines = function (disp, suppressScrollEvent) { + if (disp < 0) { + if (this.buffer.ydisp === 0) { + return; + } + this._userScrolling = true; + } + else if (disp + this.buffer.ydisp >= this.buffer.ybase) { + this._userScrolling = false; + } + var oldYdisp = this.buffer.ydisp; + this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); + if (oldYdisp === this.buffer.ydisp) { + return; + } + if (!suppressScrollEvent) { + this._onScroll.fire(this.buffer.ydisp); + } + this.refresh(0, this.rows - 1); + }; + Terminal.prototype.scrollPages = function (pageCount) { + this.scrollLines(pageCount * (this.rows - 1)); + }; + Terminal.prototype.scrollToTop = function () { + this.scrollLines(-this.buffer.ydisp); + }; + Terminal.prototype.scrollToBottom = function () { + this.scrollLines(this.buffer.ybase - this.buffer.ydisp); + }; + Terminal.prototype.scrollToLine = function (line) { + var scrollAmount = line - this.buffer.ydisp; + if (scrollAmount !== 0) { + this.scrollLines(scrollAmount); + } + }; + Terminal.prototype.writeUtf8 = function (data) { + var _this = this; + if (this._isDisposed) { + return; + } + if (!data) { + return; + } + this.writeBufferUtf8.push(data); + if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + this.handler(EscapeSequences_1.C0.DC3); + this._xoffSentToCatchUp = true; + } + if (!this._writeInProgress && this.writeBufferUtf8.length > 0) { + this._writeInProgress = true; + setTimeout(function () { + _this._innerWriteUtf8(); + }); + } + }; + Terminal.prototype._innerWriteUtf8 = function (bufferOffset) { + var _this = this; + if (bufferOffset === void 0) { bufferOffset = 0; } + if (this._isDisposed) { + this.writeBufferUtf8 = []; + } + var startTime = Date.now(); + while (this.writeBufferUtf8.length > bufferOffset) { + var data = this.writeBufferUtf8[bufferOffset]; + bufferOffset++; + if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) { + this.handler(EscapeSequences_1.C0.DC1); + this._xoffSentToCatchUp = false; + } + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; + this._inputHandler.parseUtf8(data); + this.updateRange(this.buffer.y); + this.refresh(this._refreshStart, this._refreshEnd); + if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { + break; + } + } + if (this.writeBufferUtf8.length > bufferOffset) { + if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) { + this.writeBufferUtf8 = this.writeBufferUtf8.slice(bufferOffset); + bufferOffset = 0; + } + setTimeout(function () { return _this._innerWriteUtf8(bufferOffset); }, 0); + } + else { + this._writeInProgress = false; + this.writeBufferUtf8 = []; + } + }; + Terminal.prototype.write = function (data) { + var _this = this; + if (this._isDisposed) { + return; + } + if (!data) { + return; + } + this.writeBuffer.push(data); + if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) { + this.handler(EscapeSequences_1.C0.DC3); + this._xoffSentToCatchUp = true; + } + if (!this._writeInProgress && this.writeBuffer.length > 0) { + this._writeInProgress = true; + setTimeout(function () { + _this._innerWrite(); + }); + } + }; + Terminal.prototype._innerWrite = function (bufferOffset) { + var _this = this; + if (bufferOffset === void 0) { bufferOffset = 0; } + if (this._isDisposed) { + this.writeBuffer = []; + } + var startTime = Date.now(); + while (this.writeBuffer.length > bufferOffset) { + var data = this.writeBuffer[bufferOffset]; + bufferOffset++; + if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) { + this.handler(EscapeSequences_1.C0.DC1); + this._xoffSentToCatchUp = false; + } + this._refreshStart = this.buffer.y; + this._refreshEnd = this.buffer.y; + this._inputHandler.parse(data); + this.updateRange(this.buffer.y); + this.refresh(this._refreshStart, this._refreshEnd); + if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { + break; + } + } + if (this.writeBuffer.length > bufferOffset) { + if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) { + this.writeBuffer = this.writeBuffer.slice(bufferOffset); + bufferOffset = 0; + } + setTimeout(function () { return _this._innerWrite(bufferOffset); }, 0); + } + else { + this._writeInProgress = false; + this.writeBuffer = []; + } + }; + Terminal.prototype.writeln = function (data) { + this.write(data + '\r\n'); + }; + Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { + this._customKeyEventHandler = customKeyEventHandler; + }; + Terminal.prototype.addCsiHandler = function (flag, callback) { + return this._inputHandler.addCsiHandler(flag, callback); + }; + Terminal.prototype.addOscHandler = function (ident, callback) { + return this._inputHandler.addOscHandler(ident, callback); + }; + Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { + var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); + this.refresh(0, this.rows - 1); + return matcherId; + }; + Terminal.prototype.deregisterLinkMatcher = function (matcherId) { + if (this.linkifier.deregisterLinkMatcher(matcherId)) { + this.refresh(0, this.rows - 1); + } + }; + Terminal.prototype.registerCharacterJoiner = function (handler) { + var joinerId = this._renderCoordinator.registerCharacterJoiner(handler); + this.refresh(0, this.rows - 1); + return joinerId; + }; + Terminal.prototype.deregisterCharacterJoiner = function (joinerId) { + if (this._renderCoordinator.deregisterCharacterJoiner(joinerId)) { + this.refresh(0, this.rows - 1); + } + }; + Object.defineProperty(Terminal.prototype, "markers", { + get: function () { + return this.buffer.markers; + }, + enumerable: true, + configurable: true + }); + Terminal.prototype.addMarker = function (cursorYOffset) { + if (this.buffer !== this.buffers.normal) { + return; + } + return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); + }; + Terminal.prototype.hasSelection = function () { + return this.selectionManager ? this.selectionManager.hasSelection : false; + }; + Terminal.prototype.select = function (column, row, length) { + this.selectionManager.setSelection(column, row, length); + }; + Terminal.prototype.getSelection = function () { + return this.selectionManager ? this.selectionManager.selectionText : ''; + }; + Terminal.prototype.getSelectionPosition = function () { + if (!this.selectionManager.hasSelection) { + return undefined; + } + return { + startColumn: this.selectionManager.selectionStart[0], + startRow: this.selectionManager.selectionStart[1], + endColumn: this.selectionManager.selectionEnd[0], + endRow: this.selectionManager.selectionEnd[1] + }; + }; + Terminal.prototype.clearSelection = function () { + if (this.selectionManager) { + this.selectionManager.clearSelection(); + } + }; + Terminal.prototype.selectAll = function () { + if (this.selectionManager) { + this.selectionManager.selectAll(); + } + }; + Terminal.prototype.selectLines = function (start, end) { + if (this.selectionManager) { + this.selectionManager.selectLines(start, end); + } + }; + Terminal.prototype._keyDown = function (event) { + if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { + return false; + } + if (!this._compositionHelper.keydown(event)) { + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + return false; + } + var result = Keyboard_1.evaluateKeyboardEvent(event, this.applicationCursor, this.browser.isMac, this.options.macOptionIsMeta); + this.updateCursorStyle(event); + if (result.type === 3 || result.type === 2) { + var scrollCount = this.rows - 1; + this.scrollLines(result.type === 2 ? -scrollCount : scrollCount); + return this.cancel(event, true); + } + if (result.type === 1) { + this.selectAll(); + } + if (this._isThirdLevelShift(this.browser, event)) { + return true; + } + if (result.cancel) { + this.cancel(event, true); + } + if (!result.key) { + return true; + } + this.emit('keydown', event); + this._onKey.fire({ key: result.key, domEvent: event }); + this.showCursor(); + this.handler(result.key); + return this.cancel(event, true); + }; + Terminal.prototype._isThirdLevelShift = function (browser, ev) { + var thirdLevelKey = (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) || + (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey); + if (ev.type === 'keypress') { + return thirdLevelKey; + } + return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47); + }; + Terminal.prototype.setgLevel = function (g) { + this.glevel = g; + this.charset = this.charsets[g]; + }; + Terminal.prototype.setgCharset = function (g, charset) { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } + }; + Terminal.prototype._keyUp = function (ev) { + this.updateCursorStyle(ev); + }; + Terminal.prototype._keyPress = function (ev) { + var key; + if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { + return false; + } + this.cancel(ev); + if (ev.charCode) { + key = ev.charCode; + } + else if (ev.which === null || ev.which === undefined) { + key = ev.keyCode; + } + else if (ev.which !== 0 && ev.charCode !== 0) { + key = ev.which; + } + else { + return false; + } + if (!key || ((ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev))) { + return false; + } + key = String.fromCharCode(key); + this.emit('keypress', key, ev); + this._onKey.fire({ key: key, domEvent: ev }); + this.showCursor(); + this.handler(key); + return true; + }; + Terminal.prototype.bell = function () { + var _this = this; + this.emit('bell'); + if (this._soundBell()) { + this.soundManager.playBellSound(); + } + if (this._visualBell()) { + this.element.classList.add('visual-bell-active'); + clearTimeout(this._visualBellTimer); + this._visualBellTimer = window.setTimeout(function () { + _this.element.classList.remove('visual-bell-active'); + }, 200); + } + }; + Terminal.prototype.log = function (text, data) { + if (!this.options.debug) + return; + if (!this._context.console || !this._context.console.log) + return; + this._context.console.log(text, data); + }; + Terminal.prototype.error = function (text, data) { + if (!this.options.debug) + return; + if (!this._context.console || !this._context.console.error) + return; + this._context.console.error(text, data); + }; + Terminal.prototype.resize = function (x, y) { + if (isNaN(x) || isNaN(y)) { + return; + } + if (x === this.cols && y === this.rows) { + if (this.charMeasure && (!this.charMeasure.width || !this.charMeasure.height)) { + this.charMeasure.measure(this.options); + } + return; + } + if (x < MINIMUM_COLS) + x = MINIMUM_COLS; + if (y < MINIMUM_ROWS) + y = MINIMUM_ROWS; + this.buffers.resize(x, y); + this.cols = x; + this.rows = y; + this.buffers.setupTabStops(this.cols); + if (this.charMeasure) { + this.charMeasure.measure(this.options); + } + this.refresh(0, this.rows - 1); + this._onResize.fire({ cols: x, rows: y }); + }; + Terminal.prototype.updateRange = function (y) { + if (y < this._refreshStart) + this._refreshStart = y; + if (y > this._refreshEnd) + this._refreshEnd = y; + }; + Terminal.prototype.maxRange = function () { + this._refreshStart = 0; + this._refreshEnd = this.rows - 1; + }; + Terminal.prototype.clear = function () { + if (this.buffer.ybase === 0 && this.buffer.y === 0) { + return; + } + this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); + this.buffer.lines.length = 1; + this.buffer.ydisp = 0; + this.buffer.ybase = 0; + this.buffer.y = 0; + for (var i = 1; i < this.rows; i++) { + this.buffer.lines.push(this.buffer.getBlankLine(BufferLine_1.DEFAULT_ATTR_DATA)); + } + this.refresh(0, this.rows - 1); + this._onScroll.fire(this.buffer.ydisp); + }; + Terminal.prototype.is = function (term) { + return (this.options.termName + '').indexOf(term) === 0; + }; + Terminal.prototype.handler = function (data) { + if (this.options.disableStdin) { + return; + } + if (this.selectionManager && this.selectionManager.hasSelection) { + this.selectionManager.clearSelection(); + } + if (this.buffer.ybase !== this.buffer.ydisp) { + this.scrollToBottom(); + } + this._onData.fire(data); + }; + Terminal.prototype.handleTitle = function (title) { + this._onTitleChange.fire(title); + }; + Terminal.prototype.index = function () { + this.buffer.y++; + if (this.buffer.y > this.buffer.scrollBottom) { + this.buffer.y--; + this.scroll(); + } + if (this.buffer.x >= this.cols) { + this.buffer.x--; + } + }; + Terminal.prototype.reverseIndex = function () { + if (this.buffer.y === this.buffer.scrollTop) { + var scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; + this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttrData())); + this.updateRange(this.buffer.scrollTop); + this.updateRange(this.buffer.scrollBottom); + } + else { + this.buffer.y--; + } + }; + Terminal.prototype.reset = function () { + this.options.rows = this.rows; + this.options.cols = this.cols; + var customKeyEventHandler = this._customKeyEventHandler; + var inputHandler = this._inputHandler; + var cursorState = this.cursorState; + var writeBuffer = this.writeBuffer; + var writeBufferUtf8 = this.writeBufferUtf8; + var writeInProgress = this._writeInProgress; + var xoffSentToCatchUp = this._xoffSentToCatchUp; + var userScrolling = this._userScrolling; + this._setup(); + this._customKeyEventHandler = customKeyEventHandler; + this._inputHandler = inputHandler; + this.cursorState = cursorState; + this.writeBuffer = writeBuffer; + this.writeBufferUtf8 = writeBufferUtf8; + this._writeInProgress = writeInProgress; + this._xoffSentToCatchUp = xoffSentToCatchUp; + this._userScrolling = userScrolling; + this.refresh(0, this.rows - 1); + if (this.viewport) { + this.viewport.syncScrollArea(); + } + }; + Terminal.prototype.tabSet = function () { + this.buffer.tabs[this.buffer.x] = true; + }; + Terminal.prototype.cancel = function (ev, force) { + if (!this.options.cancelEvents && !force) { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + return false; + }; + Terminal.prototype._visualBell = function () { + return false; + }; + Terminal.prototype._soundBell = function () { + return this.options.bellStyle === 'sound'; + }; + return Terminal; +}(EventEmitter_1.EventEmitter)); +exports.Terminal = Terminal; +function wasModifierKeyOnlyEvent(ev) { + return ev.keyCode === 16 || + ev.keyCode === 17 || + ev.keyCode === 18; +} + +},{"./AccessibilityManager":1,"./Buffer":2,"./BufferSet":3,"./CharMeasure":4,"./Clipboard":6,"./CompositionHelper":7,"./InputHandler":9,"./Linkifier":10,"./MouseHelper":11,"./MouseZoneManager":12,"./SelectionManager":13,"./SoundManager":15,"./Strings":16,"./Viewport":18,"./WindowsMode":19,"./common/Clone":21,"./common/EventEmitter":22,"./common/EventEmitter2":23,"./common/Platform":25,"./common/data/EscapeSequences":28,"./core/buffer/BufferLine":29,"./core/input/Keyboard":33,"./renderer/RenderCoordinator":43,"./renderer/Renderer":44,"./renderer/atlas/CharAtlasCache":48,"./renderer/dom/DomRenderer":56,"./ui/ColorManager":58,"./ui/Lifecycle":59}],18:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./common/Lifecycle"); +var Lifecycle_2 = require("./ui/Lifecycle"); +var FALLBACK_SCROLL_BAR_WIDTH = 15; +var Viewport = (function (_super) { + __extends(Viewport, _super); + function Viewport(_terminal, _viewportElement, _scrollArea, _charMeasure, _dimensions) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._viewportElement = _viewportElement; + _this._scrollArea = _scrollArea; + _this._charMeasure = _charMeasure; + _this._dimensions = _dimensions; + _this.scrollBarWidth = 0; + _this._currentRowHeight = 0; + _this._lastRecordedBufferLength = 0; + _this._lastRecordedViewportHeight = 0; + _this._lastRecordedBufferHeight = 0; + _this._lastScrollTop = 0; + _this._wheelPartialScroll = 0; + _this._refreshAnimationFrame = null; + _this._ignoreNextScrollEvent = false; + _this.scrollBarWidth = (_this._viewportElement.offsetWidth - _this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; + _this.register(Lifecycle_2.addDisposableDomListener(_this._viewportElement, 'scroll', _this._onScroll.bind(_this))); + setTimeout(function () { return _this.syncScrollArea(); }, 0); + return _this; + } + Viewport.prototype.onDimensionsChance = function (dimensions) { + this._dimensions = dimensions; + }; + Viewport.prototype.onThemeChange = function (colors) { + this._viewportElement.style.backgroundColor = colors.background.css; + }; + Viewport.prototype._refresh = function () { + var _this = this; + if (this._refreshAnimationFrame === null) { + this._refreshAnimationFrame = requestAnimationFrame(function () { return _this._innerRefresh(); }); + } + }; + Viewport.prototype._innerRefresh = function () { + if (this._charMeasure.height > 0) { + this._currentRowHeight = this._dimensions.scaledCellHeight / window.devicePixelRatio; + this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; + var newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._dimensions.canvasHeight); + if (this._lastRecordedBufferHeight !== newBufferHeight) { + this._lastRecordedBufferHeight = newBufferHeight; + this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px'; + } + } + var scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight; + if (this._viewportElement.scrollTop !== scrollTop) { + this._ignoreNextScrollEvent = true; + this._viewportElement.scrollTop = scrollTop; + } + this._refreshAnimationFrame = null; + }; + Viewport.prototype.syncScrollArea = function () { + if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) { + this._lastRecordedBufferLength = this._terminal.buffer.lines.length; + this._refresh(); + return; + } + if (this._lastRecordedViewportHeight !== this._dimensions.canvasHeight) { + this._refresh(); + return; + } + var newScrollTop = this._terminal.buffer.ydisp * this._currentRowHeight; + if (this._lastScrollTop !== newScrollTop) { + this._refresh(); + return; + } + if (this._lastScrollTop !== this._viewportElement.scrollTop) { + this._refresh(); + return; + } + if (this._dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) { + this._refresh(); + return; + } + }; + Viewport.prototype._onScroll = function (ev) { + this._lastScrollTop = this._viewportElement.scrollTop; + if (!this._viewportElement.offsetParent) { + return; + } + if (this._ignoreNextScrollEvent) { + this._ignoreNextScrollEvent = false; + return; + } + var newRow = Math.round(this._lastScrollTop / this._currentRowHeight); + var diff = newRow - this._terminal.buffer.ydisp; + this._terminal.scrollLines(diff, true); + }; + Viewport.prototype.onWheel = function (ev) { + var amount = this._getPixelsScrolled(ev); + if (amount === 0) { + return; + } + this._viewportElement.scrollTop += amount; + ev.preventDefault(); + }; + Viewport.prototype._getPixelsScrolled = function (ev) { + if (ev.deltaY === 0) { + return 0; + } + var amount = ev.deltaY; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + amount *= this._currentRowHeight; + } + else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + amount *= this._currentRowHeight * this._terminal.rows; + } + return amount; + }; + Viewport.prototype.getLinesScrolled = function (ev) { + if (ev.deltaY === 0) { + return 0; + } + var amount = ev.deltaY; + if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + amount /= this._currentRowHeight + 0.0; + this._wheelPartialScroll += amount; + amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1); + this._wheelPartialScroll %= 1; + } + else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + amount *= this._terminal.rows; + } + return amount; + }; + Viewport.prototype.onTouchStart = function (ev) { + this._lastTouchY = ev.touches[0].pageY; + }; + Viewport.prototype.onTouchMove = function (ev) { + var deltaY = this._lastTouchY - ev.touches[0].pageY; + this._lastTouchY = ev.touches[0].pageY; + if (deltaY === 0) { + return; + } + this._viewportElement.scrollTop += deltaY; + ev.preventDefault(); + }; + return Viewport; +}(Lifecycle_1.Disposable)); +exports.Viewport = Viewport; + +},{"./common/Lifecycle":24,"./ui/Lifecycle":59}],19:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var BufferLine_1 = require("./core/buffer/BufferLine"); +function applyWindowsMode(terminal) { + return terminal.onLineFeed(function () { + var line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1); + var lastChar = line.get(terminal.cols - 1); + var nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y); + nextLine.isWrapped = (lastChar[BufferLine_1.CHAR_DATA_CODE_INDEX] !== BufferLine_1.NULL_CELL_CODE && lastChar[BufferLine_1.CHAR_DATA_CODE_INDEX] !== BufferLine_1.WHITESPACE_CELL_CODE); + }); +} +exports.applyWindowsMode = applyWindowsMode; + +},{"./core/buffer/BufferLine":29}],20:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter2_1 = require("./EventEmitter2"); +var CircularList = (function () { + function CircularList(_maxLength) { + this._maxLength = _maxLength; + this.onDeleteEmitter = new EventEmitter2_1.EventEmitter2(); + this.onInsertEmitter = new EventEmitter2_1.EventEmitter2(); + this.onTrimEmitter = new EventEmitter2_1.EventEmitter2(); + this._array = new Array(this._maxLength); + this._startIndex = 0; + this._length = 0; + } + Object.defineProperty(CircularList.prototype, "onDelete", { + get: function () { return this.onDeleteEmitter.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "onInsert", { + get: function () { return this.onInsertEmitter.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "onTrim", { + get: function () { return this.onTrimEmitter.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "maxLength", { + get: function () { + return this._maxLength; + }, + set: function (newMaxLength) { + if (this._maxLength === newMaxLength) { + return; + } + var newArray = new Array(newMaxLength); + for (var i = 0; i < Math.min(newMaxLength, this.length); i++) { + newArray[i] = this._array[this._getCyclicIndex(i)]; + } + this._array = newArray; + this._maxLength = newMaxLength; + this._startIndex = 0; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(CircularList.prototype, "length", { + get: function () { + return this._length; + }, + set: function (newLength) { + if (newLength > this._length) { + for (var i = this._length; i < newLength; i++) { + this._array[i] = undefined; + } + } + this._length = newLength; + }, + enumerable: true, + configurable: true + }); + CircularList.prototype.get = function (index) { + return this._array[this._getCyclicIndex(index)]; + }; + CircularList.prototype.set = function (index, value) { + this._array[this._getCyclicIndex(index)] = value; + }; + CircularList.prototype.push = function (value) { + this._array[this._getCyclicIndex(this._length)] = value; + if (this._length === this._maxLength) { + this._startIndex = ++this._startIndex % this._maxLength; + this.onTrimEmitter.fire(1); + } + else { + this._length++; + } + }; + CircularList.prototype.recycle = function () { + if (this._length !== this._maxLength) { + throw new Error('Can only recycle when the buffer is full'); + } + this._startIndex = ++this._startIndex % this._maxLength; + this.onTrimEmitter.fire(1); + return this._array[this._getCyclicIndex(this._length - 1)]; + }; + Object.defineProperty(CircularList.prototype, "isFull", { + get: function () { + return this._length === this._maxLength; + }, + enumerable: true, + configurable: true + }); + CircularList.prototype.pop = function () { + return this._array[this._getCyclicIndex(this._length-- - 1)]; + }; + CircularList.prototype.splice = function (start, deleteCount) { + var items = []; + for (var _i = 2; _i < arguments.length; _i++) { + items[_i - 2] = arguments[_i]; + } + if (deleteCount) { + for (var i = start; i < this._length - deleteCount; i++) { + this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; + } + this._length -= deleteCount; + } + for (var i = this._length - 1; i >= start; i--) { + this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)]; + } + for (var i = 0; i < items.length; i++) { + this._array[this._getCyclicIndex(start + i)] = items[i]; + } + if (this._length + items.length > this._maxLength) { + var countToTrim = (this._length + items.length) - this._maxLength; + this._startIndex += countToTrim; + this._length = this._maxLength; + this.onTrimEmitter.fire(countToTrim); + } + else { + this._length += items.length; + } + }; + CircularList.prototype.trimStart = function (count) { + if (count > this._length) { + count = this._length; + } + this._startIndex += count; + this._length -= count; + this.onTrimEmitter.fire(count); + }; + CircularList.prototype.shiftElements = function (start, count, offset) { + if (count <= 0) { + return; + } + if (start < 0 || start >= this._length) { + throw new Error('start argument out of range'); + } + if (start + offset < 0) { + throw new Error('Cannot shift elements in list beyond index 0'); + } + if (offset > 0) { + for (var i = count - 1; i >= 0; i--) { + this.set(start + i + offset, this.get(start + i)); + } + var expandListBy = (start + count + offset) - this._length; + if (expandListBy > 0) { + this._length += expandListBy; + while (this._length > this._maxLength) { + this._length--; + this._startIndex++; + this.onTrimEmitter.fire(1); + } + } + } + else { + for (var i = 0; i < count; i++) { + this.set(start + i + offset, this.get(start + i)); + } + } + }; + CircularList.prototype._getCyclicIndex = function (index) { + return (this._startIndex + index) % this._maxLength; + }; + return CircularList; +}()); +exports.CircularList = CircularList; + +},{"./EventEmitter2":23}],21:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function clone(val, depth) { + if (depth === void 0) { depth = 5; } + if (typeof val !== 'object') { + return val; + } + if (val === null) { + return null; + } + var clonedObject = Array.isArray(val) ? [] : {}; + for (var key in val) { + clonedObject[key] = depth <= 1 ? val[key] : clone(val[key], depth - 1); + } + return clonedObject; +} +exports.clone = clone; + +},{}],22:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("./Lifecycle"); +var EventEmitter = (function (_super) { + __extends(EventEmitter, _super); + function EventEmitter() { + var _this = _super.call(this) || this; + _this._events = _this._events || {}; + return _this; + } + EventEmitter.prototype.on = function (type, listener) { + this._events[type] = this._events[type] || []; + this._events[type].push(listener); + }; + EventEmitter.prototype.addDisposableListener = function (type, handler) { + var _this = this; + this.on(type, handler); + var disposed = false; + return { + dispose: function () { + if (disposed) { + return; + } + _this.off(type, handler); + disposed = true; + } + }; + }; + EventEmitter.prototype.off = function (type, listener) { + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + var i = obj.length; + while (i--) { + if (obj[i] === listener) { + obj.splice(i, 1); + return; + } + } + }; + EventEmitter.prototype.removeAllListeners = function (type) { + if (this._events[type]) { + delete this._events[type]; + } + }; + EventEmitter.prototype.emit = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + for (var i = 0; i < obj.length; i++) { + obj[i].apply(this, args); + } + }; + EventEmitter.prototype.emitMayRemoveListeners = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + if (!this._events[type]) { + return; + } + var obj = this._events[type]; + var length = obj.length; + for (var i = 0; i < obj.length; i++) { + obj[i].apply(this, args); + i -= length - obj.length; + length = obj.length; + } + }; + EventEmitter.prototype.listeners = function (type) { + return this._events[type] || []; + }; + EventEmitter.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._events = {}; + }; + return EventEmitter; +}(Lifecycle_1.Disposable)); +exports.EventEmitter = EventEmitter; + +},{"./Lifecycle":24}],23:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter2 = (function () { + function EventEmitter2() { + this._listeners = []; + } + Object.defineProperty(EventEmitter2.prototype, "event", { + get: function () { + var _this = this; + if (!this._event) { + this._event = function (listener) { + _this._listeners.push(listener); + var disposable = { + dispose: function () { + for (var i = 0; i < _this._listeners.length; i++) { + if (_this._listeners[i] === listener) { + _this._listeners.splice(i, 1); + return; + } + } + } + }; + return disposable; + }; + } + return this._event; + }, + enumerable: true, + configurable: true + }); + EventEmitter2.prototype.fire = function (data) { + var queue = []; + for (var i = 0; i < this._listeners.length; i++) { + queue.push(this._listeners[i]); + } + for (var i = 0; i < queue.length; i++) { + queue[i].call(undefined, data); + } + }; + return EventEmitter2; +}()); +exports.EventEmitter2 = EventEmitter2; + +},{}],24:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Disposable = (function () { + function Disposable() { + this._disposables = []; + this._isDisposed = false; + } + Disposable.prototype.dispose = function () { + this._isDisposed = true; + this._disposables.forEach(function (d) { return d.dispose(); }); + this._disposables.length = 0; + }; + Disposable.prototype.register = function (d) { + this._disposables.push(d); + }; + Disposable.prototype.unregister = function (d) { + var index = this._disposables.indexOf(d); + if (index !== -1) { + this._disposables.splice(index, 1); + } + }; + return Disposable; +}()); +exports.Disposable = Disposable; + +},{}],25:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var isNode = (typeof navigator === 'undefined') ? true : false; +var userAgent = (isNode) ? 'node' : navigator.userAgent; +var platform = (isNode) ? 'node' : navigator.platform; +exports.isFirefox = !!~userAgent.indexOf('Firefox'); +exports.isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); +exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident'); +exports.isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform); +exports.isIpad = platform === 'iPad'; +exports.isIphone = platform === 'iPhone'; +exports.isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform); +exports.isLinux = platform.indexOf('Linux') >= 0; +function contains(arr, el) { + return arr.indexOf(el) >= 0; +} + +},{}],26:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function fill(array, value, start, end) { + if (array.fill) { + return array.fill(value, start, end); + } + return fillFallback(array, value, start, end); +} +exports.fill = fill; +function fillFallback(array, value, start, end) { + if (start === void 0) { start = 0; } + if (end === void 0) { end = array.length; } + if (start >= array.length) { + return array; + } + start = (array.length + start) % array.length; + if (end >= array.length) { + end = array.length; + } + else { + end = (array.length + end) % array.length; + } + for (var i = start; i < end; ++i) { + array[i] = value; + } + return array; +} +exports.fillFallback = fillFallback; +function concat(a, b) { + var result = new a.constructor(a.length + b.length); + result.set(a); + result.set(b, a.length); + return result; +} +exports.concat = concat; + +},{}],27:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DEFAULT_COLOR = 256; + +},{}],28:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var C0; +(function (C0) { + C0.NUL = '\x00'; + C0.SOH = '\x01'; + C0.STX = '\x02'; + C0.ETX = '\x03'; + C0.EOT = '\x04'; + C0.ENQ = '\x05'; + C0.ACK = '\x06'; + C0.BEL = '\x07'; + C0.BS = '\x08'; + C0.HT = '\x09'; + C0.LF = '\x0a'; + C0.VT = '\x0b'; + C0.FF = '\x0c'; + C0.CR = '\x0d'; + C0.SO = '\x0e'; + C0.SI = '\x0f'; + C0.DLE = '\x10'; + C0.DC1 = '\x11'; + C0.DC2 = '\x12'; + C0.DC3 = '\x13'; + C0.DC4 = '\x14'; + C0.NAK = '\x15'; + C0.SYN = '\x16'; + C0.ETB = '\x17'; + C0.CAN = '\x18'; + C0.EM = '\x19'; + C0.SUB = '\x1a'; + C0.ESC = '\x1b'; + C0.FS = '\x1c'; + C0.GS = '\x1d'; + C0.RS = '\x1e'; + C0.US = '\x1f'; + C0.SP = '\x20'; + C0.DEL = '\x7f'; +})(C0 = exports.C0 || (exports.C0 = {})); +var C1; +(function (C1) { + C1.PAD = '\x80'; + C1.HOP = '\x81'; + C1.BPH = '\x82'; + C1.NBH = '\x83'; + C1.IND = '\x84'; + C1.NEL = '\x85'; + C1.SSA = '\x86'; + C1.ESA = '\x87'; + C1.HTS = '\x88'; + C1.HTJ = '\x89'; + C1.VTS = '\x8a'; + C1.PLD = '\x8b'; + C1.PLU = '\x8c'; + C1.RI = '\x8d'; + C1.SS2 = '\x8e'; + C1.SS3 = '\x8f'; + C1.DCS = '\x90'; + C1.PU1 = '\x91'; + C1.PU2 = '\x92'; + C1.STS = '\x93'; + C1.CCH = '\x94'; + C1.MW = '\x95'; + C1.SPA = '\x96'; + C1.EPA = '\x97'; + C1.SOS = '\x98'; + C1.SGCI = '\x99'; + C1.SCI = '\x9a'; + C1.CSI = '\x9b'; + C1.ST = '\x9c'; + C1.OSC = '\x9d'; + C1.PM = '\x9e'; + C1.APC = '\x9f'; +})(C1 = exports.C1 || (exports.C1 = {})); + +},{}],29:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var TextDecoder_1 = require("../input/TextDecoder"); +var Types_1 = require("../../common/Types"); +exports.DEFAULT_ATTR = (0 << 18) | (Types_1.DEFAULT_COLOR << 9) | (256 << 0); +exports.CHAR_DATA_ATTR_INDEX = 0; +exports.CHAR_DATA_CHAR_INDEX = 1; +exports.CHAR_DATA_WIDTH_INDEX = 2; +exports.CHAR_DATA_CODE_INDEX = 3; +exports.NULL_CELL_CHAR = ''; +exports.NULL_CELL_WIDTH = 1; +exports.NULL_CELL_CODE = 0; +exports.WHITESPACE_CELL_CHAR = ' '; +exports.WHITESPACE_CELL_WIDTH = 1; +exports.WHITESPACE_CELL_CODE = 32; +var CELL_SIZE = 3; +var AttributeData = (function () { + function AttributeData() { + this.fg = 0; + this.bg = 0; + } + AttributeData.toColorRGB = function (value) { + return [ + value >>> 16 & 255, + value >>> 8 & 255, + value & 255 + ]; + }; + AttributeData.fromColorRGB = function (value) { + return (value[0] & 255) << 16 | (value[1] & 255) << 8 | value[2] & 255; + }; + AttributeData.prototype.clone = function () { + var newObj = new AttributeData(); + newObj.fg = this.fg; + newObj.bg = this.bg; + return newObj; + }; + AttributeData.prototype.isInverse = function () { return this.fg & 67108864; }; + AttributeData.prototype.isBold = function () { return this.fg & 134217728; }; + AttributeData.prototype.isUnderline = function () { return this.fg & 268435456; }; + AttributeData.prototype.isBlink = function () { return this.fg & 536870912; }; + AttributeData.prototype.isInvisible = function () { return this.fg & 1073741824; }; + AttributeData.prototype.isItalic = function () { return this.bg & 67108864; }; + AttributeData.prototype.isDim = function () { return this.bg & 134217728; }; + AttributeData.prototype.getFgColorMode = function () { return this.fg & 50331648; }; + AttributeData.prototype.getBgColorMode = function () { return this.bg & 50331648; }; + AttributeData.prototype.isFgRGB = function () { return (this.fg & 50331648) === 50331648; }; + AttributeData.prototype.isBgRGB = function () { return (this.bg & 50331648) === 50331648; }; + AttributeData.prototype.isFgPalette = function () { return (this.fg & 50331648) === 16777216 || (this.fg & 50331648) === 33554432; }; + AttributeData.prototype.isBgPalette = function () { return (this.bg & 50331648) === 16777216 || (this.bg & 50331648) === 33554432; }; + AttributeData.prototype.isFgDefault = function () { return (this.fg & 50331648) === 0; }; + AttributeData.prototype.isBgDefault = function () { return (this.bg & 50331648) === 0; }; + AttributeData.prototype.getFgColor = function () { + switch (this.fg & 50331648) { + case 16777216: + case 33554432: return this.fg & 255; + case 50331648: return this.fg & 16777215; + default: return -1; + } + }; + AttributeData.prototype.getBgColor = function () { + switch (this.bg & 50331648) { + case 16777216: + case 33554432: return this.bg & 255; + case 50331648: return this.bg & 16777215; + default: return -1; + } + }; + return AttributeData; +}()); +exports.AttributeData = AttributeData; +exports.DEFAULT_ATTR_DATA = Object.freeze(new AttributeData()); +var CellData = (function (_super) { + __extends(CellData, _super); + function CellData() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.content = 0; + _this.fg = 0; + _this.bg = 0; + _this.combinedData = ''; + return _this; + } + CellData.fromCharData = function (value) { + var obj = new CellData(); + obj.setFromCharData(value); + return obj; + }; + CellData.prototype.isCombined = function () { + return this.content & 2097152; + }; + CellData.prototype.getWidth = function () { + return this.content >> 22; + }; + CellData.prototype.getChars = function () { + if (this.content & 2097152) { + return this.combinedData; + } + if (this.content & 2097151) { + return TextDecoder_1.stringFromCodePoint(this.content & 2097151); + } + return ''; + }; + CellData.prototype.getCode = function () { + return (this.isCombined()) + ? this.combinedData.charCodeAt(this.combinedData.length - 1) + : this.content & 2097151; + }; + CellData.prototype.setFromCharData = function (value) { + this.fg = value[exports.CHAR_DATA_ATTR_INDEX]; + this.bg = 0; + var combined = false; + if (value[exports.CHAR_DATA_CHAR_INDEX].length > 2) { + combined = true; + } + else if (value[exports.CHAR_DATA_CHAR_INDEX].length === 2) { + var code = value[exports.CHAR_DATA_CHAR_INDEX].charCodeAt(0); + if (0xD800 <= code && code <= 0xDBFF) { + var second = value[exports.CHAR_DATA_CHAR_INDEX].charCodeAt(1); + if (0xDC00 <= second && second <= 0xDFFF) { + this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[exports.CHAR_DATA_WIDTH_INDEX] << 22); + } + else { + combined = true; + } + } + else { + combined = true; + } + } + else { + this.content = value[exports.CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[exports.CHAR_DATA_WIDTH_INDEX] << 22); + } + if (combined) { + this.combinedData = value[exports.CHAR_DATA_CHAR_INDEX]; + this.content = 2097152 | (value[exports.CHAR_DATA_WIDTH_INDEX] << 22); + } + }; + CellData.prototype.getAsCharData = function () { + return [this.fg, this.getChars(), this.getWidth(), this.getCode()]; + }; + return CellData; +}(AttributeData)); +exports.CellData = CellData; +var BufferLine = (function () { + function BufferLine(cols, fillCellData, isWrapped) { + if (isWrapped === void 0) { isWrapped = false; } + this.isWrapped = isWrapped; + this._combined = {}; + this._data = new Uint32Array(cols * CELL_SIZE); + var cell = fillCellData || CellData.fromCharData([0, exports.NULL_CELL_CHAR, exports.NULL_CELL_WIDTH, exports.NULL_CELL_CODE]); + for (var i = 0; i < cols; ++i) { + this.setCell(i, cell); + } + this.length = cols; + } + BufferLine.prototype.get = function (index) { + var content = this._data[index * CELL_SIZE + 0]; + var cp = content & 2097151; + return [ + this._data[index * CELL_SIZE + 1], + (content & 2097152) + ? this._combined[index] + : (cp) ? TextDecoder_1.stringFromCodePoint(cp) : '', + content >> 22, + (content & 2097152) + ? this._combined[index].charCodeAt(this._combined[index].length - 1) + : cp + ]; + }; + BufferLine.prototype.set = function (index, value) { + this._data[index * CELL_SIZE + 1] = value[exports.CHAR_DATA_ATTR_INDEX]; + if (value[exports.CHAR_DATA_CHAR_INDEX].length > 1) { + this._combined[index] = value[1]; + this._data[index * CELL_SIZE + 0] = index | 2097152 | (value[exports.CHAR_DATA_WIDTH_INDEX] << 22); + } + else { + this._data[index * CELL_SIZE + 0] = value[exports.CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[exports.CHAR_DATA_WIDTH_INDEX] << 22); + } + }; + BufferLine.prototype.getWidth = function (index) { + return this._data[index * CELL_SIZE + 0] >> 22; + }; + BufferLine.prototype.hasWidth = function (index) { + return this._data[index * CELL_SIZE + 0] & 12582912; + }; + BufferLine.prototype.getFg = function (index) { + return this._data[index * CELL_SIZE + 1]; + }; + BufferLine.prototype.getBg = function (index) { + return this._data[index * CELL_SIZE + 2]; + }; + BufferLine.prototype.hasContent = function (index) { + return this._data[index * CELL_SIZE + 0] & 4194303; + }; + BufferLine.prototype.getCodePoint = function (index) { + var content = this._data[index * CELL_SIZE + 0]; + if (content & 2097152) { + return this._combined[index].charCodeAt(this._combined[index].length - 1); + } + return content & 2097151; + }; + BufferLine.prototype.isCombined = function (index) { + return this._data[index * CELL_SIZE + 0] & 2097152; + }; + BufferLine.prototype.getString = function (index) { + var content = this._data[index * CELL_SIZE + 0]; + if (content & 2097152) { + return this._combined[index]; + } + if (content & 2097151) { + return TextDecoder_1.stringFromCodePoint(content & 2097151); + } + return ''; + }; + BufferLine.prototype.loadCell = function (index, cell) { + var startIndex = index * CELL_SIZE; + cell.content = this._data[startIndex + 0]; + cell.fg = this._data[startIndex + 1]; + cell.bg = this._data[startIndex + 2]; + if (cell.content & 2097152) { + cell.combinedData = this._combined[index]; + } + return cell; + }; + BufferLine.prototype.setCell = function (index, cell) { + if (cell.content & 2097152) { + this._combined[index] = cell.combinedData; + } + this._data[index * CELL_SIZE + 0] = cell.content; + this._data[index * CELL_SIZE + 1] = cell.fg; + this._data[index * CELL_SIZE + 2] = cell.bg; + }; + BufferLine.prototype.setCellFromCodePoint = function (index, codePoint, width, fg, bg) { + this._data[index * CELL_SIZE + 0] = codePoint | (width << 22); + this._data[index * CELL_SIZE + 1] = fg; + this._data[index * CELL_SIZE + 2] = bg; + }; + BufferLine.prototype.addCodepointToCell = function (index, codePoint) { + var content = this._data[index * CELL_SIZE + 0]; + if (content & 2097152) { + this._combined[index] += TextDecoder_1.stringFromCodePoint(codePoint); + } + else { + if (content & 2097151) { + this._combined[index] = TextDecoder_1.stringFromCodePoint(content & 2097151) + TextDecoder_1.stringFromCodePoint(codePoint); + content &= ~2097151; + content |= 2097152; + } + else { + content = codePoint | (1 << 22); + } + this._data[index * CELL_SIZE + 0] = content; + } + }; + BufferLine.prototype.insertCells = function (pos, n, fillCellData) { + pos %= this.length; + if (n < this.length - pos) { + var cell = new CellData(); + for (var i = this.length - pos - n - 1; i >= 0; --i) { + this.setCell(pos + n + i, this.loadCell(pos + i, cell)); + } + for (var i = 0; i < n; ++i) { + this.setCell(pos + i, fillCellData); + } + } + else { + for (var i = pos; i < this.length; ++i) { + this.setCell(i, fillCellData); + } + } + }; + BufferLine.prototype.deleteCells = function (pos, n, fillCellData) { + pos %= this.length; + if (n < this.length - pos) { + var cell = new CellData(); + for (var i = 0; i < this.length - pos - n; ++i) { + this.setCell(pos + i, this.loadCell(pos + n + i, cell)); + } + for (var i = this.length - n; i < this.length; ++i) { + this.setCell(i, fillCellData); + } + } + else { + for (var i = pos; i < this.length; ++i) { + this.setCell(i, fillCellData); + } + } + }; + BufferLine.prototype.replaceCells = function (start, end, fillCellData) { + while (start < end && start < this.length) { + this.setCell(start++, fillCellData); + } + }; + BufferLine.prototype.resize = function (cols, fillCellData) { + if (cols === this.length) { + return; + } + if (cols > this.length) { + var data = new Uint32Array(cols * CELL_SIZE); + if (this.length) { + if (cols * CELL_SIZE < this._data.length) { + data.set(this._data.subarray(0, cols * CELL_SIZE)); + } + else { + data.set(this._data); + } + } + this._data = data; + for (var i = this.length; i < cols; ++i) { + this.setCell(i, fillCellData); + } + } + else { + if (cols) { + var data = new Uint32Array(cols * CELL_SIZE); + data.set(this._data.subarray(0, cols * CELL_SIZE)); + this._data = data; + var keys = Object.keys(this._combined); + for (var i = 0; i < keys.length; i++) { + var key = parseInt(keys[i], 10); + if (key >= cols) { + delete this._combined[key]; + } + } + } + else { + this._data = new Uint32Array(0); + this._combined = {}; + } + } + this.length = cols; + }; + BufferLine.prototype.fill = function (fillCellData) { + this._combined = {}; + for (var i = 0; i < this.length; ++i) { + this.setCell(i, fillCellData); + } + }; + BufferLine.prototype.copyFrom = function (line) { + if (this.length !== line.length) { + this._data = new Uint32Array(line._data); + } + else { + this._data.set(line._data); + } + this.length = line.length; + this._combined = {}; + for (var el in line._combined) { + this._combined[el] = line._combined[el]; + } + this.isWrapped = line.isWrapped; + }; + BufferLine.prototype.clone = function () { + var newLine = new BufferLine(0); + newLine._data = new Uint32Array(this._data); + newLine.length = this.length; + for (var el in this._combined) { + newLine._combined[el] = this._combined[el]; + } + newLine.isWrapped = this.isWrapped; + return newLine; + }; + BufferLine.prototype.getTrimmedLength = function () { + for (var i = this.length - 1; i >= 0; --i) { + if ((this._data[i * CELL_SIZE + 0] & 4194303)) { + return i + (this._data[i * CELL_SIZE + 0] >> 22); + } + } + return 0; + }; + BufferLine.prototype.copyCellsFrom = function (src, srcCol, destCol, length, applyInReverse) { + var srcData = src._data; + if (applyInReverse) { + for (var cell = length - 1; cell >= 0; cell--) { + for (var i = 0; i < CELL_SIZE; i++) { + this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i]; + } + } + } + else { + for (var cell = 0; cell < length; cell++) { + for (var i = 0; i < CELL_SIZE; i++) { + this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i]; + } + } + } + var srcCombinedKeys = Object.keys(src._combined); + for (var i = 0; i < srcCombinedKeys.length; i++) { + var key = parseInt(srcCombinedKeys[i], 10); + if (key >= srcCol) { + this._combined[key - srcCol + destCol] = src._combined[key]; + } + } + }; + BufferLine.prototype.translateToString = function (trimRight, startCol, endCol) { + if (trimRight === void 0) { trimRight = false; } + if (startCol === void 0) { startCol = 0; } + if (endCol === void 0) { endCol = this.length; } + if (trimRight) { + endCol = Math.min(endCol, this.getTrimmedLength()); + } + var result = ''; + while (startCol < endCol) { + var content = this._data[startCol * CELL_SIZE + 0]; + var cp = content & 2097151; + result += (content & 2097152) ? this._combined[startCol] : (cp) ? TextDecoder_1.stringFromCodePoint(cp) : exports.WHITESPACE_CELL_CHAR; + startCol += (content >> 22) || 1; + } + return result; + }; + return BufferLine; +}()); +exports.BufferLine = BufferLine; + +},{"../../common/Types":27,"../input/TextDecoder":34}],30:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function reflowLargerGetLinesToRemove(lines, oldCols, newCols, bufferAbsoluteY, nullCell) { + var toRemove = []; + for (var y = 0; y < lines.length - 1; y++) { + var i = y; + var nextLine = lines.get(++i); + if (!nextLine.isWrapped) { + continue; + } + var wrappedLines = [lines.get(y)]; + while (i < lines.length && nextLine.isWrapped) { + wrappedLines.push(nextLine); + nextLine = lines.get(++i); + } + if (bufferAbsoluteY >= y && bufferAbsoluteY < i) { + y += wrappedLines.length - 1; + continue; + } + var destLineIndex = 0; + var destCol = getWrappedLineTrimmedLength(wrappedLines, destLineIndex, oldCols); + var srcLineIndex = 1; + var srcCol = 0; + while (srcLineIndex < wrappedLines.length) { + var srcTrimmedTineLength = getWrappedLineTrimmedLength(wrappedLines, srcLineIndex, oldCols); + var srcRemainingCells = srcTrimmedTineLength - srcCol; + var destRemainingCells = newCols - destCol; + var cellsToCopy = Math.min(srcRemainingCells, destRemainingCells); + wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol, destCol, cellsToCopy, false); + destCol += cellsToCopy; + if (destCol === newCols) { + destLineIndex++; + destCol = 0; + } + srcCol += cellsToCopy; + if (srcCol === srcTrimmedTineLength) { + srcLineIndex++; + srcCol = 0; + } + if (destCol === 0 && destLineIndex !== 0) { + if (wrappedLines[destLineIndex - 1].getWidth(newCols - 1) === 2) { + wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1, false); + wrappedLines[destLineIndex - 1].setCell(newCols - 1, nullCell); + } + } + } + wrappedLines[destLineIndex].replaceCells(destCol, newCols, nullCell); + var countToRemove = 0; + for (var i_1 = wrappedLines.length - 1; i_1 > 0; i_1--) { + if (i_1 > destLineIndex || wrappedLines[i_1].getTrimmedLength() === 0) { + countToRemove++; + } + else { + break; + } + } + if (countToRemove > 0) { + toRemove.push(y + wrappedLines.length - countToRemove); + toRemove.push(countToRemove); + } + y += wrappedLines.length - 1; + } + return toRemove; +} +exports.reflowLargerGetLinesToRemove = reflowLargerGetLinesToRemove; +function reflowLargerCreateNewLayout(lines, toRemove) { + var layout = []; + var nextToRemoveIndex = 0; + var nextToRemoveStart = toRemove[nextToRemoveIndex]; + var countRemovedSoFar = 0; + for (var i = 0; i < lines.length; i++) { + if (nextToRemoveStart === i) { + var countToRemove = toRemove[++nextToRemoveIndex]; + lines.onDeleteEmitter.fire({ + index: i - countRemovedSoFar, + amount: countToRemove + }); + i += countToRemove - 1; + countRemovedSoFar += countToRemove; + nextToRemoveStart = toRemove[++nextToRemoveIndex]; + } + else { + layout.push(i); + } + } + return { + layout: layout, + countRemoved: countRemovedSoFar + }; +} +exports.reflowLargerCreateNewLayout = reflowLargerCreateNewLayout; +function reflowLargerApplyNewLayout(lines, newLayout) { + var newLayoutLines = []; + for (var i = 0; i < newLayout.length; i++) { + newLayoutLines.push(lines.get(newLayout[i])); + } + for (var i = 0; i < newLayoutLines.length; i++) { + lines.set(i, newLayoutLines[i]); + } + lines.length = newLayout.length; +} +exports.reflowLargerApplyNewLayout = reflowLargerApplyNewLayout; +function reflowSmallerGetNewLineLengths(wrappedLines, oldCols, newCols) { + var newLineLengths = []; + var cellsNeeded = wrappedLines.map(function (l, i) { return getWrappedLineTrimmedLength(wrappedLines, i, oldCols); }).reduce(function (p, c) { return p + c; }); + var srcCol = 0; + var srcLine = 0; + var cellsAvailable = 0; + while (cellsAvailable < cellsNeeded) { + if (cellsNeeded - cellsAvailable < newCols) { + newLineLengths.push(cellsNeeded - cellsAvailable); + break; + } + srcCol += newCols; + var oldTrimmedLength = getWrappedLineTrimmedLength(wrappedLines, srcLine, oldCols); + if (srcCol > oldTrimmedLength) { + srcCol -= oldTrimmedLength; + srcLine++; + } + var endsWithWide = wrappedLines[srcLine].getWidth(srcCol - 1) === 2; + if (endsWithWide) { + srcCol--; + } + var lineLength = endsWithWide ? newCols - 1 : newCols; + newLineLengths.push(lineLength); + cellsAvailable += lineLength; + } + return newLineLengths; +} +exports.reflowSmallerGetNewLineLengths = reflowSmallerGetNewLineLengths; +function getWrappedLineTrimmedLength(lines, i, cols) { + if (i === lines.length - 1) { + return lines[i].getTrimmedLength(); + } + var endsInNull = !(lines[i].hasContent(cols - 1)) && lines[i].getWidth(cols - 1) === 1; + var followingLineStartsWithWide = lines[i + 1].getWidth(0) === 2; + if (endsInNull && followingLineStartsWithWide) { + return cols - 1; + } + return cols; +} +exports.getWrappedLineTrimmedLength = getWrappedLineTrimmedLength; + +},{}],31:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var EventEmitter2_1 = require("../../common/EventEmitter2"); +var Lifecycle_1 = require("../../common/Lifecycle"); +var Marker = (function (_super) { + __extends(Marker, _super); + function Marker(line) { + var _this = _super.call(this) || this; + _this.line = line; + _this._id = Marker._nextId++; + _this.isDisposed = false; + _this._onDispose = new EventEmitter2_1.EventEmitter2(); + return _this; + } + Object.defineProperty(Marker.prototype, "id", { + get: function () { return this._id; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Marker.prototype, "onDispose", { + get: function () { return this._onDispose.event; }, + enumerable: true, + configurable: true + }); + Marker.prototype.dispose = function () { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + this._onDispose.fire(); + }; + Marker._nextId = 1; + return Marker; +}(Lifecycle_1.Disposable)); +exports.Marker = Marker; + +},{"../../common/EventEmitter2":23,"../../common/Lifecycle":24}],32:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CHARSETS = {}; +exports.DEFAULT_CHARSET = exports.CHARSETS['B']; +exports.CHARSETS['0'] = { + '`': '\u25c6', + 'a': '\u2592', + 'b': '\u0009', + 'c': '\u000c', + 'd': '\u000d', + 'e': '\u000a', + 'f': '\u00b0', + 'g': '\u00b1', + 'h': '\u2424', + 'i': '\u000b', + 'j': '\u2518', + 'k': '\u2510', + 'l': '\u250c', + 'm': '\u2514', + 'n': '\u253c', + 'o': '\u23ba', + 'p': '\u23bb', + 'q': '\u2500', + 'r': '\u23bc', + 's': '\u23bd', + 't': '\u251c', + 'u': '\u2524', + 'v': '\u2534', + 'w': '\u252c', + 'x': '\u2502', + 'y': '\u2264', + 'z': '\u2265', + '{': '\u03c0', + '|': '\u2260', + '}': '\u00a3', + '~': '\u00b7' +}; +exports.CHARSETS['A'] = { + '#': '£' +}; +exports.CHARSETS['B'] = null; +exports.CHARSETS['4'] = { + '#': '£', + '@': '¾', + '[': 'ij', + '\\': '½', + ']': '|', + '{': '¨', + '|': 'f', + '}': '¼', + '~': '´' +}; +exports.CHARSETS['C'] = + exports.CHARSETS['5'] = { + '[': 'Ä', + '\\': 'Ö', + ']': 'Å', + '^': 'Ü', + '`': 'é', + '{': 'ä', + '|': 'ö', + '}': 'å', + '~': 'ü' + }; +exports.CHARSETS['R'] = { + '#': '£', + '@': 'à', + '[': '°', + '\\': 'ç', + ']': '§', + '{': 'é', + '|': 'ù', + '}': 'è', + '~': '¨' +}; +exports.CHARSETS['Q'] = { + '@': 'à', + '[': 'â', + '\\': 'ç', + ']': 'ê', + '^': 'î', + '`': 'ô', + '{': 'é', + '|': 'ù', + '}': 'è', + '~': 'û' +}; +exports.CHARSETS['K'] = { + '@': '§', + '[': 'Ä', + '\\': 'Ö', + ']': 'Ü', + '{': 'ä', + '|': 'ö', + '}': 'ü', + '~': 'ß' +}; +exports.CHARSETS['Y'] = { + '#': '£', + '@': '§', + '[': '°', + '\\': 'ç', + ']': 'é', + '`': 'ù', + '{': 'à', + '|': 'ò', + '}': 'è', + '~': 'ì' +}; +exports.CHARSETS['E'] = + exports.CHARSETS['6'] = { + '@': 'Ä', + '[': 'Æ', + '\\': 'Ø', + ']': 'Å', + '^': 'Ü', + '`': 'ä', + '{': 'æ', + '|': 'ø', + '}': 'å', + '~': 'ü' + }; +exports.CHARSETS['Z'] = { + '#': '£', + '@': '§', + '[': '¡', + '\\': 'Ñ', + ']': '¿', + '{': '°', + '|': 'ñ', + '}': 'ç' +}; +exports.CHARSETS['H'] = + exports.CHARSETS['7'] = { + '@': 'É', + '[': 'Ä', + '\\': 'Ö', + ']': 'Å', + '^': 'Ü', + '`': 'é', + '{': 'ä', + '|': 'ö', + '}': 'å', + '~': 'ü' + }; +exports.CHARSETS['='] = { + '#': 'ù', + '@': 'à', + '[': 'é', + '\\': 'ç', + ']': 'ê', + '^': 'î', + '_': 'è', + '`': 'ô', + '{': 'ä', + '|': 'ö', + '}': 'ü', + '~': 'û' +}; + +},{}],33:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("../../common/data/EscapeSequences"); +var KEYCODE_KEY_MAPPINGS = { + 48: ['0', ')'], + 49: ['1', '!'], + 50: ['2', '@'], + 51: ['3', '#'], + 52: ['4', '$'], + 53: ['5', '%'], + 54: ['6', '^'], + 55: ['7', '&'], + 56: ['8', '*'], + 57: ['9', '('], + 186: [';', ':'], + 187: ['=', '+'], + 188: [',', '<'], + 189: ['-', '_'], + 190: ['.', '>'], + 191: ['/', '?'], + 192: ['`', '~'], + 219: ['[', '{'], + 220: ['\\', '|'], + 221: [']', '}'], + 222: ['\'', '"'] +}; +function evaluateKeyboardEvent(ev, applicationCursorMode, isMac, macOptionIsMeta) { + var result = { + type: 0, + cancel: false, + key: undefined + }; + var modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); + switch (ev.keyCode) { + case 0: + if (ev.key === 'UIKeyInputUpArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OA'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[A'; + } + } + else if (ev.key === 'UIKeyInputLeftArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OD'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[D'; + } + } + else if (ev.key === 'UIKeyInputRightArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OC'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[C'; + } + } + else if (ev.key === 'UIKeyInputDownArrow') { + if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OB'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[B'; + } + } + break; + case 8: + if (ev.shiftKey) { + result.key = EscapeSequences_1.C0.BS; + break; + } + else if (ev.altKey) { + result.key = EscapeSequences_1.C0.ESC + EscapeSequences_1.C0.DEL; + break; + } + result.key = EscapeSequences_1.C0.DEL; + break; + case 9: + if (ev.shiftKey) { + result.key = EscapeSequences_1.C0.ESC + '[Z'; + break; + } + result.key = EscapeSequences_1.C0.HT; + result.cancel = true; + break; + case 13: + result.key = EscapeSequences_1.C0.CR; + result.cancel = true; + break; + case 27: + result.key = EscapeSequences_1.C0.ESC; + result.cancel = true; + break; + case 37: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'D'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3D') { + result.key = isMac ? EscapeSequences_1.C0.ESC + 'b' : EscapeSequences_1.C0.ESC + '[1;5D'; + } + } + else if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OD'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[D'; + } + break; + case 39: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'C'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3C') { + result.key = isMac ? EscapeSequences_1.C0.ESC + 'f' : EscapeSequences_1.C0.ESC + '[1;5C'; + } + } + else if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OC'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[C'; + } + break; + case 38: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'A'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3A') { + result.key = EscapeSequences_1.C0.ESC + '[1;5A'; + } + } + else if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OA'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[A'; + } + break; + case 40: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'B'; + if (result.key === EscapeSequences_1.C0.ESC + '[1;3B') { + result.key = EscapeSequences_1.C0.ESC + '[1;5B'; + } + } + else if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OB'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[B'; + } + break; + case 45: + if (!ev.shiftKey && !ev.ctrlKey) { + result.key = EscapeSequences_1.C0.ESC + '[2~'; + } + break; + case 46: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[3;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[3~'; + } + break; + case 36: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'H'; + } + else if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OH'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[H'; + } + break; + case 35: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'F'; + } + else if (applicationCursorMode) { + result.key = EscapeSequences_1.C0.ESC + 'OF'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[F'; + } + break; + case 33: + if (ev.shiftKey) { + result.type = 2; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[5~'; + } + break; + case 34: + if (ev.shiftKey) { + result.type = 3; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[6~'; + } + break; + case 112: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'P'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OP'; + } + break; + case 113: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'Q'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OQ'; + } + break; + case 114: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'R'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OR'; + } + break; + case 115: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'S'; + } + else { + result.key = EscapeSequences_1.C0.ESC + 'OS'; + } + break; + case 116: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[15;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[15~'; + } + break; + case 117: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[17;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[17~'; + } + break; + case 118: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[18;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[18~'; + } + break; + case 119: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[19;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[19~'; + } + break; + case 120: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[20;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[20~'; + } + break; + case 121: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[21;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[21~'; + } + break; + case 122: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[23;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[23~'; + } + break; + case 123: + if (modifiers) { + result.key = EscapeSequences_1.C0.ESC + '[24;' + (modifiers + 1) + '~'; + } + else { + result.key = EscapeSequences_1.C0.ESC + '[24~'; + } + break; + default: + if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + if (ev.keyCode >= 65 && ev.keyCode <= 90) { + result.key = String.fromCharCode(ev.keyCode - 64); + } + else if (ev.keyCode === 32) { + result.key = String.fromCharCode(0); + } + else if (ev.keyCode >= 51 && ev.keyCode <= 55) { + result.key = String.fromCharCode(ev.keyCode - 51 + 27); + } + else if (ev.keyCode === 56) { + result.key = String.fromCharCode(127); + } + else if (ev.keyCode === 219) { + result.key = String.fromCharCode(27); + } + else if (ev.keyCode === 220) { + result.key = String.fromCharCode(28); + } + else if (ev.keyCode === 221) { + result.key = String.fromCharCode(29); + } + } + else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) { + var keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; + var key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1]; + if (key) { + result.key = EscapeSequences_1.C0.ESC + key; + } + else if (ev.keyCode >= 65 && ev.keyCode <= 90) { + var keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; + result.key = EscapeSequences_1.C0.ESC + String.fromCharCode(keyCode); + } + } + else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { + if (ev.keyCode === 65) { + result.type = 1; + } + } + else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) { + result.key = ev.key; + } + else if (ev.key && ev.ctrlKey) { + if (ev.key === '_') { + result.key = EscapeSequences_1.C0.US; + } + } + break; + } + return result; +} +exports.evaluateKeyboardEvent = evaluateKeyboardEvent; + +},{"../../common/data/EscapeSequences":28}],34:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function stringFromCodePoint(codePoint) { + if (codePoint > 0xFFFF) { + codePoint -= 0x10000; + return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00); + } + return String.fromCharCode(codePoint); +} +exports.stringFromCodePoint = stringFromCodePoint; +function utf32ToString(data, start, end) { + if (start === void 0) { start = 0; } + if (end === void 0) { end = data.length; } + var result = ''; + for (var i = start; i < end; ++i) { + var codepoint = data[i]; + if (codepoint > 0xFFFF) { + codepoint -= 0x10000; + result += String.fromCharCode((codepoint >> 10) + 0xD800) + String.fromCharCode((codepoint % 0x400) + 0xDC00); + } + else { + result += String.fromCharCode(codepoint); + } + } + return result; +} +exports.utf32ToString = utf32ToString; +var StringToUtf32 = (function () { + function StringToUtf32() { + this._interim = 0; + } + StringToUtf32.prototype.clear = function () { + this._interim = 0; + }; + StringToUtf32.prototype.decode = function (input, target) { + var length = input.length; + if (!length) { + return 0; + } + var size = 0; + var startPos = 0; + if (this._interim) { + var second = input.charCodeAt(startPos++); + if (0xDC00 <= second && second <= 0xDFFF) { + target[size++] = (this._interim - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + else { + target[size++] = this._interim; + target[size++] = second; + } + this._interim = 0; + } + for (var i = startPos; i < length; ++i) { + var code = input.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + if (++i >= length) { + this._interim = code; + return size; + } + var second = input.charCodeAt(i); + if (0xDC00 <= second && second <= 0xDFFF) { + target[size++] = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + else { + target[size++] = code; + target[size++] = second; + } + continue; + } + target[size++] = code; + } + return size; + }; + return StringToUtf32; +}()); +exports.StringToUtf32 = StringToUtf32; +var Utf8ToUtf32 = (function () { + function Utf8ToUtf32() { + this.interim = new Uint8Array(3); + } + Utf8ToUtf32.prototype.clear = function () { + this.interim.fill(0); + }; + Utf8ToUtf32.prototype.decode = function (input, target) { + var length = input.length; + if (!length) { + return 0; + } + var size = 0; + var byte1; + var byte2; + var byte3; + var byte4; + var codepoint = 0; + var startPos = 0; + if (this.interim[0]) { + var discardInterim = false; + var cp = this.interim[0]; + cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07); + var pos = 0; + var tmp = void 0; + while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) { + cp <<= 6; + cp |= tmp; + } + var type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4; + var missing = type - pos; + while (startPos < missing) { + if (startPos >= length) { + return 0; + } + tmp = input[startPos++]; + if ((tmp & 0xC0) !== 0x80) { + startPos--; + discardInterim = true; + break; + } + else { + this.interim[pos++] = tmp; + cp <<= 6; + cp |= tmp & 0x3F; + } + } + if (!discardInterim) { + if (type === 2) { + if (cp < 0x80) { + startPos--; + } + else { + target[size++] = cp; + } + } + else if (type === 3) { + if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF)) { + } + else { + target[size++] = cp; + } + } + else { + if (codepoint < 0x010000 || codepoint > 0x10FFFF) { + } + else { + target[size++] = cp; + } + } + } + this.interim.fill(0); + } + var fourStop = length - 4; + var i = startPos; + while (i < length) { + while (i < fourStop + && !((byte1 = input[i]) & 0x80) + && !((byte2 = input[i + 1]) & 0x80) + && !((byte3 = input[i + 2]) & 0x80) + && !((byte4 = input[i + 3]) & 0x80)) { + target[size++] = byte1; + target[size++] = byte2; + target[size++] = byte3; + target[size++] = byte4; + i += 4; + } + byte1 = input[i++]; + if (byte1 < 0x80) { + target[size++] = byte1; + } + else if ((byte1 & 0xE0) === 0xC0) { + if (i >= length) { + this.interim[0] = byte1; + return size; + } + byte2 = input[i++]; + if ((byte2 & 0xC0) !== 0x80) { + i--; + continue; + } + codepoint = (byte1 & 0x1F) << 6 | (byte2 & 0x3F); + if (codepoint < 0x80) { + i--; + continue; + } + target[size++] = codepoint; + } + else if ((byte1 & 0xF0) === 0xE0) { + if (i >= length) { + this.interim[0] = byte1; + return size; + } + byte2 = input[i++]; + if ((byte2 & 0xC0) !== 0x80) { + i--; + continue; + } + if (i >= length) { + this.interim[0] = byte1; + this.interim[1] = byte2; + return size; + } + byte3 = input[i++]; + if ((byte3 & 0xC0) !== 0x80) { + i--; + continue; + } + codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F); + if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + continue; + } + target[size++] = codepoint; + } + else if ((byte1 & 0xF8) === 0xF0) { + if (i >= length) { + this.interim[0] = byte1; + return size; + } + byte2 = input[i++]; + if ((byte2 & 0xC0) !== 0x80) { + i--; + continue; + } + if (i >= length) { + this.interim[0] = byte1; + this.interim[1] = byte2; + return size; + } + byte3 = input[i++]; + if ((byte3 & 0xC0) !== 0x80) { + i--; + continue; + } + if (i >= length) { + this.interim[0] = byte1; + this.interim[1] = byte2; + this.interim[2] = byte3; + return size; + } + byte4 = input[i++]; + if ((byte4 & 0xC0) !== 0x80) { + i--; + continue; + } + codepoint = (byte1 & 0x07) << 18 | (byte2 & 0x3F) << 12 | (byte3 & 0x3F) << 6 | (byte4 & 0x3F); + if (codepoint < 0x010000 || codepoint > 0x10FFFF) { + continue; + } + target[size++] = codepoint; + } + else { + } + } + return size; + }; + return Utf8ToUtf32; +}()); +exports.Utf8ToUtf32 = Utf8ToUtf32; + +},{}],35:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var EscapeSequences_1 = require("../common/data/EscapeSequences"); +var AltClickHandler = (function () { + function AltClickHandler(_mouseEvent, _terminal) { + var _a; + this._mouseEvent = _mouseEvent; + this._terminal = _terminal; + this._lines = this._terminal.buffer.lines; + this._startCol = this._terminal.buffer.x; + this._startRow = this._terminal.buffer.y; + var coordinates = this._terminal.mouseHelper.getCoords(this._mouseEvent, this._terminal.element, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows, false); + if (coordinates) { + _a = coordinates.map(function (coordinate) { + return coordinate - 1; + }), this._endCol = _a[0], this._endRow = _a[1]; + } + } + AltClickHandler.prototype.move = function () { + if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) { + this._terminal.handler(this._arrowSequences()); + } + }; + AltClickHandler.prototype._arrowSequences = function () { + if (!this._terminal.buffer.hasScrollback) { + return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol(); + } + return this._moveHorizontallyOnly(); + }; + AltClickHandler.prototype._resetStartingRow = function () { + if (this._moveToRequestedRow().length === 0) { + return ''; + } + return repeat(this._bufferLine(this._startCol, this._startRow, this._startCol, this._startRow - this._wrappedRowsForRow(this._startRow), false).length, this._sequence("D")); + }; + AltClickHandler.prototype._moveToRequestedRow = function () { + var startRow = this._startRow - this._wrappedRowsForRow(this._startRow); + var endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + var rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount(); + return repeat(rowsToMove, this._sequence(this._verticalDirection())); + }; + AltClickHandler.prototype._moveToRequestedCol = function () { + var startRow; + if (this._moveToRequestedRow().length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + } + else { + startRow = this._startRow; + } + var endRow = this._endRow; + var direction = this._horizontalDirection(); + return repeat(this._bufferLine(this._startCol, startRow, this._endCol, endRow, direction === "C").length, this._sequence(direction)); + }; + AltClickHandler.prototype._moveHorizontallyOnly = function () { + var direction = this._horizontalDirection(); + return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction)); + }; + AltClickHandler.prototype._wrappedRowsCount = function () { + var wrappedRows = 0; + var startRow = this._startRow - this._wrappedRowsForRow(this._startRow); + var endRow = this._endRow - this._wrappedRowsForRow(this._endRow); + for (var i = 0; i < Math.abs(startRow - endRow); i++) { + var direction = this._verticalDirection() === "A" ? -1 : 1; + if (this._lines.get(startRow + (direction * i)).isWrapped) { + wrappedRows++; + } + } + return wrappedRows; + }; + AltClickHandler.prototype._wrappedRowsForRow = function (currentRow) { + var rowCount = 0; + var lineWraps = this._lines.get(currentRow).isWrapped; + while (lineWraps && currentRow >= 0 && currentRow < this._terminal.rows) { + rowCount++; + currentRow--; + lineWraps = this._lines.get(currentRow).isWrapped; + } + return rowCount; + }; + AltClickHandler.prototype._horizontalDirection = function () { + var startRow; + if (this._moveToRequestedRow().length > 0) { + startRow = this._endRow - this._wrappedRowsForRow(this._endRow); + } + else { + startRow = this._startRow; + } + if ((this._startCol < this._endCol && + startRow <= this._endRow) || + (this._startCol >= this._endCol && + startRow < this._endRow)) { + return "C"; + } + return "D"; + }; + AltClickHandler.prototype._verticalDirection = function () { + if (this._startRow > this._endRow) { + return "A"; + } + return "B"; + }; + AltClickHandler.prototype._bufferLine = function (startCol, startRow, endCol, endRow, forward) { + var currentCol = startCol; + var currentRow = startRow; + var bufferStr = ''; + while (currentCol !== endCol || currentRow !== endRow) { + currentCol += forward ? 1 : -1; + if (forward && currentCol > this._terminal.cols - 1) { + bufferStr += this._terminal.buffer.translateBufferLineToString(currentRow, false, startCol, currentCol); + currentCol = 0; + startCol = 0; + currentRow++; + } + else if (!forward && currentCol < 0) { + bufferStr += this._terminal.buffer.translateBufferLineToString(currentRow, false, 0, startCol + 1); + currentCol = this._terminal.cols - 1; + startCol = currentCol; + currentRow--; + } + } + return bufferStr + this._terminal.buffer.translateBufferLineToString(currentRow, false, startCol, currentCol); + }; + AltClickHandler.prototype._sequence = function (direction) { + var mod = this._terminal.applicationCursor ? 'O' : '['; + return EscapeSequences_1.C0.ESC + mod + direction; + }; + return AltClickHandler; +}()); +exports.AltClickHandler = AltClickHandler; +function repeat(count, str) { + count = Math.floor(count); + var rpt = ''; + for (var i = 0; i < count; i++) { + rpt += str; + } + return rpt; +} + +},{"../common/data/EscapeSequences":28}],36:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var AddonManager = (function () { + function AddonManager() { + this._addons = []; + } + AddonManager.prototype.dispose = function () { + for (var i = this._addons.length - 1; i >= 0; i--) { + this._addons[i].instance.dispose(); + } + }; + AddonManager.prototype.loadAddon = function (terminal, instance) { + var _this = this; + var loadedAddon = { + instance: instance, + dispose: instance.dispose, + isDisposed: false + }; + this._addons.push(loadedAddon); + instance.dispose = function () { return _this._wrappedAddonDispose(loadedAddon); }; + instance.activate(terminal); + }; + AddonManager.prototype._wrappedAddonDispose = function (loadedAddon) { + if (loadedAddon.isDisposed) { + return; + } + var index = -1; + for (var i = 0; i < this._addons.length; i++) { + if (this._addons[i] === loadedAddon) { + index = i; + break; + } + } + if (index === -1) { + throw new Error('Could not dispose an addon that has not been loaded'); + } + loadedAddon.isDisposed = true; + loadedAddon.dispose.apply(loadedAddon.instance); + this._addons.splice(index, 1); + }; + return AddonManager; +}()); +exports.AddonManager = AddonManager; + +},{}],37:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Terminal_1 = require("../Terminal"); +var Strings = require("../Strings"); +var AddonManager_1 = require("./AddonManager"); +var Terminal = (function () { + function Terminal(options) { + this._core = new Terminal_1.Terminal(options); + this._addonManager = new AddonManager_1.AddonManager(); + } + Object.defineProperty(Terminal.prototype, "onCursorMove", { + get: function () { return this._core.onCursorMove; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onLineFeed", { + get: function () { return this._core.onLineFeed; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onSelectionChange", { + get: function () { return this._core.onSelectionChange; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onData", { + get: function () { return this._core.onData; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onTitleChange", { + get: function () { return this._core.onTitleChange; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onScroll", { + get: function () { return this._core.onScroll; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onKey", { + get: function () { return this._core.onKey; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onRender", { + get: function () { return this._core.onRender; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "onResize", { + get: function () { return this._core.onResize; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "element", { + get: function () { return this._core.element; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "textarea", { + get: function () { return this._core.textarea; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "rows", { + get: function () { return this._core.rows; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "cols", { + get: function () { return this._core.cols; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "buffer", { + get: function () { return new BufferApiView(this._core.buffer); }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Terminal.prototype, "markers", { + get: function () { return this._core.markers; }, + enumerable: true, + configurable: true + }); + Terminal.prototype.blur = function () { + this._core.blur(); + }; + Terminal.prototype.focus = function () { + this._core.focus(); + }; + Terminal.prototype.on = function (type, listener) { + this._core.on(type, listener); + }; + Terminal.prototype.off = function (type, listener) { + this._core.off(type, listener); + }; + Terminal.prototype.emit = function (type, data) { + this._core.emit(type, data); + }; + Terminal.prototype.addDisposableListener = function (type, handler) { + return this._core.addDisposableListener(type, handler); + }; + Terminal.prototype.resize = function (columns, rows) { + this._core.resize(columns, rows); + }; + Terminal.prototype.writeln = function (data) { + this._core.writeln(data); + }; + Terminal.prototype.open = function (parent) { + this._core.open(parent); + }; + Terminal.prototype.attachCustomKeyEventHandler = function (customKeyEventHandler) { + this._core.attachCustomKeyEventHandler(customKeyEventHandler); + }; + Terminal.prototype.addCsiHandler = function (flag, callback) { + return this._core.addCsiHandler(flag, callback); + }; + Terminal.prototype.addOscHandler = function (ident, callback) { + return this._core.addOscHandler(ident, callback); + }; + Terminal.prototype.registerLinkMatcher = function (regex, handler, options) { + return this._core.registerLinkMatcher(regex, handler, options); + }; + Terminal.prototype.deregisterLinkMatcher = function (matcherId) { + this._core.deregisterLinkMatcher(matcherId); + }; + Terminal.prototype.registerCharacterJoiner = function (handler) { + return this._core.registerCharacterJoiner(handler); + }; + Terminal.prototype.deregisterCharacterJoiner = function (joinerId) { + this._core.deregisterCharacterJoiner(joinerId); + }; + Terminal.prototype.addMarker = function (cursorYOffset) { + return this._core.addMarker(cursorYOffset); + }; + Terminal.prototype.hasSelection = function () { + return this._core.hasSelection(); + }; + Terminal.prototype.select = function (column, row, length) { + this._core.select(column, row, length); + }; + Terminal.prototype.getSelection = function () { + return this._core.getSelection(); + }; + Terminal.prototype.getSelectionPosition = function () { + return this._core.getSelectionPosition(); + }; + Terminal.prototype.clearSelection = function () { + this._core.clearSelection(); + }; + Terminal.prototype.selectAll = function () { + this._core.selectAll(); + }; + Terminal.prototype.selectLines = function (start, end) { + this._core.selectLines(start, end); + }; + Terminal.prototype.dispose = function () { + this._addonManager.dispose(); + this._core.dispose(); + }; + Terminal.prototype.destroy = function () { + this._core.destroy(); + }; + Terminal.prototype.scrollLines = function (amount) { + this._core.scrollLines(amount); + }; + Terminal.prototype.scrollPages = function (pageCount) { + this._core.scrollPages(pageCount); + }; + Terminal.prototype.scrollToTop = function () { + this._core.scrollToTop(); + }; + Terminal.prototype.scrollToBottom = function () { + this._core.scrollToBottom(); + }; + Terminal.prototype.scrollToLine = function (line) { + this._core.scrollToLine(line); + }; + Terminal.prototype.clear = function () { + this._core.clear(); + }; + Terminal.prototype.write = function (data) { + this._core.write(data); + }; + Terminal.prototype.writeUtf8 = function (data) { + this._core.writeUtf8(data); + }; + Terminal.prototype.getOption = function (key) { + return this._core.getOption(key); + }; + Terminal.prototype.setOption = function (key, value) { + this._core.setOption(key, value); + }; + Terminal.prototype.refresh = function (start, end) { + this._core.refresh(start, end); + }; + Terminal.prototype.reset = function () { + this._core.reset(); + }; + Terminal.applyAddon = function (addon) { + addon.apply(Terminal); + }; + Terminal.prototype.loadAddon = function (addon) { + return this._addonManager.loadAddon(this, addon); + }; + Object.defineProperty(Terminal, "strings", { + get: function () { + return Strings; + }, + enumerable: true, + configurable: true + }); + return Terminal; +}()); +exports.Terminal = Terminal; +var BufferApiView = (function () { + function BufferApiView(_buffer) { + this._buffer = _buffer; + } + Object.defineProperty(BufferApiView.prototype, "cursorY", { + get: function () { return this._buffer.y; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferApiView.prototype, "cursorX", { + get: function () { return this._buffer.x; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferApiView.prototype, "viewportY", { + get: function () { return this._buffer.ydisp; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferApiView.prototype, "baseY", { + get: function () { return this._buffer.ybase; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferApiView.prototype, "length", { + get: function () { return this._buffer.lines.length; }, + enumerable: true, + configurable: true + }); + BufferApiView.prototype.getLine = function (y) { + var line = this._buffer.lines.get(y); + if (!line) { + return undefined; + } + return new BufferLineApiView(line); + }; + return BufferApiView; +}()); +var BufferLineApiView = (function () { + function BufferLineApiView(_line) { + this._line = _line; + } + Object.defineProperty(BufferLineApiView.prototype, "isWrapped", { + get: function () { return this._line.isWrapped; }, + enumerable: true, + configurable: true + }); + BufferLineApiView.prototype.getCell = function (x) { + if (x < 0 || x >= this._line.length) { + return undefined; + } + return new BufferCellApiView(this._line, x); + }; + BufferLineApiView.prototype.translateToString = function (trimRight, startColumn, endColumn) { + return this._line.translateToString(trimRight, startColumn, endColumn); + }; + return BufferLineApiView; +}()); +var BufferCellApiView = (function () { + function BufferCellApiView(_line, _x) { + this._line = _line; + this._x = _x; + } + Object.defineProperty(BufferCellApiView.prototype, "char", { + get: function () { return this._line.getString(this._x); }, + enumerable: true, + configurable: true + }); + Object.defineProperty(BufferCellApiView.prototype, "width", { + get: function () { return this._line.getWidth(this._x); }, + enumerable: true, + configurable: true + }); + return BufferCellApiView; +}()); + +},{"../Strings":16,"../Terminal":17,"./AddonManager":36}],38:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("../common/Types"); +var Types_2 = require("./atlas/Types"); +var CharAtlasCache_1 = require("./atlas/CharAtlasCache"); +var BufferLine_1 = require("../core/buffer/BufferLine"); +var BaseRenderLayer = (function () { + function BaseRenderLayer(_container, id, zIndex, _alpha, _colors) { + this._container = _container; + this._alpha = _alpha; + this._colors = _colors; + this._scaledCharWidth = 0; + this._scaledCharHeight = 0; + this._scaledCellWidth = 0; + this._scaledCellHeight = 0; + this._scaledCharLeft = 0; + this._scaledCharTop = 0; + this._currentGlyphIdentifier = { + chars: '', + code: 0, + bg: 0, + fg: 0, + bold: false, + dim: false, + italic: false + }; + this._canvas = document.createElement('canvas'); + this._canvas.classList.add("xterm-" + id + "-layer"); + this._canvas.style.zIndex = zIndex.toString(); + this._initCanvas(); + this._container.appendChild(this._canvas); + } + BaseRenderLayer.prototype.dispose = function () { + this._container.removeChild(this._canvas); + if (this._charAtlas) { + this._charAtlas.dispose(); + } + }; + BaseRenderLayer.prototype._initCanvas = function () { + this._ctx = this._canvas.getContext('2d', { alpha: this._alpha }); + if (!this._alpha) { + this.clearAll(); + } + }; + BaseRenderLayer.prototype.onOptionsChanged = function (terminal) { }; + BaseRenderLayer.prototype.onBlur = function (terminal) { }; + BaseRenderLayer.prototype.onFocus = function (terminal) { }; + BaseRenderLayer.prototype.onCursorMove = function (terminal) { }; + BaseRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { }; + BaseRenderLayer.prototype.onSelectionChanged = function (terminal, start, end, columnSelectMode) { + if (columnSelectMode === void 0) { columnSelectMode = false; } + }; + BaseRenderLayer.prototype.setColors = function (terminal, colorSet) { + this._refreshCharAtlas(terminal, colorSet); + }; + BaseRenderLayer.prototype.setTransparency = function (terminal, alpha) { + if (alpha === this._alpha) { + return; + } + var oldCanvas = this._canvas; + this._alpha = alpha; + this._canvas = this._canvas.cloneNode(); + this._initCanvas(); + this._container.replaceChild(this._canvas, oldCanvas); + this._refreshCharAtlas(terminal, this._colors); + this.onGridChanged(terminal, 0, terminal.rows - 1); + }; + BaseRenderLayer.prototype._refreshCharAtlas = function (terminal, colorSet) { + if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + return; + } + this._charAtlas = CharAtlasCache_1.acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas.warmUp(); + }; + BaseRenderLayer.prototype.resize = function (terminal, dim) { + this._scaledCellWidth = dim.scaledCellWidth; + this._scaledCellHeight = dim.scaledCellHeight; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledCharLeft = dim.scaledCharLeft; + this._scaledCharTop = dim.scaledCharTop; + this._canvas.width = dim.scaledCanvasWidth; + this._canvas.height = dim.scaledCanvasHeight; + this._canvas.style.width = dim.canvasWidth + "px"; + this._canvas.style.height = dim.canvasHeight + "px"; + if (!this._alpha) { + this.clearAll(); + } + this._refreshCharAtlas(terminal, this._colors); + }; + BaseRenderLayer.prototype.fillCells = function (x, y, width, height) { + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + }; + BaseRenderLayer.prototype.fillBottomLineAtCells = function (x, y, width) { + if (width === void 0) { width = 1; } + this._ctx.fillRect(x * this._scaledCellWidth, (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1, width * this._scaledCellWidth, window.devicePixelRatio); + }; + BaseRenderLayer.prototype.fillLeftLineAtCell = function (x, y) { + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, window.devicePixelRatio, this._scaledCellHeight); + }; + BaseRenderLayer.prototype.strokeRectAtCell = function (x, y, width, height) { + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect(x * this._scaledCellWidth + window.devicePixelRatio / 2, y * this._scaledCellHeight + (window.devicePixelRatio / 2), width * this._scaledCellWidth - window.devicePixelRatio, (height * this._scaledCellHeight) - window.devicePixelRatio); + }; + BaseRenderLayer.prototype.clearAll = function () { + if (this._alpha) { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } + else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + } + }; + BaseRenderLayer.prototype.clearCells = function (x, y, width, height) { + if (this._alpha) { + this._ctx.clearRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + } + else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect(x * this._scaledCellWidth, y * this._scaledCellHeight, width * this._scaledCellWidth, height * this._scaledCellHeight); + } + }; + BaseRenderLayer.prototype.fillCharTrueColor = function (terminal, cell, x, y) { + this._ctx.font = this._getFont(terminal, false, false); + this._ctx.textBaseline = 'middle'; + this._clipRow(terminal, y); + this._ctx.fillText(cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + }; + BaseRenderLayer.prototype.drawChars = function (terminal, cell, x, y) { + if (cell.isFgRGB() || cell.isBgRGB()) { + this._drawUncachedChars(terminal, cell, x, y); + return; + } + var fg; + var bg; + if (cell.isInverse()) { + fg = (cell.isBgDefault()) ? Types_2.INVERTED_DEFAULT_COLOR : cell.getBgColor(); + bg = (cell.isFgDefault()) ? Types_2.INVERTED_DEFAULT_COLOR : cell.getFgColor(); + } + else { + bg = (cell.isBgDefault()) ? Types_1.DEFAULT_COLOR : cell.getBgColor(); + fg = (cell.isFgDefault()) ? Types_1.DEFAULT_COLOR : cell.getFgColor(); + } + var drawInBrightColor = terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== Types_2.INVERTED_DEFAULT_COLOR; + fg += drawInBrightColor ? 8 : 0; + this._currentGlyphIdentifier.chars = cell.getChars() || BufferLine_1.WHITESPACE_CELL_CHAR; + this._currentGlyphIdentifier.code = cell.getCode() || BufferLine_1.WHITESPACE_CELL_CODE; + this._currentGlyphIdentifier.bg = bg; + this._currentGlyphIdentifier.fg = fg; + this._currentGlyphIdentifier.bold = cell.isBold() && terminal.options.enableBold; + this._currentGlyphIdentifier.dim = !!cell.isDim(); + this._currentGlyphIdentifier.italic = !!cell.isItalic(); + var atlasDidDraw = this._charAtlas && this._charAtlas.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + if (!atlasDidDraw) { + this._drawUncachedChars(terminal, cell, x, y); + } + }; + BaseRenderLayer.prototype._drawUncachedChars = function (terminal, cell, x, y) { + this._ctx.save(); + this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.options.enableBold, !!cell.isItalic()); + this._ctx.textBaseline = 'middle'; + if (cell.isInverse()) { + if (cell.isBgDefault()) { + this._ctx.fillStyle = this._colors.background.css; + } + else if (cell.isBgRGB()) { + this._ctx.fillStyle = "rgb(" + BufferLine_1.AttributeData.toColorRGB(cell.getBgColor()).join(',') + ")"; + } + else { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; + } + } + else { + if (cell.isFgDefault()) { + this._ctx.fillStyle = this._colors.foreground.css; + } + else if (cell.isFgRGB()) { + this._ctx.fillStyle = "rgb(" + BufferLine_1.AttributeData.toColorRGB(cell.getFgColor()).join(',') + ")"; + } + else { + var fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; + } + } + this._clipRow(terminal, y); + if (cell.isDim()) { + this._ctx.globalAlpha = Types_2.DIM_OPACITY; + } + this._ctx.fillText(cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + this._ctx.restore(); + }; + BaseRenderLayer.prototype._clipRow = function (terminal, y) { + this._ctx.beginPath(); + this._ctx.rect(0, y * this._scaledCellHeight, terminal.cols * this._scaledCellWidth, this._scaledCellHeight); + this._ctx.clip(); + }; + BaseRenderLayer.prototype._getFont = function (terminal, isBold, isItalic) { + var fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight; + var fontStyle = isItalic ? 'italic' : ''; + return fontStyle + " " + fontWeight + " " + terminal.options.fontSize * window.devicePixelRatio + "px " + terminal.options.fontFamily; + }; + return BaseRenderLayer; +}()); +exports.BaseRenderLayer = BaseRenderLayer; + +},{"../common/Types":27,"../core/buffer/BufferLine":29,"./atlas/CharAtlasCache":48,"./atlas/Types":55}],39:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BufferLine_1 = require("../core/buffer/BufferLine"); +var JoinedCellData = (function (_super) { + __extends(JoinedCellData, _super); + function JoinedCellData(firstCell, chars, width) { + var _this = _super.call(this) || this; + _this.content = 0; + _this.combinedData = ''; + _this.fg = firstCell.fg; + _this.bg = firstCell.bg; + _this.combinedData = chars; + _this._width = width; + return _this; + } + JoinedCellData.prototype.isCombined = function () { + return 2097152; + }; + JoinedCellData.prototype.getWidth = function () { + return this._width; + }; + JoinedCellData.prototype.getChars = function () { + return this.combinedData; + }; + JoinedCellData.prototype.getCode = function () { + return 0x1FFFFF; + }; + JoinedCellData.prototype.setFromCharData = function (value) { + throw new Error('not implemented'); + }; + JoinedCellData.prototype.getAsCharData = function () { + return [this.fg, this.getChars(), this.getWidth(), this.getCode()]; + }; + return JoinedCellData; +}(BufferLine_1.AttributeData)); +exports.JoinedCellData = JoinedCellData; +var CharacterJoinerRegistry = (function () { + function CharacterJoinerRegistry(_terminal) { + this._terminal = _terminal; + this._characterJoiners = []; + this._nextCharacterJoinerId = 0; + this._workCell = new BufferLine_1.CellData(); + } + CharacterJoinerRegistry.prototype.registerCharacterJoiner = function (handler) { + var joiner = { + id: this._nextCharacterJoinerId++, + handler: handler + }; + this._characterJoiners.push(joiner); + return joiner.id; + }; + CharacterJoinerRegistry.prototype.deregisterCharacterJoiner = function (joinerId) { + for (var i = 0; i < this._characterJoiners.length; i++) { + if (this._characterJoiners[i].id === joinerId) { + this._characterJoiners.splice(i, 1); + return true; + } + } + return false; + }; + CharacterJoinerRegistry.prototype.getJoinedCharacters = function (row) { + if (this._characterJoiners.length === 0) { + return []; + } + var line = this._terminal.buffer.lines.get(row); + if (line.length === 0) { + return []; + } + var ranges = []; + var lineStr = line.translateToString(true); + var rangeStartColumn = 0; + var currentStringIndex = 0; + var rangeStartStringIndex = 0; + var rangeAttrFG = line.getFg(0); + var rangeAttrBG = line.getBg(0); + for (var x = 0; x < line.getTrimmedLength(); x++) { + line.loadCell(x, this._workCell); + if (this._workCell.getWidth() === 0) { + continue; + } + if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) { + if (x - rangeStartColumn > 1) { + var joinedRanges = this._getJoinedRanges(lineStr, rangeStartStringIndex, currentStringIndex, line, rangeStartColumn); + for (var i = 0; i < joinedRanges.length; i++) { + ranges.push(joinedRanges[i]); + } + } + rangeStartColumn = x; + rangeStartStringIndex = currentStringIndex; + rangeAttrFG = this._workCell.fg; + rangeAttrBG = this._workCell.bg; + } + currentStringIndex += this._workCell.getChars().length || BufferLine_1.WHITESPACE_CELL_CHAR.length; + } + if (this._terminal.cols - rangeStartColumn > 1) { + var joinedRanges = this._getJoinedRanges(lineStr, rangeStartStringIndex, currentStringIndex, line, rangeStartColumn); + for (var i = 0; i < joinedRanges.length; i++) { + ranges.push(joinedRanges[i]); + } + } + return ranges; + }; + CharacterJoinerRegistry.prototype._getJoinedRanges = function (line, startIndex, endIndex, lineData, startCol) { + var text = line.substring(startIndex, endIndex); + var joinedRanges = this._characterJoiners[0].handler(text); + for (var i = 1; i < this._characterJoiners.length; i++) { + var joinerRanges = this._characterJoiners[i].handler(text); + for (var j = 0; j < joinerRanges.length; j++) { + CharacterJoinerRegistry._mergeRanges(joinedRanges, joinerRanges[j]); + } + } + this._stringRangesToCellRanges(joinedRanges, lineData, startCol); + return joinedRanges; + }; + CharacterJoinerRegistry.prototype._stringRangesToCellRanges = function (ranges, line, startCol) { + var currentRangeIndex = 0; + var currentRangeStarted = false; + var currentStringIndex = 0; + var currentRange = ranges[currentRangeIndex]; + if (!currentRange) { + return; + } + for (var x = startCol; x < this._terminal.cols; x++) { + var width = line.getWidth(x); + var length_1 = line.getString(x).length || BufferLine_1.WHITESPACE_CELL_CHAR.length; + if (width === 0) { + continue; + } + if (!currentRangeStarted && currentRange[0] <= currentStringIndex) { + currentRange[0] = x; + currentRangeStarted = true; + } + if (currentRange[1] <= currentStringIndex) { + currentRange[1] = x; + currentRange = ranges[++currentRangeIndex]; + if (!currentRange) { + break; + } + if (currentRange[0] <= currentStringIndex) { + currentRange[0] = x; + currentRangeStarted = true; + } + else { + currentRangeStarted = false; + } + } + currentStringIndex += length_1; + } + if (currentRange) { + currentRange[1] = this._terminal.cols; + } + }; + CharacterJoinerRegistry._mergeRanges = function (ranges, newRange) { + var inRange = false; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!inRange) { + if (newRange[1] <= range[0]) { + ranges.splice(i, 0, newRange); + return ranges; + } + if (newRange[1] <= range[1]) { + range[0] = Math.min(newRange[0], range[0]); + return ranges; + } + if (newRange[0] < range[1]) { + range[0] = Math.min(newRange[0], range[0]); + inRange = true; + } + continue; + } + else { + if (newRange[1] <= range[0]) { + ranges[i - 1][1] = newRange[1]; + return ranges; + } + if (newRange[1] <= range[1]) { + ranges[i - 1][1] = Math.max(newRange[1], range[1]); + ranges.splice(i, 1); + inRange = false; + return ranges; + } + ranges.splice(i, 1); + i--; + } + } + if (inRange) { + ranges[ranges.length - 1][1] = newRange[1]; + } + else { + ranges.push(newRange); + } + return ranges; + }; + return CharacterJoinerRegistry; +}()); +exports.CharacterJoinerRegistry = CharacterJoinerRegistry; + +},{"../core/buffer/BufferLine":29}],40:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var BufferLine_1 = require("../core/buffer/BufferLine"); +var BLINK_INTERVAL = 600; +var CursorRenderLayer = (function (_super) { + __extends(CursorRenderLayer, _super); + function CursorRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'cursor', zIndex, true, colors) || this; + _this._cell = new BufferLine_1.CellData(); + _this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + _this._cursorRenderers = { + 'bar': _this._renderBarCursor.bind(_this), + 'block': _this._renderBlockCursor.bind(_this), + 'underline': _this._renderUnderlineCursor.bind(_this) + }; + return _this; + } + CursorRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + }; + CursorRenderLayer.prototype.reset = function (terminal) { + this._clearCursor(); + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + this.onOptionsChanged(terminal); + } + }; + CursorRenderLayer.prototype.onBlur = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + }; + CursorRenderLayer.prototype.onFocus = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.resume(terminal); + } + else { + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + }; + CursorRenderLayer.prototype.onOptionsChanged = function (terminal) { + var _this = this; + if (terminal.options.cursorBlink) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, function () { + _this._render(terminal, true); + }); + } + } + else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + terminal.refresh(terminal.buffer.y, terminal.buffer.y); + } + }; + CursorRenderLayer.prototype.onCursorMove = function (terminal) { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + }; + CursorRenderLayer.prototype.onGridChanged = function (terminal, startRow, endRow) { + if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + this._render(terminal, false); + } + else { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + }; + CursorRenderLayer.prototype._render = function (terminal, triggeredByAnimationFrame) { + if (!terminal.cursorState || terminal.cursorHidden) { + this._clearCursor(); + return; + } + var cursorY = terminal.buffer.ybase + terminal.buffer.y; + var viewportRelativeCursorY = cursorY - terminal.buffer.ydisp; + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + this._clearCursor(); + return; + } + terminal.buffer.lines.get(cursorY).loadCell(terminal.buffer.x, this._cell); + if (this._cell.content === undefined) { + return; + } + if (!terminal.isFocused) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell); + this._ctx.restore(); + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = this._cell.getWidth(); + return; + } + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + if (this._state) { + if (this._state.x === terminal.buffer.x && + this._state.y === viewportRelativeCursorY && + this._state.isFocused === terminal.isFocused && + this._state.style === terminal.options.cursorStyle && + this._state.width === this._cell.getWidth()) { + return; + } + this._clearCursor(); + } + this._ctx.save(); + this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell); + this._ctx.restore(); + this._state.x = terminal.buffer.x; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.options.cursorStyle; + this._state.width = this._cell.getWidth(); + }; + CursorRenderLayer.prototype._clearCursor = function () { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.width, 1); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + } + }; + CursorRenderLayer.prototype._renderBarCursor = function (terminal, x, y, cell) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillLeftLineAtCell(x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderBlockCursor = function (terminal, x, y, cell) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillCells(x, y, cell.getWidth(), 1); + this._ctx.fillStyle = this._colors.cursorAccent.css; + this.fillCharTrueColor(terminal, cell, x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderUnderlineCursor = function (terminal, x, y, cell) { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillBottomLineAtCells(x, y); + this._ctx.restore(); + }; + CursorRenderLayer.prototype._renderBlurCursor = function (terminal, x, y, cell) { + this._ctx.save(); + this._ctx.strokeStyle = this._colors.cursor.css; + this.strokeRectAtCell(x, y, cell.getWidth(), 1); + this._ctx.restore(); + }; + return CursorRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.CursorRenderLayer = CursorRenderLayer; +var CursorBlinkStateManager = (function () { + function CursorBlinkStateManager(terminal, _renderCallback) { + this._renderCallback = _renderCallback; + this.isCursorVisible = true; + if (terminal.isFocused) { + this._restartInterval(); + } + } + Object.defineProperty(CursorBlinkStateManager.prototype, "isPaused", { + get: function () { return !(this._blinkStartTimeout || this._blinkInterval); }, + enumerable: true, + configurable: true + }); + CursorBlinkStateManager.prototype.dispose = function () { + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + CursorBlinkStateManager.prototype.restartBlinkAnimation = function (terminal) { + var _this = this; + if (this.isPaused) { + return; + } + this._animationTimeRestarted = Date.now(); + this.isCursorVisible = true; + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(function () { + _this._renderCallback(); + _this._animationFrame = null; + }); + } + }; + CursorBlinkStateManager.prototype._restartInterval = function (timeToStart) { + var _this = this; + if (timeToStart === void 0) { timeToStart = BLINK_INTERVAL; } + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + } + this._blinkStartTimeout = setTimeout(function () { + if (_this._animationTimeRestarted) { + var time = BLINK_INTERVAL - (Date.now() - _this._animationTimeRestarted); + _this._animationTimeRestarted = null; + if (time > 0) { + _this._restartInterval(time); + return; + } + } + _this.isCursorVisible = false; + _this._animationFrame = window.requestAnimationFrame(function () { + _this._renderCallback(); + _this._animationFrame = null; + }); + _this._blinkInterval = setInterval(function () { + if (_this._animationTimeRestarted) { + var time = BLINK_INTERVAL - (Date.now() - _this._animationTimeRestarted); + _this._animationTimeRestarted = null; + _this._restartInterval(time); + return; + } + _this.isCursorVisible = !_this.isCursorVisible; + _this._animationFrame = window.requestAnimationFrame(function () { + _this._renderCallback(); + _this._animationFrame = null; + }); + }, BLINK_INTERVAL); + }, timeToStart); + }; + CursorBlinkStateManager.prototype.pause = function () { + this.isCursorVisible = true; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + }; + CursorBlinkStateManager.prototype.resume = function (terminal) { + this._animationTimeRestarted = null; + this._restartInterval(); + this.restartBlinkAnimation(terminal); + }; + return CursorBlinkStateManager; +}()); + +},{"../core/buffer/BufferLine":29,"./BaseRenderLayer":38}],41:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var GridCache = (function () { + function GridCache() { + this.cache = []; + } + GridCache.prototype.resize = function (width, height) { + for (var x = 0; x < width; x++) { + if (this.cache.length <= x) { + this.cache.push([]); + } + for (var y = this.cache[x].length; y < height; y++) { + this.cache[x].push(null); + } + this.cache[x].length = height; + } + this.cache.length = width; + }; + GridCache.prototype.clear = function () { + for (var x = 0; x < this.cache.length; x++) { + for (var y = 0; y < this.cache[x].length; y++) { + this.cache[x][y] = null; + } + } + }; + return GridCache; +}()); +exports.GridCache = GridCache; + +},{}],42:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var Types_1 = require("./atlas/Types"); +var CharAtlasUtils_1 = require("./atlas/CharAtlasUtils"); +var LinkRenderLayer = (function (_super) { + __extends(LinkRenderLayer, _super); + function LinkRenderLayer(container, zIndex, colors, terminal) { + var _this = _super.call(this, container, 'link', zIndex, true, colors) || this; + _this._state = null; + terminal.linkifier.onLinkHover(function (e) { return _this._onLinkHover(e); }); + terminal.linkifier.onLinkLeave(function (e) { return _this._onLinkLeave(e); }); + return _this; + } + LinkRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + this._state = null; + }; + LinkRenderLayer.prototype.reset = function (terminal) { + this._clearCurrentLink(); + }; + LinkRenderLayer.prototype._clearCurrentLink = function () { + if (this._state) { + this.clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1); + var middleRowCount = this._state.y2 - this._state.y1 - 1; + if (middleRowCount > 0) { + this.clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); + } + this.clearCells(0, this._state.y2, this._state.x2, 1); + this._state = null; + } + }; + LinkRenderLayer.prototype._onLinkHover = function (e) { + if (e.fg === Types_1.INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background.css; + } + else if (CharAtlasUtils_1.is256Color(e.fg)) { + this._ctx.fillStyle = this._colors.ansi[e.fg].css; + } + else { + this._ctx.fillStyle = this._colors.foreground.css; + } + if (e.y1 === e.y2) { + this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); + } + else { + this.fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1); + for (var y = e.y1 + 1; y < e.y2; y++) { + this.fillBottomLineAtCells(0, y, e.cols); + } + this.fillBottomLineAtCells(0, e.y2, e.x2); + } + this._state = e; + }; + LinkRenderLayer.prototype._onLinkLeave = function (e) { + this._clearCurrentLink(); + }; + return LinkRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.LinkRenderLayer = LinkRenderLayer; + +},{"./BaseRenderLayer":38,"./atlas/CharAtlasUtils":50,"./atlas/Types":55}],43:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var RenderDebouncer_1 = require("../ui/RenderDebouncer"); +var EventEmitter2_1 = require("../common/EventEmitter2"); +var Lifecycle_1 = require("../common/Lifecycle"); +var ScreenDprMonitor_1 = require("../ui/ScreenDprMonitor"); +var Lifecycle_2 = require("../ui/Lifecycle"); +var RenderCoordinator = (function (_super) { + __extends(RenderCoordinator, _super); + function RenderCoordinator(_renderer, _rowCount, screenElement) { + var _this = _super.call(this) || this; + _this._renderer = _renderer; + _this._rowCount = _rowCount; + _this._isPaused = false; + _this._needsFullRefresh = false; + _this._canvasWidth = 0; + _this._canvasHeight = 0; + _this._onDimensionsChange = new EventEmitter2_1.EventEmitter2(); + _this._onRender = new EventEmitter2_1.EventEmitter2(); + _this._onRefreshRequest = new EventEmitter2_1.EventEmitter2(); + _this._renderDebouncer = new RenderDebouncer_1.RenderDebouncer(function (start, end) { return _this._renderRows(start, end); }); + _this.register(_this._renderDebouncer); + _this._screenDprMonitor = new ScreenDprMonitor_1.ScreenDprMonitor(); + _this._screenDprMonitor.setListener(function () { return _this._renderer.onDevicePixelRatioChange(); }); + _this.register(_this._screenDprMonitor); + _this.register(Lifecycle_2.addDisposableDomListener(window, 'resize', function () { return _this._renderer.onDevicePixelRatioChange(); })); + if ('IntersectionObserver' in window) { + var observer_1 = new IntersectionObserver(function (e) { return _this._onIntersectionChange(e[e.length - 1]); }, { threshold: 0 }); + observer_1.observe(screenElement); + _this.register({ dispose: function () { return observer_1.disconnect(); } }); + } + return _this; + } + Object.defineProperty(RenderCoordinator.prototype, "onDimensionsChange", { + get: function () { return this._onDimensionsChange.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(RenderCoordinator.prototype, "onRender", { + get: function () { return this._onRender.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(RenderCoordinator.prototype, "onRefreshRequest", { + get: function () { return this._onRefreshRequest.event; }, + enumerable: true, + configurable: true + }); + Object.defineProperty(RenderCoordinator.prototype, "dimensions", { + get: function () { return this._renderer.dimensions; }, + enumerable: true, + configurable: true + }); + RenderCoordinator.prototype._onIntersectionChange = function (entry) { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this.refreshRows(0, this._rowCount - 1); + this._needsFullRefresh = false; + } + }; + RenderCoordinator.prototype.refreshRows = function (start, end) { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } + this._renderDebouncer.refresh(start, end, this._rowCount); + }; + RenderCoordinator.prototype._renderRows = function (start, end) { + this._renderer.renderRows(start, end); + this._onRender.fire({ start: start, end: end }); + }; + RenderCoordinator.prototype.resize = function (cols, rows) { + this._rowCount = rows; + this._fireOnCanvasResize(); + }; + RenderCoordinator.prototype.changeOptions = function () { + this._renderer.onOptionsChanged(); + this._fireOnCanvasResize(); + }; + RenderCoordinator.prototype._fireOnCanvasResize = function () { + if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { + return; + } + this._onDimensionsChange.fire(this._renderer.dimensions); + }; + RenderCoordinator.prototype.setRenderer = function (renderer) { + this._renderer.dispose(); + this._renderer = renderer; + }; + RenderCoordinator.prototype._fullRefresh = function () { + if (this._isPaused) { + this._needsFullRefresh = true; + } + else { + this.refreshRows(0, this._rowCount); + } + }; + RenderCoordinator.prototype.setColors = function (colors) { + this._renderer.setColors(colors); + this._fullRefresh(); + }; + RenderCoordinator.prototype.onDevicePixelRatioChange = function () { + this._renderer.onDevicePixelRatioChange(); + }; + RenderCoordinator.prototype.onResize = function (cols, rows) { + this._renderer.onResize(cols, rows); + this._fullRefresh(); + }; + RenderCoordinator.prototype.onCharSizeChanged = function () { + this._renderer.onCharSizeChanged(); + }; + RenderCoordinator.prototype.onBlur = function () { + this._renderer.onBlur(); + }; + RenderCoordinator.prototype.onFocus = function () { + this._renderer.onFocus(); + }; + RenderCoordinator.prototype.onSelectionChanged = function (start, end, columnSelectMode) { + this._renderer.onSelectionChanged(start, end, columnSelectMode); + }; + RenderCoordinator.prototype.onCursorMove = function () { + this._renderer.onCursorMove(); + }; + RenderCoordinator.prototype.onOptionsChanged = function () { + this._renderer.onOptionsChanged(); + }; + RenderCoordinator.prototype.clear = function () { + this._renderer.clear(); + }; + RenderCoordinator.prototype.registerCharacterJoiner = function (handler) { + return this._renderer.registerCharacterJoiner(handler); + }; + RenderCoordinator.prototype.deregisterCharacterJoiner = function (joinerId) { + return this._renderer.deregisterCharacterJoiner(joinerId); + }; + return RenderCoordinator; +}(Lifecycle_1.Disposable)); +exports.RenderCoordinator = RenderCoordinator; + +},{"../common/EventEmitter2":23,"../common/Lifecycle":24,"../ui/Lifecycle":59,"../ui/RenderDebouncer":60,"../ui/ScreenDprMonitor":61}],44:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var TextRenderLayer_1 = require("./TextRenderLayer"); +var SelectionRenderLayer_1 = require("./SelectionRenderLayer"); +var CursorRenderLayer_1 = require("./CursorRenderLayer"); +var LinkRenderLayer_1 = require("./LinkRenderLayer"); +var CharacterJoinerRegistry_1 = require("../renderer/CharacterJoinerRegistry"); +var Lifecycle_1 = require("../common/Lifecycle"); +var Renderer = (function (_super) { + __extends(Renderer, _super); + function Renderer(_terminal, _colors) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._colors = _colors; + var allowTransparency = _this._terminal.options.allowTransparency; + _this._characterJoinerRegistry = new CharacterJoinerRegistry_1.CharacterJoinerRegistry(_terminal); + _this._renderLayers = [ + new TextRenderLayer_1.TextRenderLayer(_this._terminal.screenElement, 0, _this._colors, _this._characterJoinerRegistry, allowTransparency), + new SelectionRenderLayer_1.SelectionRenderLayer(_this._terminal.screenElement, 1, _this._colors), + new LinkRenderLayer_1.LinkRenderLayer(_this._terminal.screenElement, 2, _this._colors, _this._terminal), + new CursorRenderLayer_1.CursorRenderLayer(_this._terminal.screenElement, 3, _this._colors) + ]; + _this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + _this._devicePixelRatio = window.devicePixelRatio; + _this._updateDimensions(); + _this.onOptionsChanged(); + return _this; + } + Renderer.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this._renderLayers.forEach(function (l) { return l.dispose(); }); + }; + Renderer.prototype.onDevicePixelRatioChange = function () { + if (this._devicePixelRatio !== window.devicePixelRatio) { + this._devicePixelRatio = window.devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows); + } + }; + Renderer.prototype.setColors = function (colors) { + var _this = this; + this._colors = colors; + this._renderLayers.forEach(function (l) { + l.setColors(_this._terminal, _this._colors); + l.reset(_this._terminal); + }); + }; + Renderer.prototype.onResize = function (cols, rows) { + var _this = this; + this._updateDimensions(); + this._renderLayers.forEach(function (l) { return l.resize(_this._terminal, _this.dimensions); }); + this._terminal.screenElement.style.width = this.dimensions.canvasWidth + "px"; + this._terminal.screenElement.style.height = this.dimensions.canvasHeight + "px"; + }; + Renderer.prototype.onCharSizeChanged = function () { + this.onResize(this._terminal.cols, this._terminal.rows); + }; + Renderer.prototype.onBlur = function () { + var _this = this; + this._runOperation(function (l) { return l.onBlur(_this._terminal); }); + }; + Renderer.prototype.onFocus = function () { + var _this = this; + this._runOperation(function (l) { return l.onFocus(_this._terminal); }); + }; + Renderer.prototype.onSelectionChanged = function (start, end, columnSelectMode) { + var _this = this; + if (columnSelectMode === void 0) { columnSelectMode = false; } + this._runOperation(function (l) { return l.onSelectionChanged(_this._terminal, start, end, columnSelectMode); }); + }; + Renderer.prototype.onCursorMove = function () { + var _this = this; + this._runOperation(function (l) { return l.onCursorMove(_this._terminal); }); + }; + Renderer.prototype.onOptionsChanged = function () { + var _this = this; + this._runOperation(function (l) { return l.onOptionsChanged(_this._terminal); }); + }; + Renderer.prototype.clear = function () { + var _this = this; + this._runOperation(function (l) { return l.reset(_this._terminal); }); + }; + Renderer.prototype._runOperation = function (operation) { + this._renderLayers.forEach(function (l) { return operation(l); }); + }; + Renderer.prototype.renderRows = function (start, end) { + var _this = this; + this._renderLayers.forEach(function (l) { return l.onGridChanged(_this._terminal, start, end); }); + }; + Renderer.prototype._updateDimensions = function () { + if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + return; + } + this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + }; + Renderer.prototype.registerCharacterJoiner = function (handler) { + return this._characterJoinerRegistry.registerCharacterJoiner(handler); + }; + Renderer.prototype.deregisterCharacterJoiner = function (joinerId) { + return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId); + }; + return Renderer; +}(Lifecycle_1.Disposable)); +exports.Renderer = Renderer; + +},{"../common/Lifecycle":24,"../renderer/CharacterJoinerRegistry":39,"./CursorRenderLayer":40,"./LinkRenderLayer":42,"./SelectionRenderLayer":45,"./TextRenderLayer":46}],45:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var SelectionRenderLayer = (function (_super) { + __extends(SelectionRenderLayer, _super); + function SelectionRenderLayer(container, zIndex, colors) { + var _this = _super.call(this, container, 'selection', zIndex, true, colors) || this; + _this._clearState(); + return _this; + } + SelectionRenderLayer.prototype._clearState = function () { + this._state = { + start: null, + end: null, + columnSelectMode: null, + ydisp: null + }; + }; + SelectionRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + this._clearState(); + }; + SelectionRenderLayer.prototype.reset = function (terminal) { + if (this._state.start && this._state.end) { + this._clearState(); + this.clearAll(); + } + }; + SelectionRenderLayer.prototype.onSelectionChanged = function (terminal, start, end, columnSelectMode) { + if (!this._didStateChange(start, end, columnSelectMode, terminal.buffer.ydisp)) { + return; + } + this.clearAll(); + if (!start || !end) { + this._clearState(); + return; + } + var viewportStartRow = start[1] - terminal.buffer.ydisp; + var viewportEndRow = end[1] - terminal.buffer.ydisp; + var viewportCappedStartRow = Math.max(viewportStartRow, 0); + var viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + return; + } + this._ctx.fillStyle = this._colors.selection.css; + if (columnSelectMode) { + var startCol = start[0]; + var width = end[0] - startCol; + var height = viewportCappedEndRow - viewportCappedStartRow + 1; + this.fillCells(startCol, viewportCappedStartRow, width, height); + } + else { + var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + var startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); + var middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); + this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount); + if (viewportCappedStartRow !== viewportCappedEndRow) { + var endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; + this.fillCells(0, viewportCappedEndRow, endCol, 1); + } + } + this._state.start = [start[0], start[1]]; + this._state.end = [end[0], end[1]]; + this._state.columnSelectMode = columnSelectMode; + this._state.ydisp = terminal.buffer.ydisp; + }; + SelectionRenderLayer.prototype._didStateChange = function (start, end, columnSelectMode, ydisp) { + return !this._areCoordinatesEqual(start, this._state.start) || + !this._areCoordinatesEqual(end, this._state.end) || + columnSelectMode !== this._state.columnSelectMode || + ydisp !== this._state.ydisp; + }; + SelectionRenderLayer.prototype._areCoordinatesEqual = function (coord1, coord2) { + if (!coord1 || !coord2) { + return false; + } + return coord1[0] === coord2[0] && coord1[1] === coord2[1]; + }; + return SelectionRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.SelectionRenderLayer = SelectionRenderLayer; + +},{"./BaseRenderLayer":38}],46:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var GridCache_1 = require("./GridCache"); +var BaseRenderLayer_1 = require("./BaseRenderLayer"); +var BufferLine_1 = require("../core/buffer/BufferLine"); +var CharacterJoinerRegistry_1 = require("./CharacterJoinerRegistry"); +var TextRenderLayer = (function (_super) { + __extends(TextRenderLayer, _super); + function TextRenderLayer(container, zIndex, colors, characterJoinerRegistry, alpha) { + var _this = _super.call(this, container, 'text', zIndex, alpha, colors) || this; + _this._characterOverlapCache = {}; + _this._workCell = new BufferLine_1.CellData(); + _this._state = new GridCache_1.GridCache(); + _this._characterJoinerRegistry = characterJoinerRegistry; + return _this; + } + TextRenderLayer.prototype.resize = function (terminal, dim) { + _super.prototype.resize.call(this, terminal, dim); + var terminalFont = this._getFont(terminal, false, false); + if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { + this._characterWidth = dim.scaledCharWidth; + this._characterFont = terminalFont; + this._characterOverlapCache = {}; + } + this._state.clear(); + this._state.resize(terminal.cols, terminal.rows); + }; + TextRenderLayer.prototype.reset = function (terminal) { + this._state.clear(); + this.clearAll(); + }; + TextRenderLayer.prototype._forEachCell = function (terminal, firstRow, lastRow, joinerRegistry, callback) { + for (var y = firstRow; y <= lastRow; y++) { + var row = y + terminal.buffer.ydisp; + var line = terminal.buffer.lines.get(row); + var joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; + for (var x = 0; x < terminal.cols; x++) { + line.loadCell(x, this._workCell); + var cell = this._workCell; + var isJoined = false; + var lastCharX = x; + if (cell.getWidth() === 0) { + continue; + } + if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { + isJoined = true; + var range = joinedRanges.shift(); + cell = new CharacterJoinerRegistry_1.JoinedCellData(this._workCell, line.translateToString(true, range[0], range[1]), range[1] - range[0]); + lastCharX = range[1] - 1; + } + if (!isJoined && this._isOverlapping(cell)) { + if (lastCharX < line.length - 1 && line.getCodePoint(lastCharX + 1) === BufferLine_1.NULL_CELL_CODE) { + cell.content &= ~12582912; + cell.content |= 2 << 22; + } + } + callback(cell, x, y); + x = lastCharX; + } + } + }; + TextRenderLayer.prototype._drawBackground = function (terminal, firstRow, lastRow) { + var _this = this; + var ctx = this._ctx; + var cols = terminal.cols; + var startX = 0; + var startY = 0; + var prevFillStyle = null; + ctx.save(); + this._forEachCell(terminal, firstRow, lastRow, null, function (cell, x, y) { + var nextFillStyle = null; + if (cell.isInverse()) { + if (cell.isFgDefault()) { + nextFillStyle = _this._colors.foreground.css; + } + else if (cell.isFgRGB()) { + nextFillStyle = "rgb(" + BufferLine_1.AttributeData.toColorRGB(cell.getFgColor()).join(',') + ")"; + } + else { + nextFillStyle = _this._colors.ansi[cell.getFgColor()].css; + } + } + else if (cell.isBgRGB()) { + nextFillStyle = "rgb(" + BufferLine_1.AttributeData.toColorRGB(cell.getBgColor()).join(',') + ")"; + } + else if (cell.isBgPalette()) { + nextFillStyle = _this._colors.ansi[cell.getBgColor()].css; + } + if (prevFillStyle === null) { + startX = x; + startY = y; + } + if (y !== startY) { + ctx.fillStyle = prevFillStyle; + _this.fillCells(startX, startY, cols - startX, 1); + startX = x; + startY = y; + } + else if (prevFillStyle !== nextFillStyle) { + ctx.fillStyle = prevFillStyle; + _this.fillCells(startX, startY, x - startX, 1); + startX = x; + startY = y; + } + prevFillStyle = nextFillStyle; + }); + if (prevFillStyle !== null) { + ctx.fillStyle = prevFillStyle; + this.fillCells(startX, startY, cols - startX, 1); + } + ctx.restore(); + }; + TextRenderLayer.prototype._drawForeground = function (terminal, firstRow, lastRow) { + var _this = this; + this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, function (cell, x, y) { + if (cell.isInvisible()) { + return; + } + _this.drawChars(terminal, cell, x, y); + if (cell.isUnderline()) { + _this._ctx.save(); + if (cell.isInverse()) { + if (cell.isBgDefault()) { + _this._ctx.fillStyle = _this._colors.background.css; + } + else if (cell.isBgRGB()) { + _this._ctx.fillStyle = "rgb(" + BufferLine_1.AttributeData.toColorRGB(cell.getBgColor()).join(',') + ")"; + } + else { + _this._ctx.fillStyle = _this._colors.ansi[cell.getBgColor()].css; + } + } + else { + if (cell.isFgDefault()) { + _this._ctx.fillStyle = _this._colors.foreground.css; + } + else if (cell.isFgRGB()) { + _this._ctx.fillStyle = "rgb(" + BufferLine_1.AttributeData.toColorRGB(cell.getFgColor()).join(',') + ")"; + } + else { + var fg = cell.getFgColor(); + if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + fg += 8; + } + _this._ctx.fillStyle = _this._colors.ansi[fg].css; + } + } + _this.fillBottomLineAtCells(x, y, cell.getWidth()); + _this._ctx.restore(); + } + }); + }; + TextRenderLayer.prototype.onGridChanged = function (terminal, firstRow, lastRow) { + if (this._state.cache.length === 0) { + return; + } + if (this._charAtlas) { + this._charAtlas.beginFrame(); + } + this.clearCells(0, firstRow, terminal.cols, lastRow - firstRow + 1); + this._drawBackground(terminal, firstRow, lastRow); + this._drawForeground(terminal, firstRow, lastRow); + }; + TextRenderLayer.prototype.onOptionsChanged = function (terminal) { + this.setTransparency(terminal, terminal.options.allowTransparency); + }; + TextRenderLayer.prototype._isOverlapping = function (cell) { + if (cell.getWidth() !== 1) { + return false; + } + if (cell.getCode() < 256) { + return false; + } + var chars = cell.getChars(); + if (this._characterOverlapCache.hasOwnProperty(chars)) { + return this._characterOverlapCache[chars]; + } + this._ctx.save(); + this._ctx.font = this._characterFont; + var overlaps = Math.floor(this._ctx.measureText(chars).width) > this._characterWidth; + this._ctx.restore(); + this._characterOverlapCache[chars] = overlaps; + return overlaps; + }; + return TextRenderLayer; +}(BaseRenderLayer_1.BaseRenderLayer)); +exports.TextRenderLayer = TextRenderLayer; + +},{"../core/buffer/BufferLine":29,"./BaseRenderLayer":38,"./CharacterJoinerRegistry":39,"./GridCache":41}],47:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseCharAtlas = (function () { + function BaseCharAtlas() { + this._didWarmUp = false; + } + BaseCharAtlas.prototype.dispose = function () { }; + BaseCharAtlas.prototype.warmUp = function () { + if (!this._didWarmUp) { + this._doWarmUp(); + this._didWarmUp = true; + } + }; + BaseCharAtlas.prototype._doWarmUp = function () { }; + BaseCharAtlas.prototype.beginFrame = function () { }; + return BaseCharAtlas; +}()); +exports.default = BaseCharAtlas; + +},{}],48:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var CharAtlasUtils_1 = require("./CharAtlasUtils"); +var DynamicCharAtlas_1 = require("./DynamicCharAtlas"); +var NoneCharAtlas_1 = require("./NoneCharAtlas"); +var StaticCharAtlas_1 = require("./StaticCharAtlas"); +var charAtlasImplementations = { + 'none': NoneCharAtlas_1.default, + 'static': StaticCharAtlas_1.default, + 'dynamic': DynamicCharAtlas_1.default +}; +var charAtlasCache = []; +function acquireCharAtlas(terminal, colors, scaledCharWidth, scaledCharHeight) { + var newConfig = CharAtlasUtils_1.generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + for (var i = 0; i < charAtlasCache.length; i++) { + var entry = charAtlasCache[i]; + var ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (CharAtlasUtils_1.configEquals(entry.config, newConfig)) { + return entry.atlas; + } + if (entry.ownedBy.length === 1) { + entry.atlas.dispose(); + charAtlasCache.splice(i, 1); + } + else { + entry.ownedBy.splice(ownedByIndex, 1); + } + break; + } + } + for (var i = 0; i < charAtlasCache.length; i++) { + var entry = charAtlasCache[i]; + if (CharAtlasUtils_1.configEquals(entry.config, newConfig)) { + entry.ownedBy.push(terminal); + return entry.atlas; + } + } + var newEntry = { + atlas: new charAtlasImplementations[terminal.options.experimentalCharAtlas](document, newConfig), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.atlas; +} +exports.acquireCharAtlas = acquireCharAtlas; +function removeTerminalFromCache(terminal) { + for (var i = 0; i < charAtlasCache.length; i++) { + var index = charAtlasCache[i].ownedBy.indexOf(terminal); + if (index !== -1) { + if (charAtlasCache[i].ownedBy.length === 1) { + charAtlasCache[i].atlas.dispose(); + charAtlasCache.splice(i, 1); + } + else { + charAtlasCache[i].ownedBy.splice(index, 1); + } + break; + } + } +} +exports.removeTerminalFromCache = removeTerminalFromCache; + +},{"./CharAtlasUtils":50,"./DynamicCharAtlas":51,"./NoneCharAtlas":53,"./StaticCharAtlas":54}],49:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Platform_1 = require("../../common/Platform"); +var Types_1 = require("./Types"); +function generateStaticCharAtlasTexture(context, canvasFactory, config) { + var cellWidth = config.scaledCharWidth + Types_1.CHAR_ATLAS_CELL_SPACING; + var cellHeight = config.scaledCharHeight + Types_1.CHAR_ATLAS_CELL_SPACING; + var canvas = canvasFactory(255 * cellWidth, (2 + 16 + 16) * cellHeight); + var ctx = canvas.getContext('2d', { alpha: config.allowTransparency }); + ctx.fillStyle = config.colors.background.css; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.fillStyle = config.colors.foreground.css; + ctx.font = getFont(config.fontWeight, config); + ctx.textBaseline = 'middle'; + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2); + ctx.restore(); + } + ctx.save(); + ctx.font = getFont(config.fontWeightBold, config); + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5); + ctx.restore(); + } + ctx.restore(); + ctx.font = getFont(config.fontWeight, config); + for (var colorIndex = 0; colorIndex < 16; colorIndex++) { + var y = (colorIndex + 2) * cellHeight; + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); + ctx.restore(); + } + } + ctx.font = getFont(config.fontWeightBold, config); + for (var colorIndex = 0; colorIndex < 16; colorIndex++) { + var y = (colorIndex + 2 + 16) * cellHeight; + for (var i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); + ctx.restore(); + } + } + ctx.restore(); + if (!('createImageBitmap' in context) || Platform_1.isFirefox || Platform_1.isSafari) { + return canvas; + } + var charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + clearColor(charAtlasImageData, config.colors.background); + return context.createImageBitmap(charAtlasImageData); +} +exports.generateStaticCharAtlasTexture = generateStaticCharAtlasTexture; +function clearColor(imageData, color) { + var isEmpty = true; + var r = color.rgba >>> 24; + var g = color.rgba >>> 16 & 0xFF; + var b = color.rgba >>> 8 & 0xFF; + for (var offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } + else { + isEmpty = false; + } + } + return isEmpty; +} +exports.clearColor = clearColor; +function getFont(fontWeight, config) { + return fontWeight + " " + config.fontSize * config.devicePixelRatio + "px " + config.fontFamily; +} + +},{"../../common/Platform":25,"./Types":55}],50:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("../../common/Types"); +function generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors) { + var clonedColors = { + foreground: colors.foreground, + background: colors.background, + cursor: null, + cursorAccent: null, + selection: null, + ansi: colors.ansi.slice(0, 16) + }; + return { + type: terminal.options.experimentalCharAtlas, + devicePixelRatio: window.devicePixelRatio, + scaledCharWidth: scaledCharWidth, + scaledCharHeight: scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + fontWeight: terminal.options.fontWeight, + fontWeightBold: terminal.options.fontWeightBold, + allowTransparency: terminal.options.allowTransparency, + colors: clonedColors + }; +} +exports.generateConfig = generateConfig; +function configEquals(a, b) { + for (var i = 0; i < a.colors.ansi.length; i++) { + if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) { + return false; + } + } + return a.type === b.type && + a.devicePixelRatio === b.devicePixelRatio && + a.fontFamily === b.fontFamily && + a.fontSize === b.fontSize && + a.fontWeight === b.fontWeight && + a.fontWeightBold === b.fontWeightBold && + a.allowTransparency === b.allowTransparency && + a.scaledCharWidth === b.scaledCharWidth && + a.scaledCharHeight === b.scaledCharHeight && + a.colors.foreground === b.colors.foreground && + a.colors.background === b.colors.background; +} +exports.configEquals = configEquals; +function is256Color(colorCode) { + return colorCode < Types_1.DEFAULT_COLOR; +} +exports.is256Color = is256Color; + +},{"../../common/Types":27}],51:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./Types"); +var BaseCharAtlas_1 = require("./BaseCharAtlas"); +var ColorManager_1 = require("../../ui/ColorManager"); +var CharAtlasGenerator_1 = require("./CharAtlasGenerator"); +var LRUMap_1 = require("./LRUMap"); +var Platform_1 = require("../../common/Platform"); +var TEXTURE_WIDTH = 1024; +var TEXTURE_HEIGHT = 1024; +var TRANSPARENT_COLOR = { + css: 'rgba(0, 0, 0, 0)', + rgba: 0 +}; +var FRAME_CACHE_DRAW_LIMIT = 100; +var GLYPH_BITMAP_COMMIT_DELAY = 100; +function getGlyphCacheKey(glyph) { + return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); +} +exports.getGlyphCacheKey = getGlyphCacheKey; +var DynamicCharAtlas = (function (_super) { + __extends(DynamicCharAtlas, _super); + function DynamicCharAtlas(document, _config) { + var _this = _super.call(this) || this; + _this._config = _config; + _this._drawToCacheCount = 0; + _this._glyphsWaitingOnBitmap = []; + _this._bitmapCommitTimeout = null; + _this._bitmap = null; + _this._cacheCanvas = document.createElement('canvas'); + _this._cacheCanvas.width = TEXTURE_WIDTH; + _this._cacheCanvas.height = TEXTURE_HEIGHT; + _this._cacheCtx = _this._cacheCanvas.getContext('2d', { alpha: true }); + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = _this._config.scaledCharWidth; + tmpCanvas.height = _this._config.scaledCharHeight; + _this._tmpCtx = tmpCanvas.getContext('2d', { alpha: _this._config.allowTransparency }); + _this._width = Math.floor(TEXTURE_WIDTH / _this._config.scaledCharWidth); + _this._height = Math.floor(TEXTURE_HEIGHT / _this._config.scaledCharHeight); + var capacity = _this._width * _this._height; + _this._cacheMap = new LRUMap_1.default(capacity); + _this._cacheMap.prealloc(capacity); + return _this; + } + DynamicCharAtlas.prototype.dispose = function () { + if (this._bitmapCommitTimeout !== null) { + window.clearTimeout(this._bitmapCommitTimeout); + this._bitmapCommitTimeout = null; + } + }; + DynamicCharAtlas.prototype.beginFrame = function () { + this._drawToCacheCount = 0; + }; + DynamicCharAtlas.prototype.draw = function (ctx, glyph, x, y) { + if (glyph.code === 32) { + return true; + } + if (!this._canCache(glyph)) { + return false; + } + var glyphKey = getGlyphCacheKey(glyph); + var cacheValue = this._cacheMap.get(glyphKey); + if (cacheValue !== null && cacheValue !== undefined) { + this._drawFromCache(ctx, cacheValue, x, y); + return true; + } + else if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) { + var index = void 0; + if (this._cacheMap.size < this._cacheMap.capacity) { + index = this._cacheMap.size; + } + else { + index = this._cacheMap.peek().index; + } + var cacheValue_1 = this._drawToCache(glyph, index); + this._cacheMap.set(glyphKey, cacheValue_1); + this._drawFromCache(ctx, cacheValue_1, x, y); + return true; + } + return false; + }; + DynamicCharAtlas.prototype._canCache = function (glyph) { + return glyph.code < 256; + }; + DynamicCharAtlas.prototype._toCoordinateX = function (index) { + return (index % this._width) * this._config.scaledCharWidth; + }; + DynamicCharAtlas.prototype._toCoordinateY = function (index) { + return Math.floor(index / this._width) * this._config.scaledCharHeight; + }; + DynamicCharAtlas.prototype._drawFromCache = function (ctx, cacheValue, x, y) { + if (cacheValue.isEmpty) { + return; + } + var cacheX = this._toCoordinateX(cacheValue.index); + var cacheY = this._toCoordinateY(cacheValue.index); + ctx.drawImage(cacheValue.inBitmap ? this._bitmap : this._cacheCanvas, cacheX, cacheY, this._config.scaledCharWidth, this._config.scaledCharHeight, x, y, this._config.scaledCharWidth, this._config.scaledCharHeight); + }; + DynamicCharAtlas.prototype._getColorFromAnsiIndex = function (idx) { + if (idx < this._config.colors.ansi.length) { + return this._config.colors.ansi[idx]; + } + return ColorManager_1.DEFAULT_ANSI_COLORS[idx]; + }; + DynamicCharAtlas.prototype._getBackgroundColor = function (glyph) { + if (this._config.allowTransparency) { + return TRANSPARENT_COLOR; + } + else if (glyph.bg === Types_1.INVERTED_DEFAULT_COLOR) { + return this._config.colors.foreground; + } + else if (glyph.bg < 256) { + return this._getColorFromAnsiIndex(glyph.bg); + } + return this._config.colors.background; + }; + DynamicCharAtlas.prototype._getForegroundColor = function (glyph) { + if (glyph.fg === Types_1.INVERTED_DEFAULT_COLOR) { + return this._config.colors.background; + } + else if (glyph.fg < 256) { + return this._getColorFromAnsiIndex(glyph.fg); + } + return this._config.colors.foreground; + }; + DynamicCharAtlas.prototype._drawToCache = function (glyph, index) { + this._drawToCacheCount++; + this._tmpCtx.save(); + var backgroundColor = this._getBackgroundColor(glyph); + this._tmpCtx.globalCompositeOperation = 'copy'; + this._tmpCtx.fillStyle = backgroundColor.css; + this._tmpCtx.fillRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); + this._tmpCtx.globalCompositeOperation = 'source-over'; + var fontWeight = glyph.bold ? this._config.fontWeightBold : this._config.fontWeight; + var fontStyle = glyph.italic ? 'italic' : ''; + this._tmpCtx.font = + fontStyle + " " + fontWeight + " " + this._config.fontSize * this._config.devicePixelRatio + "px " + this._config.fontFamily; + this._tmpCtx.textBaseline = 'middle'; + this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; + if (glyph.dim) { + this._tmpCtx.globalAlpha = Types_1.DIM_OPACITY; + } + this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight / 2); + this._tmpCtx.restore(); + var imageData = this._tmpCtx.getImageData(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); + var isEmpty = false; + if (!this._config.allowTransparency) { + isEmpty = CharAtlasGenerator_1.clearColor(imageData, backgroundColor); + } + var x = this._toCoordinateX(index); + var y = this._toCoordinateY(index); + this._cacheCtx.putImageData(imageData, x, y); + var cacheValue = { + index: index, + isEmpty: isEmpty, + inBitmap: false + }; + this._addGlyphToBitmap(cacheValue); + return cacheValue; + }; + DynamicCharAtlas.prototype._addGlyphToBitmap = function (cacheValue) { + var _this = this; + if (!('createImageBitmap' in window) || Platform_1.isFirefox || Platform_1.isSafari) { + return; + } + this._glyphsWaitingOnBitmap.push(cacheValue); + if (this._bitmapCommitTimeout !== null) { + return; + } + this._bitmapCommitTimeout = window.setTimeout(function () { return _this._generateBitmap(); }, GLYPH_BITMAP_COMMIT_DELAY); + }; + DynamicCharAtlas.prototype._generateBitmap = function () { + var _this = this; + var glyphsMovingToBitmap = this._glyphsWaitingOnBitmap; + this._glyphsWaitingOnBitmap = []; + window.createImageBitmap(this._cacheCanvas).then(function (bitmap) { + _this._bitmap = bitmap; + for (var i = 0; i < glyphsMovingToBitmap.length; i++) { + var value = glyphsMovingToBitmap[i]; + value.inBitmap = true; + } + }); + this._bitmapCommitTimeout = null; + }; + return DynamicCharAtlas; +}(BaseCharAtlas_1.default)); +exports.default = DynamicCharAtlas; + +},{"../../common/Platform":25,"../../ui/ColorManager":58,"./BaseCharAtlas":47,"./CharAtlasGenerator":49,"./LRUMap":52,"./Types":55}],52:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LRUMap = (function () { + function LRUMap(capacity) { + this.capacity = capacity; + this._map = {}; + this._head = null; + this._tail = null; + this._nodePool = []; + this.size = 0; + } + LRUMap.prototype._unlinkNode = function (node) { + var prev = node.prev; + var next = node.next; + if (node === this._head) { + this._head = next; + } + if (node === this._tail) { + this._tail = prev; + } + if (prev !== null) { + prev.next = next; + } + if (next !== null) { + next.prev = prev; + } + }; + LRUMap.prototype._appendNode = function (node) { + var tail = this._tail; + if (tail !== null) { + tail.next = node; + } + node.prev = tail; + node.next = null; + this._tail = node; + if (this._head === null) { + this._head = node; + } + }; + LRUMap.prototype.prealloc = function (count) { + var nodePool = this._nodePool; + for (var i = 0; i < count; i++) { + nodePool.push({ + prev: null, + next: null, + key: null, + value: null + }); + } + }; + LRUMap.prototype.get = function (key) { + var node = this._map[key]; + if (node !== undefined) { + this._unlinkNode(node); + this._appendNode(node); + return node.value; + } + return null; + }; + LRUMap.prototype.peekValue = function (key) { + var node = this._map[key]; + if (node !== undefined) { + return node.value; + } + return null; + }; + LRUMap.prototype.peek = function () { + var head = this._head; + return head === null ? null : head.value; + }; + LRUMap.prototype.set = function (key, value) { + var node = this._map[key]; + if (node !== undefined) { + node = this._map[key]; + this._unlinkNode(node); + node.value = value; + } + else if (this.size >= this.capacity) { + node = this._head; + this._unlinkNode(node); + delete this._map[node.key]; + node.key = key; + node.value = value; + this._map[key] = node; + } + else { + var nodePool = this._nodePool; + if (nodePool.length > 0) { + node = nodePool.pop(); + node.key = key; + node.value = value; + } + else { + node = { + prev: null, + next: null, + key: key, + value: value + }; + } + this._map[key] = node; + this.size++; + } + this._appendNode(node); + }; + return LRUMap; +}()); +exports.default = LRUMap; + +},{}],53:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var BaseCharAtlas_1 = require("./BaseCharAtlas"); +var NoneCharAtlas = (function (_super) { + __extends(NoneCharAtlas, _super); + function NoneCharAtlas(document, config) { + return _super.call(this) || this; + } + NoneCharAtlas.prototype.draw = function (ctx, glyph, x, y) { + return false; + }; + return NoneCharAtlas; +}(BaseCharAtlas_1.default)); +exports.default = NoneCharAtlas; + +},{"./BaseCharAtlas":47}],54:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("./Types"); +var CharAtlasGenerator_1 = require("./CharAtlasGenerator"); +var BaseCharAtlas_1 = require("./BaseCharAtlas"); +var CharAtlasUtils_1 = require("./CharAtlasUtils"); +var Types_2 = require("../../common/Types"); +var StaticCharAtlas = (function (_super) { + __extends(StaticCharAtlas, _super); + function StaticCharAtlas(_document, _config) { + var _this = _super.call(this) || this; + _this._document = _document; + _this._config = _config; + _this._canvasFactory = function (width, height) { + var canvas = _this._document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + }; + return _this; + } + StaticCharAtlas.prototype._doWarmUp = function () { + var _this = this; + var result = CharAtlasGenerator_1.generateStaticCharAtlasTexture(window, this._canvasFactory, this._config); + if (result instanceof HTMLCanvasElement) { + this._texture = result; + } + else { + result.then(function (texture) { + _this._texture = texture; + }); + } + }; + StaticCharAtlas.prototype._isCached = function (glyph, colorIndex) { + var isAscii = glyph.code < 256; + var isBasicColor = glyph.fg < 16; + var isDefaultColor = glyph.fg === Types_2.DEFAULT_COLOR; + var isDefaultBackground = glyph.bg === Types_2.DEFAULT_COLOR; + return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic; + }; + StaticCharAtlas.prototype.draw = function (ctx, glyph, x, y) { + if (this._texture === null || this._texture === undefined) { + return false; + } + var colorIndex = 0; + if (CharAtlasUtils_1.is256Color(glyph.fg)) { + colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0); + } + else if (glyph.fg === Types_2.DEFAULT_COLOR) { + if (glyph.bold) { + colorIndex = 1; + } + } + if (!this._isCached(glyph, colorIndex)) { + return false; + } + ctx.save(); + var charAtlasCellWidth = this._config.scaledCharWidth + Types_1.CHAR_ATLAS_CELL_SPACING; + var charAtlasCellHeight = this._config.scaledCharHeight + Types_1.CHAR_ATLAS_CELL_SPACING; + if (glyph.dim) { + ctx.globalAlpha = Types_1.DIM_OPACITY; + } + ctx.drawImage(this._texture, glyph.code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, charAtlasCellWidth, this._config.scaledCharHeight, x, y, charAtlasCellWidth, this._config.scaledCharHeight); + ctx.restore(); + return true; + }; + return StaticCharAtlas; +}(BaseCharAtlas_1.default)); +exports.default = StaticCharAtlas; + +},{"../../common/Types":27,"./BaseCharAtlas":47,"./CharAtlasGenerator":49,"./CharAtlasUtils":50,"./Types":55}],55:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.INVERTED_DEFAULT_COLOR = 257; +exports.DIM_OPACITY = 0.5; +exports.CHAR_ATLAS_CELL_SPACING = 1; + +},{}],56:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var DomRendererRowFactory_1 = require("./DomRendererRowFactory"); +var Types_1 = require("../atlas/Types"); +var Lifecycle_1 = require("../../common/Lifecycle"); +var TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; +var ROW_CONTAINER_CLASS = 'xterm-rows'; +var FG_CLASS_PREFIX = 'xterm-fg-'; +var BG_CLASS_PREFIX = 'xterm-bg-'; +var FOCUS_CLASS = 'xterm-focus'; +var SELECTION_CLASS = 'xterm-selection'; +var nextTerminalId = 1; +var DomRenderer = (function (_super) { + __extends(DomRenderer, _super); + function DomRenderer(_terminal, _colors) { + var _this = _super.call(this) || this; + _this._terminal = _terminal; + _this._colors = _colors; + _this._terminalClass = nextTerminalId++; + _this._rowElements = []; + _this._rowContainer = document.createElement('div'); + _this._rowContainer.classList.add(ROW_CONTAINER_CLASS); + _this._rowContainer.style.lineHeight = 'normal'; + _this._rowContainer.setAttribute('aria-hidden', 'true'); + _this._refreshRowElements(_this._terminal.cols, _this._terminal.rows); + _this._selectionContainer = document.createElement('div'); + _this._selectionContainer.classList.add(SELECTION_CLASS); + _this._selectionContainer.setAttribute('aria-hidden', 'true'); + _this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + _this._updateDimensions(); + _this._injectCss(); + _this._rowFactory = new DomRendererRowFactory_1.DomRendererRowFactory(_terminal.options, document); + _this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + _this._terminalClass); + _this._terminal.screenElement.appendChild(_this._rowContainer); + _this._terminal.screenElement.appendChild(_this._selectionContainer); + _this._terminal.linkifier.onLinkHover(function (e) { return _this._onLinkHover(e); }); + _this._terminal.linkifier.onLinkLeave(function (e) { return _this._onLinkLeave(e); }); + return _this; + } + DomRenderer.prototype.dispose = function () { + this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); + this._terminal.screenElement.removeChild(this._rowContainer); + this._terminal.screenElement.removeChild(this._selectionContainer); + this._terminal.screenElement.removeChild(this._themeStyleElement); + this._terminal.screenElement.removeChild(this._dimensionsStyleElement); + _super.prototype.dispose.call(this); + }; + DomRenderer.prototype._updateDimensions = function () { + var _this = this; + this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCharLeft = 0; + this.dimensions.scaledCharTop = 0; + this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols; + this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows; + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + this._rowElements.forEach(function (element) { + element.style.width = _this.dimensions.canvasWidth + "px"; + element.style.height = _this.dimensions.actualCellHeight + "px"; + element.style.lineHeight = _this.dimensions.actualCellHeight + "px"; + element.style.overflow = 'hidden'; + }); + if (!this._dimensionsStyleElement) { + this._dimensionsStyleElement = document.createElement('style'); + this._terminal.screenElement.appendChild(this._dimensionsStyleElement); + } + var styles = this._terminalSelector + " ." + ROW_CONTAINER_CLASS + " span {" + + " display: inline-block;" + + " height: 100%;" + + " vertical-align: top;" + + (" width: " + this.dimensions.actualCellWidth + "px") + + "}"; + this._dimensionsStyleElement.innerHTML = styles; + this._selectionContainer.style.height = this._terminal._viewportElement.style.height; + this._terminal.screenElement.style.width = this.dimensions.canvasWidth + "px"; + this._terminal.screenElement.style.height = this.dimensions.canvasHeight + "px"; + }; + DomRenderer.prototype.setColors = function (colors) { + this._colors = colors; + this._injectCss(); + }; + DomRenderer.prototype._injectCss = function () { + var _this = this; + if (!this._themeStyleElement) { + this._themeStyleElement = document.createElement('style'); + this._terminal.screenElement.appendChild(this._themeStyleElement); + } + var styles = this._terminalSelector + " ." + ROW_CONTAINER_CLASS + " {" + + (" color: " + this._colors.foreground.css + ";") + + (" background-color: " + this._colors.background.css + ";") + + (" font-family: " + this._terminal.options.fontFamily + ";") + + (" font-size: " + this._terminal.options.fontSize + "px;") + + "}"; + styles += + this._terminalSelector + " span:not(." + DomRendererRowFactory_1.BOLD_CLASS + ") {" + + (" font-weight: " + this._terminal.options.fontWeight + ";") + + "}" + + (this._terminalSelector + " span." + DomRendererRowFactory_1.BOLD_CLASS + " {") + + (" font-weight: " + this._terminal.options.fontWeightBold + ";") + + "}" + + (this._terminalSelector + " span." + DomRendererRowFactory_1.ITALIC_CLASS + " {") + + " font-style: italic;" + + "}"; + styles += + "@keyframes blink {" + + " 0% { opacity: 1.0; }" + + " 50% { opacity: 0.0; }" + + " 100% { opacity: 1.0; }" + + "}"; + styles += + this._terminalSelector + " ." + ROW_CONTAINER_CLASS + ":not(." + FOCUS_CLASS + ") ." + DomRendererRowFactory_1.CURSOR_CLASS + " {" + + (" outline: 1px solid " + this._colors.cursor.css + ";") + + " outline-offset: -1px;" + + "}" + + (this._terminalSelector + " ." + ROW_CONTAINER_CLASS + "." + FOCUS_CLASS + " ." + DomRendererRowFactory_1.CURSOR_CLASS + "." + DomRendererRowFactory_1.CURSOR_BLINK_CLASS + " {") + + " animation: blink 1s step-end infinite;" + + "}" + + (this._terminalSelector + " ." + ROW_CONTAINER_CLASS + "." + FOCUS_CLASS + " ." + DomRendererRowFactory_1.CURSOR_CLASS + "." + DomRendererRowFactory_1.CURSOR_STYLE_BLOCK_CLASS + " {") + + (" background-color: " + this._colors.cursor.css + ";") + + (" color: " + this._colors.cursorAccent.css + ";") + + "}" + + (this._terminalSelector + " ." + ROW_CONTAINER_CLASS + "." + FOCUS_CLASS + " ." + DomRendererRowFactory_1.CURSOR_CLASS + "." + DomRendererRowFactory_1.CURSOR_STYLE_BAR_CLASS + " {") + + (" box-shadow: 1px 0 0 " + this._colors.cursor.css + " inset;") + + "}" + + (this._terminalSelector + " ." + ROW_CONTAINER_CLASS + "." + FOCUS_CLASS + " ." + DomRendererRowFactory_1.CURSOR_CLASS + "." + DomRendererRowFactory_1.CURSOR_STYLE_UNDERLINE_CLASS + " {") + + (" box-shadow: 0 -1px 0 " + this._colors.cursor.css + " inset;") + + "}"; + styles += + this._terminalSelector + " ." + SELECTION_CLASS + " {" + + " position: absolute;" + + " top: 0;" + + " left: 0;" + + " z-index: 1;" + + " pointer-events: none;" + + "}" + + (this._terminalSelector + " ." + SELECTION_CLASS + " div {") + + " position: absolute;" + + (" background-color: " + this._colors.selection.css + ";") + + "}"; + this._colors.ansi.forEach(function (c, i) { + styles += + _this._terminalSelector + " ." + FG_CLASS_PREFIX + i + " { color: " + c.css + "; }" + + (_this._terminalSelector + " ." + BG_CLASS_PREFIX + i + " { background-color: " + c.css + "; }"); + }); + styles += + this._terminalSelector + " ." + FG_CLASS_PREFIX + Types_1.INVERTED_DEFAULT_COLOR + " { color: " + this._colors.background.css + "; }" + + (this._terminalSelector + " ." + BG_CLASS_PREFIX + Types_1.INVERTED_DEFAULT_COLOR + " { background-color: " + this._colors.foreground.css + "; }"); + this._themeStyleElement.innerHTML = styles; + }; + DomRenderer.prototype.onDevicePixelRatioChange = function () { + this._updateDimensions(); + }; + DomRenderer.prototype._refreshRowElements = function (cols, rows) { + for (var i = this._rowElements.length; i <= rows; i++) { + var row = document.createElement('div'); + this._rowContainer.appendChild(row); + this._rowElements.push(row); + } + while (this._rowElements.length > rows) { + this._rowContainer.removeChild(this._rowElements.pop()); + } + }; + DomRenderer.prototype.onResize = function (cols, rows) { + this._refreshRowElements(cols, rows); + this._updateDimensions(); + }; + DomRenderer.prototype.onCharSizeChanged = function () { + this._updateDimensions(); + }; + DomRenderer.prototype.onBlur = function () { + this._rowContainer.classList.remove(FOCUS_CLASS); + }; + DomRenderer.prototype.onFocus = function () { + this._rowContainer.classList.add(FOCUS_CLASS); + }; + DomRenderer.prototype.onSelectionChanged = function (start, end, columnSelectMode) { + while (this._selectionContainer.children.length) { + this._selectionContainer.removeChild(this._selectionContainer.children[0]); + } + if (!start || !end) { + return; + } + var viewportStartRow = start[1] - this._terminal.buffer.ydisp; + var viewportEndRow = end[1] - this._terminal.buffer.ydisp; + var viewportCappedStartRow = Math.max(viewportStartRow, 0); + var viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1); + if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) { + return; + } + var documentFragment = document.createDocumentFragment(); + if (columnSelectMode) { + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)); + } + else { + var startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; + var endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol)); + var middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1; + documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount)); + if (viewportCappedStartRow !== viewportCappedEndRow) { + var endCol_1 = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols; + documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol_1)); + } + } + this._selectionContainer.appendChild(documentFragment); + }; + DomRenderer.prototype._createSelectionElement = function (row, colStart, colEnd, rowCount) { + if (rowCount === void 0) { rowCount = 1; } + var element = document.createElement('div'); + element.style.height = rowCount * this.dimensions.actualCellHeight + "px"; + element.style.top = row * this.dimensions.actualCellHeight + "px"; + element.style.left = colStart * this.dimensions.actualCellWidth + "px"; + element.style.width = this.dimensions.actualCellWidth * (colEnd - colStart) + "px"; + return element; + }; + DomRenderer.prototype.onCursorMove = function () { + }; + DomRenderer.prototype.onOptionsChanged = function () { + this._updateDimensions(); + this._injectCss(); + this._terminal.refresh(0, this._terminal.rows - 1); + }; + DomRenderer.prototype.clear = function () { + this._rowElements.forEach(function (e) { return e.innerHTML = ''; }); + }; + DomRenderer.prototype.renderRows = function (start, end) { + var terminal = this._terminal; + var cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y; + var cursorX = this._terminal.buffer.x; + var cursorBlink = this._terminal.options.cursorBlink; + for (var y = start; y <= end; y++) { + var rowElement = this._rowElements[y]; + rowElement.innerHTML = ''; + var row = y + terminal.buffer.ydisp; + var lineData = terminal.buffer.lines.get(row); + var cursorStyle = terminal.options.cursorStyle; + rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols)); + } + }; + Object.defineProperty(DomRenderer.prototype, "_terminalSelector", { + get: function () { + return "." + TERMINAL_CLASS_PREFIX + this._terminalClass; + }, + enumerable: true, + configurable: true + }); + DomRenderer.prototype.registerCharacterJoiner = function (handler) { return -1; }; + DomRenderer.prototype.deregisterCharacterJoiner = function (joinerId) { return false; }; + DomRenderer.prototype._onLinkHover = function (e) { + this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); + }; + DomRenderer.prototype._onLinkLeave = function (e) { + this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false); + }; + DomRenderer.prototype._setCellUnderline = function (x, x2, y, y2, cols, enabled) { + while (x !== x2 || y !== y2) { + var row = this._rowElements[y]; + if (!row) { + return; + } + var span = row.children[x]; + if (span) { + span.style.textDecoration = enabled ? 'underline' : 'none'; + } + if (++x >= cols) { + x = 0; + y++; + } + } + }; + return DomRenderer; +}(Lifecycle_1.Disposable)); +exports.DomRenderer = DomRenderer; + +},{"../../common/Lifecycle":24,"../atlas/Types":55,"./DomRendererRowFactory":57}],57:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Types_1 = require("../atlas/Types"); +var BufferLine_1 = require("../../core/buffer/BufferLine"); +exports.BOLD_CLASS = 'xterm-bold'; +exports.DIM_CLASS = 'xterm-dim'; +exports.ITALIC_CLASS = 'xterm-italic'; +exports.UNDERLINE_CLASS = 'xterm-underline'; +exports.CURSOR_CLASS = 'xterm-cursor'; +exports.CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; +exports.CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; +exports.CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar'; +exports.CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; +var DomRendererRowFactory = (function () { + function DomRendererRowFactory(_terminalOptions, _document) { + this._terminalOptions = _terminalOptions; + this._document = _document; + this._workCell = new BufferLine_1.CellData(); + } + DomRendererRowFactory.prototype.createRow = function (lineData, isCursorRow, cursorStyle, cursorX, cursorBlink, cellWidth, cols) { + var fragment = this._document.createDocumentFragment(); + var lineLength = 0; + for (var x = Math.min(lineData.length, cols) - 1; x >= 0; x--) { + if (lineData.loadCell(x, this._workCell).getCode() !== BufferLine_1.NULL_CELL_CODE || (isCursorRow && x === cursorX)) { + lineLength = x + 1; + break; + } + } + for (var x = 0; x < lineLength; x++) { + lineData.loadCell(x, this._workCell); + var width = this._workCell.getWidth(); + if (width === 0) { + continue; + } + var charElement = this._document.createElement('span'); + if (width > 1) { + charElement.style.width = cellWidth * width + "px"; + } + if (isCursorRow && x === cursorX) { + charElement.classList.add(exports.CURSOR_CLASS); + if (cursorBlink) { + charElement.classList.add(exports.CURSOR_BLINK_CLASS); + } + switch (cursorStyle) { + case 'bar': + charElement.classList.add(exports.CURSOR_STYLE_BAR_CLASS); + break; + case 'underline': + charElement.classList.add(exports.CURSOR_STYLE_UNDERLINE_CLASS); + break; + default: + charElement.classList.add(exports.CURSOR_STYLE_BLOCK_CLASS); + break; + } + } + if (this._workCell.isBold() && this._terminalOptions.enableBold) { + charElement.classList.add(exports.BOLD_CLASS); + } + if (this._workCell.isItalic()) { + charElement.classList.add(exports.ITALIC_CLASS); + } + if (this._workCell.isDim()) { + charElement.classList.add(exports.DIM_CLASS); + } + if (this._workCell.isUnderline()) { + charElement.classList.add(exports.UNDERLINE_CLASS); + } + charElement.textContent = this._workCell.getChars() || BufferLine_1.WHITESPACE_CELL_CHAR; + var swapColor = this._workCell.isInverse(); + if (this._workCell.isFgRGB()) { + var style = charElement.getAttribute('style') || ''; + style += (swapColor ? 'background-' : '') + "color:rgb(" + (BufferLine_1.AttributeData.toColorRGB(this._workCell.getFgColor())).join(',') + ");"; + charElement.setAttribute('style', style); + } + else if (this._workCell.isFgPalette()) { + var fg = this._workCell.getFgColor(); + if (this._workCell.isBold() && fg < 8 && !swapColor && + this._terminalOptions.enableBold && this._terminalOptions.drawBoldTextInBrightColors) { + fg += 8; + } + charElement.classList.add("xterm-" + (swapColor ? 'b' : 'f') + "g-" + fg); + } + else if (swapColor) { + charElement.classList.add("xterm-bg-" + Types_1.INVERTED_DEFAULT_COLOR); + } + if (this._workCell.isBgRGB()) { + var style = charElement.getAttribute('style') || ''; + style += (swapColor ? '' : 'background-') + "color:rgb(" + (BufferLine_1.AttributeData.toColorRGB(this._workCell.getBgColor())).join(',') + ");"; + charElement.setAttribute('style', style); + } + else if (this._workCell.isBgPalette()) { + charElement.classList.add("xterm-" + (swapColor ? 'f' : 'b') + "g-" + this._workCell.getBgColor()); + } + else if (swapColor) { + charElement.classList.add("xterm-fg-" + Types_1.INVERTED_DEFAULT_COLOR); + } + fragment.appendChild(charElement); + } + return fragment; + }; + return DomRendererRowFactory; +}()); +exports.DomRendererRowFactory = DomRendererRowFactory; + +},{"../../core/buffer/BufferLine":29,"../atlas/Types":55}],58:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var DEFAULT_FOREGROUND = fromHex('#ffffff'); +var DEFAULT_BACKGROUND = fromHex('#000000'); +var DEFAULT_CURSOR = fromHex('#ffffff'); +var DEFAULT_CURSOR_ACCENT = fromHex('#000000'); +var DEFAULT_SELECTION = { + css: 'rgba(255, 255, 255, 0.3)', + rgba: 0xFFFFFF77 +}; +exports.DEFAULT_ANSI_COLORS = (function () { + var colors = [ + fromHex('#2e3436'), + fromHex('#cc0000'), + fromHex('#4e9a06'), + fromHex('#c4a000'), + fromHex('#3465a4'), + fromHex('#75507b'), + fromHex('#06989a'), + fromHex('#d3d7cf'), + fromHex('#555753'), + fromHex('#ef2929'), + fromHex('#8ae234'), + fromHex('#fce94f'), + fromHex('#729fcf'), + fromHex('#ad7fa8'), + fromHex('#34e2e2'), + fromHex('#eeeeec') + ]; + var v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (var i = 0; i < 216; i++) { + var r = v[(i / 36) % 6 | 0]; + var g = v[(i / 6) % 6 | 0]; + var b = v[i % 6]; + colors.push({ + css: "#" + toPaddedHex(r) + toPaddedHex(g) + toPaddedHex(b), + rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0 + }); + } + for (var i = 0; i < 24; i++) { + var c = 8 + i * 10; + var ch = toPaddedHex(c); + colors.push({ + css: "#" + ch + ch + ch, + rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0 + }); + } + return colors; +})(); +function fromHex(css) { + return { + css: css, + rgba: parseInt(css.slice(1), 16) << 8 | 0xFF + }; +} +function toPaddedHex(c) { + var s = c.toString(16); + return s.length < 2 ? '0' + s : s; +} +var ColorManager = (function () { + function ColorManager(document, allowTransparency) { + this.allowTransparency = allowTransparency; + var canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + var ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Could not get rendering context'); + } + this._ctx = ctx; + this._ctx.globalCompositeOperation = 'copy'; + this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); + this.colors = { + foreground: DEFAULT_FOREGROUND, + background: DEFAULT_BACKGROUND, + cursor: DEFAULT_CURSOR, + cursorAccent: DEFAULT_CURSOR_ACCENT, + selection: DEFAULT_SELECTION, + ansi: exports.DEFAULT_ANSI_COLORS.slice() + }; + } + ColorManager.prototype.setTheme = function (theme) { + if (theme === void 0) { theme = {}; } + this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); + this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); + this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); + this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); + this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + this.colors.ansi[0] = this._parseColor(theme.black, exports.DEFAULT_ANSI_COLORS[0]); + this.colors.ansi[1] = this._parseColor(theme.red, exports.DEFAULT_ANSI_COLORS[1]); + this.colors.ansi[2] = this._parseColor(theme.green, exports.DEFAULT_ANSI_COLORS[2]); + this.colors.ansi[3] = this._parseColor(theme.yellow, exports.DEFAULT_ANSI_COLORS[3]); + this.colors.ansi[4] = this._parseColor(theme.blue, exports.DEFAULT_ANSI_COLORS[4]); + this.colors.ansi[5] = this._parseColor(theme.magenta, exports.DEFAULT_ANSI_COLORS[5]); + this.colors.ansi[6] = this._parseColor(theme.cyan, exports.DEFAULT_ANSI_COLORS[6]); + this.colors.ansi[7] = this._parseColor(theme.white, exports.DEFAULT_ANSI_COLORS[7]); + this.colors.ansi[8] = this._parseColor(theme.brightBlack, exports.DEFAULT_ANSI_COLORS[8]); + this.colors.ansi[9] = this._parseColor(theme.brightRed, exports.DEFAULT_ANSI_COLORS[9]); + this.colors.ansi[10] = this._parseColor(theme.brightGreen, exports.DEFAULT_ANSI_COLORS[10]); + this.colors.ansi[11] = this._parseColor(theme.brightYellow, exports.DEFAULT_ANSI_COLORS[11]); + this.colors.ansi[12] = this._parseColor(theme.brightBlue, exports.DEFAULT_ANSI_COLORS[12]); + this.colors.ansi[13] = this._parseColor(theme.brightMagenta, exports.DEFAULT_ANSI_COLORS[13]); + this.colors.ansi[14] = this._parseColor(theme.brightCyan, exports.DEFAULT_ANSI_COLORS[14]); + this.colors.ansi[15] = this._parseColor(theme.brightWhite, exports.DEFAULT_ANSI_COLORS[15]); + }; + ColorManager.prototype._parseColor = function (css, fallback, allowTransparency) { + if (allowTransparency === void 0) { allowTransparency = this.allowTransparency; } + if (css === undefined) { + return fallback; + } + this._ctx.fillStyle = this._litmusColor; + this._ctx.fillStyle = css; + if (typeof this._ctx.fillStyle !== 'string') { + console.warn("Color: " + css + " is invalid using fallback " + fallback.css); + return fallback; + } + this._ctx.fillRect(0, 0, 1, 1); + var data = this._ctx.getImageData(0, 0, 1, 1).data; + if (!allowTransparency && data[3] !== 0xFF) { + console.warn("Color: " + css + " is using transparency, but allowTransparency is false. " + + ("Using fallback " + fallback.css + ".")); + return fallback; + } + return { + css: css, + rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0 + }; + }; + return ColorManager; +}()); +exports.ColorManager = ColorManager; + +},{}],59:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function addDisposableDomListener(node, type, handler, useCapture) { + node.addEventListener(type, handler, useCapture); + return { + dispose: function () { + if (!handler) { + return; + } + node.removeEventListener(type, handler, useCapture); + } + }; +} +exports.addDisposableDomListener = addDisposableDomListener; + +},{}],60:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var RenderDebouncer = (function () { + function RenderDebouncer(_renderCallback) { + this._renderCallback = _renderCallback; + } + RenderDebouncer.prototype.dispose = function () { + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = undefined; + } + }; + RenderDebouncer.prototype.refresh = function (rowStart, rowEnd, rowCount) { + var _this = this; + this._rowCount = rowCount; + rowStart = rowStart !== undefined ? rowStart : 0; + rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1; + this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; + if (this._animationFrame) { + return; + } + this._animationFrame = window.requestAnimationFrame(function () { return _this._innerRefresh(); }); + }; + RenderDebouncer.prototype._innerRefresh = function () { + if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) { + return; + } + this._rowStart = Math.max(this._rowStart, 0); + this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1); + this._renderCallback(this._rowStart, this._rowEnd); + this._rowStart = undefined; + this._rowEnd = undefined; + this._animationFrame = undefined; + }; + return RenderDebouncer; +}()); +exports.RenderDebouncer = RenderDebouncer; + +},{}],61:[function(require,module,exports){ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var Lifecycle_1 = require("../common/Lifecycle"); +var ScreenDprMonitor = (function (_super) { + __extends(ScreenDprMonitor, _super); + function ScreenDprMonitor() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this._currentDevicePixelRatio = window.devicePixelRatio; + return _this; + } + ScreenDprMonitor.prototype.setListener = function (listener) { + var _this = this; + if (this._listener) { + this.clearListener(); + } + this._listener = listener; + this._outerListener = function () { + if (!_this._listener) { + return; + } + _this._listener(window.devicePixelRatio, _this._currentDevicePixelRatio); + _this._updateDpr(); + }; + this._updateDpr(); + }; + ScreenDprMonitor.prototype.dispose = function () { + _super.prototype.dispose.call(this); + this.clearListener(); + }; + ScreenDprMonitor.prototype._updateDpr = function () { + if (!this._resolutionMediaMatchList || !this._outerListener) { + return; + } + this._resolutionMediaMatchList.removeListener(this._outerListener); + this._currentDevicePixelRatio = window.devicePixelRatio; + this._resolutionMediaMatchList = window.matchMedia("screen and (resolution: " + window.devicePixelRatio + "dppx)"); + this._resolutionMediaMatchList.addListener(this._outerListener); + }; + ScreenDprMonitor.prototype.clearListener = function () { + if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) { + return; + } + this._resolutionMediaMatchList.removeListener(this._outerListener); + this._resolutionMediaMatchList = undefined; + this._listener = undefined; + this._outerListener = undefined; + }; + return ScreenDprMonitor; +}(Lifecycle_1.Disposable)); +exports.ScreenDprMonitor = ScreenDprMonitor; + +},{"../common/Lifecycle":24}],62:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Terminal_1 = require("./public/Terminal"); +module.exports = Terminal_1.Terminal; + +},{"./public/Terminal":37}]},{},[62])(62) +}); +//# sourceMappingURL=xterm.js.map diff --git a/handlers/www/assets/xterm/xterm.js.map b/handlers/www/assets/xterm/xterm.js.map new file mode 100644 index 0000000000000000000000000000000000000000..86c2325ee7a2c55369179cfd1721680e966548da --- /dev/null +++ b/handlers/www/assets/xterm/xterm.js.map @@ -0,0 +1 @@ +{"version":3,"file":"xterm.js","sources":["../src/xterm.ts","../src/ui/ScreenDprMonitor.ts","../src/ui/RenderDebouncer.ts","../src/ui/Lifecycle.ts","../src/ui/ColorManager.ts","../src/renderer/dom/DomRendererRowFactory.ts","../src/renderer/dom/DomRenderer.ts","../src/renderer/atlas/Types.ts","../src/renderer/atlas/StaticCharAtlas.ts","../src/renderer/atlas/NoneCharAtlas.ts","../src/renderer/atlas/LRUMap.ts","../src/renderer/atlas/DynamicCharAtlas.ts","../src/renderer/atlas/CharAtlasUtils.ts","../src/renderer/atlas/CharAtlasGenerator.ts","../src/renderer/atlas/CharAtlasCache.ts","../src/renderer/atlas/BaseCharAtlas.ts","../src/renderer/TextRenderLayer.ts","../src/renderer/SelectionRenderLayer.ts","../src/renderer/Renderer.ts","../src/renderer/RenderCoordinator.ts","../src/renderer/LinkRenderLayer.ts","../src/renderer/GridCache.ts","../src/renderer/CursorRenderLayer.ts","../src/renderer/CharacterJoinerRegistry.ts","../src/renderer/BaseRenderLayer.ts","../src/public/Terminal.ts","../src/public/AddonManager.ts","../src/handlers/AltClickHandler.ts","../src/core/input/TextDecoder.ts","../src/core/input/Keyboard.ts","../src/core/data/Charsets.ts","../src/core/buffer/Marker.ts","../src/core/buffer/BufferReflow.ts","../src/core/buffer/BufferLine.ts","../src/common/data/EscapeSequences.ts","../src/common/Types.ts","../src/common/TypedArrayUtils.ts","../src/common/Platform.ts","../src/common/Lifecycle.ts","../src/common/EventEmitter2.ts","../src/common/EventEmitter.ts","../src/common/Clone.ts","../src/common/CircularList.ts","../src/WindowsMode.ts","../src/Viewport.ts","../src/Terminal.ts","../src/Strings.ts","../src/SoundManager.ts","../src/SelectionModel.ts","../src/SelectionManager.ts","../src/MouseZoneManager.ts","../src/MouseHelper.ts","../src/Linkifier.ts","../src/InputHandler.ts","../src/EscapeSequenceParser.ts","../src/CompositionHelper.ts","../src/Clipboard.ts","../src/CharWidth.ts","../src/CharMeasure.ts","../src/BufferSet.ts","../src/Buffer.ts","../src/AccessibilityManager.ts","../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This file is the entry point for browserify.\n */\n\nimport { Terminal } from './public/Terminal';\n\nmodule.exports = Terminal;\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Disposable } from '../common/Lifecycle';\n\nexport type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void;\n\n/**\n * The screen device pixel ratio monitor allows listening for when the\n * window.devicePixelRatio value changes. This is done not with polling but with\n * the use of window.matchMedia to watch media queries. When the event fires,\n * the listener will be reattached using a different media query to ensure that\n * any further changes will register.\n *\n * The listener should fire on both window zoom changes and switching to a\n * monitor with a different DPI.\n */\nexport class ScreenDprMonitor extends Disposable {\n private _currentDevicePixelRatio: number = window.devicePixelRatio;\n private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;\n private _listener: ScreenDprListener | undefined;\n private _resolutionMediaMatchList: MediaQueryList | undefined;\n\n public setListener(listener: ScreenDprListener): void {\n if (this._listener) {\n this.clearListener();\n }\n this._listener = listener;\n this._outerListener = () => {\n if (!this._listener) {\n return;\n }\n this._listener(window.devicePixelRatio, this._currentDevicePixelRatio);\n this._updateDpr();\n };\n this._updateDpr();\n }\n\n public dispose(): void {\n super.dispose();\n this.clearListener();\n }\n\n private _updateDpr(): void {\n if (!this._resolutionMediaMatchList || !this._outerListener) {\n return;\n }\n\n // Clear listeners for old DPR\n this._resolutionMediaMatchList.removeListener(this._outerListener);\n\n // Add listeners for new DPR\n this._currentDevicePixelRatio = window.devicePixelRatio;\n this._resolutionMediaMatchList = window.matchMedia(`screen and (resolution: ${window.devicePixelRatio}dppx)`);\n this._resolutionMediaMatchList.addListener(this._outerListener);\n }\n\n public clearListener(): void {\n if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) {\n return;\n }\n this._resolutionMediaMatchList.removeListener(this._outerListener);\n this._resolutionMediaMatchList = undefined;\n this._listener = undefined;\n this._outerListener = undefined;\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from '../common/Types';\n\n/**\n * Debounces calls to render terminal rows using animation frames.\n */\nexport class RenderDebouncer implements IDisposable {\n private _rowStart: number | undefined;\n private _rowEnd: number | undefined;\n private _rowCount: number | undefined;\n private _animationFrame: number | undefined;\n\n constructor(\n private _renderCallback: (start: number, end: number) => void\n ) {\n }\n\n public dispose(): void {\n if (this._animationFrame) {\n window.cancelAnimationFrame(this._animationFrame);\n this._animationFrame = undefined;\n }\n }\n\n public refresh(rowStart: number, rowEnd: number, rowCount: number): void {\n this._rowCount = rowCount;\n // Get the min/max row start/end for the arg values\n rowStart = rowStart !== undefined ? rowStart : 0;\n rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;\n // Set the properties to the updated values\n this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;\n this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;\n\n if (this._animationFrame) {\n return;\n }\n\n this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh());\n }\n\n private _innerRefresh(): void {\n // Make sure values are set\n if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {\n return;\n }\n\n // Clamp values\n this._rowStart = Math.max(this._rowStart, 0);\n this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1);\n\n // Run render callback\n this._renderCallback(this._rowStart, this._rowEnd);\n\n // Reset debouncer\n this._rowStart = undefined;\n this._rowEnd = undefined;\n this._animationFrame = undefined;\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from '../common/Types';\n\n/**\n * Adds a disposable listener to a node in the DOM, returning the disposable.\n * @param type The event type.\n * @param handler The handler for the listener.\n */\nexport function addDisposableDomListener(\n node: Element | Window | Document,\n type: string,\n handler: (e: any) => void,\n useCapture?: boolean\n): IDisposable {\n node.addEventListener(type, handler, useCapture);\n return {\n dispose: () => {\n if (!handler) {\n // Already disposed\n return;\n }\n node.removeEventListener(type, handler, useCapture);\n }\n };\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IColorManager, IColor, IColorSet, ITheme } from './Types';\n\nconst DEFAULT_FOREGROUND = fromHex('#ffffff');\nconst DEFAULT_BACKGROUND = fromHex('#000000');\nconst DEFAULT_CURSOR = fromHex('#ffffff');\nconst DEFAULT_CURSOR_ACCENT = fromHex('#000000');\nconst DEFAULT_SELECTION = {\n css: 'rgba(255, 255, 255, 0.3)',\n rgba: 0xFFFFFF77\n};\n\n// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy\n// and mutate that.\nexport const DEFAULT_ANSI_COLORS = (() => {\n const colors = [\n // dark:\n fromHex('#2e3436'),\n fromHex('#cc0000'),\n fromHex('#4e9a06'),\n fromHex('#c4a000'),\n fromHex('#3465a4'),\n fromHex('#75507b'),\n fromHex('#06989a'),\n fromHex('#d3d7cf'),\n // bright:\n fromHex('#555753'),\n fromHex('#ef2929'),\n fromHex('#8ae234'),\n fromHex('#fce94f'),\n fromHex('#729fcf'),\n fromHex('#ad7fa8'),\n fromHex('#34e2e2'),\n fromHex('#eeeeec')\n ];\n\n // Fill in the remaining 240 ANSI colors.\n // Generate colors (16-231)\n const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];\n for (let i = 0; i < 216; i++) {\n const r = v[(i / 36) % 6 | 0];\n const g = v[(i / 6) % 6 | 0];\n const b = v[i % 6];\n colors.push({\n css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,\n // Use >>> 0 to force a conversion to an unsigned int\n rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0\n });\n }\n\n // Generate greys (232-255)\n for (let i = 0; i < 24; i++) {\n const c = 8 + i * 10;\n const ch = toPaddedHex(c);\n colors.push({\n css: `#${ch}${ch}${ch}`,\n rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0\n });\n }\n\n return colors;\n})();\n\nfunction fromHex(css: string): IColor {\n return {\n css,\n rgba: parseInt(css.slice(1), 16) << 8 | 0xFF\n };\n}\n\nfunction toPaddedHex(c: number): string {\n const s = c.toString(16);\n return s.length < 2 ? '0' + s : s;\n}\n\n/**\n * Manages the source of truth for a terminal's colors.\n */\nexport class ColorManager implements IColorManager {\n public colors: IColorSet;\n private _ctx: CanvasRenderingContext2D;\n private _litmusColor: CanvasGradient;\n\n constructor(document: Document, public allowTransparency: boolean) {\n const canvas = document.createElement('canvas');\n canvas.width = 1;\n canvas.height = 1;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n throw new Error('Could not get rendering context');\n }\n this._ctx = ctx;\n this._ctx.globalCompositeOperation = 'copy';\n this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);\n this.colors = {\n foreground: DEFAULT_FOREGROUND,\n background: DEFAULT_BACKGROUND,\n cursor: DEFAULT_CURSOR,\n cursorAccent: DEFAULT_CURSOR_ACCENT,\n selection: DEFAULT_SELECTION,\n ansi: DEFAULT_ANSI_COLORS.slice()\n };\n }\n\n /**\n * Sets the terminal's theme.\n * @param theme The theme to use. If a partial theme is provided then default\n * colors will be used where colors are not defined.\n */\n public setTheme(theme: ITheme = {}): void {\n this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND);\n this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);\n this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);\n this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);\n this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);\n this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);\n this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);\n this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);\n this.colors.ansi[3] = this._parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]);\n this.colors.ansi[4] = this._parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]);\n this.colors.ansi[5] = this._parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]);\n this.colors.ansi[6] = this._parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]);\n this.colors.ansi[7] = this._parseColor(theme.white, DEFAULT_ANSI_COLORS[7]);\n this.colors.ansi[8] = this._parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]);\n this.colors.ansi[9] = this._parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]);\n this.colors.ansi[10] = this._parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]);\n this.colors.ansi[11] = this._parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]);\n this.colors.ansi[12] = this._parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]);\n this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);\n this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);\n this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);\n }\n\n private _parseColor(\n css: string | undefined,\n fallback: IColor,\n allowTransparency: boolean = this.allowTransparency\n ): IColor {\n if (css === undefined) {\n return fallback;\n }\n\n // If parsing the value results in failure, then it must be ignored, and the attribute must\n // retain its previous value.\n // -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles\n this._ctx.fillStyle = this._litmusColor;\n this._ctx.fillStyle = css;\n if (typeof this._ctx.fillStyle !== 'string') {\n console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`);\n return fallback;\n }\n\n this._ctx.fillRect(0, 0, 1, 1);\n const data = this._ctx.getImageData(0, 0, 1, 1).data;\n\n if (!allowTransparency && data[3] !== 0xFF) {\n // Ideally we'd just ignore the alpha channel, but...\n //\n // Browsers may not give back exactly the same RGB values we put in, because most/all\n // convert the color to a pre-multiplied representation. getImageData converts that back to\n // a un-premultipled representation, but the precision loss may make the RGB channels unuable\n // on their own.\n //\n // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns\n // into 0x00000000.\n //\n // \"Note: Due to the lossy nature of converting to and from premultiplied alpha color values,\n // pixels that have just been set using putImageData() might be returned to an equivalent\n // getImageData() as different values.\"\n // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation\n //\n // So let's just use the fallback color in this case instead.\n console.warn(\n `Color: ${css} is using transparency, but allowTransparency is false. ` +\n `Using fallback ${fallback.css}.`\n );\n return fallback;\n }\n\n return {\n css,\n rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0\n };\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminalOptions } from '../../Types';\nimport { IBufferLine } from '../../core/Types';\nimport { INVERTED_DEFAULT_COLOR } from '../atlas/Types';\nimport { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../core/buffer/BufferLine';\n\nexport const BOLD_CLASS = 'xterm-bold';\nexport const DIM_CLASS = 'xterm-dim';\nexport const ITALIC_CLASS = 'xterm-italic';\nexport const UNDERLINE_CLASS = 'xterm-underline';\nexport const CURSOR_CLASS = 'xterm-cursor';\nexport const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';\nexport const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block';\nexport const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar';\nexport const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';\n\nexport class DomRendererRowFactory {\n private _workCell: CellData = new CellData();\n\n constructor(\n private _terminalOptions: ITerminalOptions,\n private _document: Document\n ) {\n }\n\n public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {\n const fragment = this._document.createDocumentFragment();\n\n // Find the line length first, this prevents the need to output a bunch of\n // empty cells at the end. This cannot easily be integrated into the main\n // loop below because of the colCount feature (which can be removed after we\n // properly support reflow and disallow data to go beyond the right-side of\n // the viewport).\n let lineLength = 0;\n for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) {\n if (lineData.loadCell(x, this._workCell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {\n lineLength = x + 1;\n break;\n }\n }\n\n for (let x = 0; x < lineLength; x++) {\n lineData.loadCell(x, this._workCell);\n const width = this._workCell.getWidth();\n\n // The character to the left is a wide character, drawing is owned by the char at x-1\n if (width === 0) {\n continue;\n }\n\n const charElement = this._document.createElement('span');\n if (width > 1) {\n charElement.style.width = `${cellWidth * width}px`;\n }\n\n if (isCursorRow && x === cursorX) {\n charElement.classList.add(CURSOR_CLASS);\n\n if (cursorBlink) {\n charElement.classList.add(CURSOR_BLINK_CLASS);\n }\n\n switch (cursorStyle) {\n case 'bar':\n charElement.classList.add(CURSOR_STYLE_BAR_CLASS);\n break;\n case 'underline':\n charElement.classList.add(CURSOR_STYLE_UNDERLINE_CLASS);\n break;\n default:\n charElement.classList.add(CURSOR_STYLE_BLOCK_CLASS);\n break;\n }\n }\n\n if (this._workCell.isBold() && this._terminalOptions.enableBold) {\n charElement.classList.add(BOLD_CLASS);\n }\n\n if (this._workCell.isItalic()) {\n charElement.classList.add(ITALIC_CLASS);\n }\n\n if (this._workCell.isDim()) {\n charElement.classList.add(DIM_CLASS);\n }\n\n if (this._workCell.isUnderline()) {\n charElement.classList.add(UNDERLINE_CLASS);\n }\n\n charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;\n\n const swapColor = this._workCell.isInverse();\n\n // fg\n if (this._workCell.isFgRGB()) {\n let style = charElement.getAttribute('style') || '';\n style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`;\n charElement.setAttribute('style', style);\n } else if (this._workCell.isFgPalette()) {\n let fg = this._workCell.getFgColor();\n if (this._workCell.isBold() && fg < 8 && !swapColor &&\n this._terminalOptions.enableBold && this._terminalOptions.drawBoldTextInBrightColors) {\n fg += 8;\n }\n charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`);\n } else if (swapColor) {\n charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);\n }\n\n // bg\n if (this._workCell.isBgRGB()) {\n let style = charElement.getAttribute('style') || '';\n style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`;\n charElement.setAttribute('style', style);\n } else if (this._workCell.isBgPalette()) {\n charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`);\n } else if (swapColor) {\n charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);\n }\n\n fragment.appendChild(charElement);\n }\n return fragment;\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderer, IRenderDimensions } from '../Types';\nimport { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types';\nimport { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory';\nimport { INVERTED_DEFAULT_COLOR } from '../atlas/Types';\nimport { Disposable } from '../../common/Lifecycle';\nimport { IColorSet } from '../../ui/Types';\n\nconst TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';\nconst ROW_CONTAINER_CLASS = 'xterm-rows';\nconst FG_CLASS_PREFIX = 'xterm-fg-';\nconst BG_CLASS_PREFIX = 'xterm-bg-';\nconst FOCUS_CLASS = 'xterm-focus';\nconst SELECTION_CLASS = 'xterm-selection';\n\nlet nextTerminalId = 1;\n\n// TODO: Pull into an addon when TS composite projects allow easier sharing of code (not just\n// interfaces) between core and addons\n\n/**\n * A fallback renderer for when canvas is slow. This is not meant to be\n * particularly fast or feature complete, more just stable and usable for when\n * canvas is not an option.\n */\nexport class DomRenderer extends Disposable implements IRenderer {\n private _rowFactory: DomRendererRowFactory;\n private _terminalClass: number = nextTerminalId++;\n\n private _themeStyleElement: HTMLStyleElement;\n private _dimensionsStyleElement: HTMLStyleElement;\n private _rowContainer: HTMLElement;\n private _rowElements: HTMLElement[] = [];\n private _selectionContainer: HTMLElement;\n\n public dimensions: IRenderDimensions;\n\n constructor(\n private _terminal: ITerminal,\n private _colors: IColorSet\n ) {\n super();\n\n this._rowContainer = document.createElement('div');\n this._rowContainer.classList.add(ROW_CONTAINER_CLASS);\n this._rowContainer.style.lineHeight = 'normal';\n this._rowContainer.setAttribute('aria-hidden', 'true');\n this._refreshRowElements(this._terminal.cols, this._terminal.rows);\n this._selectionContainer = document.createElement('div');\n this._selectionContainer.classList.add(SELECTION_CLASS);\n this._selectionContainer.setAttribute('aria-hidden', 'true');\n\n this.dimensions = {\n scaledCharWidth: null,\n scaledCharHeight: null,\n scaledCellWidth: null,\n scaledCellHeight: null,\n scaledCharLeft: null,\n scaledCharTop: null,\n scaledCanvasWidth: null,\n scaledCanvasHeight: null,\n canvasWidth: null,\n canvasHeight: null,\n actualCellWidth: null,\n actualCellHeight: null\n };\n this._updateDimensions();\n this._injectCss();\n\n this._rowFactory = new DomRendererRowFactory(_terminal.options, document);\n\n this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);\n this._terminal.screenElement.appendChild(this._rowContainer);\n this._terminal.screenElement.appendChild(this._selectionContainer);\n\n this._terminal.linkifier.onLinkHover(e => this._onLinkHover(e));\n this._terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));\n }\n\n public dispose(): void {\n this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);\n this._terminal.screenElement.removeChild(this._rowContainer);\n this._terminal.screenElement.removeChild(this._selectionContainer);\n this._terminal.screenElement.removeChild(this._themeStyleElement);\n this._terminal.screenElement.removeChild(this._dimensionsStyleElement);\n super.dispose();\n }\n\n private _updateDimensions(): void {\n this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;\n this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);\n this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);\n this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);\n this.dimensions.scaledCharLeft = 0;\n this.dimensions.scaledCharTop = 0;\n this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;\n this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;\n this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);\n this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);\n this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;\n this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;\n\n this._rowElements.forEach(element => {\n element.style.width = `${this.dimensions.canvasWidth}px`;\n element.style.height = `${this.dimensions.actualCellHeight}px`;\n element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;\n // Make sure rows don't overflow onto following row\n element.style.overflow = 'hidden';\n });\n\n if (!this._dimensionsStyleElement) {\n this._dimensionsStyleElement = document.createElement('style');\n this._terminal.screenElement.appendChild(this._dimensionsStyleElement);\n }\n\n const styles =\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +\n ` display: inline-block;` +\n ` height: 100%;` +\n ` vertical-align: top;` +\n ` width: ${this.dimensions.actualCellWidth}px` +\n `}`;\n\n this._dimensionsStyleElement.innerHTML = styles;\n\n this._selectionContainer.style.height = (this._terminal)._viewportElement.style.height;\n this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;\n this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;\n }\n\n public setColors(colors: IColorSet): void {\n this._colors = colors;\n this._injectCss();\n }\n\n private _injectCss(): void {\n if (!this._themeStyleElement) {\n this._themeStyleElement = document.createElement('style');\n this._terminal.screenElement.appendChild(this._themeStyleElement);\n }\n\n // Base CSS\n let styles =\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +\n ` color: ${this._colors.foreground.css};` +\n ` background-color: ${this._colors.background.css};` +\n ` font-family: ${this._terminal.options.fontFamily};` +\n ` font-size: ${this._terminal.options.fontSize}px;` +\n `}`;\n // Text styles\n styles +=\n `${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +\n ` font-weight: ${this._terminal.options.fontWeight};` +\n `}` +\n `${this._terminalSelector} span.${BOLD_CLASS} {` +\n ` font-weight: ${this._terminal.options.fontWeightBold};` +\n `}` +\n `${this._terminalSelector} span.${ITALIC_CLASS} {` +\n ` font-style: italic;` +\n `}`;\n // Blink animation\n styles +=\n `@keyframes blink {` +\n ` 0% { opacity: 1.0; }` +\n ` 50% { opacity: 0.0; }` +\n ` 100% { opacity: 1.0; }` +\n `}`;\n // Cursor\n styles +=\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS} {` +\n ` outline: 1px solid ${this._colors.cursor.css};` +\n ` outline-offset: -1px;` +\n `}` +\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS} {` +\n ` animation: blink 1s step-end infinite;` +\n `}` +\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +\n ` background-color: ${this._colors.cursor.css};` +\n ` color: ${this._colors.cursorAccent.css};` +\n `}` +\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +\n ` box-shadow: 1px 0 0 ${this._colors.cursor.css} inset;` +\n `}` +\n `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +\n ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +\n `}`;\n // Selection\n styles +=\n `${this._terminalSelector} .${SELECTION_CLASS} {` +\n ` position: absolute;` +\n ` top: 0;` +\n ` left: 0;` +\n ` z-index: 1;` +\n ` pointer-events: none;` +\n `}` +\n `${this._terminalSelector} .${SELECTION_CLASS} div {` +\n ` position: absolute;` +\n ` background-color: ${this._colors.selection.css};` +\n `}`;\n // Colors\n this._colors.ansi.forEach((c, i) => {\n styles +=\n `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +\n `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;\n });\n styles +=\n `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this._colors.background.css}; }` +\n `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;\n\n this._themeStyleElement.innerHTML = styles;\n }\n\n public onDevicePixelRatioChange(): void {\n this._updateDimensions();\n }\n\n private _refreshRowElements(cols: number, rows: number): void {\n // Add missing elements\n for (let i = this._rowElements.length; i <= rows; i++) {\n const row = document.createElement('div');\n this._rowContainer.appendChild(row);\n this._rowElements.push(row);\n }\n // Remove excess elements\n while (this._rowElements.length > rows) {\n this._rowContainer.removeChild(this._rowElements.pop());\n }\n }\n\n public onResize(cols: number, rows: number): void {\n this._refreshRowElements(cols, rows);\n this._updateDimensions();\n }\n\n public onCharSizeChanged(): void {\n this._updateDimensions();\n }\n\n public onBlur(): void {\n this._rowContainer.classList.remove(FOCUS_CLASS);\n }\n\n public onFocus(): void {\n this._rowContainer.classList.add(FOCUS_CLASS);\n }\n\n public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {\n // Remove all selections\n while (this._selectionContainer.children.length) {\n this._selectionContainer.removeChild(this._selectionContainer.children[0]);\n }\n\n // Selection does not exist\n if (!start || !end) {\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - this._terminal.buffer.ydisp;\n const viewportEndRow = end[1] - this._terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n // Create the selections\n const documentFragment = document.createDocumentFragment();\n\n if (columnSelectMode) {\n documentFragment.appendChild(\n this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)\n );\n } else {\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));\n // Draw middle rows\n const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewporttartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));\n }\n }\n this._selectionContainer.appendChild(documentFragment);\n }\n\n /**\n * Creates a selection element at the specified position.\n * @param row The row of the selection.\n * @param colStart The start column.\n * @param colEnd The end columns.\n */\n private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {\n const element = document.createElement('div');\n element.style.height = `${rowCount * this.dimensions.actualCellHeight}px`;\n element.style.top = `${row * this.dimensions.actualCellHeight}px`;\n element.style.left = `${colStart * this.dimensions.actualCellWidth}px`;\n element.style.width = `${this.dimensions.actualCellWidth * (colEnd - colStart)}px`;\n return element;\n }\n\n public onCursorMove(): void {\n // No-op, the cursor is drawn when rows are drawn\n }\n\n public onOptionsChanged(): void {\n // Force a refresh\n this._updateDimensions();\n this._injectCss();\n this._terminal.refresh(0, this._terminal.rows - 1);\n }\n\n public clear(): void {\n this._rowElements.forEach(e => e.innerHTML = '');\n }\n\n public renderRows(start: number, end: number): void {\n const terminal = this._terminal;\n\n const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;\n const cursorX = this._terminal.buffer.x;\n const cursorBlink = this._terminal.options.cursorBlink;\n\n for (let y = start; y <= end; y++) {\n const rowElement = this._rowElements[y];\n rowElement.innerHTML = '';\n\n const row = y + terminal.buffer.ydisp;\n const lineData = terminal.buffer.lines.get(row);\n const cursorStyle = terminal.options.cursorStyle;\n rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols));\n }\n }\n\n private get _terminalSelector(): string {\n return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;\n }\n\n public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; }\n public deregisterCharacterJoiner(joinerId: number): boolean { return false; }\n\n private _onLinkHover(e: ILinkifierEvent): void {\n this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);\n }\n\n private _onLinkLeave(e: ILinkifierEvent): void {\n this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false);\n }\n\n private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {\n while (x !== x2 || y !== y2) {\n const row = this._rowElements[y];\n if (!row) {\n return;\n }\n const span = row.children[x];\n if (span) {\n span.style.textDecoration = enabled ? 'underline' : 'none';\n }\n if (++x >= cols) {\n x = 0;\n y++;\n }\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { FontWeight } from 'xterm';\nimport { IColorSet } from '../../ui/Types';\n\nexport const INVERTED_DEFAULT_COLOR = 257;\nexport const DIM_OPACITY = 0.5;\n\nexport const CHAR_ATLAS_CELL_SPACING = 1;\n\nexport interface IGlyphIdentifier {\n chars: string;\n code: number;\n bg: number;\n fg: number;\n bold: boolean;\n dim: boolean;\n italic: boolean;\n}\n\nexport interface ICharAtlasConfig {\n type: 'none' | 'static' | 'dynamic';\n devicePixelRatio: number;\n fontSize: number;\n fontFamily: string;\n fontWeight: FontWeight;\n fontWeightBold: FontWeight;\n scaledCharWidth: number;\n scaledCharHeight: number;\n allowTransparency: boolean;\n colors: IColorSet;\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { DIM_OPACITY, IGlyphIdentifier, ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types';\nimport { generateStaticCharAtlasTexture } from './CharAtlasGenerator';\nimport BaseCharAtlas from './BaseCharAtlas';\nimport { is256Color } from './CharAtlasUtils';\nimport { DEFAULT_COLOR } from '../../common/Types';\n\nexport default class StaticCharAtlas extends BaseCharAtlas {\n private _texture: HTMLCanvasElement | ImageBitmap;\n\n constructor(private _document: Document, private _config: ICharAtlasConfig) {\n super();\n }\n\n private _canvasFactory = (width: number, height: number) => {\n const canvas = this._document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n // This is useful for debugging\n // document.body.appendChild(canvas);\n\n return canvas;\n }\n\n protected _doWarmUp(): void {\n const result = generateStaticCharAtlasTexture(window, this._canvasFactory, this._config);\n if (result instanceof HTMLCanvasElement) {\n this._texture = result;\n } else {\n result.then(texture => {\n this._texture = texture;\n });\n }\n }\n\n private _isCached(glyph: IGlyphIdentifier, colorIndex: number): boolean {\n const isAscii = glyph.code < 256;\n // A color is basic if it is one of the 4 bit ANSI colors.\n const isBasicColor = glyph.fg < 16;\n const isDefaultColor = glyph.fg === DEFAULT_COLOR;\n const isDefaultBackground = glyph.bg === DEFAULT_COLOR;\n return isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground && !glyph.italic;\n }\n\n public draw(\n ctx: CanvasRenderingContext2D,\n glyph: IGlyphIdentifier,\n x: number,\n y: number\n ): boolean {\n // we're not warmed up yet\n if (this._texture === null || this._texture === undefined) {\n return false;\n }\n\n let colorIndex = 0;\n if (is256Color(glyph.fg)) {\n colorIndex = 2 + glyph.fg + (glyph.bold ? 16 : 0);\n } else if (glyph.fg === DEFAULT_COLOR) {\n // If default color and bold\n if (glyph.bold) {\n colorIndex = 1;\n }\n }\n if (!this._isCached(glyph, colorIndex)) {\n return false;\n }\n\n ctx.save();\n\n // ImageBitmap's draw about twice as fast as from a canvas\n const charAtlasCellWidth = this._config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;\n const charAtlasCellHeight = this._config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;\n\n // Apply alpha to dim the character\n if (glyph.dim) {\n ctx.globalAlpha = DIM_OPACITY;\n }\n\n ctx.drawImage(\n this._texture,\n glyph.code * charAtlasCellWidth,\n colorIndex * charAtlasCellHeight,\n charAtlasCellWidth,\n this._config.scaledCharHeight,\n x,\n y,\n charAtlasCellWidth,\n this._config.scaledCharHeight\n );\n\n ctx.restore();\n\n return true;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * A dummy CharAtlas implementation that always fails to draw characters.\n */\n\nimport { IGlyphIdentifier, ICharAtlasConfig } from './Types';\nimport BaseCharAtlas from './BaseCharAtlas';\n\nexport default class NoneCharAtlas extends BaseCharAtlas {\n constructor(document: Document, config: ICharAtlasConfig) {\n super();\n }\n\n public draw(\n ctx: CanvasRenderingContext2D,\n glyph: IGlyphIdentifier,\n x: number,\n y: number\n ): boolean {\n return false;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\ninterface ILinkedListNode {\n prev: ILinkedListNode;\n next: ILinkedListNode;\n key: number;\n value: T;\n}\n\nexport default class LRUMap {\n private _map: { [key: number]: ILinkedListNode } = {};\n private _head: ILinkedListNode = null;\n private _tail: ILinkedListNode = null;\n private _nodePool: ILinkedListNode[] = [];\n public size: number = 0;\n\n constructor(public capacity: number) { }\n\n private _unlinkNode(node: ILinkedListNode): void {\n const prev = node.prev;\n const next = node.next;\n if (node === this._head) {\n this._head = next;\n }\n if (node === this._tail) {\n this._tail = prev;\n }\n if (prev !== null) {\n prev.next = next;\n }\n if (next !== null) {\n next.prev = prev;\n }\n }\n\n private _appendNode(node: ILinkedListNode): void {\n const tail = this._tail;\n if (tail !== null) {\n tail.next = node;\n }\n node.prev = tail;\n node.next = null;\n this._tail = node;\n if (this._head === null) {\n this._head = node;\n }\n }\n\n /**\n * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that\n * they're more likely to live next to each other in memory, which seems to improve performance.\n *\n * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for\n * large maps.\n */\n public prealloc(count: number): void {\n const nodePool = this._nodePool;\n for (let i = 0; i < count; i++) {\n nodePool.push({\n prev: null,\n next: null,\n key: null,\n value: null\n });\n }\n }\n\n public get(key: number): T | null {\n // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However,\n // it's faster than calling hasOwnProperty, and in our case, it would never overlap.\n const node = this._map[key];\n if (node !== undefined) {\n this._unlinkNode(node);\n this._appendNode(node);\n return node.value;\n }\n return null;\n }\n\n /**\n * Gets a value from a key without marking it as the most recently used item.\n */\n public peekValue(key: number): T | null {\n const node = this._map[key];\n if (node !== undefined) {\n return node.value;\n }\n return null;\n }\n\n public peek(): T | null {\n const head = this._head;\n return head === null ? null : head.value;\n }\n\n public set(key: number, value: T): void {\n // This is unsafe: See note above.\n let node = this._map[key];\n if (node !== undefined) {\n // already exists, we just need to mutate it and move it to the end of the list\n node = this._map[key];\n this._unlinkNode(node);\n node.value = value;\n } else if (this.size >= this.capacity) {\n // we're out of space: recycle the head node, move it to the tail\n node = this._head;\n this._unlinkNode(node);\n delete this._map[node.key];\n node.key = key;\n node.value = value;\n this._map[key] = node;\n } else {\n // make a new element\n const nodePool = this._nodePool;\n if (nodePool.length > 0) {\n // use a preallocated node if we can\n node = nodePool.pop();\n node.key = key;\n node.value = value;\n } else {\n node = {\n prev: null,\n next: null,\n key,\n value\n };\n }\n this._map[key] = node;\n this.size++;\n }\n this._appendNode(node);\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types';\nimport BaseCharAtlas from './BaseCharAtlas';\nimport { DEFAULT_ANSI_COLORS } from '../../ui/ColorManager';\nimport { clearColor } from './CharAtlasGenerator';\nimport LRUMap from './LRUMap';\nimport { isFirefox, isSafari } from '../../common/Platform';\nimport { IColor } from '../../ui/Types';\n\n// In practice we're probably never going to exhaust a texture this large. For debugging purposes,\n// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.\nconst TEXTURE_WIDTH = 1024;\nconst TEXTURE_HEIGHT = 1024;\n\nconst TRANSPARENT_COLOR = {\n css: 'rgba(0, 0, 0, 0)',\n rgba: 0\n};\n\n// Drawing to the cache is expensive: If we have to draw more than this number of glyphs to the\n// cache in a single frame, give up on trying to cache anything else, and try to finish the current\n// frame ASAP.\n//\n// This helps to limit the amount of damage a program can do when it would otherwise thrash the\n// cache.\nconst FRAME_CACHE_DRAW_LIMIT = 100;\n\n/**\n * The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch\n * the operation as window.createImageBitmap is asynchronous.\n */\nconst GLYPH_BITMAP_COMMIT_DELAY = 100;\n\ninterface IGlyphCacheValue {\n index: number;\n isEmpty: boolean;\n inBitmap: boolean;\n}\n\nexport function getGlyphCacheKey(glyph: IGlyphIdentifier): number {\n // Note that this only returns a valid key when code < 256\n // Layout:\n // 0b00000000000000000000000000000001: italic (1)\n // 0b00000000000000000000000000000010: dim (1)\n // 0b00000000000000000000000000000100: bold (1)\n // 0b00000000000000000000111111111000: fg (9)\n // 0b00000000000111111111000000000000: bg (9)\n // 0b00011111111000000000000000000000: code (8)\n // 0b11100000000000000000000000000000: unused (3)\n return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1);\n}\n\nexport default class DynamicCharAtlas extends BaseCharAtlas {\n // An ordered map that we're using to keep track of where each glyph is in the atlas texture.\n // It's ordered so that we can determine when to remove the old entries.\n private _cacheMap: LRUMap;\n\n // The texture that the atlas is drawn to\n private _cacheCanvas: HTMLCanvasElement;\n private _cacheCtx: CanvasRenderingContext2D;\n\n // A temporary context that glyphs are drawn to before being transfered to the atlas.\n private _tmpCtx: CanvasRenderingContext2D;\n\n // The number of characters stored in the atlas by width/height\n private _width: number;\n private _height: number;\n\n private _drawToCacheCount: number = 0;\n\n // An array of glyph keys that are waiting on the bitmap to be generated.\n private _glyphsWaitingOnBitmap: IGlyphCacheValue[] = [];\n\n // The timeout that is used to batch bitmap generation so it's not requested for every new glyph.\n private _bitmapCommitTimeout: number | null = null;\n\n // The bitmap to draw from, this is much faster on other browsers than others.\n private _bitmap: ImageBitmap | null = null;\n\n constructor(document: Document, private _config: ICharAtlasConfig) {\n super();\n this._cacheCanvas = document.createElement('canvas');\n this._cacheCanvas.width = TEXTURE_WIDTH;\n this._cacheCanvas.height = TEXTURE_HEIGHT;\n // The canvas needs alpha because we use clearColor to convert the background color to alpha.\n // It might also contain some characters with transparent backgrounds if allowTransparency is\n // set.\n this._cacheCtx = this._cacheCanvas.getContext('2d', {alpha: true});\n\n const tmpCanvas = document.createElement('canvas');\n tmpCanvas.width = this._config.scaledCharWidth;\n tmpCanvas.height = this._config.scaledCharHeight;\n this._tmpCtx = tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency});\n\n this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth);\n this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight);\n const capacity = this._width * this._height;\n this._cacheMap = new LRUMap(capacity);\n this._cacheMap.prealloc(capacity);\n\n // This is useful for debugging\n // document.body.appendChild(this._cacheCanvas);\n }\n\n public dispose(): void {\n if (this._bitmapCommitTimeout !== null) {\n window.clearTimeout(this._bitmapCommitTimeout);\n this._bitmapCommitTimeout = null;\n }\n }\n\n public beginFrame(): void {\n this._drawToCacheCount = 0;\n }\n\n public draw(\n ctx: CanvasRenderingContext2D,\n glyph: IGlyphIdentifier,\n x: number,\n y: number\n ): boolean {\n // Space is always an empty cell, special case this as it's so common\n if (glyph.code === 32) {\n return true;\n }\n\n // Exit early for uncachable glyphs\n if (!this._canCache(glyph)) {\n return false;\n }\n\n const glyphKey = getGlyphCacheKey(glyph);\n const cacheValue = this._cacheMap.get(glyphKey);\n if (cacheValue !== null && cacheValue !== undefined) {\n this._drawFromCache(ctx, cacheValue, x, y);\n return true;\n } else if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) {\n let index;\n if (this._cacheMap.size < this._cacheMap.capacity) {\n index = this._cacheMap.size;\n } else {\n // we're out of space, so our call to set will delete this item\n index = this._cacheMap.peek().index;\n }\n const cacheValue = this._drawToCache(glyph, index);\n this._cacheMap.set(glyphKey, cacheValue);\n this._drawFromCache(ctx, cacheValue, x, y);\n return true;\n }\n return false;\n }\n\n private _canCache(glyph: IGlyphIdentifier): boolean {\n // Only cache ascii and extended characters for now, to be safe. In the future, we could do\n // something more complicated to determine the expected width of a character.\n //\n // If we switch the renderer over to webgl at some point, we may be able to use blending modes\n // to draw overlapping glyphs from the atlas:\n // https://github.com/servo/webrender/issues/464#issuecomment-255632875\n // https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html\n return glyph.code < 256;\n }\n\n private _toCoordinateX(index: number): number {\n return (index % this._width) * this._config.scaledCharWidth;\n }\n\n private _toCoordinateY(index: number): number {\n return Math.floor(index / this._width) * this._config.scaledCharHeight;\n }\n\n private _drawFromCache(\n ctx: CanvasRenderingContext2D,\n cacheValue: IGlyphCacheValue,\n x: number,\n y: number\n ): void {\n // We don't actually need to do anything if this is whitespace.\n if (cacheValue.isEmpty) {\n return;\n }\n const cacheX = this._toCoordinateX(cacheValue.index);\n const cacheY = this._toCoordinateY(cacheValue.index);\n ctx.drawImage(\n cacheValue.inBitmap ? this._bitmap : this._cacheCanvas,\n cacheX,\n cacheY,\n this._config.scaledCharWidth,\n this._config.scaledCharHeight,\n x,\n y,\n this._config.scaledCharWidth,\n this._config.scaledCharHeight\n );\n }\n\n private _getColorFromAnsiIndex(idx: number): IColor {\n if (idx < this._config.colors.ansi.length) {\n return this._config.colors.ansi[idx];\n }\n return DEFAULT_ANSI_COLORS[idx];\n }\n\n private _getBackgroundColor(glyph: IGlyphIdentifier): IColor {\n if (this._config.allowTransparency) {\n // The background color might have some transparency, so we need to render it as fully\n // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice\n // around the anti-aliased edges of the glyph, and it would look too dark.\n return TRANSPARENT_COLOR;\n } else if (glyph.bg === INVERTED_DEFAULT_COLOR) {\n return this._config.colors.foreground;\n } else if (glyph.bg < 256) {\n return this._getColorFromAnsiIndex(glyph.bg);\n }\n return this._config.colors.background;\n }\n\n private _getForegroundColor(glyph: IGlyphIdentifier): IColor {\n if (glyph.fg === INVERTED_DEFAULT_COLOR) {\n return this._config.colors.background;\n } else if (glyph.fg < 256) {\n // 256 color support\n return this._getColorFromAnsiIndex(glyph.fg);\n }\n return this._config.colors.foreground;\n }\n\n // TODO: We do this (or something similar) in multiple places. We should split this off\n // into a shared function.\n private _drawToCache(glyph: IGlyphIdentifier, index: number): IGlyphCacheValue {\n this._drawToCacheCount++;\n\n this._tmpCtx.save();\n\n // draw the background\n const backgroundColor = this._getBackgroundColor(glyph);\n // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of\n // transparency in backgroundColor\n this._tmpCtx.globalCompositeOperation = 'copy';\n this._tmpCtx.fillStyle = backgroundColor.css;\n this._tmpCtx.fillRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight);\n this._tmpCtx.globalCompositeOperation = 'source-over';\n\n // draw the foreground/glyph\n const fontWeight = glyph.bold ? this._config.fontWeightBold : this._config.fontWeight;\n const fontStyle = glyph.italic ? 'italic' : '';\n this._tmpCtx.font =\n `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;\n this._tmpCtx.textBaseline = 'middle';\n\n this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;\n\n // Apply alpha to dim the character\n if (glyph.dim) {\n this._tmpCtx.globalAlpha = DIM_OPACITY;\n }\n // Draw the character\n this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight / 2);\n this._tmpCtx.restore();\n\n // clear the background from the character to avoid issues with drawing over the previous\n // character if it extends past it's bounds\n const imageData = this._tmpCtx.getImageData(\n 0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight\n );\n let isEmpty = false;\n if (!this._config.allowTransparency) {\n isEmpty = clearColor(imageData, backgroundColor);\n }\n\n // copy the data from imageData to _cacheCanvas\n const x = this._toCoordinateX(index);\n const y = this._toCoordinateY(index);\n // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us\n this._cacheCtx.putImageData(imageData, x, y);\n\n // Add the glyph and queue it to the bitmap (if the browser supports it)\n const cacheValue = {\n index,\n isEmpty,\n inBitmap: false\n };\n this._addGlyphToBitmap(cacheValue);\n\n return cacheValue;\n }\n\n private _addGlyphToBitmap(cacheValue: IGlyphCacheValue): void {\n // Support is patchy for createImageBitmap at the moment, pass a canvas back\n // if support is lacking as drawImage works there too. Firefox is also\n // included here as ImageBitmap appears both buggy and has horrible\n // performance (tested on v55).\n if (!('createImageBitmap' in window) || isFirefox || isSafari) {\n return;\n }\n\n // Add the glyph to the queue\n this._glyphsWaitingOnBitmap.push(cacheValue);\n\n // Check if bitmap generation timeout already exists\n if (this._bitmapCommitTimeout !== null) {\n return;\n }\n\n this._bitmapCommitTimeout = window.setTimeout(() => this._generateBitmap(), GLYPH_BITMAP_COMMIT_DELAY);\n }\n\n private _generateBitmap(): void {\n const glyphsMovingToBitmap = this._glyphsWaitingOnBitmap;\n this._glyphsWaitingOnBitmap = [];\n window.createImageBitmap(this._cacheCanvas).then(bitmap => {\n // Set bitmap\n this._bitmap = bitmap;\n\n // Mark all new glyphs as in bitmap, excluding glyphs that came in after\n // the bitmap was requested\n for (let i = 0; i < glyphsMovingToBitmap.length; i++) {\n const value = glyphsMovingToBitmap[i];\n // It doesn't matter if the value was already evicted, it will be\n // released from memory after this block if so.\n value.inBitmap = true;\n }\n });\n this._bitmapCommitTimeout = null;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from '../../Types';\nimport { ICharAtlasConfig } from './Types';\nimport { DEFAULT_COLOR } from '../../common/Types';\nimport { IColorSet } from '../../ui/Types';\n\nexport function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig {\n // null out some fields that don't matter\n const clonedColors = {\n foreground: colors.foreground,\n background: colors.background,\n cursor: null,\n cursorAccent: null,\n selection: null,\n // For the static char atlas, we only use the first 16 colors, but we need all 256 for the\n // dynamic character atlas.\n ansi: colors.ansi.slice(0, 16)\n };\n return {\n type: terminal.options.experimentalCharAtlas,\n devicePixelRatio: window.devicePixelRatio,\n scaledCharWidth,\n scaledCharHeight,\n fontFamily: terminal.options.fontFamily,\n fontSize: terminal.options.fontSize,\n fontWeight: terminal.options.fontWeight,\n fontWeightBold: terminal.options.fontWeightBold,\n allowTransparency: terminal.options.allowTransparency,\n colors: clonedColors\n };\n}\n\nexport function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {\n for (let i = 0; i < a.colors.ansi.length; i++) {\n if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) {\n return false;\n }\n }\n return a.type === b.type &&\n a.devicePixelRatio === b.devicePixelRatio &&\n a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.fontWeight === b.fontWeight &&\n a.fontWeightBold === b.fontWeightBold &&\n a.allowTransparency === b.allowTransparency &&\n a.scaledCharWidth === b.scaledCharWidth &&\n a.scaledCharHeight === b.scaledCharHeight &&\n a.colors.foreground === b.colors.foreground &&\n a.colors.background === b.colors.background;\n}\n\nexport function is256Color(colorCode: number): boolean {\n return colorCode < DEFAULT_COLOR;\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { FontWeight } from 'xterm';\nimport { isFirefox, isSafari } from '../../common/Platform';\nimport { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types';\nimport { IColor } from '../../ui/Types';\n\n/**\n * Generates a char atlas.\n * @param context The window or worker context.\n * @param canvasFactory A function to generate a canvas with a width or height.\n * @param config The config for the new char atlas.\n */\nexport function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise {\n const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING;\n const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING;\n const canvas = canvasFactory(\n /*255 ascii chars*/255 * cellWidth,\n (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight\n );\n const ctx = canvas.getContext('2d', {alpha: config.allowTransparency});\n\n ctx.fillStyle = config.colors.background.css;\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n ctx.save();\n ctx.fillStyle = config.colors.foreground.css;\n ctx.font = getFont(config.fontWeight, config);\n ctx.textBaseline = 'middle';\n\n // Default color\n for (let i = 0; i < 256; i++) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);\n ctx.clip();\n ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2);\n ctx.restore();\n }\n // Default color bold\n ctx.save();\n ctx.font = getFont(config.fontWeightBold, config);\n for (let i = 0; i < 256; i++) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);\n ctx.clip();\n ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5);\n ctx.restore();\n }\n ctx.restore();\n\n // Colors 0-15\n ctx.font = getFont(config.fontWeight, config);\n for (let colorIndex = 0; colorIndex < 16; colorIndex++) {\n const y = (colorIndex + 2) * cellHeight;\n // Draw ascii characters\n for (let i = 0; i < 256; i++) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(i * cellWidth, y, cellWidth, cellHeight);\n ctx.clip();\n ctx.fillStyle = config.colors.ansi[colorIndex].css;\n ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);\n ctx.restore();\n }\n }\n\n // Colors 0-15 bold\n ctx.font = getFont(config.fontWeightBold, config);\n for (let colorIndex = 0; colorIndex < 16; colorIndex++) {\n const y = (colorIndex + 2 + 16) * cellHeight;\n // Draw ascii characters\n for (let i = 0; i < 256; i++) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(i * cellWidth, y, cellWidth, cellHeight);\n ctx.clip();\n ctx.fillStyle = config.colors.ansi[colorIndex].css;\n ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);\n ctx.restore();\n }\n }\n ctx.restore();\n\n // Support is patchy for createImageBitmap at the moment, pass a canvas back\n // if support is lacking as drawImage works there too. Firefox is also\n // included here as ImageBitmap appears both buggy and has horrible\n // performance (tested on v55).\n if (!('createImageBitmap' in context) || isFirefox || isSafari) {\n // Don't attempt to clear background colors if createImageBitmap is not supported\n return canvas;\n }\n\n const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n\n // Remove the background color from the image so characters may overlap\n clearColor(charAtlasImageData, config.colors.background);\n\n return context.createImageBitmap(charAtlasImageData);\n}\n\n/**\n * Makes a partiicular rgb color in an ImageData completely transparent.\n * @returns True if the result is \"empty\", meaning all pixels are fully transparent.\n */\nexport function clearColor(imageData: ImageData, color: IColor): boolean {\n let isEmpty = true;\n const r = color.rgba >>> 24;\n const g = color.rgba >>> 16 & 0xFF;\n const b = color.rgba >>> 8 & 0xFF;\n for (let offset = 0; offset < imageData.data.length; offset += 4) {\n if (imageData.data[offset] === r &&\n imageData.data[offset + 1] === g &&\n imageData.data[offset + 2] === b) {\n imageData.data[offset + 3] = 0;\n } else {\n isEmpty = false;\n }\n }\n return isEmpty;\n}\n\nfunction getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string {\n return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`;\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from '../../Types';\nimport { generateConfig, configEquals } from './CharAtlasUtils';\nimport BaseCharAtlas from './BaseCharAtlas';\nimport DynamicCharAtlas from './DynamicCharAtlas';\nimport NoneCharAtlas from './NoneCharAtlas';\nimport StaticCharAtlas from './StaticCharAtlas';\nimport { ICharAtlasConfig } from './Types';\nimport { IColorSet } from '../../ui/Types';\n\nconst charAtlasImplementations = {\n 'none': NoneCharAtlas,\n 'static': StaticCharAtlas,\n 'dynamic': DynamicCharAtlas\n};\n\ninterface ICharAtlasCacheEntry {\n atlas: BaseCharAtlas;\n config: ICharAtlasConfig;\n // N.B. This implementation potentially holds onto copies of the terminal forever, so\n // this may cause memory leaks.\n ownedBy: ITerminal[];\n}\n\nconst charAtlasCache: ICharAtlasCacheEntry[] = [];\n\n/**\n * Acquires a char atlas, either generating a new one or returning an existing\n * one that is in use by another terminal.\n * @param terminal The terminal.\n * @param colors The colors to use.\n */\nexport function acquireCharAtlas(\n terminal: ITerminal,\n colors: IColorSet,\n scaledCharWidth: number,\n scaledCharHeight: number\n): BaseCharAtlas {\n const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);\n\n // Check to see if the terminal already owns this config\n for (let i = 0; i < charAtlasCache.length; i++) {\n const entry = charAtlasCache[i];\n const ownedByIndex = entry.ownedBy.indexOf(terminal);\n if (ownedByIndex >= 0) {\n if (configEquals(entry.config, newConfig)) {\n return entry.atlas;\n }\n // The configs differ, release the terminal from the entry\n if (entry.ownedBy.length === 1) {\n entry.atlas.dispose();\n charAtlasCache.splice(i, 1);\n } else {\n entry.ownedBy.splice(ownedByIndex, 1);\n }\n break;\n }\n }\n\n // Try match a char atlas from the cache\n for (let i = 0; i < charAtlasCache.length; i++) {\n const entry = charAtlasCache[i];\n if (configEquals(entry.config, newConfig)) {\n // Add the terminal to the cache entry and return\n entry.ownedBy.push(terminal);\n return entry.atlas;\n }\n }\n\n const newEntry: ICharAtlasCacheEntry = {\n atlas: new charAtlasImplementations[terminal.options.experimentalCharAtlas](\n document,\n newConfig\n ),\n config: newConfig,\n ownedBy: [terminal]\n };\n charAtlasCache.push(newEntry);\n return newEntry.atlas;\n}\n\n/**\n * Removes a terminal reference from the cache, allowing its memory to be freed.\n * @param terminal The terminal to remove.\n */\nexport function removeTerminalFromCache(terminal: ITerminal): void {\n for (let i = 0; i < charAtlasCache.length; i++) {\n const index = charAtlasCache[i].ownedBy.indexOf(terminal);\n if (index !== -1) {\n if (charAtlasCache[i].ownedBy.length === 1) {\n // Remove the cache entry if it's the only terminal\n charAtlasCache[i].atlas.dispose();\n charAtlasCache.splice(i, 1);\n } else {\n // Remove the reference from the cache entry\n charAtlasCache[i].ownedBy.splice(index, 1);\n }\n break;\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IGlyphIdentifier } from './Types';\nimport { IDisposable } from 'xterm';\n\nexport default abstract class BaseCharAtlas implements IDisposable {\n private _didWarmUp: boolean = false;\n\n public dispose(): void { }\n\n /**\n * Perform any work needed to warm the cache before it can be used. May be called multiple times.\n * Implement _doWarmUp instead if you only want to get called once.\n */\n public warmUp(): void {\n if (!this._didWarmUp) {\n this._doWarmUp();\n this._didWarmUp = true;\n }\n }\n\n /**\n * Perform any work needed to warm the cache before it can be used. Used by the default\n * implementation of warmUp(), and will only be called once.\n */\n protected _doWarmUp(): void { }\n\n /**\n * Called when we start drawing a new frame.\n *\n * TODO: We rely on this getting called by TextRenderLayer. This should really be called by\n * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead\n * of BaseRenderLayer.\n */\n public beginFrame(): void { }\n\n /**\n * May be called before warmUp finishes, however it is okay for the implementation to\n * do nothing and return false in that case.\n *\n * @param ctx Where to draw the character onto.\n * @param glyph Information about what to draw\n * @param x The position on the context to start drawing at\n * @param y The position on the context to start drawing at\n * @returns The success state. True if we drew the character.\n */\n public abstract draw(\n ctx: CanvasRenderingContext2D,\n glyph: IGlyphIdentifier,\n x: number,\n y: number\n ): boolean;\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderDimensions, ICharacterJoinerRegistry } from './Types';\nimport { ITerminal } from '../Types';\nimport { CharData, ICellData } from '../core/Types';\nimport { GridCache } from './GridCache';\nimport { BaseRenderLayer } from './BaseRenderLayer';\nimport { CellData, AttributeData, Content, NULL_CELL_CODE } from '../core/buffer/BufferLine';\nimport { JoinedCellData } from './CharacterJoinerRegistry';\nimport { IColorSet } from '../ui/Types';\n\n/**\n * This CharData looks like a null character, which will forc a clear and render\n * when the character changes (a regular space ' ' character may not as it's\n * drawn state is a cleared cell).\n */\n// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];\n\nexport class TextRenderLayer extends BaseRenderLayer {\n private _state: GridCache;\n private _characterWidth: number;\n private _characterFont: string;\n private _characterOverlapCache: { [key: string]: boolean } = {};\n private _characterJoinerRegistry: ICharacterJoinerRegistry;\n private _workCell = new CellData();\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) {\n super(container, 'text', zIndex, alpha, colors);\n this._state = new GridCache();\n this._characterJoinerRegistry = characterJoinerRegistry;\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions): void {\n super.resize(terminal, dim);\n\n // Clear the character width cache if the font or width has changed\n const terminalFont = this._getFont(terminal, false, false);\n if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) {\n this._characterWidth = dim.scaledCharWidth;\n this._characterFont = terminalFont;\n this._characterOverlapCache = {};\n }\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state.clear();\n this._state.resize(terminal.cols, terminal.rows);\n }\n\n public reset(terminal: ITerminal): void {\n this._state.clear();\n this.clearAll();\n }\n\n private _forEachCell(\n terminal: ITerminal,\n firstRow: number,\n lastRow: number,\n joinerRegistry: ICharacterJoinerRegistry | null,\n callback: (\n cell: ICellData,\n x: number,\n y: number\n ) => void\n ): void {\n for (let y = firstRow; y <= lastRow; y++) {\n const row = y + terminal.buffer.ydisp;\n const line = terminal.buffer.lines.get(row);\n const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : [];\n for (let x = 0; x < terminal.cols; x++) {\n line.loadCell(x, this._workCell);\n let cell = this._workCell;\n\n // If true, indicates that the current character(s) to draw were joined.\n let isJoined = false;\n let lastCharX = x;\n\n // The character to the left is a wide character, drawing is owned by\n // the char at x-1\n if (cell.getWidth() === 0) {\n continue;\n }\n\n // Process any joined character ranges as needed. Because of how the\n // ranges are produced, we know that they are valid for the characters\n // and attributes of our input.\n if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {\n isJoined = true;\n const range = joinedRanges.shift();\n\n // We already know the exact start and end column of the joined range,\n // so we get the string and width representing it directly\n\n cell = new JoinedCellData(\n this._workCell,\n line.translateToString(true, range[0], range[1]),\n range[1] - range[0]\n );\n\n // Skip over the cells occupied by this range in the loop\n lastCharX = range[1] - 1;\n }\n\n // If the character is an overlapping char and the character to the\n // right is a space, take ownership of the cell to the right. We skip\n // this check for joined characters because their rendering likely won't\n // yield the same result as rendering the last character individually.\n if (!isJoined && this._isOverlapping(cell)) {\n // If the character is overlapping, we want to force a re-render on every\n // frame. This is specifically to work around the case where two\n // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a\n // space is added. Without this, the first half of `b` would never\n // get removed, and `a` would not re-render because it thinks it's\n // already in the correct state.\n // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;\n if (lastCharX < line.length - 1 && line.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) {\n // patch width to 2\n cell.content &= ~Content.WIDTH_MASK;\n cell.content |= 2 << Content.WIDTH_SHIFT;\n // this._clearChar(x + 1, y);\n // The overlapping char's char data will force a clear and render when the\n // overlapping char is no longer to the left of the character and also when\n // the space changes to another character.\n // this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA;\n }\n }\n\n callback(\n cell,\n x,\n y\n );\n\n x = lastCharX;\n }\n }\n }\n\n /**\n * Draws the background for a specified range of columns. Tries to batch adjacent cells of the\n * same color together to reduce draw calls.\n */\n private _drawBackground(terminal: ITerminal, firstRow: number, lastRow: number): void {\n const ctx = this._ctx;\n const cols = terminal.cols;\n let startX: number = 0;\n let startY: number = 0;\n let prevFillStyle: string | null = null;\n\n ctx.save();\n\n this._forEachCell(terminal, firstRow, lastRow, null, (cell, x, y) => {\n // libvte and xterm both draw the background (but not foreground) of invisible characters,\n // so we should too.\n let nextFillStyle = null; // null represents default background color\n\n if (cell.isInverse()) {\n if (cell.isFgDefault()) {\n nextFillStyle = this._colors.foreground.css;\n } else if (cell.isFgRGB()) {\n nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;\n } else {\n nextFillStyle = this._colors.ansi[cell.getFgColor()].css;\n }\n } else if (cell.isBgRGB()) {\n nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;\n } else if (cell.isBgPalette()) {\n nextFillStyle = this._colors.ansi[cell.getBgColor()].css;\n }\n\n if (prevFillStyle === null) {\n // This is either the first iteration, or the default background was set. Either way, we\n // don't need to draw anything.\n startX = x;\n startY = y;\n } if (y !== startY) {\n // our row changed, draw the previous row\n ctx.fillStyle = prevFillStyle;\n this.fillCells(startX, startY, cols - startX, 1);\n startX = x;\n startY = y;\n } else if (prevFillStyle !== nextFillStyle) {\n // our color changed, draw the previous characters in this row\n ctx.fillStyle = prevFillStyle;\n this.fillCells(startX, startY, x - startX, 1);\n startX = x;\n startY = y;\n }\n\n prevFillStyle = nextFillStyle;\n });\n\n // flush the last color we encountered\n if (prevFillStyle !== null) {\n ctx.fillStyle = prevFillStyle;\n this.fillCells(startX, startY, cols - startX, 1);\n }\n\n ctx.restore();\n }\n\n private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void {\n this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => {\n if (cell.isInvisible()) {\n return;\n }\n this.drawChars(terminal, cell, x, y);\n if (cell.isUnderline()) {\n this._ctx.save();\n\n if (cell.isInverse()) {\n if (cell.isBgDefault()) {\n this._ctx.fillStyle = this._colors.background.css;\n } else if (cell.isBgRGB()) {\n this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;\n } else {\n this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;\n }\n } else {\n if (cell.isFgDefault()) {\n this._ctx.fillStyle = this._colors.foreground.css;\n } else if (cell.isFgRGB()) {\n this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;\n } else {\n let fg = cell.getFgColor();\n if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {\n fg += 8;\n }\n this._ctx.fillStyle = this._colors.ansi[fg].css;\n }\n }\n\n this.fillBottomLineAtCells(x, y, cell.getWidth());\n this._ctx.restore();\n }\n });\n }\n\n public onGridChanged(terminal: ITerminal, firstRow: number, lastRow: number): void {\n // Resize has not been called yet\n if (this._state.cache.length === 0) {\n return;\n }\n\n if (this._charAtlas) {\n this._charAtlas.beginFrame();\n }\n\n this.clearCells(0, firstRow, terminal.cols, lastRow - firstRow + 1);\n this._drawBackground(terminal, firstRow, lastRow);\n this._drawForeground(terminal, firstRow, lastRow);\n }\n\n public onOptionsChanged(terminal: ITerminal): void {\n this.setTransparency(terminal, terminal.options.allowTransparency);\n }\n\n /**\n * Whether a character is overlapping to the next cell.\n */\n private _isOverlapping(cell: ICellData): boolean {\n // Only single cell characters can be overlapping, rendering issues can\n // occur without this check\n if (cell.getWidth() !== 1) {\n return false;\n }\n\n // We assume that any ascii character will not overlap\n if (cell.getCode() < 256) {\n return false;\n }\n\n const chars = cell.getChars();\n\n // Deliver from cache if available\n if (this._characterOverlapCache.hasOwnProperty(chars)) {\n return this._characterOverlapCache[chars];\n }\n\n // Setup the font\n this._ctx.save();\n this._ctx.font = this._characterFont;\n\n // Measure the width of the character, but Math.floor it\n // because that is what the renderer does when it calculates\n // the character dimensions we are comparing against\n const overlaps = Math.floor(this._ctx.measureText(chars).width) > this._characterWidth;\n\n // Restore the original context\n this._ctx.restore();\n\n // Cache and return\n this._characterOverlapCache[chars] = overlaps;\n return overlaps;\n }\n\n /**\n * Clear the charcater at the cell specified.\n * @param x The column of the char.\n * @param y The row of the char.\n */\n // private _clearChar(x: number, y: number): void {\n // let colsToClear = 1;\n // // Clear the adjacent character if it was wide\n // const state = this._state.cache[x][y];\n // if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {\n // colsToClear = 2;\n // }\n // this.clearCells(x, y, colsToClear, 1);\n // }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from '../Types';\nimport { IRenderDimensions } from './Types';\nimport { BaseRenderLayer } from './BaseRenderLayer';\nimport { IColorSet } from '../ui/Types';\n\ninterface ISelectionState {\n start: [number, number];\n end: [number, number];\n columnSelectMode: boolean;\n ydisp: number;\n}\n\nexport class SelectionRenderLayer extends BaseRenderLayer {\n private _state: ISelectionState;\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {\n super(container, 'selection', zIndex, true, colors);\n this._clearState();\n }\n\n private _clearState(): void {\n this._state = {\n start: null,\n end: null,\n columnSelectMode: null,\n ydisp: null\n };\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions): void {\n super.resize(terminal, dim);\n // Resizing the canvas discards the contents of the canvas so clear state\n this._clearState();\n }\n\n public reset(terminal: ITerminal): void {\n if (this._state.start && this._state.end) {\n this._clearState();\n this.clearAll();\n }\n }\n\n public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void {\n // Selection has not changed\n if (!this._didStateChange(start, end, columnSelectMode, terminal.buffer.ydisp)) {\n return;\n }\n\n // Remove all selections\n this.clearAll();\n\n // Selection does not exist\n if (!start || !end) {\n this._clearState();\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - terminal.buffer.ydisp;\n const viewportEndRow = end[1] - terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n this._ctx.fillStyle = this._colors.selection.css;\n\n if (columnSelectMode) {\n const startCol = start[0];\n const width = end[0] - startCol;\n const height = viewportCappedEndRow - viewportCappedStartRow + 1;\n this.fillCells(startCol, viewportCappedStartRow, width, height);\n } else {\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols;\n this.fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);\n\n // Draw middle rows\n const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);\n this.fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount);\n\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewportStartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols;\n this.fillCells(0, viewportCappedEndRow, endCol, 1);\n }\n }\n\n // Save state for next render\n this._state.start = [start[0], start[1]];\n this._state.end = [end[0], end[1]];\n this._state.columnSelectMode = columnSelectMode;\n this._state.ydisp = terminal.buffer.ydisp;\n }\n\n private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean {\n return !this._areCoordinatesEqual(start, this._state.start) ||\n !this._areCoordinatesEqual(end, this._state.end) ||\n columnSelectMode !== this._state.columnSelectMode ||\n ydisp !== this._state.ydisp;\n }\n\n private _areCoordinatesEqual(coord1: [number, number], coord2: [number, number]): boolean {\n if (!coord1 || !coord2) {\n return false;\n }\n\n return coord1[0] === coord2[0] && coord1[1] === coord2[1];\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { TextRenderLayer } from './TextRenderLayer';\nimport { SelectionRenderLayer } from './SelectionRenderLayer';\nimport { CursorRenderLayer } from './CursorRenderLayer';\nimport { IRenderLayer, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types';\nimport { ITerminal, CharacterJoinerHandler } from '../Types';\nimport { LinkRenderLayer } from './LinkRenderLayer';\nimport { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry';\nimport { Disposable } from '../common/Lifecycle';\nimport { IColorSet } from '../ui/Types';\n\nexport class Renderer extends Disposable implements IRenderer {\n private _renderLayers: IRenderLayer[];\n private _devicePixelRatio: number;\n private _characterJoinerRegistry: ICharacterJoinerRegistry;\n\n public dimensions: IRenderDimensions;\n\n constructor(\n private _terminal: ITerminal,\n private _colors: IColorSet\n ) {\n super();\n const allowTransparency = this._terminal.options.allowTransparency;\n this._characterJoinerRegistry = new CharacterJoinerRegistry(_terminal);\n\n this._renderLayers = [\n new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency),\n new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors),\n new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal),\n new CursorRenderLayer(this._terminal.screenElement, 3, this._colors)\n ];\n this.dimensions = {\n scaledCharWidth: null,\n scaledCharHeight: null,\n scaledCellWidth: null,\n scaledCellHeight: null,\n scaledCharLeft: null,\n scaledCharTop: null,\n scaledCanvasWidth: null,\n scaledCanvasHeight: null,\n canvasWidth: null,\n canvasHeight: null,\n actualCellWidth: null,\n actualCellHeight: null\n };\n this._devicePixelRatio = window.devicePixelRatio;\n this._updateDimensions();\n this.onOptionsChanged();\n }\n\n public dispose(): void {\n super.dispose();\n this._renderLayers.forEach(l => l.dispose());\n }\n\n public onDevicePixelRatioChange(): void {\n // If the device pixel ratio changed, the char atlas needs to be regenerated\n // and the terminal needs to refreshed\n if (this._devicePixelRatio !== window.devicePixelRatio) {\n this._devicePixelRatio = window.devicePixelRatio;\n this.onResize(this._terminal.cols, this._terminal.rows);\n }\n }\n\n public setColors(colors: IColorSet): void {\n this._colors = colors;\n\n // Clear layers and force a full render\n this._renderLayers.forEach(l => {\n l.setColors(this._terminal, this._colors);\n l.reset(this._terminal);\n });\n }\n\n public onResize(cols: number, rows: number): void {\n // Update character and canvas dimensions\n this._updateDimensions();\n\n // Resize all render layers\n this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions));\n\n // Resize the screen\n this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;\n this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;\n }\n\n public onCharSizeChanged(): void {\n this.onResize(this._terminal.cols, this._terminal.rows);\n }\n\n public onBlur(): void {\n this._runOperation(l => l.onBlur(this._terminal));\n }\n\n public onFocus(): void {\n this._runOperation(l => l.onFocus(this._terminal));\n }\n\n public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {\n this._runOperation(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode));\n }\n\n public onCursorMove(): void {\n this._runOperation(l => l.onCursorMove(this._terminal));\n }\n\n public onOptionsChanged(): void {\n this._runOperation(l => l.onOptionsChanged(this._terminal));\n }\n\n public clear(): void {\n this._runOperation(l => l.reset(this._terminal));\n }\n\n private _runOperation(operation: (layer: IRenderLayer) => void): void {\n this._renderLayers.forEach(l => operation(l));\n }\n\n /**\n * Performs the refresh loop callback, calling refresh only if a refresh is\n * necessary before queueing up the next one.\n */\n public renderRows(start: number, end: number): void {\n this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));\n }\n\n /**\n * Recalculates the character and canvas dimensions.\n */\n private _updateDimensions(): void {\n // Perform a new measure if the CharMeasure dimensions are not yet available\n if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) {\n return;\n }\n\n // Calculate the scaled character width. Width is floored as it must be\n // drawn to an integer grid in order for the CharAtlas \"stamps\" to not be\n // blurry. When text is drawn to the grid not using the CharAtlas, it is\n // clipped to ensure there is no overlap with the next cell.\n this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio);\n\n // Calculate the scaled character height. Height is ceiled in case\n // devicePixelRatio is a floating point number in order to ensure there is\n // enough space to draw the character to the cell.\n this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);\n\n // Calculate the scaled cell height, if lineHeight is not 1 then the value\n // will be floored because since lineHeight can never be lower then 1, there\n // is a guarentee that the scaled line height will always be larger than\n // scaled char height.\n this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);\n\n // Calculate the y coordinate within a cell that text should draw from in\n // order to draw in the center of a cell.\n this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);\n\n // Calculate the scaled cell width, taking the letterSpacing into account.\n this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);\n\n // Calculate the x coordinate with a cell that text should draw from in\n // order to draw in the center of a cell.\n this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);\n\n // Recalculate the canvas dimensions; scaled* define the actual number of\n // pixel in the canvas\n this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;\n this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;\n\n // The the size of the canvas on the page. It's very important that this\n // rounds to nearest integer and not ceils as browsers often set\n // window.devicePixelRatio as something like 1.100000023841858, when it's\n // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1\n // pixel too large for the canvas element size.\n this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);\n this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);\n\n // Get the _actual_ dimensions of an individual cell. This needs to be\n // derived from the canvasWidth/Height calculated above which takes into\n // account window.devicePixelRatio. CharMeasure.width/height by itself is\n // insufficient when the page is not at 100% zoom level as CharMeasure is\n // measured in CSS pixels, but the actual char size on the canvas can\n // differ.\n this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;\n this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;\n }\n\n public registerCharacterJoiner(handler: CharacterJoinerHandler): number {\n return this._characterJoinerRegistry.registerCharacterJoiner(handler);\n }\n\n public deregisterCharacterJoiner(joinerId: number): boolean {\n return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId);\n }\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderer, IRenderDimensions } from './Types';\nimport { RenderDebouncer } from '../ui/RenderDebouncer';\nimport { EventEmitter2, IEvent } from '../common/EventEmitter2';\nimport { Disposable } from '../common/Lifecycle';\nimport { ScreenDprMonitor } from '../ui/ScreenDprMonitor';\nimport { addDisposableDomListener } from '../ui/Lifecycle';\nimport { IColorSet } from '..//ui/Types';\nimport { CharacterJoinerHandler } from '../Types';\n\nexport class RenderCoordinator extends Disposable {\n private _renderDebouncer: RenderDebouncer;\n private _screenDprMonitor: ScreenDprMonitor;\n\n private _isPaused: boolean = false;\n private _needsFullRefresh: boolean = false;\n private _canvasWidth: number = 0;\n private _canvasHeight: number = 0;\n\n private _onDimensionsChange = new EventEmitter2();\n public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; }\n private _onRender = new EventEmitter2<{ start: number, end: number }>();\n public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }\n private _onRefreshRequest = new EventEmitter2<{ start: number, end: number }>();\n public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }\n\n public get dimensions(): IRenderDimensions { return this._renderer.dimensions; }\n\n constructor(\n private _renderer: IRenderer,\n private _rowCount: number,\n screenElement: HTMLElement\n ) {\n super();\n this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));\n this.register(this._renderDebouncer);\n\n this._screenDprMonitor = new ScreenDprMonitor();\n this._screenDprMonitor.setListener(() => this._renderer.onDevicePixelRatioChange());\n this.register(this._screenDprMonitor);\n\n // dprchange should handle this case, we need this as well for browsers that don't support the\n // matchMedia query.\n this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange()));\n\n // Detect whether IntersectionObserver is detected and enable renderer pause\n // and resume based on terminal visibility if so\n if ('IntersectionObserver' in window) {\n const observer = new IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 });\n observer.observe(screenElement);\n this.register({ dispose: () => observer.disconnect() });\n }\n }\n\n private _onIntersectionChange(entry: IntersectionObserverEntry): void {\n this._isPaused = entry.intersectionRatio === 0;\n if (!this._isPaused && this._needsFullRefresh) {\n this.refreshRows(0, this._rowCount - 1);\n this._needsFullRefresh = false;\n }\n }\n\n public refreshRows(start: number, end: number): void {\n if (this._isPaused) {\n this._needsFullRefresh = true;\n return;\n }\n this._renderDebouncer.refresh(start, end, this._rowCount);\n }\n\n private _renderRows(start: number, end: number): void {\n this._renderer.renderRows(start, end);\n this._onRender.fire({ start, end });\n }\n\n public resize(cols: number, rows: number): void {\n this._rowCount = rows;\n this._fireOnCanvasResize();\n }\n\n public changeOptions(): void {\n this._renderer.onOptionsChanged();\n this._fireOnCanvasResize();\n }\n\n private _fireOnCanvasResize(): void {\n // Don't fire the event if the dimensions haven't changed\n if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) {\n return;\n }\n this._onDimensionsChange.fire(this._renderer.dimensions);\n }\n\n public setRenderer(renderer: IRenderer): void {\n // TODO: RenderCoordinator should be the only one to dispose the renderer\n this._renderer.dispose();\n this._renderer = renderer;\n }\n\n private _fullRefresh(): void {\n if (this._isPaused) {\n this._needsFullRefresh = true;\n } else {\n this.refreshRows(0, this._rowCount);\n }\n }\n\n public setColors(colors: IColorSet): void {\n this._renderer.setColors(colors);\n this._fullRefresh();\n }\n\n public onDevicePixelRatioChange(): void {\n this._renderer.onDevicePixelRatioChange();\n }\n\n public onResize(cols: number, rows: number): void {\n this._renderer.onResize(cols, rows);\n this._fullRefresh();\n }\n\n // TODO: Is this useful when we have onResize?\n public onCharSizeChanged(): void {\n this._renderer.onCharSizeChanged();\n }\n\n public onBlur(): void {\n this._renderer.onBlur();\n }\n\n public onFocus(): void {\n this._renderer.onFocus();\n }\n\n public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {\n this._renderer.onSelectionChanged(start, end, columnSelectMode);\n }\n\n public onCursorMove(): void {\n this._renderer.onCursorMove();\n }\n\n public onOptionsChanged(): void {\n this._renderer.onOptionsChanged();\n }\n\n public clear(): void {\n this._renderer.clear();\n }\n\n public registerCharacterJoiner(handler: CharacterJoinerHandler): number {\n return this._renderer.registerCharacterJoiner(handler);\n }\n\n public deregisterCharacterJoiner(joinerId: number): boolean {\n return this._renderer.deregisterCharacterJoiner(joinerId);\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ILinkifierEvent, ITerminal, ILinkifierAccessor } from '../Types';\nimport { IRenderDimensions } from './Types';\nimport { BaseRenderLayer } from './BaseRenderLayer';\nimport { INVERTED_DEFAULT_COLOR } from './atlas/Types';\nimport { is256Color } from './atlas/CharAtlasUtils';\nimport { IColorSet } from '../ui/Types';\n\nexport class LinkRenderLayer extends BaseRenderLayer {\n private _state: ILinkifierEvent = null;\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {\n super(container, 'link', zIndex, true, colors);\n terminal.linkifier.onLinkHover(e => this._onLinkHover(e));\n terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions): void {\n super.resize(terminal, dim);\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state = null;\n }\n\n public reset(terminal: ITerminal): void {\n this._clearCurrentLink();\n }\n\n private _clearCurrentLink(): void {\n if (this._state) {\n this.clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);\n const middleRowCount = this._state.y2 - this._state.y1 - 1;\n if (middleRowCount > 0) {\n this.clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);\n }\n this.clearCells(0, this._state.y2, this._state.x2, 1);\n this._state = null;\n }\n }\n\n private _onLinkHover(e: ILinkifierEvent): void {\n if (e.fg === INVERTED_DEFAULT_COLOR) {\n this._ctx.fillStyle = this._colors.background.css;\n } else if (is256Color(e.fg)) {\n // 256 color support\n this._ctx.fillStyle = this._colors.ansi[e.fg].css;\n } else {\n this._ctx.fillStyle = this._colors.foreground.css;\n }\n\n if (e.y1 === e.y2) {\n // Single line link\n this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);\n } else {\n // Multi-line link\n this.fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);\n for (let y = e.y1 + 1; y < e.y2; y++) {\n this.fillBottomLineAtCells(0, y, e.cols);\n }\n this.fillBottomLineAtCells(0, e.y2, e.x2);\n }\n this._state = e;\n }\n\n private _onLinkLeave(e: ILinkifierEvent): void {\n this._clearCurrentLink();\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport class GridCache {\n public cache: T[][];\n\n public constructor() {\n this.cache = [];\n }\n\n public resize(width: number, height: number): void {\n for (let x = 0; x < width; x++) {\n if (this.cache.length <= x) {\n this.cache.push([]);\n }\n for (let y = this.cache[x].length; y < height; y++) {\n this.cache[x].push(null);\n }\n this.cache[x].length = height;\n }\n this.cache.length = width;\n }\n\n public clear(): void {\n for (let x = 0; x < this.cache.length; x++) {\n for (let y = 0; y < this.cache[x].length; y++) {\n this.cache[x][y] = null;\n }\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderDimensions } from './Types';\nimport { BaseRenderLayer } from './BaseRenderLayer';\nimport { ITerminal } from '../Types';\nimport { ICellData } from '../core/Types';\nimport { CellData } from '../core/buffer/BufferLine';\nimport { IColorSet } from '../ui/Types';\n\ninterface ICursorState {\n x: number;\n y: number;\n isFocused: boolean;\n style: string;\n width: number;\n}\n\n/**\n * The time between cursor blinks.\n */\nconst BLINK_INTERVAL = 600;\n\nexport class CursorRenderLayer extends BaseRenderLayer {\n private _state: ICursorState;\n private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, cell: ICellData) => void};\n private _cursorBlinkStateManager: CursorBlinkStateManager;\n private _cell: ICellData = new CellData();\n\n constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {\n super(container, 'cursor', zIndex, true, colors);\n this._state = {\n x: null,\n y: null,\n isFocused: null,\n style: null,\n width: null\n };\n this._cursorRenderers = {\n 'bar': this._renderBarCursor.bind(this),\n 'block': this._renderBlockCursor.bind(this),\n 'underline': this._renderUnderlineCursor.bind(this)\n };\n // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions): void {\n super.resize(terminal, dim);\n // Resizing the canvas discards the contents of the canvas so clear state\n this._state = {\n x: null,\n y: null,\n isFocused: null,\n style: null,\n width: null\n };\n }\n\n public reset(terminal: ITerminal): void {\n this._clearCursor();\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.dispose();\n this._cursorBlinkStateManager = null;\n this.onOptionsChanged(terminal);\n }\n }\n\n public onBlur(terminal: ITerminal): void {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.pause();\n }\n terminal.refresh(terminal.buffer.y, terminal.buffer.y);\n }\n\n public onFocus(terminal: ITerminal): void {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.resume(terminal);\n } else {\n terminal.refresh(terminal.buffer.y, terminal.buffer.y);\n }\n }\n\n public onOptionsChanged(terminal: ITerminal): void {\n if (terminal.options.cursorBlink) {\n if (!this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => {\n this._render(terminal, true);\n });\n }\n } else {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.dispose();\n this._cursorBlinkStateManager = null;\n }\n // Request a refresh from the terminal as management of rendering is being\n // moved back to the terminal\n terminal.refresh(terminal.buffer.y, terminal.buffer.y);\n }\n }\n\n public onCursorMove(terminal: ITerminal): void {\n if (this._cursorBlinkStateManager) {\n this._cursorBlinkStateManager.restartBlinkAnimation(terminal);\n }\n }\n\n public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {\n if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {\n this._render(terminal, false);\n } else {\n this._cursorBlinkStateManager.restartBlinkAnimation(terminal);\n }\n }\n\n private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void {\n // Don't draw the cursor if it's hidden\n if (!terminal.cursorState || terminal.cursorHidden) {\n this._clearCursor();\n return;\n }\n\n const cursorY = terminal.buffer.ybase + terminal.buffer.y;\n const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp;\n\n // Don't draw the cursor if it's off-screen\n if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) {\n this._clearCursor();\n return;\n }\n\n terminal.buffer.lines.get(cursorY).loadCell(terminal.buffer.x, this._cell);\n if (this._cell.content === undefined) {\n return;\n }\n\n if (!terminal.isFocused) {\n this._clearCursor();\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor.css;\n this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell);\n this._ctx.restore();\n this._state.x = terminal.buffer.x;\n this._state.y = viewportRelativeCursorY;\n this._state.isFocused = false;\n this._state.style = terminal.options.cursorStyle;\n this._state.width = this._cell.getWidth();\n return;\n }\n\n // Don't draw the cursor if it's blinking\n if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) {\n this._clearCursor();\n return;\n }\n\n if (this._state) {\n // The cursor is already in the correct spot, don't redraw\n if (this._state.x === terminal.buffer.x &&\n this._state.y === viewportRelativeCursorY &&\n this._state.isFocused === terminal.isFocused &&\n this._state.style === terminal.options.cursorStyle &&\n this._state.width === this._cell.getWidth()) {\n return;\n }\n this._clearCursor();\n }\n\n this._ctx.save();\n this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell);\n this._ctx.restore();\n\n this._state.x = terminal.buffer.x;\n this._state.y = viewportRelativeCursorY;\n this._state.isFocused = false;\n this._state.style = terminal.options.cursorStyle;\n this._state.width = this._cell.getWidth();\n }\n\n private _clearCursor(): void {\n if (this._state) {\n this.clearCells(this._state.x, this._state.y, this._state.width, 1);\n this._state = {\n x: null,\n y: null,\n isFocused: null,\n style: null,\n width: null\n };\n }\n }\n\n private _renderBarCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor.css;\n this.fillLeftLineAtCell(x, y);\n this._ctx.restore();\n }\n\n private _renderBlockCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor.css;\n this.fillCells(x, y, cell.getWidth(), 1);\n this._ctx.fillStyle = this._colors.cursorAccent.css;\n this.fillCharTrueColor(terminal, cell, x, y);\n this._ctx.restore();\n }\n\n private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {\n this._ctx.save();\n this._ctx.fillStyle = this._colors.cursor.css;\n this.fillBottomLineAtCells(x, y);\n this._ctx.restore();\n }\n\n private _renderBlurCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void {\n this._ctx.save();\n this._ctx.strokeStyle = this._colors.cursor.css;\n this.strokeRectAtCell(x, y, cell.getWidth(), 1);\n this._ctx.restore();\n }\n}\n\nclass CursorBlinkStateManager {\n public isCursorVisible: boolean;\n\n private _animationFrame: number;\n private _blinkStartTimeout: number;\n private _blinkInterval: number;\n\n /**\n * The time at which the animation frame was restarted, this is used on the\n * next render to restart the timers so they don't need to restart the timers\n * multiple times over a short period.\n */\n private _animationTimeRestarted: number;\n\n constructor(\n terminal: ITerminal,\n private _renderCallback: () => void\n ) {\n this.isCursorVisible = true;\n if (terminal.isFocused) {\n this._restartInterval();\n }\n }\n\n public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }\n\n public dispose(): void {\n if (this._blinkInterval) {\n window.clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n if (this._blinkStartTimeout) {\n window.clearTimeout(this._blinkStartTimeout);\n this._blinkStartTimeout = null;\n }\n if (this._animationFrame) {\n window.cancelAnimationFrame(this._animationFrame);\n this._animationFrame = null;\n }\n }\n\n public restartBlinkAnimation(terminal: ITerminal): void {\n if (this.isPaused) {\n return;\n }\n // Save a timestamp so that the restart can be done on the next interval\n this._animationTimeRestarted = Date.now();\n // Force a cursor render to ensure it's visible and in the correct position\n this.isCursorVisible = true;\n if (!this._animationFrame) {\n this._animationFrame = window.requestAnimationFrame(() => {\n this._renderCallback();\n this._animationFrame = null;\n });\n }\n }\n\n private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {\n // Clear any existing interval\n if (this._blinkInterval) {\n window.clearInterval(this._blinkInterval);\n }\n\n // Setup the initial timeout which will hide the cursor, this is done before\n // the regular interval is setup in order to support restarting the blink\n // animation in a lightweight way (without thrashing clearInterval and\n // setInterval).\n this._blinkStartTimeout = setTimeout(() => {\n // Check if another animation restart was requested while this was being\n // started\n if (this._animationTimeRestarted) {\n const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);\n this._animationTimeRestarted = null;\n if (time > 0) {\n this._restartInterval(time);\n return;\n }\n }\n\n // Hide the cursor\n this.isCursorVisible = false;\n this._animationFrame = window.requestAnimationFrame(() => {\n this._renderCallback();\n this._animationFrame = null;\n });\n\n // Setup the blink interval\n this._blinkInterval = setInterval(() => {\n // Adjust the animation time if it was restarted\n if (this._animationTimeRestarted) {\n // calc time diff\n // Make restart interval do a setTimeout initially?\n const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);\n this._animationTimeRestarted = null;\n this._restartInterval(time);\n return;\n }\n\n // Invert visibility and render\n this.isCursorVisible = !this.isCursorVisible;\n this._animationFrame = window.requestAnimationFrame(() => {\n this._renderCallback();\n this._animationFrame = null;\n });\n }, BLINK_INTERVAL);\n }, timeToStart);\n }\n\n public pause(): void {\n this.isCursorVisible = true;\n if (this._blinkInterval) {\n window.clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n if (this._blinkStartTimeout) {\n window.clearTimeout(this._blinkStartTimeout);\n this._blinkStartTimeout = null;\n }\n if (this._animationFrame) {\n window.cancelAnimationFrame(this._animationFrame);\n this._animationFrame = null;\n }\n }\n\n public resume(terminal: ITerminal): void {\n this._animationTimeRestarted = null;\n this._restartInterval();\n this.restartBlinkAnimation(terminal);\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from '../Types';\nimport { IBufferLine, ICellData, CharData } from '../core/Types';\nimport { ICharacterJoinerRegistry, ICharacterJoiner } from './Types';\nimport { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from '../core/buffer/BufferLine';\n\nexport class JoinedCellData extends AttributeData implements ICellData {\n private _width: number;\n // .content carries no meaning for joined CellData, simply nullify it\n // thus we have to overload all other .content accessors\n public content: number = 0;\n public fg: number;\n public bg: number;\n public combinedData: string = '';\n\n constructor(firstCell: ICellData, chars: string, width: number) {\n super();\n this.fg = firstCell.fg;\n this.bg = firstCell.bg;\n this.combinedData = chars;\n this._width = width;\n }\n\n public isCombined(): number {\n // always mark joined cell data as combined\n return Content.IS_COMBINED_MASK;\n }\n\n public getWidth(): number {\n return this._width;\n }\n\n public getChars(): string {\n return this.combinedData;\n }\n\n public getCode(): number {\n // code always gets the highest possible fake codepoint (read as -1)\n // this is needed as code is used by caches as identifier\n return 0x1FFFFF;\n }\n\n public setFromCharData(value: CharData): void {\n throw new Error('not implemented');\n }\n\n public getAsCharData(): CharData {\n return [this.fg, this.getChars(), this.getWidth(), this.getCode()];\n }\n}\n\nexport class CharacterJoinerRegistry implements ICharacterJoinerRegistry {\n\n private _characterJoiners: ICharacterJoiner[] = [];\n private _nextCharacterJoinerId: number = 0;\n private _workCell: CellData = new CellData();\n\n constructor(private _terminal: ITerminal) {\n }\n\n public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {\n const joiner: ICharacterJoiner = {\n id: this._nextCharacterJoinerId++,\n handler\n };\n\n this._characterJoiners.push(joiner);\n return joiner.id;\n }\n\n public deregisterCharacterJoiner(joinerId: number): boolean {\n for (let i = 0; i < this._characterJoiners.length; i++) {\n if (this._characterJoiners[i].id === joinerId) {\n this._characterJoiners.splice(i, 1);\n return true;\n }\n }\n\n return false;\n }\n\n public getJoinedCharacters(row: number): [number, number][] {\n if (this._characterJoiners.length === 0) {\n return [];\n }\n\n const line = this._terminal.buffer.lines.get(row);\n if (line.length === 0) {\n return [];\n }\n\n const ranges: [number, number][] = [];\n const lineStr = line.translateToString(true);\n\n // Because some cells can be represented by multiple javascript characters,\n // we track the cell and the string indexes separately. This allows us to\n // translate the string ranges we get from the joiners back into cell ranges\n // for use when rendering\n let rangeStartColumn = 0;\n let currentStringIndex = 0;\n let rangeStartStringIndex = 0;\n let rangeAttrFG = line.getFg(0);\n let rangeAttrBG = line.getBg(0);\n\n for (let x = 0; x < line.getTrimmedLength(); x++) {\n line.loadCell(x, this._workCell);\n\n if (this._workCell.getWidth() === 0) {\n // If this character is of width 0, skip it.\n continue;\n }\n\n // End of range\n if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) {\n // If we ended up with a sequence of more than one character,\n // look for ranges to join.\n if (x - rangeStartColumn > 1) {\n const joinedRanges = this._getJoinedRanges(\n lineStr,\n rangeStartStringIndex,\n currentStringIndex,\n line,\n rangeStartColumn\n );\n for (let i = 0; i < joinedRanges.length; i++) {\n ranges.push(joinedRanges[i]);\n }\n }\n\n // Reset our markers for a new range.\n rangeStartColumn = x;\n rangeStartStringIndex = currentStringIndex;\n rangeAttrFG = this._workCell.fg;\n rangeAttrBG = this._workCell.bg;\n }\n\n currentStringIndex += this._workCell.getChars().length || WHITESPACE_CELL_CHAR.length;\n }\n\n // Process any trailing ranges.\n if (this._terminal.cols - rangeStartColumn > 1) {\n const joinedRanges = this._getJoinedRanges(\n lineStr,\n rangeStartStringIndex,\n currentStringIndex,\n line,\n rangeStartColumn\n );\n for (let i = 0; i < joinedRanges.length; i++) {\n ranges.push(joinedRanges[i]);\n }\n }\n\n return ranges;\n }\n\n /**\n * Given a segment of a line of text, find all ranges of text that should be\n * joined in a single rendering unit. Ranges are internally converted to\n * column ranges, rather than string ranges.\n * @param line String representation of the full line of text\n * @param startIndex Start position of the range to search in the string (inclusive)\n * @param endIndex End position of the range to search in the string (exclusive)\n */\n private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, startCol: number): [number, number][] {\n const text = line.substring(startIndex, endIndex);\n // At this point we already know that there is at least one joiner so\n // we can just pull its value and assign it directly rather than\n // merging it into an empty array, which incurs unnecessary writes.\n const joinedRanges: [number, number][] = this._characterJoiners[0].handler(text);\n for (let i = 1; i < this._characterJoiners.length; i++) {\n // We merge any overlapping ranges across the different joiners\n const joinerRanges = this._characterJoiners[i].handler(text);\n for (let j = 0; j < joinerRanges.length; j++) {\n CharacterJoinerRegistry._mergeRanges(joinedRanges, joinerRanges[j]);\n }\n }\n this._stringRangesToCellRanges(joinedRanges, lineData, startCol);\n return joinedRanges;\n }\n\n /**\n * Modifies the provided ranges in-place to adjust for variations between\n * string length and cell width so that the range represents a cell range,\n * rather than the string range the joiner provides.\n * @param ranges String ranges containing start (inclusive) and end (exclusive) index\n * @param line Cell data for the relevant line in the terminal\n * @param startCol Offset within the line to start from\n */\n private _stringRangesToCellRanges(ranges: [number, number][], line: IBufferLine, startCol: number): void {\n let currentRangeIndex = 0;\n let currentRangeStarted = false;\n let currentStringIndex = 0;\n let currentRange = ranges[currentRangeIndex];\n\n // If we got through all of the ranges, stop searching\n if (!currentRange) {\n return;\n }\n\n for (let x = startCol; x < this._terminal.cols; x++) {\n const width = line.getWidth(x);\n const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;\n\n // We skip zero-width characters when creating the string to join the text\n // so we do the same here\n if (width === 0) {\n continue;\n }\n\n // Adjust the start of the range\n if (!currentRangeStarted && currentRange[0] <= currentStringIndex) {\n currentRange[0] = x;\n currentRangeStarted = true;\n }\n\n // Adjust the end of the range\n if (currentRange[1] <= currentStringIndex) {\n currentRange[1] = x;\n\n // We're finished with this range, so we move to the next one\n currentRange = ranges[++currentRangeIndex];\n\n // If there are no more ranges left, stop searching\n if (!currentRange) {\n break;\n }\n\n // Ranges can be on adjacent characters. Because the end index of the\n // ranges are exclusive, this means that the index for the start of a\n // range can be the same as the end index of the previous range. To\n // account for the start of the next range, we check here just in case.\n if (currentRange[0] <= currentStringIndex) {\n currentRange[0] = x;\n currentRangeStarted = true;\n } else {\n currentRangeStarted = false;\n }\n }\n\n // Adjust the string index based on the character length to line up with\n // the column adjustment\n currentStringIndex += length;\n }\n\n // If there is still a range left at the end, it must extend all the way to\n // the end of the line.\n if (currentRange) {\n currentRange[1] = this._terminal.cols;\n }\n }\n\n /**\n * Merges the range defined by the provided start and end into the list of\n * existing ranges. The merge is done in place on the existing range for\n * performance and is also returned.\n * @param ranges Existing range list\n * @param newRange Tuple of two numbers representing the new range to merge in.\n * @returns The ranges input with the new range merged in place\n */\n private static _mergeRanges(ranges: [number, number][], newRange: [number, number]): [number, number][] {\n let inRange = false;\n for (let i = 0; i < ranges.length; i++) {\n const range = ranges[i];\n if (!inRange) {\n if (newRange[1] <= range[0]) {\n // Case 1: New range is before the search range\n ranges.splice(i, 0, newRange);\n return ranges;\n }\n\n if (newRange[1] <= range[1]) {\n // Case 2: New range is either wholly contained within the\n // search range or overlaps with the front of it\n range[0] = Math.min(newRange[0], range[0]);\n return ranges;\n }\n\n if (newRange[0] < range[1]) {\n // Case 3: New range either wholly contains the search range\n // or overlaps with the end of it\n range[0] = Math.min(newRange[0], range[0]);\n inRange = true;\n }\n\n // Case 4: New range starts after the search range\n continue;\n } else {\n if (newRange[1] <= range[0]) {\n // Case 5: New range extends from previous range but doesn't\n // reach the current one\n ranges[i - 1][1] = newRange[1];\n return ranges;\n }\n\n if (newRange[1] <= range[1]) {\n // Case 6: New range extends from prvious range into the\n // current range\n ranges[i - 1][1] = Math.max(newRange[1], range[1]);\n ranges.splice(i, 1);\n inRange = false;\n return ranges;\n }\n\n // Case 7: New range extends from previous range past the\n // end of the current range\n ranges.splice(i, 1);\n i--;\n }\n }\n\n if (inRange) {\n // Case 8: New range extends past the last existing range\n ranges[ranges.length - 1][1] = newRange[1];\n } else {\n // Case 9: New range starts after the last existing range\n ranges.push(newRange);\n }\n\n return ranges;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IRenderLayer, IRenderDimensions } from './Types';\nimport { ITerminal } from '../Types';\nimport { ICellData } from '../core/Types';\nimport { DEFAULT_COLOR } from '../common/Types';\nimport { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types';\nimport BaseCharAtlas from './atlas/BaseCharAtlas';\nimport { acquireCharAtlas } from './atlas/CharAtlasCache';\nimport { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../core/buffer/BufferLine';\nimport { IColorSet } from '../ui/Types';\n\nexport abstract class BaseRenderLayer implements IRenderLayer {\n private _canvas: HTMLCanvasElement;\n protected _ctx: CanvasRenderingContext2D;\n private _scaledCharWidth: number = 0;\n private _scaledCharHeight: number = 0;\n private _scaledCellWidth: number = 0;\n private _scaledCellHeight: number = 0;\n private _scaledCharLeft: number = 0;\n private _scaledCharTop: number = 0;\n\n protected _charAtlas: BaseCharAtlas;\n\n /**\n * An object that's reused when drawing glyphs in order to reduce GC.\n */\n private _currentGlyphIdentifier: IGlyphIdentifier = {\n chars: '',\n code: 0,\n bg: 0,\n fg: 0,\n bold: false,\n dim: false,\n italic: false\n };\n\n constructor(\n private _container: HTMLElement,\n id: string,\n zIndex: number,\n private _alpha: boolean,\n protected _colors: IColorSet\n ) {\n this._canvas = document.createElement('canvas');\n this._canvas.classList.add(`xterm-${id}-layer`);\n this._canvas.style.zIndex = zIndex.toString();\n this._initCanvas();\n this._container.appendChild(this._canvas);\n }\n\n public dispose(): void {\n this._container.removeChild(this._canvas);\n if (this._charAtlas) {\n this._charAtlas.dispose();\n }\n }\n\n private _initCanvas(): void {\n this._ctx = this._canvas.getContext('2d', {alpha: this._alpha});\n // Draw the background if this is an opaque layer\n if (!this._alpha) {\n this.clearAll();\n }\n }\n\n public onOptionsChanged(terminal: ITerminal): void {}\n public onBlur(terminal: ITerminal): void {}\n public onFocus(terminal: ITerminal): void {}\n public onCursorMove(terminal: ITerminal): void {}\n public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {}\n public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}\n\n public setColors(terminal: ITerminal, colorSet: IColorSet): void {\n this._refreshCharAtlas(terminal, colorSet);\n }\n\n protected setTransparency(terminal: ITerminal, alpha: boolean): void {\n // Do nothing when alpha doesn't change\n if (alpha === this._alpha) {\n return;\n }\n\n // Create new canvas and replace old one\n const oldCanvas = this._canvas;\n this._alpha = alpha;\n // Cloning preserves properties\n this._canvas = this._canvas.cloneNode();\n this._initCanvas();\n this._container.replaceChild(this._canvas, oldCanvas);\n\n // Regenerate char atlas and force a full redraw\n this._refreshCharAtlas(terminal, this._colors);\n this.onGridChanged(terminal, 0, terminal.rows - 1);\n }\n\n /**\n * Refreshes the char atlas, aquiring a new one if necessary.\n * @param terminal The terminal.\n * @param colorSet The color set to use for the char atlas.\n */\n private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void {\n if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {\n return;\n }\n this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);\n this._charAtlas.warmUp();\n }\n\n public resize(terminal: ITerminal, dim: IRenderDimensions): void {\n this._scaledCellWidth = dim.scaledCellWidth;\n this._scaledCellHeight = dim.scaledCellHeight;\n this._scaledCharWidth = dim.scaledCharWidth;\n this._scaledCharHeight = dim.scaledCharHeight;\n this._scaledCharLeft = dim.scaledCharLeft;\n this._scaledCharTop = dim.scaledCharTop;\n this._canvas.width = dim.scaledCanvasWidth;\n this._canvas.height = dim.scaledCanvasHeight;\n this._canvas.style.width = `${dim.canvasWidth}px`;\n this._canvas.style.height = `${dim.canvasHeight}px`;\n\n // Draw the background if this is an opaque layer\n if (!this._alpha) {\n this.clearAll();\n }\n\n this._refreshCharAtlas(terminal, this._colors);\n }\n\n public abstract reset(terminal: ITerminal): void;\n\n /**\n * Fills 1+ cells completely. This uses the existing fillStyle on the context.\n * @param x The column to start at.\n * @param y The row to start at\n * @param width The number of columns to fill.\n * @param height The number of rows to fill.\n */\n protected fillCells(x: number, y: number, width: number, height: number): void {\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n width * this._scaledCellWidth,\n height * this._scaledCellHeight);\n }\n\n /**\n * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the\n * existing fillStyle on the context.\n * @param x The column to fill.\n * @param y The row to fill.\n */\n protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void {\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,\n width * this._scaledCellWidth,\n window.devicePixelRatio);\n }\n\n /**\n * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the\n * existing fillStyle on the context.\n * @param x The column to fill.\n * @param y The row to fill.\n */\n protected fillLeftLineAtCell(x: number, y: number): void {\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n window.devicePixelRatio,\n this._scaledCellHeight);\n }\n\n /**\n * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing\n * strokeStyle on the context.\n * @param x The column to fill.\n * @param y The row to fill.\n */\n protected strokeRectAtCell(x: number, y: number, width: number, height: number): void {\n this._ctx.lineWidth = window.devicePixelRatio;\n this._ctx.strokeRect(\n x * this._scaledCellWidth + window.devicePixelRatio / 2,\n y * this._scaledCellHeight + (window.devicePixelRatio / 2),\n width * this._scaledCellWidth - window.devicePixelRatio,\n (height * this._scaledCellHeight) - window.devicePixelRatio);\n }\n\n /**\n * Clears the entire canvas.\n */\n protected clearAll(): void {\n if (this._alpha) {\n this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);\n } else {\n this._ctx.fillStyle = this._colors.background.css;\n this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);\n }\n }\n\n /**\n * Clears 1+ cells completely.\n * @param x The column to start at.\n * @param y The row to start at.\n * @param width The number of columns to clear.\n * @param height The number of rows to clear.\n */\n protected clearCells(x: number, y: number, width: number, height: number): void {\n if (this._alpha) {\n this._ctx.clearRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n width * this._scaledCellWidth,\n height * this._scaledCellHeight);\n } else {\n this._ctx.fillStyle = this._colors.background.css;\n this._ctx.fillRect(\n x * this._scaledCellWidth,\n y * this._scaledCellHeight,\n width * this._scaledCellWidth,\n height * this._scaledCellHeight);\n }\n }\n\n /**\n * Draws a truecolor character at the cell. The character will be clipped to\n * ensure that it fits with the cell, including the cell to the right if it's\n * a wide character. This uses the existing fillStyle on the context.\n * @param terminal The terminal.\n * @param cell The cell data for the character to draw.\n * @param x The column to draw at.\n * @param y The row to draw at.\n * @param color The color of the character.\n */\n protected fillCharTrueColor(terminal: ITerminal, cell: CellData, x: number, y: number): void {\n this._ctx.font = this._getFont(terminal, false, false);\n this._ctx.textBaseline = 'middle';\n this._clipRow(terminal, y);\n this._ctx.fillText(\n cell.getChars(),\n x * this._scaledCellWidth + this._scaledCharLeft,\n y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);\n }\n\n /**\n * Draws one or more characters at a cell. If possible this will draw using\n * the character atlas to reduce draw time.\n * @param terminal The terminal.\n * @param chars The character or characters.\n * @param code The character code.\n * @param width The width of the characters.\n * @param x The column to draw at.\n * @param y The row to draw at.\n * @param fg The foreground color, in the format stored within the attributes.\n * @param bg The background color, in the format stored within the attributes.\n * This is used to validate whether a cached image can be used.\n * @param bold Whether the text is bold.\n */\n protected drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void {\n\n // skip cache right away if we draw in RGB\n // Note: to avoid bad runtime JoinedCellData will be skipped\n // in the cache handler itself (atlasDidDraw == false) and\n // fall through to uncached later down below\n if (cell.isFgRGB() || cell.isBgRGB()) {\n this._drawUncachedChars(terminal, cell, x, y);\n return;\n }\n\n let fg;\n let bg;\n if (cell.isInverse()) {\n fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor();\n bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor();\n } else {\n bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor();\n fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();\n }\n\n const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;\n\n fg += drawInBrightColor ? 8 : 0;\n this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;\n this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE;\n this._currentGlyphIdentifier.bg = bg;\n this._currentGlyphIdentifier.fg = fg;\n this._currentGlyphIdentifier.bold = cell.isBold() && terminal.options.enableBold;\n this._currentGlyphIdentifier.dim = !!cell.isDim();\n this._currentGlyphIdentifier.italic = !!cell.isItalic();\n const atlasDidDraw = this._charAtlas && this._charAtlas.draw(\n this._ctx,\n this._currentGlyphIdentifier,\n x * this._scaledCellWidth + this._scaledCharLeft,\n y * this._scaledCellHeight + this._scaledCharTop\n );\n\n if (!atlasDidDraw) {\n this._drawUncachedChars(terminal, cell, x, y);\n }\n }\n\n /**\n * Draws one or more characters at one or more cells. The character(s) will be\n * clipped to ensure that they fit with the cell(s), including the cell to the\n * right if the last character is a wide character.\n * @param terminal The terminal.\n * @param chars The character.\n * @param width The width of the character.\n * @param fg The foreground color, in the format stored within the attributes.\n * @param x The column to draw at.\n * @param y The row to draw at.\n */\n private _drawUncachedChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void {\n this._ctx.save();\n this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.options.enableBold, !!cell.isItalic());\n this._ctx.textBaseline = 'middle';\n\n if (cell.isInverse()) {\n if (cell.isBgDefault()) {\n this._ctx.fillStyle = this._colors.background.css;\n } else if (cell.isBgRGB()) {\n this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;\n } else {\n this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;\n }\n } else {\n if (cell.isFgDefault()) {\n this._ctx.fillStyle = this._colors.foreground.css;\n } else if (cell.isFgRGB()) {\n this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;\n } else {\n let fg = cell.getFgColor();\n if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {\n fg += 8;\n }\n this._ctx.fillStyle = this._colors.ansi[fg].css;\n }\n }\n\n this._clipRow(terminal, y);\n\n // Apply alpha to dim the character\n if (cell.isDim()) {\n this._ctx.globalAlpha = DIM_OPACITY;\n }\n // Draw the character\n this._ctx.fillText(\n cell.getChars(),\n x * this._scaledCellWidth + this._scaledCharLeft,\n y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);\n this._ctx.restore();\n }\n\n /**\n * Clips a row to ensure no pixels will be drawn outside the cells in the row.\n * @param terminal The terminal.\n * @param y The row to clip.\n */\n private _clipRow(terminal: ITerminal, y: number): void {\n this._ctx.beginPath();\n this._ctx.rect(\n 0,\n y * this._scaledCellHeight,\n terminal.cols * this._scaledCellWidth,\n this._scaledCellHeight);\n this._ctx.clip();\n }\n\n /**\n * Gets the current font.\n * @param terminal The terminal.\n * @param isBold If we should use the bold fontWeight.\n */\n protected _getFont(terminal: ITerminal, isBold: boolean, isItalic: boolean): string {\n const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;\n const fontStyle = isItalic ? 'italic' : '';\n\n return `${fontStyle} ${fontWeight} ${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;\n }\n}\n\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';\nimport { ITerminal, IBuffer } from '../Types';\nimport { IBufferLine } from '../core/Types';\nimport { Terminal as TerminalCore } from '../Terminal';\nimport * as Strings from '../Strings';\nimport { IEvent } from '../common/EventEmitter2';\nimport { AddonManager } from './AddonManager';\n\nexport class Terminal implements ITerminalApi {\n private _core: ITerminal;\n private _addonManager: AddonManager;\n\n constructor(options?: ITerminalOptions) {\n this._core = new TerminalCore(options);\n this._addonManager = new AddonManager();\n }\n\n public get onCursorMove(): IEvent { return this._core.onCursorMove; }\n public get onLineFeed(): IEvent { return this._core.onLineFeed; }\n public get onSelectionChange(): IEvent { return this._core.onSelectionChange; }\n public get onData(): IEvent { return this._core.onData; }\n public get onTitleChange(): IEvent { return this._core.onTitleChange; }\n public get onScroll(): IEvent { return this._core.onScroll; }\n public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }\n public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }\n public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }\n\n public get element(): HTMLElement { return this._core.element; }\n public get textarea(): HTMLTextAreaElement { return this._core.textarea; }\n public get rows(): number { return this._core.rows; }\n public get cols(): number { return this._core.cols; }\n public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); }\n public get markers(): ReadonlyArray { return this._core.markers; }\n public blur(): void {\n this._core.blur();\n }\n public focus(): void {\n this._core.focus();\n }\n public on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void;\n public on(type: 'data', listener: (...args: any[]) => void): void;\n public on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void;\n public on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void;\n public on(type: 'refresh', listener: (data?: { start: number; end: number; }) => void): void;\n public on(type: 'resize', listener: (data?: { cols: number; rows: number; }) => void): void;\n public on(type: 'scroll', listener: (ydisp?: number) => void): void;\n public on(type: 'title', listener: (title?: string) => void): void;\n public on(type: string, listener: (...args: any[]) => void): void;\n public on(type: any, listener: any): void {\n this._core.on(type, listener);\n }\n public off(type: string, listener: (...args: any[]) => void): void {\n this._core.off(type, listener);\n }\n public emit(type: string, data?: any): void {\n this._core.emit(type, data);\n }\n public addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable {\n return this._core.addDisposableListener(type, handler);\n }\n public resize(columns: number, rows: number): void {\n this._core.resize(columns, rows);\n }\n public writeln(data: string): void {\n this._core.writeln(data);\n }\n public open(parent: HTMLElement): void {\n this._core.open(parent);\n }\n public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {\n this._core.attachCustomKeyEventHandler(customKeyEventHandler);\n }\n public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {\n return this._core.addCsiHandler(flag, callback);\n }\n public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {\n return this._core.addOscHandler(ident, callback);\n }\n public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {\n return this._core.registerLinkMatcher(regex, handler, options);\n }\n public deregisterLinkMatcher(matcherId: number): void {\n this._core.deregisterLinkMatcher(matcherId);\n }\n public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {\n return this._core.registerCharacterJoiner(handler);\n }\n public deregisterCharacterJoiner(joinerId: number): void {\n this._core.deregisterCharacterJoiner(joinerId);\n }\n public addMarker(cursorYOffset: number): IMarker {\n return this._core.addMarker(cursorYOffset);\n }\n public hasSelection(): boolean {\n return this._core.hasSelection();\n }\n public select(column: number, row: number, length: number): void {\n this._core.select(column, row, length);\n }\n public getSelection(): string {\n return this._core.getSelection();\n }\n public getSelectionPosition(): ISelectionPosition | undefined {\n return this._core.getSelectionPosition();\n }\n public clearSelection(): void {\n this._core.clearSelection();\n }\n public selectAll(): void {\n this._core.selectAll();\n }\n public selectLines(start: number, end: number): void {\n this._core.selectLines(start, end);\n }\n public dispose(): void {\n this._addonManager.dispose();\n this._core.dispose();\n }\n public destroy(): void {\n this._core.destroy();\n }\n public scrollLines(amount: number): void {\n this._core.scrollLines(amount);\n }\n public scrollPages(pageCount: number): void {\n this._core.scrollPages(pageCount);\n }\n public scrollToTop(): void {\n this._core.scrollToTop();\n }\n public scrollToBottom(): void {\n this._core.scrollToBottom();\n }\n public scrollToLine(line: number): void {\n this._core.scrollToLine(line);\n }\n public clear(): void {\n this._core.clear();\n }\n public write(data: string): void {\n this._core.write(data);\n }\n public writeUtf8(data: Uint8Array): void {\n this._core.writeUtf8(data);\n }\n public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'rendererType' | 'termName'): string;\n public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean;\n public getOption(key: 'colors'): string[];\n public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;\n public getOption(key: 'handler'): (data: string) => void;\n public getOption(key: string): any;\n public getOption(key: any): any {\n return this._core.getOption(key);\n }\n public setOption(key: 'bellSound' | 'fontFamily' | 'termName', value: string): void;\n public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;\n public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;\n public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;\n public setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void;\n public setOption(key: 'colors', value: string[]): void;\n public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;\n public setOption(key: 'handler', value: (data: string) => void): void;\n public setOption(key: 'theme', value: ITheme): void;\n public setOption(key: 'cols' | 'rows', value: number): void;\n public setOption(key: string, value: any): void;\n public setOption(key: any, value: any): void {\n this._core.setOption(key, value);\n }\n public refresh(start: number, end: number): void {\n this._core.refresh(start, end);\n }\n public reset(): void {\n this._core.reset();\n }\n public static applyAddon(addon: any): void {\n addon.apply(Terminal);\n }\n public loadAddon(addon: ITerminalAddon): void {\n return this._addonManager.loadAddon(this, addon);\n }\n public static get strings(): ILocalizableStrings {\n return Strings;\n }\n}\n\nclass BufferApiView implements IBufferApi {\n constructor(private _buffer: IBuffer) {}\n\n public get cursorY(): number { return this._buffer.y; }\n public get cursorX(): number { return this._buffer.x; }\n public get viewportY(): number { return this._buffer.ydisp; }\n public get baseY(): number { return this._buffer.ybase; }\n public get length(): number { return this._buffer.lines.length; }\n public getLine(y: number): IBufferLineApi | undefined {\n const line = this._buffer.lines.get(y);\n if (!line) {\n return undefined;\n }\n return new BufferLineApiView(line);\n }\n}\n\nclass BufferLineApiView implements IBufferLineApi {\n constructor(private _line: IBufferLine) {}\n\n public get isWrapped(): boolean { return this._line.isWrapped; }\n public getCell(x: number): IBufferCellApi | undefined {\n if (x < 0 || x >= this._line.length) {\n return undefined;\n }\n return new BufferCellApiView(this._line, x);\n }\n public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {\n return this._line.translateToString(trimRight, startColumn, endColumn);\n }\n}\n\nclass BufferCellApiView implements IBufferCellApi {\n constructor(private _line: IBufferLine, private _x: number) {}\n public get char(): string { return this._line.getString(this._x); }\n public get width(): number { return this._line.getWidth(this._x); }\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminalAddon, IDisposable, Terminal } from 'xterm';\n\nexport interface ILoadedAddon {\n instance: ITerminalAddon;\n dispose: () => void;\n isDisposed: boolean;\n}\n\nexport class AddonManager implements IDisposable {\n protected _addons: ILoadedAddon[] = [];\n\n constructor() {\n }\n\n public dispose(): void {\n for (let i = this._addons.length - 1; i >= 0; i--) {\n this._addons[i].instance.dispose();\n }\n }\n\n public loadAddon(terminal: Terminal, instance: ITerminalAddon): void {\n const loadedAddon: ILoadedAddon = {\n instance,\n dispose: instance.dispose,\n isDisposed: false\n };\n this._addons.push(loadedAddon);\n instance.dispose = () => this._wrappedAddonDispose(loadedAddon);\n instance.activate(terminal);\n }\n\n private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void {\n if (loadedAddon.isDisposed) {\n // Do nothing if already disposed\n return;\n }\n let index = -1;\n for (let i = 0; i < this._addons.length; i++) {\n if (this._addons[i] === loadedAddon) {\n index = i;\n break;\n }\n }\n if (index === -1) {\n throw new Error('Could not dispose an addon that has not been loaded');\n }\n loadedAddon.isDisposed = true;\n loadedAddon.dispose.apply(loadedAddon.instance);\n this._addons.splice(index, 1);\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from '../Types';\nimport { IBufferLine } from '../core/Types';\nimport { ICircularList } from '../common/Types';\nimport { C0 } from '../common/data/EscapeSequences';\n\nconst enum Direction {\n UP = 'A',\n DOWN = 'B',\n RIGHT = 'C',\n LEFT = 'D'\n}\n\nexport class AltClickHandler {\n private _startRow: number;\n private _startCol: number;\n private _endRow: number;\n private _endCol: number;\n private _lines: ICircularList;\n\n constructor(\n private _mouseEvent: MouseEvent,\n private _terminal: ITerminal\n ) {\n this._lines = this._terminal.buffer.lines;\n this._startCol = this._terminal.buffer.x;\n this._startRow = this._terminal.buffer.y;\n\n const coordinates = this._terminal.mouseHelper.getCoords(\n this._mouseEvent,\n this._terminal.element,\n this._terminal.charMeasure,\n this._terminal.cols,\n this._terminal.rows,\n false\n );\n\n if (coordinates) {\n [this._endCol, this._endRow] = coordinates.map((coordinate: number) => {\n return coordinate - 1;\n });\n }\n }\n\n /**\n * Writes the escape sequences of arrows to the terminal\n */\n public move(): void {\n if (this._mouseEvent.altKey && this._endCol !== undefined && this._endRow !== undefined) {\n this._terminal.handler(this._arrowSequences());\n }\n }\n\n /**\n * Concatenates all the arrow sequences together.\n * Resets the starting row to an unwrapped row, moves to the requested row,\n * then moves to requested col.\n */\n private _arrowSequences(): string {\n // The alt buffer should try to navigate between rows\n if (!this._terminal.buffer.hasScrollback) {\n return this._resetStartingRow() + this._moveToRequestedRow() + this._moveToRequestedCol();\n }\n\n // Only move horizontally for the normal buffer\n return this._moveHorizontallyOnly();\n }\n\n /**\n * If the initial position of the cursor is on a row that is wrapped, move the\n * cursor up to the first row that is not wrapped to have accurate vertical\n * positioning.\n */\n private _resetStartingRow(): string {\n if (this._moveToRequestedRow().length === 0) {\n return '';\n }\n return repeat(this._bufferLine(\n this._startCol, this._startRow, this._startCol,\n this._startRow - this._wrappedRowsForRow(this._startRow), false\n ).length, this._sequence(Direction.LEFT));\n }\n\n /**\n * Using the reset starting and ending row, move to the requested row,\n * ignoring wrapped rows\n */\n private _moveToRequestedRow(): string {\n const startRow = this._startRow - this._wrappedRowsForRow(this._startRow);\n const endRow = this._endRow - this._wrappedRowsForRow(this._endRow);\n\n const rowsToMove = Math.abs(startRow - endRow) - this._wrappedRowsCount();\n\n return repeat(rowsToMove, this._sequence(this._verticalDirection()));\n }\n\n /**\n * Move to the requested col on the ending row\n */\n private _moveToRequestedCol(): string {\n let startRow;\n if (this._moveToRequestedRow().length > 0) {\n startRow = this._endRow - this._wrappedRowsForRow(this._endRow);\n } else {\n startRow = this._startRow;\n }\n\n const endRow = this._endRow;\n const direction = this._horizontalDirection();\n\n return repeat(this._bufferLine(\n this._startCol, startRow, this._endCol, endRow,\n direction === Direction.RIGHT\n ).length, this._sequence(direction));\n }\n\n private _moveHorizontallyOnly(): string {\n const direction = this._horizontalDirection();\n return repeat(Math.abs(this._startCol - this._endCol), this._sequence(direction));\n }\n\n /**\n * Utility functions\n */\n\n /**\n * Calculates the number of wrapped rows between the unwrapped starting and\n * ending rows. These rows need to ignored since the cursor skips over them.\n */\n private _wrappedRowsCount(): number {\n let wrappedRows = 0;\n const startRow = this._startRow - this._wrappedRowsForRow(this._startRow);\n const endRow = this._endRow - this._wrappedRowsForRow(this._endRow);\n\n for (let i = 0; i < Math.abs(startRow - endRow); i++) {\n const direction = this._verticalDirection() === Direction.UP ? -1 : 1;\n\n if (this._lines.get(startRow + (direction * i)).isWrapped) {\n wrappedRows++;\n }\n }\n\n return wrappedRows;\n }\n\n /**\n * Calculates the number of wrapped rows that make up a given row.\n * @param currentRow The row to determine how many wrapped rows make it up\n */\n private _wrappedRowsForRow(currentRow: number): number {\n let rowCount = 0;\n let lineWraps = this._lines.get(currentRow).isWrapped;\n\n while (lineWraps && currentRow >= 0 && currentRow < this._terminal.rows) {\n rowCount++;\n currentRow--;\n lineWraps = this._lines.get(currentRow).isWrapped;\n }\n\n return rowCount;\n }\n\n /**\n * Direction determiners\n */\n\n /**\n * Determines if the right or left arrow is needed\n */\n private _horizontalDirection(): Direction {\n let startRow;\n if (this._moveToRequestedRow().length > 0) {\n startRow = this._endRow - this._wrappedRowsForRow(this._endRow);\n } else {\n startRow = this._startRow;\n }\n\n if ((this._startCol < this._endCol &&\n startRow <= this._endRow) || // down/right or same y/right\n (this._startCol >= this._endCol &&\n startRow < this._endRow)) { // down/left or same y/left\n return Direction.RIGHT;\n }\n return Direction.LEFT;\n }\n\n /**\n * Determines if the up or down arrow is needed\n */\n private _verticalDirection(): Direction {\n if (this._startRow > this._endRow) {\n return Direction.UP;\n }\n return Direction.DOWN;\n }\n\n /**\n * Constructs the string of chars in the buffer from a starting row and col\n * to an ending row and col\n * @param startCol The starting column position\n * @param startRow The starting row position\n * @param endCol The ending column position\n * @param endRow The ending row position\n * @param forward Direction to move\n */\n private _bufferLine(\n startCol: number,\n startRow: number,\n endCol: number,\n endRow: number,\n forward: boolean\n ): string {\n let currentCol = startCol;\n let currentRow = startRow;\n let bufferStr = '';\n\n while (currentCol !== endCol || currentRow !== endRow) {\n currentCol += forward ? 1 : -1;\n\n if (forward && currentCol > this._terminal.cols - 1) {\n bufferStr += this._terminal.buffer.translateBufferLineToString(\n currentRow, false, startCol, currentCol\n );\n currentCol = 0;\n startCol = 0;\n currentRow++;\n } else if (!forward && currentCol < 0) {\n bufferStr += this._terminal.buffer.translateBufferLineToString(\n currentRow, false, 0, startCol + 1\n );\n currentCol = this._terminal.cols - 1;\n startCol = currentCol;\n currentRow--;\n }\n }\n\n return bufferStr + this._terminal.buffer.translateBufferLineToString(\n currentRow, false, startCol, currentCol\n );\n }\n\n /**\n * Constructs the escape sequence for clicking an arrow\n * @param direction The direction to move\n */\n private _sequence(direction: Direction): string {\n const mod = this._terminal.applicationCursor ? 'O' : '[';\n return C0.ESC + mod + direction;\n }\n}\n\n/**\n * Returns a string repeated a given number of times\n * Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat\n * @param count The number of times to repeat the string\n * @param string The string that is to be repeated\n */\nfunction repeat(count: number, str: string): string {\n count = Math.floor(count);\n let rpt = '';\n for (let i = 0; i < count; i++) {\n rpt += str;\n }\n return rpt;\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * Polyfill - Convert UTF32 codepoint into JS string.\n * Note: The built-in String.fromCodePoint happens to be much slower\n * due to additional sanity checks. We can avoid them since\n * we always operate on legal UTF32 (granted by the input decoders)\n * and use this faster version instead.\n */\nexport function stringFromCodePoint(codePoint: number): string {\n if (codePoint > 0xFFFF) {\n codePoint -= 0x10000;\n return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);\n }\n return String.fromCharCode(codePoint);\n}\n\n/**\n * Convert UTF32 char codes into JS string.\n * Basically the same as `stringFromCodePoint` but for multiple codepoints\n * in a loop (which is a lot faster).\n */\nexport function utf32ToString(data: Uint32Array, start: number = 0, end: number = data.length): string {\n let result = '';\n for (let i = start; i < end; ++i) {\n let codepoint = data[i];\n if (codepoint > 0xFFFF) {\n // JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair\n // conversion rules:\n // - subtract 0x10000 from code point, leaving a 20 bit number\n // - add high 10 bits to 0xD800 --> first surrogate\n // - add low 10 bits to 0xDC00 --> second surrogate\n codepoint -= 0x10000;\n result += String.fromCharCode((codepoint >> 10) + 0xD800) + String.fromCharCode((codepoint % 0x400) + 0xDC00);\n } else {\n result += String.fromCharCode(codepoint);\n }\n }\n return result;\n}\n\n/**\n * StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints.\n * To keep the decoder in line with JS strings it handles single surrogates as UCS2.\n */\nexport class StringToUtf32 {\n private _interim: number = 0;\n\n /**\n * Clears interim and resets decoder to clean state.\n */\n public clear(): void {\n this._interim = 0;\n }\n\n /**\n * Decode JS string to UTF32 codepoints.\n * The methods assumes stream input and will store partly transmitted\n * surrogate pairs and decode them with the next data chunk.\n * Note: The method does no bound checks for target, therefore make sure\n * the provided input data does not exceed the size of `target`.\n * Returns the number of written codepoints in `target`.\n */\n decode(input: string, target: Uint32Array): number {\n const length = input.length;\n\n if (!length) {\n return 0;\n }\n\n let size = 0;\n let startPos = 0;\n\n // handle leftover surrogate high\n if (this._interim) {\n const second = input.charCodeAt(startPos++);\n if (0xDC00 <= second && second <= 0xDFFF) {\n target[size++] = (this._interim - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;\n } else {\n // illegal codepoint (USC2 handling)\n target[size++] = this._interim;\n target[size++] = second;\n }\n this._interim = 0;\n }\n\n for (let i = startPos; i < length; ++i) {\n const code = input.charCodeAt(i);\n // surrogate pair first\n if (0xD800 <= code && code <= 0xDBFF) {\n if (++i >= length) {\n this._interim = code;\n return size;\n }\n const second = input.charCodeAt(i);\n if (0xDC00 <= second && second <= 0xDFFF) {\n target[size++] = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;\n } else {\n // illegal codepoint (USC2 handling)\n target[size++] = code;\n target[size++] = second;\n }\n continue;\n }\n target[size++] = code;\n }\n return size;\n }\n}\n\n/**\n * Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints.\n */\nexport class Utf8ToUtf32 {\n public interim: Uint8Array = new Uint8Array(3);\n\n /**\n * Clears interim bytes and resets decoder to clean state.\n */\n public clear(): void {\n this.interim.fill(0);\n }\n\n /**\n * Decodes UTF8 byte sequences in `input` to UTF32 codepoints in `target`.\n * The methods assumes stream input and will store partly transmitted bytes\n * and decode them with the next data chunk.\n * Note: The method does no bound checks for target, therefore make sure\n * the provided data chunk does not exceed the size of `target`.\n * Returns the number of written codepoints in `target`.\n */\n decode(input: Uint8Array, target: Uint32Array): number {\n const length = input.length;\n\n if (!length) {\n return 0;\n }\n\n let size = 0;\n let byte1: number;\n let byte2: number;\n let byte3: number;\n let byte4: number;\n let codepoint = 0;\n let startPos = 0;\n\n // handle leftover bytes\n if (this.interim[0]) {\n let discardInterim = false;\n let cp = this.interim[0];\n cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);\n let pos = 0;\n let tmp: number;\n while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) {\n cp <<= 6;\n cp |= tmp;\n }\n // missing bytes - read ahead from input\n const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;\n const missing = type - pos;\n while (startPos < missing) {\n if (startPos >= length) {\n return 0;\n }\n tmp = input[startPos++];\n if ((tmp & 0xC0) !== 0x80) {\n // wrong continuation, discard interim bytes completely\n startPos--;\n discardInterim = true;\n break;\n } else {\n // need to save so we can continue short inputs in next call\n this.interim[pos++] = tmp;\n cp <<= 6;\n cp |= tmp & 0x3F;\n }\n }\n if (!discardInterim) {\n // final test is type dependent\n if (type === 2) {\n if (cp < 0x80) {\n // wrong starter byte\n startPos--;\n } else {\n target[size++] = cp;\n }\n } else if (type === 3) {\n if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF)) {\n // illegal codepoint\n } else {\n target[size++] = cp;\n }\n } else {\n if (codepoint < 0x010000 || codepoint > 0x10FFFF) {\n // illegal codepoint\n } else {\n target[size++] = cp;\n }\n }\n }\n this.interim.fill(0);\n }\n\n // loop through input\n const fourStop = length - 4;\n let i = startPos;\n while (i < length) {\n /**\n * ASCII shortcut with loop unrolled to 4 consecutive ASCII chars.\n * This is a compromise between speed gain for ASCII\n * and penalty for non ASCII:\n * For best ASCII performance the char should be stored directly into target,\n * but even a single attempt to write to target and compare afterwards\n * penalizes non ASCII really bad (-50%), thus we load the char into byteX first,\n * which reduces ASCII performance by ~15%.\n * This trial for ASCII reduces non ASCII performance by ~10% which seems acceptible\n * compared to the gains.\n * Note that this optimization only takes place for 4 consecutive ASCII chars,\n * for any shorter it bails out. Worst case - all 4 bytes being read but\n * thrown away due to the last being a non ASCII char (-10% performance).\n */\n while (i < fourStop\n && !((byte1 = input[i]) & 0x80)\n && !((byte2 = input[i + 1]) & 0x80)\n && !((byte3 = input[i + 2]) & 0x80)\n && !((byte4 = input[i + 3]) & 0x80))\n {\n target[size++] = byte1;\n target[size++] = byte2;\n target[size++] = byte3;\n target[size++] = byte4;\n i += 4;\n }\n\n // reread byte1\n byte1 = input[i++];\n\n // 1 byte\n if (byte1 < 0x80) {\n target[size++] = byte1;\n\n // 2 bytes\n } else if ((byte1 & 0xE0) === 0xC0) {\n if (i >= length) {\n this.interim[0] = byte1;\n return size;\n }\n byte2 = input[i++];\n if ((byte2 & 0xC0) !== 0x80) {\n // wrong continuation\n i--;\n continue;\n }\n codepoint = (byte1 & 0x1F) << 6 | (byte2 & 0x3F);\n if (codepoint < 0x80) {\n // wrong starter byte\n i--;\n continue;\n }\n target[size++] = codepoint;\n\n // 3 bytes\n } else if ((byte1 & 0xF0) === 0xE0) {\n if (i >= length) {\n this.interim[0] = byte1;\n return size;\n }\n byte2 = input[i++];\n if ((byte2 & 0xC0) !== 0x80) {\n // wrong continuation\n i--;\n continue;\n }\n if (i >= length) {\n this.interim[0] = byte1;\n this.interim[1] = byte2;\n return size;\n }\n byte3 = input[i++];\n if ((byte3 & 0xC0) !== 0x80) {\n // wrong continuation\n i--;\n continue;\n }\n codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F);\n if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) {\n // illegal codepoint, no i-- here\n continue;\n }\n target[size++] = codepoint;\n\n // 4 bytes\n } else if ((byte1 & 0xF8) === 0xF0) {\n if (i >= length) {\n this.interim[0] = byte1;\n return size;\n }\n byte2 = input[i++];\n if ((byte2 & 0xC0) !== 0x80) {\n // wrong continuation\n i--;\n continue;\n }\n if (i >= length) {\n this.interim[0] = byte1;\n this.interim[1] = byte2;\n return size;\n }\n byte3 = input[i++];\n if ((byte3 & 0xC0) !== 0x80) {\n // wrong continuation\n i--;\n continue;\n }\n if (i >= length) {\n this.interim[0] = byte1;\n this.interim[1] = byte2;\n this.interim[2] = byte3;\n return size;\n }\n byte4 = input[i++];\n if ((byte4 & 0xC0) !== 0x80) {\n // wrong continuation\n i--;\n continue;\n }\n codepoint = (byte1 & 0x07) << 18 | (byte2 & 0x3F) << 12 | (byte3 & 0x3F) << 6 | (byte4 & 0x3F);\n if (codepoint < 0x010000 || codepoint > 0x10FFFF) {\n // illegal codepoint, no i-- here\n continue;\n }\n target[size++] = codepoint;\n } else {\n // illegal byte, just skip\n }\n }\n return size;\n }\n}\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n */\n\nimport { IKeyboardEvent } from '../../common/Types';\nimport { IKeyboardResult, KeyboardResultType } from '../Types';\nimport { C0 } from '../../common/data/EscapeSequences';\n\n// reg + shift key mappings for digits and special chars\nconst KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = {\n // digits 0-9\n 48: ['0', ')'],\n 49: ['1', '!'],\n 50: ['2', '@'],\n 51: ['3', '#'],\n 52: ['4', '$'],\n 53: ['5', '%'],\n 54: ['6', '^'],\n 55: ['7', '&'],\n 56: ['8', '*'],\n 57: ['9', '('],\n\n // special chars\n 186: [';', ':'],\n 187: ['=', '+'],\n 188: [',', '<'],\n 189: ['-', '_'],\n 190: ['.', '>'],\n 191: ['/', '?'],\n 192: ['`', '~'],\n 219: ['[', '{'],\n 220: ['\\\\', '|'],\n 221: [']', '}'],\n 222: ['\\'', '\"']\n};\n\nexport function evaluateKeyboardEvent(\n ev: IKeyboardEvent,\n applicationCursorMode: boolean,\n isMac: boolean,\n macOptionIsMeta: boolean\n): IKeyboardResult {\n const result: IKeyboardResult = {\n type: KeyboardResultType.SEND_KEY,\n // Whether to cancel event propagation (NOTE: this may not be needed since the event is\n // canceled at the end of keyDown\n cancel: false,\n // The new key even to emit\n key: undefined\n };\n const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);\n switch (ev.keyCode) {\n case 0:\n if (ev.key === 'UIKeyInputUpArrow') {\n if (applicationCursorMode) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n }\n else if (ev.key === 'UIKeyInputLeftArrow') {\n if (applicationCursorMode) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n }\n else if (ev.key === 'UIKeyInputRightArrow') {\n if (applicationCursorMode) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n }\n else if (ev.key === 'UIKeyInputDownArrow') {\n if (applicationCursorMode) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n }\n break;\n case 8:\n // backspace\n if (ev.shiftKey) {\n result.key = C0.BS; // ^H\n break;\n } else if (ev.altKey) {\n result.key = C0.ESC + C0.DEL; // \\e ^?\n break;\n }\n result.key = C0.DEL; // ^?\n break;\n case 9:\n // tab\n if (ev.shiftKey) {\n result.key = C0.ESC + '[Z';\n break;\n }\n result.key = C0.HT;\n result.cancel = true;\n break;\n case 13:\n // return/enter\n result.key = C0.CR;\n result.cancel = true;\n break;\n case 27:\n // escape\n result.key = C0.ESC;\n result.cancel = true;\n break;\n case 37:\n // left-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key === C0.ESC + '[1;3D') {\n result.key = isMac ? C0.ESC + 'b' : C0.ESC + '[1;5D';\n }\n } else if (applicationCursorMode) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n break;\n case 39:\n // right-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key === C0.ESC + '[1;3C') {\n result.key = isMac ? C0.ESC + 'f' : C0.ESC + '[1;5C';\n }\n } else if (applicationCursorMode) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n break;\n case 38:\n // up-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key === C0.ESC + '[1;3A') {\n result.key = C0.ESC + '[1;5A';\n }\n } else if (applicationCursorMode) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n break;\n case 40:\n // down-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key === C0.ESC + '[1;3B') {\n result.key = C0.ESC + '[1;5B';\n }\n } else if (applicationCursorMode) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n break;\n case 45:\n // insert\n if (!ev.shiftKey && !ev.ctrlKey) {\n // or + are used to\n // copy-paste on some systems.\n result.key = C0.ESC + '[2~';\n }\n break;\n case 46:\n // delete\n if (modifiers) {\n result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[3~';\n }\n break;\n case 36:\n // home\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n } else if (applicationCursorMode) {\n result.key = C0.ESC + 'OH';\n } else {\n result.key = C0.ESC + '[H';\n }\n break;\n case 35:\n // end\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n } else if (applicationCursorMode) {\n result.key = C0.ESC + 'OF';\n } else {\n result.key = C0.ESC + '[F';\n }\n break;\n case 33:\n // page up\n if (ev.shiftKey) {\n result.type = KeyboardResultType.PAGE_UP;\n } else {\n result.key = C0.ESC + '[5~';\n }\n break;\n case 34:\n // page down\n if (ev.shiftKey) {\n result.type = KeyboardResultType.PAGE_DOWN;\n } else {\n result.key = C0.ESC + '[6~';\n }\n break;\n case 112:\n // F1-F12\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n } else {\n result.key = C0.ESC + 'OP';\n }\n break;\n case 113:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n } else {\n result.key = C0.ESC + 'OQ';\n }\n break;\n case 114:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n } else {\n result.key = C0.ESC + 'OR';\n }\n break;\n case 115:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n } else {\n result.key = C0.ESC + 'OS';\n }\n break;\n case 116:\n if (modifiers) {\n result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[15~';\n }\n break;\n case 117:\n if (modifiers) {\n result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[17~';\n }\n break;\n case 118:\n if (modifiers) {\n result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[18~';\n }\n break;\n case 119:\n if (modifiers) {\n result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[19~';\n }\n break;\n case 120:\n if (modifiers) {\n result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[20~';\n }\n break;\n case 121:\n if (modifiers) {\n result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[21~';\n }\n break;\n case 122:\n if (modifiers) {\n result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[23~';\n }\n break;\n case 123:\n if (modifiers) {\n result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[24~';\n }\n break;\n default:\n // a-z and space\n if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = String.fromCharCode(ev.keyCode - 64);\n } else if (ev.keyCode === 32) {\n // NUL\n result.key = String.fromCharCode(0);\n } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n // escape, file sep, group sep, record sep, unit sep\n result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n } else if (ev.keyCode === 56) {\n // delete\n result.key = String.fromCharCode(127);\n } else if (ev.keyCode === 219) {\n // ^[ - Control Sequence Introducer (CSI)\n result.key = String.fromCharCode(27);\n } else if (ev.keyCode === 220) {\n // ^\\ - String Terminator (ST)\n result.key = String.fromCharCode(28);\n } else if (ev.keyCode === 221) {\n // ^] - Operating System Command (OSC)\n result.key = String.fromCharCode(29);\n }\n } else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {\n // On macOS this is a third level shift when !macOptionIsMeta. Use instead.\n const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode];\n const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1];\n if (key) {\n result.key = C0.ESC + key;\n } else if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32;\n result.key = C0.ESC + String.fromCharCode(keyCode);\n }\n } else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {\n if (ev.keyCode === 65) { // cmd + a\n result.type = KeyboardResultType.SELECT_ALL;\n }\n } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) {\n // Include only keys that that result in a _single_ character; don't include num lock, volume up, etc.\n result.key = ev.key;\n } else if (ev.key && ev.ctrlKey) {\n if (ev.key === '_') { // ^_\n result.key = C0.US;\n }\n }\n break;\n }\n\n return result;\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharset } from '../Types';\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: { [key: string]: ICharset | null } = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET: ICharset | null = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n '`': '\\u25c6', // '◆'\n 'a': '\\u2592', // '▒'\n 'b': '\\u0009', // '\\t'\n 'c': '\\u000c', // '\\f'\n 'd': '\\u000d', // '\\r'\n 'e': '\\u000a', // '\\n'\n 'f': '\\u00b0', // '°'\n 'g': '\\u00b1', // '±'\n 'h': '\\u2424', // '\\u2424' (NL)\n 'i': '\\u000b', // '\\v'\n 'j': '\\u2518', // '┘'\n 'k': '\\u2510', // '┐'\n 'l': '\\u250c', // '┌'\n 'm': '\\u2514', // '└'\n 'n': '\\u253c', // '┼'\n 'o': '\\u23ba', // '⎺'\n 'p': '\\u23bb', // '⎻'\n 'q': '\\u2500', // '─'\n 'r': '\\u23bc', // '⎼'\n 's': '\\u23bd', // '⎽'\n 't': '\\u251c', // '├'\n 'u': '\\u2524', // '┤'\n 'v': '\\u2534', // '┴'\n 'w': '\\u252c', // '┬'\n 'x': '\\u2502', // '│'\n 'y': '\\u2264', // '≤'\n 'z': '\\u2265', // '≥'\n '{': '\\u03c0', // 'π'\n '|': '\\u2260', // '≠'\n '}': '\\u00a3', // '£'\n '~': '\\u00b7' // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = null;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n '#': '£',\n '@': '¾',\n '[': 'ij',\n '\\\\': '½',\n ']': '|',\n '{': '¨',\n '|': 'f',\n '}': '¼',\n '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n '#': '£',\n '@': 'à',\n '[': '°',\n '\\\\': 'ç',\n ']': '§',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n '@': 'à',\n '[': 'â',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '`': 'ô',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n '@': '§',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Ü',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n '#': '£',\n '@': '§',\n '[': '°',\n '\\\\': 'ç',\n ']': 'é',\n '`': 'ù',\n '{': 'à',\n '|': 'ò',\n '}': 'è',\n '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n '@': 'Ä',\n '[': 'Æ',\n '\\\\': 'Ø',\n ']': 'Å',\n '^': 'Ü',\n '`': 'ä',\n '{': 'æ',\n '|': 'ø',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n '#': '£',\n '@': '§',\n '[': '¡',\n '\\\\': 'Ñ',\n ']': '¿',\n '{': '°',\n '|': 'ñ',\n '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n '@': 'É',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n '#': 'ù',\n '@': 'à',\n '[': 'é',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '_': 'è',\n '`': 'ô',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'û'\n};\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { EventEmitter2, IEvent } from '../../common/EventEmitter2';\nimport { Disposable } from '../../common/Lifecycle';\nimport { IMarker } from '../Types';\n\nexport class Marker extends Disposable implements IMarker {\n private static _nextId = 1;\n\n private _id: number = Marker._nextId++;\n public isDisposed: boolean = false;\n\n public get id(): number { return this._id; }\n\n private _onDispose = new EventEmitter2();\n public get onDispose(): IEvent { return this._onDispose.event; }\n\n constructor(\n public line: number\n ) {\n super();\n }\n\n public dispose(): void {\n if (this.isDisposed) {\n return;\n }\n this.isDisposed = true;\n // Emit before super.dispose such that dispose listeners get a change to react\n this._onDispose.fire();\n }\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { BufferLine } from './BufferLine';\nimport { CircularList } from '../../common/CircularList';\nimport { IBufferLine, ICellData } from '../Types';\n\nexport interface INewLayoutResult {\n layout: number[];\n countRemoved: number;\n}\n\n/**\n * Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed\n * when a wrapped line unwraps.\n * @param lines The buffer lines.\n * @param newCols The columns after resize.\n */\nexport function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData): number[] {\n // Gather all BufferLines that need to be removed from the Buffer here so that they can be\n // batched up and only committed once\n const toRemove: number[] = [];\n\n for (let y = 0; y < lines.length - 1; y++) {\n // Check if this row is wrapped\n let i = y;\n let nextLine = lines.get(++i) as BufferLine;\n if (!nextLine.isWrapped) {\n continue;\n }\n\n // Check how many lines it's wrapped for\n const wrappedLines: BufferLine[] = [lines.get(y) as BufferLine];\n while (i < lines.length && nextLine.isWrapped) {\n wrappedLines.push(nextLine);\n nextLine = lines.get(++i) as BufferLine;\n }\n\n // If these lines contain the cursor don't touch them, the program will handle fixing up wrapped\n // lines with the cursor\n if (bufferAbsoluteY >= y && bufferAbsoluteY < i) {\n y += wrappedLines.length - 1;\n continue;\n }\n\n // Copy buffer data to new locations\n let destLineIndex = 0;\n let destCol = getWrappedLineTrimmedLength(wrappedLines, destLineIndex, oldCols);\n let srcLineIndex = 1;\n let srcCol = 0;\n while (srcLineIndex < wrappedLines.length) {\n const srcTrimmedTineLength = getWrappedLineTrimmedLength(wrappedLines, srcLineIndex, oldCols);\n const srcRemainingCells = srcTrimmedTineLength - srcCol;\n const destRemainingCells = newCols - destCol;\n const cellsToCopy = Math.min(srcRemainingCells, destRemainingCells);\n\n wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol, destCol, cellsToCopy, false);\n\n destCol += cellsToCopy;\n if (destCol === newCols) {\n destLineIndex++;\n destCol = 0;\n }\n srcCol += cellsToCopy;\n if (srcCol === srcTrimmedTineLength) {\n srcLineIndex++;\n srcCol = 0;\n }\n\n // Make sure the last cell isn't wide, if it is copy it to the current dest\n if (destCol === 0 && destLineIndex !== 0) {\n if (wrappedLines[destLineIndex - 1].getWidth(newCols - 1) === 2) {\n wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1, false);\n // Null out the end of the last row\n wrappedLines[destLineIndex - 1].setCell(newCols - 1, nullCell);\n }\n }\n }\n\n // Clear out remaining cells or fragments could remain;\n wrappedLines[destLineIndex].replaceCells(destCol, newCols, nullCell);\n\n // Work backwards and remove any rows at the end that only contain null cells\n let countToRemove = 0;\n for (let i = wrappedLines.length - 1; i > 0; i--) {\n if (i > destLineIndex || wrappedLines[i].getTrimmedLength() === 0) {\n countToRemove++;\n } else {\n break;\n }\n }\n\n if (countToRemove > 0) {\n toRemove.push(y + wrappedLines.length - countToRemove); // index\n toRemove.push(countToRemove);\n }\n\n y += wrappedLines.length - 1;\n }\n return toRemove;\n}\n\n/**\n * Creates and return the new layout for lines given an array of indexes to be removed.\n * @param lines The buffer lines.\n * @param toRemove The indexes to remove.\n */\nexport function reflowLargerCreateNewLayout(lines: CircularList, toRemove: number[]): INewLayoutResult {\n const layout: number[] = [];\n // First iterate through the list and get the actual indexes to use for rows\n let nextToRemoveIndex = 0;\n let nextToRemoveStart = toRemove[nextToRemoveIndex];\n let countRemovedSoFar = 0;\n for (let i = 0; i < lines.length; i++) {\n if (nextToRemoveStart === i) {\n const countToRemove = toRemove[++nextToRemoveIndex];\n\n // Tell markers that there was a deletion\n lines.onDeleteEmitter.fire({\n index: i - countRemovedSoFar,\n amount: countToRemove\n });\n\n i += countToRemove - 1;\n countRemovedSoFar += countToRemove;\n nextToRemoveStart = toRemove[++nextToRemoveIndex];\n } else {\n layout.push(i);\n }\n }\n return {\n layout,\n countRemoved: countRemovedSoFar\n };\n}\n\n/**\n * Applies a new layout to the buffer. This essentially does the same as many splice calls but it's\n * done all at once in a single iteration through the list since splice is very expensive.\n * @param lines The buffer lines.\n * @param newLayout The new layout to apply.\n */\nexport function reflowLargerApplyNewLayout(lines: CircularList, newLayout: number[]): void {\n // Record original lines so they don't get overridden when we rearrange the list\n const newLayoutLines: BufferLine[] = [];\n for (let i = 0; i < newLayout.length; i++) {\n newLayoutLines.push(lines.get(newLayout[i]) as BufferLine);\n }\n\n // Rearrange the list\n for (let i = 0; i < newLayoutLines.length; i++) {\n lines.set(i, newLayoutLines[i]);\n }\n lines.length = newLayout.length;\n}\n\n/**\n * Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-\n * compute the wrapping points since wide characters may need to be wrapped onto the following line.\n * This function will return an array of numbers of where each line wraps to, the resulting array\n * will only contain the values `newCols` (when the line does not end with a wide character) and\n * `newCols - 1` (when the line does end with a wide character), except for the last value which\n * will contain the remaining items to fill the line.\n *\n * Calling this with a `newCols` value of `1` will lock up.\n *\n * @param wrappedLines The wrapped lines to evaluate.\n * @param oldCols The columns before resize.\n * @param newCols The columns after resize.\n */\nexport function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {\n const newLineLengths: number[] = [];\n const cellsNeeded = wrappedLines.map((l, i) => getWrappedLineTrimmedLength(wrappedLines, i, oldCols)).reduce((p, c) => p + c);\n\n // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and\n // linesNeeded\n let srcCol = 0;\n let srcLine = 0;\n let cellsAvailable = 0;\n while (cellsAvailable < cellsNeeded) {\n if (cellsNeeded - cellsAvailable < newCols) {\n // Add the final line and exit the loop\n newLineLengths.push(cellsNeeded - cellsAvailable);\n break;\n }\n srcCol += newCols;\n const oldTrimmedLength = getWrappedLineTrimmedLength(wrappedLines, srcLine, oldCols);\n if (srcCol > oldTrimmedLength) {\n srcCol -= oldTrimmedLength;\n srcLine++;\n }\n const endsWithWide = wrappedLines[srcLine].getWidth(srcCol - 1) === 2;\n if (endsWithWide) {\n srcCol--;\n }\n const lineLength = endsWithWide ? newCols - 1 : newCols;\n newLineLengths.push(lineLength);\n cellsAvailable += lineLength;\n }\n\n return newLineLengths;\n}\n\nexport function getWrappedLineTrimmedLength(lines: BufferLine[], i: number, cols: number): number {\n // If this is the last row in the wrapped line, get the actual trimmed length\n if (i === lines.length - 1) {\n return lines[i].getTrimmedLength();\n }\n // Detect whether the following line starts with a wide character and the end of the current line\n // is null, if so then we can be pretty sure the null character should be excluded from the line\n // length]\n const endsInNull = !(lines[i].hasContent(cols - 1)) && lines[i].getWidth(cols - 1) === 1;\n const followingLineStartsWithWide = lines[i + 1].getWidth(0) === 2;\n if (endsInNull && followingLineStartsWithWide) {\n return cols - 1;\n }\n return cols;\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\nimport { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from '../Types';\nimport { stringFromCodePoint } from '../input/TextDecoder';\nimport { DEFAULT_COLOR } from '../../common/Types';\n\nexport const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);\n\nexport const CHAR_DATA_ATTR_INDEX = 0;\nexport const CHAR_DATA_CHAR_INDEX = 1;\nexport const CHAR_DATA_WIDTH_INDEX = 2;\nexport const CHAR_DATA_CODE_INDEX = 3;\n\n/**\n * Null cell - a real empty cell (containing nothing).\n * Note that code should always be 0 for a null cell as\n * several test condition of the buffer line rely on this.\n */\nexport const NULL_CELL_CHAR = '';\nexport const NULL_CELL_WIDTH = 1;\nexport const NULL_CELL_CODE = 0;\n\n/**\n * Whitespace cell.\n * This is meant as a replacement for empty cells when needed\n * during rendering lines to preserve correct aligment.\n */\nexport const WHITESPACE_CELL_CHAR = ' ';\nexport const WHITESPACE_CELL_WIDTH = 1;\nexport const WHITESPACE_CELL_CODE = 32;\n\n/**\n * buffer memory layout:\n *\n * | uint32_t | uint32_t | uint32_t |\n * | `content` | `FG` | `BG` |\n * | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |\n */\n\n\n/** typed array slots taken by one cell */\nconst CELL_SIZE = 3;\n\n/**\n * Cell member indices.\n *\n * Direct access:\n * `content = data[column * CELL_SIZE + Cell.CONTENT];`\n * `fg = data[column * CELL_SIZE + Cell.FG];`\n * `bg = data[column * CELL_SIZE + Cell.BG];`\n */\nconst enum Cell {\n CONTENT = 0,\n FG = 1, // currently simply holds all known attrs\n BG = 2 // currently unused\n}\n\n/**\n * Bitmasks for accessing data in `content`.\n */\nexport const enum Content {\n /**\n * bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)\n * read: `codepoint = content & Content.codepointMask;`\n * write: `content |= codepoint & Content.codepointMask;`\n * shortcut if precondition `codepoint <= 0x10FFFF` is met:\n * `content |= codepoint;`\n */\n CODEPOINT_MASK = 0x1FFFFF,\n\n /**\n * bit 22 flag indication whether a cell contains combined content\n * read: `isCombined = content & Content.isCombined;`\n * set: `content |= Content.isCombined;`\n * clear: `content &= ~Content.isCombined;`\n */\n IS_COMBINED_MASK = 0x200000, // 1 << 21\n\n /**\n * bit 1..22 mask to check whether a cell contains any string data\n * we need to check for codepoint and isCombined bits to see\n * whether a cell contains anything\n * read: `isEmpty = !(content & Content.hasContent)`\n */\n HAS_CONTENT_MASK = 0x3FFFFF,\n\n /**\n * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)\n * read: `width = (content & Content.widthMask) >> Content.widthShift;`\n * `hasWidth = content & Content.widthMask;`\n * as long as wcwidth is highest value in `content`:\n * `width = content >> Content.widthShift;`\n * write: `content |= (width << Content.widthShift) & Content.widthMask;`\n * shortcut if precondition `0 <= width <= 3` is met:\n * `content |= width << Content.widthShift;`\n */\n WIDTH_MASK = 0xC00000, // 3 << 22\n WIDTH_SHIFT = 22\n}\n\n\nexport const enum Attributes {\n /**\n * bit 1..8 blue in RGB, color in P256 and P16\n */\n BLUE_MASK = 0xFF,\n BLUE_SHIFT = 0,\n PCOLOR_MASK = 0xFF,\n PCOLOR_SHIFT = 0,\n\n /**\n * bit 9..16 green in RGB\n */\n GREEN_MASK = 0xFF00,\n GREEN_SHIFT = 8,\n\n /**\n * bit 17..24 red in RGB\n */\n RED_MASK = 0xFF0000,\n RED_SHIFT = 16,\n\n /**\n * bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)\n */\n CM_MASK = 0x3000000,\n CM_DEFAULT = 0,\n CM_P16 = 0x1000000,\n CM_P256 = 0x2000000,\n CM_RGB = 0x3000000,\n\n /**\n * bit 1..24 RGB room\n */\n RGB_MASK = 0xFFFFFF\n}\n\nexport const enum FgFlags {\n /**\n * bit 27..31 (32th bit unused)\n */\n INVERSE = 0x4000000,\n BOLD = 0x8000000,\n UNDERLINE = 0x10000000,\n BLINK = 0x20000000,\n INVISIBLE = 0x40000000\n}\n\nexport const enum BgFlags {\n /**\n * bit 27..32 (upper 4 unused)\n */\n ITALIC = 0x4000000,\n DIM = 0x8000000\n}\n\nexport class AttributeData implements IAttributeData {\n static toColorRGB(value: number): IColorRGB {\n return [\n value >>> Attributes.RED_SHIFT & 255,\n value >>> Attributes.GREEN_SHIFT & 255,\n value & 255\n ];\n }\n static fromColorRGB(value: IColorRGB): number {\n return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;\n }\n\n public clone(): IAttributeData {\n const newObj = new AttributeData();\n newObj.fg = this.fg;\n newObj.bg = this.bg;\n return newObj;\n }\n\n // data\n public fg: number = 0;\n public bg: number = 0;\n\n // flags\n public isInverse(): number { return this.fg & FgFlags.INVERSE; }\n public isBold(): number { return this.fg & FgFlags.BOLD; }\n public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; }\n public isBlink(): number { return this.fg & FgFlags.BLINK; }\n public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; }\n public isItalic(): number { return this.bg & BgFlags.ITALIC; }\n public isDim(): number { return this.bg & BgFlags.DIM; }\n\n // color modes\n public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }\n public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }\n public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }\n public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }\n public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }\n public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }\n public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }\n public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }\n\n // colors\n public getFgColor(): number {\n switch (this.fg & Attributes.CM_MASK) {\n case Attributes.CM_P16:\n case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK;\n case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK;\n default: return -1; // CM_DEFAULT defaults to -1\n }\n }\n public getBgColor(): number {\n switch (this.bg & Attributes.CM_MASK) {\n case Attributes.CM_P16:\n case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK;\n case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK;\n default: return -1; // CM_DEFAULT defaults to -1\n }\n }\n}\n\nexport const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());\n\n/**\n * CellData - represents a single Cell in the terminal buffer.\n */\nexport class CellData extends AttributeData implements ICellData {\n\n /** Helper to create CellData from CharData. */\n public static fromCharData(value: CharData): CellData {\n const obj = new CellData();\n obj.setFromCharData(value);\n return obj;\n }\n\n /** Primitives from terminal buffer. */\n public content: number = 0;\n public fg: number = 0;\n public bg: number = 0;\n public combinedData: string = '';\n\n /** Whether cell contains a combined string. */\n public isCombined(): number {\n return this.content & Content.IS_COMBINED_MASK;\n }\n\n /** Width of the cell. */\n public getWidth(): number {\n return this.content >> Content.WIDTH_SHIFT;\n }\n\n /** JS string of the content. */\n public getChars(): string {\n if (this.content & Content.IS_COMBINED_MASK) {\n return this.combinedData;\n }\n if (this.content & Content.CODEPOINT_MASK) {\n return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);\n }\n return '';\n }\n\n /**\n * Codepoint of cell\n * Note this returns the UTF32 codepoint of single chars,\n * if content is a combined string it returns the codepoint\n * of the last char in string to be in line with code in CharData.\n * */\n public getCode(): number {\n return (this.isCombined())\n ? this.combinedData.charCodeAt(this.combinedData.length - 1)\n : this.content & Content.CODEPOINT_MASK;\n }\n\n /** Set data from CharData */\n public setFromCharData(value: CharData): void {\n this.fg = value[CHAR_DATA_ATTR_INDEX];\n this.bg = 0;\n let combined = false;\n\n // surrogates and combined strings need special treatment\n if (value[CHAR_DATA_CHAR_INDEX].length > 2) {\n combined = true;\n } else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {\n const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);\n // if the 2-char string is a surrogate create single codepoint\n // everything else is combined\n if (0xD800 <= code && code <= 0xDBFF) {\n const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);\n if (0xDC00 <= second && second <= 0xDFFF) {\n this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n } else {\n combined = true;\n }\n } else {\n combined = true;\n }\n } else {\n this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n }\n if (combined) {\n this.combinedData = value[CHAR_DATA_CHAR_INDEX];\n this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n }\n }\n\n /** Get data as CharData. */\n public getAsCharData(): CharData {\n return [this.fg, this.getChars(), this.getWidth(), this.getCode()];\n }\n}\n\n\n/**\n * Typed array based bufferline implementation.\n *\n * There are 2 ways to insert data into the cell buffer:\n * - `setCellFromCodepoint` + `addCodepointToCell`\n * Use these for data that is already UTF32.\n * Used during normal input in `InputHandler` for faster buffer access.\n * - `setCell`\n * This method takes a CellData object and stores the data in the buffer.\n * Use `CellData.fromCharData` to create the CellData object (e.g. from JS string).\n *\n * To retrieve data from the buffer use either one of the primitive methods\n * (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop\n * memory allocs / GC pressure can be greatly reduced by reusing the CellData object.\n */\nexport class BufferLine implements IBufferLine {\n protected _data: Uint32Array;\n protected _combined: {[index: number]: string} = {};\n public length: number;\n\n constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {\n this._data = new Uint32Array(cols * CELL_SIZE);\n const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n for (let i = 0; i < cols; ++i) {\n this.setCell(i, cell);\n }\n this.length = cols;\n }\n\n /**\n * Get cell data CharData.\n * @deprecated\n */\n public get(index: number): CharData {\n const content = this._data[index * CELL_SIZE + Cell.CONTENT];\n const cp = content & Content.CODEPOINT_MASK;\n return [\n this._data[index * CELL_SIZE + Cell.FG],\n (content & Content.IS_COMBINED_MASK)\n ? this._combined[index]\n : (cp) ? stringFromCodePoint(cp) : '',\n content >> Content.WIDTH_SHIFT,\n (content & Content.IS_COMBINED_MASK)\n ? this._combined[index].charCodeAt(this._combined[index].length - 1)\n : cp\n ];\n }\n\n /**\n * Set cell data from CharData.\n * @deprecated\n */\n public set(index: number, value: CharData): void {\n this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];\n if (value[CHAR_DATA_CHAR_INDEX].length > 1) {\n this._combined[index] = value[1];\n this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n } else {\n this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);\n }\n }\n\n /**\n * primitive getters\n * use these when only one value is needed, otherwise use `loadCell`\n */\n public getWidth(index: number): number {\n return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;\n }\n\n /** Test whether content has width. */\n public hasWidth(index: number): number {\n return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;\n }\n\n /** Get FG cell component. */\n public getFg(index: number): number {\n return this._data[index * CELL_SIZE + Cell.FG];\n }\n\n /** Get BG cell component. */\n public getBg(index: number): number {\n return this._data[index * CELL_SIZE + Cell.BG];\n }\n\n /**\n * Test whether contains any chars.\n * Basically an empty has no content, but other cells might differ in FG/BG\n * from real empty cells.\n * */\n public hasContent(index: number): number {\n return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;\n }\n\n /**\n * Get codepoint of the cell.\n * To be in line with `code` in CharData this either returns\n * a single UTF32 codepoint or the last codepoint of a combined string.\n */\n public getCodePoint(index: number): number {\n const content = this._data[index * CELL_SIZE + Cell.CONTENT];\n if (content & Content.IS_COMBINED_MASK) {\n return this._combined[index].charCodeAt(this._combined[index].length - 1);\n }\n return content & Content.CODEPOINT_MASK;\n }\n\n /** Test whether the cell contains a combined string. */\n public isCombined(index: number): number {\n return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;\n }\n\n /** Returns the string content of the cell. */\n public getString(index: number): string {\n const content = this._data[index * CELL_SIZE + Cell.CONTENT];\n if (content & Content.IS_COMBINED_MASK) {\n return this._combined[index];\n }\n if (content & Content.CODEPOINT_MASK) {\n return stringFromCodePoint(content & Content.CODEPOINT_MASK);\n }\n // return empty string for empty cells\n return '';\n }\n\n /**\n * Load data at `index` into `cell`. This is used to access cells in a way that's more friendly\n * to GC as it significantly reduced the amount of new objects/references needed.\n */\n public loadCell(index: number, cell: ICellData): ICellData {\n const startIndex = index * CELL_SIZE;\n cell.content = this._data[startIndex + Cell.CONTENT];\n cell.fg = this._data[startIndex + Cell.FG];\n cell.bg = this._data[startIndex + Cell.BG];\n if (cell.content & Content.IS_COMBINED_MASK) {\n cell.combinedData = this._combined[index];\n }\n return cell;\n }\n\n /**\n * Set data at `index` to `cell`.\n */\n public setCell(index: number, cell: ICellData): void {\n if (cell.content & Content.IS_COMBINED_MASK) {\n this._combined[index] = cell.combinedData;\n }\n this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;\n this._data[index * CELL_SIZE + Cell.FG] = cell.fg;\n this._data[index * CELL_SIZE + Cell.BG] = cell.bg;\n }\n\n /**\n * Set cell data from input handler.\n * Since the input handler see the incoming chars as UTF32 codepoints,\n * it gets an optimized access method.\n */\n public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void {\n this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);\n this._data[index * CELL_SIZE + Cell.FG] = fg;\n this._data[index * CELL_SIZE + Cell.BG] = bg;\n }\n\n /**\n * Add a codepoint to a cell from input handler.\n * During input stage combining chars with a width of 0 follow and stack\n * onto a leading char. Since we already set the attrs\n * by the previous `setDataFromCodePoint` call, we can omit it here.\n */\n public addCodepointToCell(index: number, codePoint: number): void {\n let content = this._data[index * CELL_SIZE + Cell.CONTENT];\n if (content & Content.IS_COMBINED_MASK) {\n // we already have a combined string, simply add\n this._combined[index] += stringFromCodePoint(codePoint);\n } else {\n if (content & Content.CODEPOINT_MASK) {\n // normal case for combining chars:\n // - move current leading char + new one into combined string\n // - set combined flag\n this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);\n content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0\n content |= Content.IS_COMBINED_MASK;\n } else {\n // should not happen - we actually have no data in the cell yet\n // simply set the data in the cell buffer with a width of 1\n content = codePoint | (1 << Content.WIDTH_SHIFT);\n }\n this._data[index * CELL_SIZE + Cell.CONTENT] = content;\n }\n }\n\n public insertCells(pos: number, n: number, fillCellData: ICellData): void {\n pos %= this.length;\n if (n < this.length - pos) {\n const cell = new CellData();\n for (let i = this.length - pos - n - 1; i >= 0; --i) {\n this.setCell(pos + n + i, this.loadCell(pos + i, cell));\n }\n for (let i = 0; i < n; ++i) {\n this.setCell(pos + i, fillCellData);\n }\n } else {\n for (let i = pos; i < this.length; ++i) {\n this.setCell(i, fillCellData);\n }\n }\n }\n\n public deleteCells(pos: number, n: number, fillCellData: ICellData): void {\n pos %= this.length;\n if (n < this.length - pos) {\n const cell = new CellData();\n for (let i = 0; i < this.length - pos - n; ++i) {\n this.setCell(pos + i, this.loadCell(pos + n + i, cell));\n }\n for (let i = this.length - n; i < this.length; ++i) {\n this.setCell(i, fillCellData);\n }\n } else {\n for (let i = pos; i < this.length; ++i) {\n this.setCell(i, fillCellData);\n }\n }\n }\n\n public replaceCells(start: number, end: number, fillCellData: ICellData): void {\n while (start < end && start < this.length) {\n this.setCell(start++, fillCellData);\n }\n }\n\n public resize(cols: number, fillCellData: ICellData): void {\n if (cols === this.length) {\n return;\n }\n if (cols > this.length) {\n const data = new Uint32Array(cols * CELL_SIZE);\n if (this.length) {\n if (cols * CELL_SIZE < this._data.length) {\n data.set(this._data.subarray(0, cols * CELL_SIZE));\n } else {\n data.set(this._data);\n }\n }\n this._data = data;\n for (let i = this.length; i < cols; ++i) {\n this.setCell(i, fillCellData);\n }\n } else {\n if (cols) {\n const data = new Uint32Array(cols * CELL_SIZE);\n data.set(this._data.subarray(0, cols * CELL_SIZE));\n this._data = data;\n // Remove any cut off combined data\n const keys = Object.keys(this._combined);\n for (let i = 0; i < keys.length; i++) {\n const key = parseInt(keys[i], 10);\n if (key >= cols) {\n delete this._combined[key];\n }\n }\n } else {\n this._data = new Uint32Array(0);\n this._combined = {};\n }\n }\n this.length = cols;\n }\n\n /** fill a line with fillCharData */\n public fill(fillCellData: ICellData): void {\n this._combined = {};\n for (let i = 0; i < this.length; ++i) {\n this.setCell(i, fillCellData);\n }\n }\n\n /** alter to a full copy of line */\n public copyFrom(line: BufferLine): void {\n if (this.length !== line.length) {\n this._data = new Uint32Array(line._data);\n } else {\n // use high speed copy if lengths are equal\n this._data.set(line._data);\n }\n this.length = line.length;\n this._combined = {};\n for (const el in line._combined) {\n this._combined[el] = line._combined[el];\n }\n this.isWrapped = line.isWrapped;\n }\n\n /** create a new clone */\n public clone(): IBufferLine {\n const newLine = new BufferLine(0);\n newLine._data = new Uint32Array(this._data);\n newLine.length = this.length;\n for (const el in this._combined) {\n newLine._combined[el] = this._combined[el];\n }\n newLine.isWrapped = this.isWrapped;\n return newLine;\n }\n\n public getTrimmedLength(): number {\n for (let i = this.length - 1; i >= 0; --i) {\n if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {\n return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);\n }\n }\n return 0;\n }\n\n public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {\n const srcData = src._data;\n if (applyInReverse) {\n for (let cell = length - 1; cell >= 0; cell--) {\n for (let i = 0; i < CELL_SIZE; i++) {\n this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];\n }\n }\n } else {\n for (let cell = 0; cell < length; cell++) {\n for (let i = 0; i < CELL_SIZE; i++) {\n this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];\n }\n }\n }\n\n // Move any combined data over as needed\n const srcCombinedKeys = Object.keys(src._combined);\n for (let i = 0; i < srcCombinedKeys.length; i++) {\n const key = parseInt(srcCombinedKeys[i], 10);\n if (key >= srcCol) {\n this._combined[key - srcCol + destCol] = src._combined[key];\n }\n }\n }\n\n public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = this.length): string {\n if (trimRight) {\n endCol = Math.min(endCol, this.getTrimmedLength());\n }\n let result = '';\n while (startCol < endCol) {\n const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];\n const cp = content & Content.CODEPOINT_MASK;\n result += (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;\n startCol += (content >> Content.WIDTH_SHIFT) || 1; // always advance by 1\n }\n return result;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C0 {\n /** Null (Caret = ^@, C = \\0) */\n export const NUL = '\\x00';\n /** Start of Heading (Caret = ^A) */\n export const SOH = '\\x01';\n /** Start of Text (Caret = ^B) */\n export const STX = '\\x02';\n /** End of Text (Caret = ^C) */\n export const ETX = '\\x03';\n /** End of Transmission (Caret = ^D) */\n export const EOT = '\\x04';\n /** Enquiry (Caret = ^E) */\n export const ENQ = '\\x05';\n /** Acknowledge (Caret = ^F) */\n export const ACK = '\\x06';\n /** Bell (Caret = ^G, C = \\a) */\n export const BEL = '\\x07';\n /** Backspace (Caret = ^H, C = \\b) */\n export const BS = '\\x08';\n /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n export const HT = '\\x09';\n /** Line Feed (Caret = ^J, C = \\n) */\n export const LF = '\\x0a';\n /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n export const VT = '\\x0b';\n /** Form Feed (Caret = ^L, C = \\f) */\n export const FF = '\\x0c';\n /** Carriage Return (Caret = ^M, C = \\r) */\n export const CR = '\\x0d';\n /** Shift Out (Caret = ^N) */\n export const SO = '\\x0e';\n /** Shift In (Caret = ^O) */\n export const SI = '\\x0f';\n /** Data Link Escape (Caret = ^P) */\n export const DLE = '\\x10';\n /** Device Control One (XON) (Caret = ^Q) */\n export const DC1 = '\\x11';\n /** Device Control Two (Caret = ^R) */\n export const DC2 = '\\x12';\n /** Device Control Three (XOFF) (Caret = ^S) */\n export const DC3 = '\\x13';\n /** Device Control Four (Caret = ^T) */\n export const DC4 = '\\x14';\n /** Negative Acknowledge (Caret = ^U) */\n export const NAK = '\\x15';\n /** Synchronous Idle (Caret = ^V) */\n export const SYN = '\\x16';\n /** End of Transmission Block (Caret = ^W) */\n export const ETB = '\\x17';\n /** Cancel (Caret = ^X) */\n export const CAN = '\\x18';\n /** End of Medium (Caret = ^Y) */\n export const EM = '\\x19';\n /** Substitute (Caret = ^Z) */\n export const SUB = '\\x1a';\n /** Escape (Caret = ^[, C = \\e) */\n export const ESC = '\\x1b';\n /** File Separator (Caret = ^\\) */\n export const FS = '\\x1c';\n /** Group Separator (Caret = ^]) */\n export const GS = '\\x1d';\n /** Record Separator (Caret = ^^) */\n export const RS = '\\x1e';\n /** Unit Separator (Caret = ^_) */\n export const US = '\\x1f';\n /** Space */\n export const SP = '\\x20';\n /** Delete (Caret = ^?) */\n export const DEL = '\\x7f';\n}\n\n/**\n * C1 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C1 {\n /** padding character */\n export const PAD = '\\x80';\n /** High Octet Preset */\n export const HOP = '\\x81';\n /** Break Permitted Here */\n export const BPH = '\\x82';\n /** No Break Here */\n export const NBH = '\\x83';\n /** Index */\n export const IND = '\\x84';\n /** Next Line */\n export const NEL = '\\x85';\n /** Start of Selected Area */\n export const SSA = '\\x86';\n /** End of Selected Area */\n export const ESA = '\\x87';\n /** Horizontal Tabulation Set */\n export const HTS = '\\x88';\n /** Horizontal Tabulation With Justification */\n export const HTJ = '\\x89';\n /** Vertical Tabulation Set */\n export const VTS = '\\x8a';\n /** Partial Line Down */\n export const PLD = '\\x8b';\n /** Partial Line Up */\n export const PLU = '\\x8c';\n /** Reverse Index */\n export const RI = '\\x8d';\n /** Single-Shift 2 */\n export const SS2 = '\\x8e';\n /** Single-Shift 3 */\n export const SS3 = '\\x8f';\n /** Device Control String */\n export const DCS = '\\x90';\n /** Private Use 1 */\n export const PU1 = '\\x91';\n /** Private Use 2 */\n export const PU2 = '\\x92';\n /** Set Transmit State */\n export const STS = '\\x93';\n /** Destructive backspace, intended to eliminate ambiguity about meaning of BS. */\n export const CCH = '\\x94';\n /** Message Waiting */\n export const MW = '\\x95';\n /** Start of Protected Area */\n export const SPA = '\\x96';\n /** End of Protected Area */\n export const EPA = '\\x97';\n /** Start of String */\n export const SOS = '\\x98';\n /** Single Graphic Character Introducer */\n export const SGCI = '\\x99';\n /** Single Character Introducer */\n export const SCI = '\\x9a';\n /** Control Sequence Introducer */\n export const CSI = '\\x9b';\n /** String Terminator */\n export const ST = '\\x9c';\n /** Operating System Command */\n export const OSC = '\\x9d';\n /** Privacy Message */\n export const PM = '\\x9e';\n /** Application Program Command */\n export const APC = '\\x9f';\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IEvent, EventEmitter2 } from './EventEmitter2';\nimport { IDeleteEvent, IInsertEvent } from './CircularList';\n\nexport const DEFAULT_COLOR = 256;\n\nexport interface IDisposable {\n dispose(): void;\n}\n\nexport interface IEventEmitter {\n on(type: string, listener: (...args: any[]) => void): void;\n off(type: string, listener: (...args: any[]) => void): void;\n emit(type: string, data?: any): void;\n addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable;\n}\n\nexport type XtermListener = (...args: any[]) => void;\n\n/**\n * A keyboard event interface which does not depend on the DOM, KeyboardEvent implicitly extends\n * this event.\n */\nexport interface IKeyboardEvent {\n altKey: boolean;\n ctrlKey: boolean;\n shiftKey: boolean;\n metaKey: boolean;\n keyCode: number;\n key: string;\n type: string;\n}\n\nexport interface ICircularList {\n length: number;\n maxLength: number;\n isFull: boolean;\n\n onDeleteEmitter: EventEmitter2;\n onDelete: IEvent;\n onInsertEmitter: EventEmitter2;\n onInsert: IEvent;\n onTrimEmitter: EventEmitter2;\n onTrim: IEvent;\n\n get(index: number): T | undefined;\n set(index: number, value: T): void;\n push(value: T): void;\n recycle(): T | undefined;\n pop(): T | undefined;\n splice(start: number, deleteCount: number, ...items: T[]): void;\n trimStart(count: number): void;\n shiftElements(start: number, count: number, offset: number): void;\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray\n | Int8Array | Int16Array | Int32Array\n | Float32Array | Float64Array;\n\n\n/**\n * polyfill for TypedArray.fill\n * This is needed to support .fill in all safari versions and IE 11.\n */\nexport function fill(array: T, value: number, start?: number, end?: number): T {\n // all modern engines that support .fill\n if (array.fill) {\n return array.fill(value, start, end) as T;\n }\n return fillFallback(array, value, start, end);\n}\n\nexport function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T {\n // safari and IE 11\n // since IE 11 does not support Array.prototype.fill either\n // we cannot use the suggested polyfill from MDN\n // instead we simply fall back to looping\n if (start >= array.length) {\n return array;\n }\n start = (array.length + start) % array.length;\n if (end >= array.length) {\n end = array.length;\n } else {\n end = (array.length + end) % array.length;\n }\n for (let i = start; i < end; ++i) {\n array[i] = value;\n }\n return array;\n}\n\n/**\n * Concat two typed arrays `a` and `b`.\n * Returns a new typed array.\n */\nexport function concat(a: T, b: T): T {\n const result = new (a.constructor as any)(a.length + b.length);\n result.set(a);\n result.set(b, a.length);\n return result;\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\ninterface INavigator {\n userAgent: string;\n language: string;\n platform: string;\n}\n\n// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but\n// we want this module to live in common.\ndeclare const navigator: INavigator;\n\nconst isNode = (typeof navigator === 'undefined') ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = !!~userAgent.indexOf('Firefox');\nexport const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);\nexport const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);\nexport const isIpad = platform === 'iPad';\nexport const isIphone = platform === 'iPhone';\nexport const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n\n/**\n * Return if the given array contains the given element\n * @param arr The array to search for the given element.\n * @param el The element to look for into the array\n */\nfunction contains(arr: any[], el: any): boolean {\n return arr.indexOf(el) >= 0;\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from './Types';\n\n/**\n * A base class that can be extended to provide convenience methods for managing the lifecycle of an\n * object and its components.\n */\nexport abstract class Disposable implements IDisposable {\n protected _disposables: IDisposable[] = [];\n protected _isDisposed: boolean = false;\n\n constructor() {\n }\n\n /**\n * Disposes the object, triggering the `dispose` method on all registered IDisposables.\n */\n public dispose(): void {\n this._isDisposed = true;\n this._disposables.forEach(d => d.dispose());\n this._disposables.length = 0;\n }\n\n /**\n * Registers a disposable object.\n * @param d The disposable to register.\n */\n public register(d: T): void {\n this._disposables.push(d);\n }\n\n /**\n * Unregisters a disposable object if it has been registered, if not do\n * nothing.\n * @param d The disposable to unregister.\n */\n public unregister(d: T): void {\n const index = this._disposables.indexOf(d);\n if (index !== -1) {\n this._disposables.splice(index, 1);\n }\n }\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from './Types';\n\ninterface IListener {\n (e: T): void;\n}\n\nexport interface IEvent {\n (listener: (e: T) => any): IDisposable;\n}\n\nexport class EventEmitter2 {\n private _listeners: IListener[] = [];\n private _event?: IEvent;\n\n public get event(): IEvent {\n if (!this._event) {\n this._event = (listener: (e: T) => any) => {\n this._listeners.push(listener);\n const disposable = {\n dispose: () => {\n for (let i = 0; i < this._listeners.length; i++) {\n if (this._listeners[i] === listener) {\n this._listeners.splice(i, 1);\n return;\n }\n }\n }\n };\n return disposable;\n };\n }\n return this._event;\n }\n\n public fire(data: T): void {\n const queue: IListener[] = [];\n for (let i = 0; i < this._listeners.length; i++) {\n queue.push(this._listeners[i]);\n }\n for (let i = 0; i < queue.length; i++) {\n queue[i].call(undefined, data);\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable, IEventEmitter, XtermListener } from './Types';\nimport { Disposable } from './Lifecycle';\n\nexport class EventEmitter extends Disposable implements IEventEmitter, IDisposable {\n private _events: {[type: string]: XtermListener[]};\n\n constructor() {\n super();\n // Restore the previous events if available, this will happen if the\n // constructor is called multiple times on the same object (terminal reset).\n this._events = (this)._events || {};\n }\n\n public on(type: string, listener: XtermListener): void {\n this._events[type] = this._events[type] || [];\n this._events[type].push(listener);\n }\n\n /**\n * Adds a disposable listener to the EventEmitter, returning the disposable.\n * @param type The event type.\n * @param handler The handler for the listener.\n */\n public addDisposableListener(type: string, handler: XtermListener): IDisposable {\n // TODO: Rename addDisposableEventListener to more easily disambiguate from Dom listener\n this.on(type, handler);\n let disposed = false;\n return {\n dispose: () => {\n if (disposed) {\n // Already disposed\n return;\n }\n this.off(type, handler);\n disposed = true;\n }\n };\n }\n\n public off(type: string, listener: XtermListener): void {\n if (!this._events[type]) {\n return;\n }\n\n const obj = this._events[type];\n let i = obj.length;\n\n while (i--) {\n if (obj[i] === listener) {\n obj.splice(i, 1);\n return;\n }\n }\n }\n\n public removeAllListeners(type: string): void {\n if (this._events[type]) {\n delete this._events[type];\n }\n }\n\n public emit(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n const obj = this._events[type];\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n }\n }\n\n public emitMayRemoveListeners(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n const obj = this._events[type];\n let length = obj.length;\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n i -= length - obj.length;\n length = obj.length;\n }\n }\n\n public listeners(type: string): XtermListener[] {\n return this._events[type] || [];\n }\n\n public dispose(): void {\n super.dispose();\n this._events = {};\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\n/*\n * A simple utility for cloning values\n */\nexport function clone(val: T, depth: number = 5): T | null {\n if (typeof val !== 'object') {\n return val;\n }\n\n // cloning null always returns null\n if (val === null) {\n return null;\n }\n\n // If we're cloning an array, use an array as the base, otherwise use an object\n const clonedObject: any = Array.isArray(val) ? [] : {};\n\n for (const key in val) {\n // Recursively clone eack item unless we're at the maximum depth\n clonedObject[key] = depth <= 1 ? val[key] : clone(val[key], depth - 1);\n }\n\n return clonedObject as T;\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICircularList } from './Types';\nimport { EventEmitter2, IEvent } from './EventEmitter2';\n\nexport interface IInsertEvent {\n index: number;\n amount: number;\n}\n\nexport interface IDeleteEvent {\n index: number;\n amount: number;\n}\n\n/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n */\nexport class CircularList implements ICircularList {\n protected _array: (T | undefined)[];\n private _startIndex: number;\n private _length: number;\n\n public onDeleteEmitter = new EventEmitter2();\n public get onDelete(): IEvent { return this.onDeleteEmitter.event; }\n public onInsertEmitter = new EventEmitter2();\n public get onInsert(): IEvent { return this.onInsertEmitter.event; }\n public onTrimEmitter = new EventEmitter2();\n public get onTrim(): IEvent { return this.onTrimEmitter.event; }\n\n constructor(\n private _maxLength: number\n ) {\n this._array = new Array(this._maxLength);\n this._startIndex = 0;\n this._length = 0;\n }\n\n public get maxLength(): number {\n return this._maxLength;\n }\n\n public set maxLength(newMaxLength: number) {\n // There was no change in maxLength, return early.\n if (this._maxLength === newMaxLength) {\n return;\n }\n\n // Reconstruct array, starting at index 0. Only transfer values from the\n // indexes 0 to length.\n const newArray = new Array(newMaxLength);\n for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n newArray[i] = this._array[this._getCyclicIndex(i)];\n }\n this._array = newArray;\n this._maxLength = newMaxLength;\n this._startIndex = 0;\n }\n\n public get length(): number {\n return this._length;\n }\n\n public set length(newLength: number) {\n if (newLength > this._length) {\n for (let i = this._length; i < newLength; i++) {\n this._array[i] = undefined;\n }\n }\n this._length = newLength;\n }\n\n /**\n * Gets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index of the value to get.\n * @return The value corresponding to the index.\n */\n public get(index: number): T | undefined {\n return this._array[this._getCyclicIndex(index)];\n }\n\n /**\n * Sets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index to set.\n * @param value The value to set.\n */\n public set(index: number, value: T | undefined): void {\n this._array[this._getCyclicIndex(index)] = value;\n }\n\n /**\n * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n * if the maximum length is reached.\n * @param value The value to push onto the list.\n */\n public push(value: T): void {\n this._array[this._getCyclicIndex(this._length)] = value;\n if (this._length === this._maxLength) {\n this._startIndex = ++this._startIndex % this._maxLength;\n this.onTrimEmitter.fire(1);\n } else {\n this._length++;\n }\n }\n\n /**\n * Advance ringbuffer index and return current element for recycling.\n * Note: The buffer must be full for this method to work.\n * @throws When the buffer is not full.\n */\n public recycle(): T {\n if (this._length !== this._maxLength) {\n throw new Error('Can only recycle when the buffer is full');\n }\n this._startIndex = ++this._startIndex % this._maxLength;\n this.onTrimEmitter.fire(1);\n return this._array[this._getCyclicIndex(this._length - 1)]!;\n }\n\n /**\n * Ringbuffer is at max length.\n */\n public get isFull(): boolean {\n return this._length === this._maxLength;\n }\n\n /**\n * Removes and returns the last value on the list.\n * @return The popped value.\n */\n public pop(): T | undefined {\n return this._array[this._getCyclicIndex(this._length-- - 1)];\n }\n\n /**\n * Deletes and/or inserts items at a particular index (in that order). Unlike\n * Array.prototype.splice, this operation does not return the deleted items as a new array in\n * order to save creating a new array. Note that this operation may shift all values in the list\n * in the worst case.\n * @param start The index to delete and/or insert.\n * @param deleteCount The number of elements to delete.\n * @param items The items to insert.\n */\n public splice(start: number, deleteCount: number, ...items: T[]): void {\n // Delete items\n if (deleteCount) {\n for (let i = start; i < this._length - deleteCount; i++) {\n this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n }\n this._length -= deleteCount;\n }\n\n // Add items\n for (let i = this._length - 1; i >= start; i--) {\n this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n }\n for (let i = 0; i < items.length; i++) {\n this._array[this._getCyclicIndex(start + i)] = items[i];\n }\n\n // Adjust length as needed\n if (this._length + items.length > this._maxLength) {\n const countToTrim = (this._length + items.length) - this._maxLength;\n this._startIndex += countToTrim;\n this._length = this._maxLength;\n this.onTrimEmitter.fire(countToTrim);\n } else {\n this._length += items.length;\n }\n }\n\n /**\n * Trims a number of items from the start of the list.\n * @param count The number of items to remove.\n */\n public trimStart(count: number): void {\n if (count > this._length) {\n count = this._length;\n }\n this._startIndex += count;\n this._length -= count;\n this.onTrimEmitter.fire(count);\n }\n\n public shiftElements(start: number, count: number, offset: number): void {\n if (count <= 0) {\n return;\n }\n if (start < 0 || start >= this._length) {\n throw new Error('start argument out of range');\n }\n if (start + offset < 0) {\n throw new Error('Cannot shift elements in list beyond index 0');\n }\n\n if (offset > 0) {\n for (let i = count - 1; i >= 0; i--) {\n this.set(start + i + offset, this.get(start + i));\n }\n const expandListBy = (start + count + offset) - this._length;\n if (expandListBy > 0) {\n this._length += expandListBy;\n while (this._length > this._maxLength) {\n this._length--;\n this._startIndex++;\n this.onTrimEmitter.fire(1);\n }\n }\n } else {\n for (let i = 0; i < count; i++) {\n this.set(start + i + offset, this.get(start + i));\n }\n }\n }\n\n /**\n * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n * backing array to get the element associated with the regular index.\n * @param index The regular index.\n * @returns The cyclic index.\n */\n private _getCyclicIndex(index: number): number {\n return (this._startIndex + index) % this._maxLength;\n }\n}\n","/**\n * Copyright (c) 2019 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { IDisposable } from 'xterm';\nimport { ITerminal } from './Types';\nimport { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './core/buffer/BufferLine';\n\nexport function applyWindowsMode(terminal: ITerminal): IDisposable {\n // Winpty does not support wraparound mode which means that lines will never\n // be marked as wrapped. This causes issues for things like copying a line\n // retaining the wrapped new line characters or if consumers are listening\n // in on the data stream.\n //\n // The workaround for this is to listen to every incoming line feed and mark\n // the line as wrapped if the last character in the previous line is not a\n // space. This is certainly not without its problems, but generally on\n // Windows when text reaches the end of the terminal it's likely going to be\n // wrapped.\n return terminal.onLineFeed(() => {\n const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1);\n const lastChar = line.get(terminal.cols - 1);\n\n const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);\n nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);\n });\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, IViewport } from './Types';\nimport { CharMeasure } from './CharMeasure';\nimport { Disposable } from './common/Lifecycle';\nimport { addDisposableDomListener } from './ui/Lifecycle';\nimport { IColorSet } from './ui/Types';\nimport { IRenderDimensions } from './renderer/Types';\n\nconst FALLBACK_SCROLL_BAR_WIDTH = 15;\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport extends Disposable implements IViewport {\n public scrollBarWidth: number = 0;\n private _currentRowHeight: number = 0;\n private _lastRecordedBufferLength: number = 0;\n private _lastRecordedViewportHeight: number = 0;\n private _lastRecordedBufferHeight: number = 0;\n private _lastTouchY: number;\n private _lastScrollTop: number = 0;\n\n // Stores a partial line amount when scrolling, this is used to keep track of how much of a line\n // is scrolled so we can \"scroll\" over partial lines and feel natural on touchpads. This is a\n // quick fix and could have a more robust solution in place that reset the value when needed.\n private _wheelPartialScroll: number = 0;\n\n private _refreshAnimationFrame: number | null = null;\n private _ignoreNextScrollEvent: boolean = false;\n\n /**\n * Creates a new Viewport.\n * @param _terminal The terminal this viewport belongs to.\n * @param _viewportElement The DOM element acting as the viewport.\n * @param _scrollArea The DOM element acting as the scroll area.\n * @param _charMeasure A DOM element used to measure the character size of. the terminal.\n */\n constructor(\n private _terminal: ITerminal,\n private _viewportElement: HTMLElement,\n private _scrollArea: HTMLElement,\n private _charMeasure: CharMeasure,\n private _dimensions: IRenderDimensions\n ) {\n super();\n\n // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.\n // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,\n // therefore we account for a standard amount to make it visible\n this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;\n this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this)));\n\n // Perform this async to ensure the CharMeasure is ready.\n setTimeout(() => this.syncScrollArea(), 0);\n }\n\n public onDimensionsChance(dimensions: IRenderDimensions): void {\n this._dimensions = dimensions;\n }\n\n public onThemeChange(colors: IColorSet): void {\n this._viewportElement.style.backgroundColor = colors.background.css;\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private _refresh(): void {\n if (this._refreshAnimationFrame === null) {\n this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh());\n }\n }\n\n private _innerRefresh(): void {\n if (this._charMeasure.height > 0) {\n this._currentRowHeight = this._dimensions.scaledCellHeight / window.devicePixelRatio;\n this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;\n const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._dimensions.canvasHeight);\n if (this._lastRecordedBufferHeight !== newBufferHeight) {\n this._lastRecordedBufferHeight = newBufferHeight;\n this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';\n }\n }\n\n // Sync scrollTop\n const scrollTop = this._terminal.buffer.ydisp * this._currentRowHeight;\n if (this._viewportElement.scrollTop !== scrollTop) {\n // Ignore the next scroll event which will be triggered by setting the scrollTop as we do not\n // want this event to scroll the terminal\n this._ignoreNextScrollEvent = true;\n this._viewportElement.scrollTop = scrollTop;\n }\n\n this._refreshAnimationFrame = null;\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(): void {\n // If buffer height changed\n if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) {\n this._lastRecordedBufferLength = this._terminal.buffer.lines.length;\n this._refresh();\n return;\n }\n\n // If viewport height changed\n if (this._lastRecordedViewportHeight !== this._dimensions.canvasHeight) {\n this._refresh();\n return;\n }\n\n // If the buffer position doesn't match last scroll top\n const newScrollTop = this._terminal.buffer.ydisp * this._currentRowHeight;\n if (this._lastScrollTop !== newScrollTop) {\n this._refresh();\n return;\n }\n\n // If element's scroll top changed, this can happen when hiding the element\n if (this._lastScrollTop !== this._viewportElement.scrollTop) {\n this._refresh();\n return;\n }\n\n // If row height changed\n if (this._dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {\n this._refresh();\n return;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private _onScroll(ev: Event): void {\n // Record current scroll top position\n this._lastScrollTop = this._viewportElement.scrollTop;\n\n // Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt\n // which causes the terminal to scroll the buffer to the top\n if (!this._viewportElement.offsetParent) {\n return;\n }\n\n // Ignore the event if it was flagged to ignore (when the source of the event is from Viewport)\n if (this._ignoreNextScrollEvent) {\n this._ignoreNextScrollEvent = false;\n return;\n }\n\n const newRow = Math.round(this._lastScrollTop / this._currentRowHeight);\n const diff = newRow - this._terminal.buffer.ydisp;\n this._terminal.scrollLines(diff, true);\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public onWheel(ev: WheelEvent): void {\n const amount = this._getPixelsScrolled(ev);\n if (amount === 0) {\n return;\n }\n this._viewportElement.scrollTop += amount;\n // Prevent the page from scrolling when the terminal scrolls\n ev.preventDefault();\n }\n\n private _getPixelsScrolled(ev: WheelEvent): number {\n // Do nothing if it's not a vertical scroll event\n if (ev.deltaY === 0) {\n return 0;\n }\n\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let amount = ev.deltaY;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n amount *= this._currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n amount *= this._currentRowHeight * this._terminal.rows;\n }\n return amount;\n }\n\n /**\n * Gets the number of pixels scrolled by the mouse event taking into account what type of delta\n * is being used.\n * @param ev The mouse wheel event.\n */\n public getLinesScrolled(ev: WheelEvent): number {\n // Do nothing if it's not a vertical scroll event\n if (ev.deltaY === 0) {\n return 0;\n }\n\n // Fallback to WheelEvent.DOM_DELTA_LINE\n let amount = ev.deltaY;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {\n amount /= this._currentRowHeight + 0.0; // Prevent integer division\n this._wheelPartialScroll += amount;\n amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);\n this._wheelPartialScroll %= 1;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n amount *= this._terminal.rows;\n }\n return amount;\n }\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public onTouchStart(ev: TouchEvent): void {\n this._lastTouchY = ev.touches[0].pageY;\n }\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public onTouchMove(ev: TouchEvent): void {\n const deltaY = this._lastTouchY - ev.touches[0].pageY;\n this._lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return;\n }\n this._viewportElement.scrollTop += deltaY;\n ev.preventDefault();\n }\n}\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n *\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n *\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\nimport { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types';\nimport { IRenderer } from './renderer/Types';\nimport { BufferSet } from './BufferSet';\nimport { Buffer, MAX_BUFFER_SIZE } from './Buffer';\nimport { CompositionHelper } from './CompositionHelper';\nimport { EventEmitter } from './common/EventEmitter';\nimport { Viewport } from './Viewport';\nimport { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard';\nimport { C0 } from './common/data/EscapeSequences';\nimport { InputHandler } from './InputHandler';\nimport { Renderer } from './renderer/Renderer';\nimport { Linkifier } from './Linkifier';\nimport { SelectionManager } from './SelectionManager';\nimport { CharMeasure } from './CharMeasure';\nimport * as Browser from './common/Platform';\nimport { addDisposableDomListener } from './ui/Lifecycle';\nimport * as Strings from './Strings';\nimport { MouseHelper } from './MouseHelper';\nimport { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';\nimport { MouseZoneManager } from './MouseZoneManager';\nimport { AccessibilityManager } from './AccessibilityManager';\nimport { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';\nimport { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache';\nimport { DomRenderer } from './renderer/dom/DomRenderer';\nimport { IKeyboardEvent } from './common/Types';\nimport { evaluateKeyboardEvent } from './core/input/Keyboard';\nimport { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from './core/Types';\nimport { clone } from './common/Clone';\nimport { EventEmitter2, IEvent } from './common/EventEmitter2';\nimport { Attributes, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';\nimport { applyWindowsMode } from './WindowsMode';\nimport { ColorManager } from './ui/ColorManager';\nimport { RenderCoordinator } from './renderer/RenderCoordinator';\n\n// Let it work inside Node.js for automated testing purposes.\nconst document = (typeof window !== 'undefined') ? window.document : null;\n\n/**\n * The amount of write requests to queue before sending an XOFF signal to the\n * pty process. This number must be small in order for ^C and similar sequences\n * to be responsive.\n */\nconst WRITE_BUFFER_PAUSE_THRESHOLD = 5;\n\n/**\n * The max number of ms to spend on writes before allowing the renderer to\n * catch up with a 0ms setTimeout. A value of < 33 to keep us close to\n * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS\n * depends on the time it takes for the renderer to draw the frame.\n */\nconst WRITE_TIMEOUT_MS = 12;\nconst WRITE_BUFFER_LENGTH_THRESHOLD = 50;\n\nconst MINIMUM_COLS = 2; // Less than 2 can mess with wide chars\nconst MINIMUM_ROWS = 1;\n\n/**\n * The set of options that only have an effect when set in the Terminal constructor.\n */\nconst CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];\n\nconst DEFAULT_OPTIONS: ITerminalOptions = {\n cols: 80,\n rows: 24,\n convertEol: false,\n termName: 'xterm',\n cursorBlink: false,\n cursorStyle: 'block',\n bellSound: DEFAULT_BELL_SOUND,\n bellStyle: 'none',\n drawBoldTextInBrightColors: true,\n enableBold: true,\n experimentalCharAtlas: 'static',\n fontFamily: 'courier-new, courier, monospace',\n fontSize: 15,\n fontWeight: 'normal',\n fontWeightBold: 'bold',\n lineHeight: 1.0,\n letterSpacing: 0,\n scrollback: 1000,\n screenKeys: false,\n screenReaderMode: false,\n debug: false,\n macOptionIsMeta: false,\n macOptionClickForcesSelection: false,\n cancelEvents: false,\n disableStdin: false,\n useFlowControl: false,\n allowTransparency: false,\n tabStopWidth: 8,\n theme: undefined,\n rightClickSelectsWord: Browser.isMac,\n rendererType: 'canvas',\n windowsMode: false\n};\n\nexport class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {\n public textarea: HTMLTextAreaElement;\n public element: HTMLElement;\n public screenElement: HTMLElement;\n\n /**\n * The HTMLElement that the terminal is created in, set by Terminal.open.\n */\n private _parent: HTMLElement;\n private _context: Window;\n private _document: Document;\n private _viewportScrollArea: HTMLElement;\n private _viewportElement: HTMLElement;\n private _helperContainer: HTMLElement;\n private _compositionView: HTMLElement;\n\n private _visualBellTimer: number;\n\n public browser: IBrowser = Browser;\n\n public options: ITerminalOptions;\n\n // TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options\n public cursorState: number;\n public cursorHidden: boolean;\n\n private _customKeyEventHandler: CustomKeyEventHandler;\n\n // modes\n public applicationKeypad: boolean;\n public applicationCursor: boolean;\n public originMode: boolean;\n public insertMode: boolean;\n public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false\n public bracketedPasteMode: boolean;\n\n // charset\n // The current charset\n public charset: ICharset;\n public gcharset: number;\n public glevel: number;\n public charsets: ICharset[];\n\n // mouse properties\n private _decLocator: boolean; // This is unstable and never set\n public x10Mouse: boolean;\n public vt200Mouse: boolean;\n private _vt300Mouse: boolean; // This is unstable and never set\n public normalMouse: boolean;\n public mouseEvents: boolean;\n public sendFocus: boolean;\n public utfMouse: boolean;\n public sgrMouse: boolean;\n public urxvtMouse: boolean;\n\n // misc\n private _refreshStart: number;\n private _refreshEnd: number;\n public savedCols: number;\n\n public curAttrData: IAttributeData;\n private _eraseAttrData: IAttributeData;\n\n public params: (string | number)[];\n public currentParam: string | number;\n\n // user input states\n public writeBuffer: string[];\n public writeBufferUtf8: Uint8Array[];\n private _writeInProgress: boolean;\n\n /**\n * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.\n * This is a distinct state from writeStopped so that if the user requested\n * XOFF via ^S that it will not automatically resume when the writeBuffer goes\n * below threshold.\n */\n private _xoffSentToCatchUp: boolean;\n\n /** Whether writing has been stopped as a result of XOFF */\n // private _writeStopped: boolean;\n\n // Store if user went browsing history in scrollback\n private _userScrolling: boolean;\n\n private _inputHandler: InputHandler;\n public soundManager: SoundManager;\n private _renderCoordinator: RenderCoordinator;\n public selectionManager: SelectionManager;\n public linkifier: ILinkifier;\n public buffers: BufferSet;\n public viewport: IViewport;\n private _compositionHelper: ICompositionHelper;\n public charMeasure: CharMeasure;\n private _mouseZoneManager: IMouseZoneManager;\n public mouseHelper: MouseHelper;\n private _accessibilityManager: AccessibilityManager;\n private _colorManager: ColorManager;\n private _theme: ITheme;\n private _windowsMode: IDisposable | undefined;\n\n // bufferline to clone/copy from for new blank lines\n private _blankLine: IBufferLine = null;\n\n public cols: number;\n public rows: number;\n\n private _onCursorMove = new EventEmitter2();\n public get onCursorMove(): IEvent { return this._onCursorMove.event; }\n private _onData = new EventEmitter2();\n public get onData(): IEvent { return this._onData.event; }\n private _onKey = new EventEmitter2<{ key: string, domEvent: KeyboardEvent }>();\n public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }\n private _onLineFeed = new EventEmitter2();\n public get onLineFeed(): IEvent { return this._onLineFeed.event; }\n private _onRender = new EventEmitter2<{ start: number, end: number }>();\n public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }\n private _onResize = new EventEmitter2<{ cols: number, rows: number }>();\n public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }\n private _onScroll = new EventEmitter2();\n public get onScroll(): IEvent { return this._onScroll.event; }\n private _onSelectionChange = new EventEmitter2();\n public get onSelectionChange(): IEvent { return this._onSelectionChange.event; }\n private _onTitleChange = new EventEmitter2();\n public get onTitleChange(): IEvent { return this._onTitleChange.event; }\n\n /**\n * Creates a new `Terminal` object.\n *\n * @param options An object containing a set of options, the available options are:\n * - `cursorBlink` (boolean): Whether the terminal cursor blinks\n * - `cols` (number): The number of columns of the terminal (horizontal size)\n * - `rows` (number): The number of rows of the terminal (vertical size)\n *\n * @public\n * @class Xterm Xterm\n * @alias module:xterm/src/xterm\n */\n constructor(\n options: ITerminalOptions = {}\n ) {\n super();\n this.options = clone(options);\n this._setup();\n\n // TODO: Remove these in v4\n // Fire old style events from new emitters\n this.onCursorMove(() => this.emit('cursormove'));\n this.onData(e => this.emit('data', e));\n this.onKey(e => this.emit('key', e.key, e.domEvent));\n this.onLineFeed(() => this.emit('linefeed'));\n this.onRender(e => this.emit('refresh', e));\n this.onResize(e => this.emit('resize', e));\n this.onSelectionChange(() => this.emit('selection'));\n this.onScroll(e => this.emit('scroll', e));\n this.onTitleChange(e => this.emit('title', e));\n }\n\n public dispose(): void {\n super.dispose();\n if (this._windowsMode) {\n this._windowsMode.dispose();\n this._windowsMode = undefined;\n }\n this._customKeyEventHandler = null;\n removeTerminalFromCache(this);\n this.handler = () => {};\n this.write = () => {};\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n }\n\n /**\n * @deprecated Use dispose instead.\n */\n public destroy(): void {\n this.dispose();\n }\n\n private _setup(): void {\n Object.keys(DEFAULT_OPTIONS).forEach((key) => {\n if (this.options[key] === null || this.options[key] === undefined) {\n this.options[key] = DEFAULT_OPTIONS[key];\n }\n });\n\n // this.context = options.context || window;\n // this.document = options.document || document;\n // TODO: WHy not document.body?\n this._parent = document ? document.body : null;\n\n this.cols = Math.max(this.options.cols, MINIMUM_COLS);\n this.rows = Math.max(this.options.rows, MINIMUM_ROWS);\n\n if (this.options.handler) {\n this.onData(this.options.handler);\n }\n\n this.cursorState = 0;\n this.cursorHidden = false;\n this._customKeyEventHandler = null;\n\n // modes\n this.applicationKeypad = false;\n this.applicationCursor = false;\n this.originMode = false;\n this.insertMode = false;\n this.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this.bracketedPasteMode = false;\n\n // charset\n this.charset = null;\n this.gcharset = null;\n this.glevel = 0;\n // TODO: Can this be just []?\n this.charsets = [null];\n\n this.curAttrData = DEFAULT_ATTR_DATA.clone();\n this._eraseAttrData = DEFAULT_ATTR_DATA.clone();\n\n this.params = [];\n this.currentParam = 0;\n\n // user input states\n this.writeBuffer = [];\n this.writeBufferUtf8 = [];\n this._writeInProgress = false;\n\n this._xoffSentToCatchUp = false;\n // this._writeStopped = false;\n this._userScrolling = false;\n\n // Register input handler and refire/handle events\n this._inputHandler = new InputHandler(this);\n this._inputHandler.onCursorMove(() => this._onCursorMove.fire());\n this._inputHandler.onLineFeed(() => this._onLineFeed.fire());\n this._inputHandler.onData(e => this._onData.fire(e));\n this.register(this._inputHandler);\n\n this.selectionManager = this.selectionManager || null;\n this.linkifier = this.linkifier || new Linkifier(this);\n this._mouseZoneManager = this._mouseZoneManager || null;\n this.soundManager = this.soundManager || new SoundManager(this);\n\n // Create the terminal's buffers and set the current buffer\n this.buffers = new BufferSet(this);\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n this.selectionManager.initBuffersListeners();\n }\n\n if (this.options.windowsMode) {\n this._windowsMode = applyWindowsMode(this);\n }\n }\n\n /**\n * Convenience property to active buffer.\n */\n public get buffer(): Buffer {\n return this.buffers.active;\n }\n\n /**\n * back_color_erase feature for xterm.\n */\n public eraseAttrData(): IAttributeData {\n this._eraseAttrData.bg &= ~(Attributes.CM_MASK | 0xFFFFFF);\n this._eraseAttrData.bg |= this.curAttrData.bg & ~0xFC000000;\n return this._eraseAttrData;\n }\n\n /**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\n public focus(): void {\n if (this.textarea) {\n this.textarea.focus({ preventScroll: true });\n }\n }\n\n public get isFocused(): boolean {\n return document.activeElement === this.textarea && document.hasFocus();\n }\n\n /**\n * Retrieves an option's value from the terminal.\n * @param key The option key.\n */\n public getOption(key: string): any {\n if (!(key in DEFAULT_OPTIONS)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n\n return this.options[key];\n }\n\n /**\n * Sets an option on the terminal.\n * @param key The option key.\n * @param value The option value.\n */\n public setOption(key: string, value: any): void {\n if (!(key in DEFAULT_OPTIONS)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) {\n console.error(`Option \"${key}\" can only be set in the constructor`);\n }\n if (this.options[key] === value) {\n return;\n }\n switch (key) {\n case 'bellStyle':\n if (!value) {\n value = 'none';\n }\n break;\n case 'cursorStyle':\n if (!value) {\n value = 'block';\n }\n break;\n case 'fontWeight':\n if (!value) {\n value = 'normal';\n }\n break;\n case 'fontWeightBold':\n if (!value) {\n value = 'bold';\n }\n break;\n case 'lineHeight':\n if (value < 1) {\n console.warn(`${key} cannot be less than 1, value: ${value}`);\n return;\n }\n case 'rendererType':\n if (!value) {\n value = 'canvas';\n }\n break;\n case 'tabStopWidth':\n if (value < 1) {\n console.warn(`${key} cannot be less than 1, value: ${value}`);\n return;\n }\n break;\n case 'theme':\n this._setTheme(value);\n break;\n case 'scrollback':\n value = Math.min(value, MAX_BUFFER_SIZE);\n\n if (value < 0) {\n console.warn(`${key} cannot be less than 0, value: ${value}`);\n return;\n }\n if (this.options[key] !== value) {\n const newBufferLength = this.rows + value;\n if (this.buffer.lines.length > newBufferLength) {\n const amountToTrim = this.buffer.lines.length - newBufferLength;\n const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);\n this.buffer.lines.trimStart(amountToTrim);\n this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);\n this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);\n if (needsRefresh) {\n this.refresh(0, this.rows - 1);\n }\n }\n }\n break;\n }\n this.options[key] = value;\n switch (key) {\n case 'fontFamily':\n case 'fontSize':\n // When the font changes the size of the cells may change which requires a renderer clear\n if (this._renderCoordinator) {\n this._renderCoordinator.clear();\n this.charMeasure.measure(this.options);\n }\n break;\n case 'drawBoldTextInBrightColors':\n case 'experimentalCharAtlas':\n case 'enableBold':\n case 'letterSpacing':\n case 'lineHeight':\n case 'fontWeight':\n case 'fontWeightBold':\n // When the font changes the size of the cells may change which requires a renderer clear\n if (this._renderCoordinator) {\n this._renderCoordinator.clear();\n this._renderCoordinator.onResize(this.cols, this.rows);\n this.refresh(0, this.rows - 1);\n }\n break;\n case 'rendererType':\n if (this._renderCoordinator) {\n this._renderCoordinator.setRenderer(this._createRenderer());\n }\n break;\n case 'scrollback':\n this.buffers.resize(this.cols, this.rows);\n if (this.viewport) {\n this.viewport.syncScrollArea();\n }\n break;\n case 'screenReaderMode':\n if (value) {\n if (!this._accessibilityManager && this._renderCoordinator) {\n this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions);\n }\n } else {\n if (this._accessibilityManager) {\n this._accessibilityManager.dispose();\n this._accessibilityManager = null;\n }\n }\n break;\n case 'tabStopWidth': this.buffers.setupTabStops(); break;\n case 'windowsMode':\n if (value) {\n if (!this._windowsMode) {\n this._windowsMode = applyWindowsMode(this);\n }\n } else {\n if (this._windowsMode) {\n this._windowsMode.dispose();\n this._windowsMode = undefined;\n }\n }\n break;\n }\n // Inform renderer of changes\n if (this._renderCoordinator) {\n this._renderCoordinator.onOptionsChanged();\n }\n }\n\n /**\n * Binds the desired focus behavior on a given terminal object.\n */\n private _onTextAreaFocus(ev: KeyboardEvent): void {\n if (this.sendFocus) {\n this.handler(C0.ESC + '[I');\n }\n this.updateCursorStyle(ev);\n this.element.classList.add('focus');\n this.showCursor();\n this.emit('focus');\n }\n\n /**\n * Blur the terminal, calling the blur function on the terminal's underlying\n * textarea.\n */\n public blur(): void {\n return this.textarea.blur();\n }\n\n /**\n * Binds the desired blur behavior on a given terminal object.\n */\n private _onTextAreaBlur(): void {\n // Text can safely be removed on blur. Doing it earlier could interfere with\n // screen readers reading it out.\n this.textarea.value = '';\n this.refresh(this.buffer.y, this.buffer.y);\n if (this.sendFocus) {\n this.handler(C0.ESC + '[O');\n }\n this.element.classList.remove('focus');\n this.emit('blur');\n }\n\n /**\n * Initialize default behavior\n */\n private _initGlobal(): void {\n this._bindKeys();\n\n // Bind clipboard functionality\n this.register(addDisposableDomListener(this.element, 'copy', (event: ClipboardEvent) => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!this.hasSelection()) {\n return;\n }\n copyHandler(event, this, this.selectionManager);\n }));\n const pasteHandlerWrapper = (event: ClipboardEvent) => pasteHandler(event, this);\n this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper));\n this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper));\n\n // Handle right click context menus\n if (Browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n this.register(addDisposableDomListener(this.element, 'mousedown', (event: MouseEvent) => {\n if (event.button === 2) {\n rightClickHandler(event, this, this.selectionManager, this.options.rightClickSelectsWord);\n }\n }));\n } else {\n this.register(addDisposableDomListener(this.element, 'contextmenu', (event: MouseEvent) => {\n rightClickHandler(event, this, this.selectionManager, this.options.rightClickSelectsWord);\n }));\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (Browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n this.register(addDisposableDomListener(this.element, 'auxclick', (event: MouseEvent) => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this);\n }\n }));\n }\n }\n\n /**\n * Apply key handling to the terminal\n */\n private _bindKeys(): void {\n const self = this;\n this.register(addDisposableDomListener(this.element, 'keydown', function (ev: KeyboardEvent): void {\n if (document.activeElement !== this) {\n return;\n }\n self._keyDown(ev);\n }, true));\n\n this.register(addDisposableDomListener(this.element, 'keypress', function (ev: KeyboardEvent): void {\n if (document.activeElement !== this) {\n return;\n }\n self._keyPress(ev);\n }, true));\n\n this.register(addDisposableDomListener(this.element, 'keyup', (ev: KeyboardEvent) => {\n if (!wasModifierKeyOnlyEvent(ev)) {\n this.focus();\n }\n\n self._keyUp(ev);\n }, true));\n\n this.register(addDisposableDomListener(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true));\n this.register(addDisposableDomListener(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true));\n this.register(addDisposableDomListener(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart()));\n this.register(addDisposableDomListener(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e)));\n this.register(addDisposableDomListener(this.textarea, 'compositionend', () => this._compositionHelper.compositionend()));\n this.register(this.onRender(() => this._compositionHelper.updateCompositionElements()));\n this.register(this.onRender(e => this._queueLinkification(e.start, e.end)));\n }\n\n /**\n * Opens the terminal within an element.\n *\n * @param parent The element to create the terminal within.\n */\n public open(parent: HTMLElement): void {\n this._parent = parent || this._parent;\n\n if (!this._parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n // Grab global elements\n this._context = this._parent.ownerDocument.defaultView;\n this._document = this._parent.ownerDocument;\n\n // Create main element container\n this.element = this._document.createElement('div');\n this.element.dir = 'ltr'; // xterm.css assumes LTR\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n this.element.setAttribute('tabindex', '0');\n this._parent.appendChild(this.element);\n\n // Performance: Use a document fragment to build the terminal\n // viewport and helper elements detached from the DOM\n const fragment = document.createDocumentFragment();\n this._viewportElement = document.createElement('div');\n this._viewportElement.classList.add('xterm-viewport');\n fragment.appendChild(this._viewportElement);\n this._viewportScrollArea = document.createElement('div');\n this._viewportScrollArea.classList.add('xterm-scroll-area');\n this._viewportElement.appendChild(this._viewportScrollArea);\n\n this.screenElement = document.createElement('div');\n this.screenElement.classList.add('xterm-screen');\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this._helperContainer = document.createElement('div');\n this._helperContainer.classList.add('xterm-helpers');\n this.screenElement.appendChild(this._helperContainer);\n fragment.appendChild(this.screenElement);\n\n this._mouseZoneManager = new MouseZoneManager(this);\n this.register(this._mouseZoneManager);\n this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));\n this.linkifier.attachToDom(this._mouseZoneManager);\n\n this.textarea = document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n // TODO: New API to set title? This could say \"Terminal bash input\", etc.\n this.textarea.setAttribute('aria-label', Strings.promptLabel);\n this.textarea.setAttribute('aria-multiline', 'false');\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._onTextAreaFocus(ev)));\n this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));\n this._helperContainer.appendChild(this.textarea);\n\n this._compositionView = document.createElement('div');\n this._compositionView.classList.add('composition-view');\n this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this);\n this._helperContainer.appendChild(this._compositionView);\n\n this.charMeasure = new CharMeasure(document, this._helperContainer);\n\n // Performance: Add viewport and helper elements from the fragment\n this.element.appendChild(fragment);\n\n this._theme = this.options.theme;\n this._colorManager = new ColorManager(document, this.options.allowTransparency);\n this._colorManager.setTheme(this._theme);\n\n const renderer = this._createRenderer();\n this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement);\n this._renderCoordinator.onRender(e => this._onRender.fire(e));\n this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows));\n\n this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure, this._renderCoordinator.dimensions);\n this.viewport.onThemeChange(this._colorManager.colors);\n this.register(this.viewport);\n\n this.register(this.onCursorMove(() => this._renderCoordinator.onCursorMove()));\n this.register(this.onResize(() => this._renderCoordinator.onResize(this.cols, this.rows)));\n this.register(this.addDisposableListener('blur', () => this._renderCoordinator.onBlur()));\n this.register(this.addDisposableListener('focus', () => this._renderCoordinator.onFocus()));\n this.register(this.charMeasure.onCharSizeChanged(() => this._renderCoordinator.onCharSizeChanged()));\n this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea()));\n\n this.selectionManager = new SelectionManager(this, this.charMeasure);\n this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire()));\n this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e)));\n this.register(this.selectionManager.onRedrawRequest(e => this._renderCoordinator.onSelectionChanged(e.start, e.end, e.columnSelectMode)));\n this.register(this.selectionManager.onLinuxMouseSelection(text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea.value = text;\n this.textarea.focus();\n this.textarea.select();\n }));\n this.register(this.onScroll(() => {\n this.viewport.syncScrollArea();\n this.selectionManager.refresh();\n }));\n this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this.selectionManager.refresh()));\n\n this.mouseHelper = new MouseHelper(this._renderCoordinator);\n // apply mouse event classes set by escape codes before terminal was attached\n this.element.classList.toggle('enable-mouse-events', this.mouseEvents);\n if (this.mouseEvents) {\n this.selectionManager.disable();\n } else {\n this.selectionManager.enable();\n }\n\n if (this.options.screenReaderMode) {\n // Note that this must be done *after* the renderer is created in order to\n // ensure the correct order of the dprchange event\n this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions);\n this._accessibilityManager.register(this._renderCoordinator.onDimensionsChange(e => this._accessibilityManager.setDimensions(e)));\n }\n\n // Measure the character size\n this.charMeasure.measure(this.options);\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that need to be taken on the document.\n this._initGlobal();\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n\n }\n\n private _createRenderer(): IRenderer {\n switch (this.options.rendererType) {\n case 'canvas': return new Renderer(this, this._colorManager.colors); break;\n case 'dom': return new DomRenderer(this, this._colorManager.colors); break;\n default: throw new Error(`Unrecognized rendererType \"${this.options.rendererType}\"`);\n }\n }\n\n /**\n * Sets the theme on the renderer. The renderer must have been initialized.\n * @param theme The theme to set.\n */\n private _setTheme(theme: ITheme): void {\n this._theme = theme;\n if (this._colorManager) {\n this._colorManager.setTheme(theme);\n }\n if (this._renderCoordinator) {\n this._renderCoordinator.setColors(this._colorManager.colors);\n }\n if (this.viewport) {\n this.viewport.onThemeChange(this._colorManager.colors);\n }\n }\n\n /**\n * XTerm mouse events\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking\n * To better understand these\n * the xterm code is very helpful:\n * Relevant files:\n * button.c, charproc.c, misc.c\n * Relevant functions in xterm/button.c:\n * BtnCode, EmitButtonCode, EditorButton, SendMousePosition\n */\n public bindMouse(): void {\n const el = this.element;\n const self = this;\n let pressed = 32;\n\n // mouseup, mousedown, wheel\n // left click: ^[[M 3<^[[M#3<\n // wheel up: ^[[M`3>\n function sendButton(ev: MouseEvent | WheelEvent): void {\n let button;\n let pos;\n\n // get the xterm-style button\n button = getButton(ev);\n\n // get mouse coordinates\n pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n sendEvent(button, pos);\n\n switch ((ev).overrideType || ev.type) {\n case 'mousedown':\n pressed = button;\n break;\n case 'mouseup':\n // keep it at the left\n // button, just in case.\n pressed = 32;\n break;\n case 'wheel':\n // nothing. don't\n // interfere with\n // `pressed`.\n break;\n }\n }\n\n // motion example of a left click:\n // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<\n function sendMove(ev: MouseEvent): void {\n let button = pressed;\n const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n // buttons marked as motions\n // are incremented by 32\n button += 32;\n\n sendEvent(button, pos);\n }\n\n // encode button and\n // position to characters\n function encode(data: number[], ch: number): void {\n if (!self.utfMouse) {\n if (ch === 255) {\n data.push(0);\n return;\n }\n if (ch > 127) ch = 127;\n data.push(ch);\n } else {\n if (ch > 2047) {\n data.push(2047);\n return;\n }\n data.push(ch);\n }\n }\n\n // send a mouse event:\n // regular/utf8: ^[[M Cb Cx Cy\n // urxvt: ^[[ Cb ; Cx ; Cy M\n // sgr: ^[[ Cb ; Cx ; Cy M/m\n // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \\r\n // locator: CSI P e ; P b ; P r ; P c ; P p & w\n function sendEvent(button: number, pos: {x: number, y: number}): void {\n // self.emit('mouse', {\n // x: pos.x - 32,\n // y: pos.x - 32,\n // button: button\n // });\n\n if (self._vt300Mouse) {\n // NOTE: Unstable.\n // http://www.vt100.net/docs/vt3xx-gp/chapter15.html\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n let data = C0.ESC + '[24';\n if (button === 0) data += '1';\n else if (button === 1) data += '3';\n else if (button === 2) data += '5';\n else if (button === 3) return;\n else data += '0';\n data += '~[' + pos.x + ',' + pos.y + ']\\r';\n self.handler(data);\n return;\n }\n\n if (self._decLocator) {\n // NOTE: Unstable.\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n if (button === 0) button = 2;\n else if (button === 1) button = 4;\n else if (button === 2) button = 6;\n else if (button === 3) button = 3;\n self.handler(C0.ESC + '['\n + button\n + ';'\n + (button === 3 ? 4 : 0)\n + ';'\n + pos.y\n + ';'\n + pos.x\n + ';'\n // Not sure what page is meant to be\n + (pos).page || 0\n + '&w');\n return;\n }\n\n if (self.urxvtMouse) {\n pos.x -= 32;\n pos.y -= 32;\n pos.x++;\n pos.y++;\n self.handler(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');\n return;\n }\n\n if (self.sgrMouse) {\n pos.x -= 32;\n pos.y -= 32;\n self.handler(C0.ESC + '[<'\n + (((button & 3) === 3 ? button & ~3 : button) - 32)\n + ';'\n + pos.x\n + ';'\n + pos.y\n + ((button & 3) === 3 ? 'm' : 'M'));\n return;\n }\n\n const data: number[] = [];\n\n encode(data, button);\n encode(data, pos.x);\n encode(data, pos.y);\n\n self.handler(C0.ESC + '[M' + String.fromCharCode.apply(String, data));\n }\n\n function getButton(ev: MouseEvent): number {\n let button;\n let shift;\n let meta;\n let ctrl;\n let mod;\n\n // two low bits:\n // 0 = left\n // 1 = middle\n // 2 = right\n // 3 = release\n // wheel up/down:\n // 1, and 2 - with 64 added\n switch ((ev).overrideType || ev.type) {\n case 'mousedown':\n button = ev.button !== null && ev.button !== undefined\n ? +ev.button\n : ev.which !== null && ev.which !== undefined\n ? ev.which - 1\n : null;\n\n if (Browser.isMSIE) {\n button = button === 1 ? 0 : button === 4 ? 1 : button;\n }\n break;\n case 'mouseup':\n button = 3;\n break;\n case 'DOMMouseScroll':\n button = ev.detail < 0\n ? 64\n : 65;\n break;\n case 'wheel':\n button = (ev).deltaY < 0\n ? 64\n : 65;\n break;\n }\n\n // next three bits are the modifiers:\n // 4 = shift, 8 = meta, 16 = control\n shift = ev.shiftKey ? 4 : 0;\n meta = ev.metaKey ? 8 : 0;\n ctrl = ev.ctrlKey ? 16 : 0;\n mod = shift | meta | ctrl;\n\n // no mods\n if (self.vt200Mouse) {\n // ctrl only\n mod &= ctrl;\n } else if (!self.normalMouse) {\n mod = 0;\n }\n\n // increment to SP\n button = (32 + (mod << 2)) + button;\n\n return button;\n }\n\n this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => {\n\n // Prevent the focus on the textarea from getting lost\n // and make sure we get focused on mousedown\n ev.preventDefault();\n this.focus();\n\n // Don't send the mouse button to the pty if mouse events are disabled or\n // if the selection manager is having selection forced (ie. a modifier is\n // held).\n if (!this.mouseEvents || this.selectionManager.shouldForceSelection(ev)) {\n return;\n }\n\n // send the button\n sendButton(ev);\n\n // fix for odd bug\n // if (this.vt200Mouse && !this.normalMouse) {\n if (this.vt200Mouse) {\n (ev).overrideType = 'mouseup';\n sendButton(ev);\n return this.cancel(ev);\n }\n\n // TODO: All mouse handling should be pulled into its own file.\n\n // bind events\n let moveHandler: (event: MouseEvent) => void;\n if (this.normalMouse) {\n moveHandler = (event: MouseEvent) => {\n // Do nothing if normal mouse mode is on. This can happen if the mouse is held down when the\n // terminal exits normalMouse mode.\n if (!this.normalMouse) {\n return;\n }\n sendMove(event);\n };\n // TODO: these event listeners should be managed by the disposable, the Terminal reference may\n // be kept aroud if Terminal.dispose is fired when the mouse is down\n this._document.addEventListener('mousemove', moveHandler);\n }\n\n // x10 compatibility mode can't send button releases\n const handler = (ev: MouseEvent) => {\n if (this.normalMouse && !this.x10Mouse) {\n sendButton(ev);\n }\n if (moveHandler) {\n // Even though this should only be attached when this.normalMouse is true, holding the\n // mouse button down when normalMouse changes can happen. Just always try to remove it.\n this._document.removeEventListener('mousemove', moveHandler);\n moveHandler = null;\n }\n this._document.removeEventListener('mouseup', handler);\n return this.cancel(ev);\n };\n this._document.addEventListener('mouseup', handler);\n\n return this.cancel(ev);\n }));\n\n // if (this.normalMouse) {\n // on(this.document, 'mousemove', sendMove);\n // }\n\n this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {\n if (!this.mouseEvents) {\n // Convert wheel events into up/down events when the buffer does not have scrollback, this\n // enables scrolling in apps hosted in the alt buffer such as vim or tmux.\n if (!this.buffer.hasScrollback) {\n const amount = this.viewport.getLinesScrolled(ev);\n\n // Do nothing if there's no vertical scroll\n if (amount === 0) {\n return;\n }\n\n // Construct and send sequences\n const sequence = C0.ESC + (this.applicationCursor ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');\n let data = '';\n for (let i = 0; i < Math.abs(amount); i++) {\n data += sequence;\n }\n this.handler(data);\n }\n return;\n }\n if (this.x10Mouse || this._vt300Mouse || this._decLocator) return;\n sendButton(ev);\n ev.preventDefault();\n }));\n\n // allow wheel scrolling in\n // the shell for example\n this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {\n if (this.mouseEvents) return;\n this.viewport.onWheel(ev);\n return this.cancel(ev);\n }));\n\n this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {\n if (this.mouseEvents) return;\n this.viewport.onTouchStart(ev);\n return this.cancel(ev);\n }));\n\n this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {\n if (this.mouseEvents) return;\n this.viewport.onTouchMove(ev);\n return this.cancel(ev);\n }));\n }\n\n /**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param start The row to start from (between 0 and this.rows - 1).\n * @param end The row to end at (between start and this.rows - 1).\n */\n public refresh(start: number, end: number): void {\n if (this._renderCoordinator) {\n this._renderCoordinator.refreshRows(start, end);\n }\n }\n\n /**\n * Queues linkification for the specified rows.\n * @param start The row to start from (between 0 and this.rows - 1).\n * @param end The row to end at (between start and this.rows - 1).\n */\n private _queueLinkification(start: number, end: number): void {\n if (this.linkifier) {\n this.linkifier.linkifyRows(start, end);\n }\n }\n\n /**\n * Change the cursor style for different selection modes\n */\n public updateCursorStyle(ev: KeyboardEvent): void {\n if (this.selectionManager && this.selectionManager.shouldColumnSelect(ev)) {\n this.element.classList.add('column-select');\n } else {\n this.element.classList.remove('column-select');\n }\n }\n\n /**\n * Display the cursor element\n */\n public showCursor(): void {\n if (!this.cursorState) {\n this.cursorState = 1;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n }\n\n /**\n * Scroll the terminal down 1 row, creating a blank line.\n * @param isWrapped Whether the new line is wrapped from the previous line.\n */\n public scroll(isWrapped: boolean = false): void {\n let newLine: IBufferLine;\n newLine = this._blankLine;\n const eraseAttr = this.eraseAttrData();\n if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {\n newLine = this.buffer.getBlankLine(eraseAttr, isWrapped);\n this._blankLine = newLine;\n }\n newLine.isWrapped = isWrapped;\n\n const topRow = this.buffer.ybase + this.buffer.scrollTop;\n const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;\n\n if (this.buffer.scrollTop === 0) {\n // Determine whether the buffer is going to be trimmed after insertion.\n const willBufferBeTrimmed = this.buffer.lines.isFull;\n\n // Insert the line using the fastest method\n if (bottomRow === this.buffer.lines.length - 1) {\n if (willBufferBeTrimmed) {\n this.buffer.lines.recycle().copyFrom(newLine);\n } else {\n this.buffer.lines.push(newLine.clone());\n }\n } else {\n this.buffer.lines.splice(bottomRow + 1, 0, newLine.clone());\n }\n\n // Only adjust ybase and ydisp when the buffer is not trimmed\n if (!willBufferBeTrimmed) {\n this.buffer.ybase++;\n // Only scroll the ydisp with ybase if the user has not scrolled up\n if (!this._userScrolling) {\n this.buffer.ydisp++;\n }\n } else {\n // When the buffer is full and the user has scrolled up, keep the text\n // stable unless ydisp is right at the top\n if (this._userScrolling) {\n this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0);\n }\n }\n } else {\n // scrollTop is non-zero which means no line will be going to the\n // scrollback, instead we can just shift them in-place.\n const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;\n this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);\n this.buffer.lines.set(bottomRow, newLine.clone());\n }\n\n // Move the viewport to the bottom of the buffer unless the user is\n // scrolling.\n if (!this._userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n\n // Flag rows that need updating\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n\n this._onScroll.fire(this.buffer.ydisp);\n }\n\n /**\n * Scroll the display of the terminal\n * @param disp The number of lines to scroll down (negative scroll up).\n * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used\n * to avoid unwanted events being handled by the viewport when the event was triggered from the\n * viewport originally.\n */\n public scrollLines(disp: number, suppressScrollEvent?: boolean): void {\n if (disp < 0) {\n if (this.buffer.ydisp === 0) {\n return;\n }\n this._userScrolling = true;\n } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {\n this._userScrolling = false;\n }\n\n const oldYdisp = this.buffer.ydisp;\n this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);\n\n // No change occurred, don't trigger scroll/refresh\n if (oldYdisp === this.buffer.ydisp) {\n return;\n }\n\n if (!suppressScrollEvent) {\n this._onScroll.fire(this.buffer.ydisp);\n }\n\n this.refresh(0, this.rows - 1);\n }\n\n /**\n * Scroll the display of the terminal by a number of pages.\n * @param pageCount The number of pages to scroll (negative scrolls up).\n */\n public scrollPages(pageCount: number): void {\n this.scrollLines(pageCount * (this.rows - 1));\n }\n\n /**\n * Scrolls the display of the terminal to the top.\n */\n public scrollToTop(): void {\n this.scrollLines(-this.buffer.ydisp);\n }\n\n /**\n * Scrolls the display of the terminal to the bottom.\n */\n public scrollToBottom(): void {\n this.scrollLines(this.buffer.ybase - this.buffer.ydisp);\n }\n\n public scrollToLine(line: number): void {\n const scrollAmount = line - this.buffer.ydisp;\n if (scrollAmount !== 0) {\n this.scrollLines(scrollAmount);\n }\n }\n\n /**\n * Writes raw utf8 bytes to the terminal.\n * @param data UintArray with UTF8 bytes to write to the terminal.\n */\n public writeUtf8(data: Uint8Array): void {\n // Ensure the terminal isn't disposed\n if (this._isDisposed) {\n return;\n }\n\n // Ignore falsy data values\n if (!data) {\n return;\n }\n\n this.writeBufferUtf8.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBufferUtf8.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.handler(C0.DC3);\n this._xoffSentToCatchUp = true;\n }\n\n if (!this._writeInProgress && this.writeBufferUtf8.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this._writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n setTimeout(() => {\n this._innerWriteUtf8();\n });\n }\n }\n\n protected _innerWriteUtf8(bufferOffset: number = 0): void {\n // Ensure the terminal isn't disposed\n if (this._isDisposed) {\n this.writeBufferUtf8 = [];\n }\n\n const startTime = Date.now();\n while (this.writeBufferUtf8.length > bufferOffset) {\n const data = this.writeBufferUtf8[bufferOffset];\n bufferOffset++;\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // we reached the end of the writeBuffer to allow more data to come in.\n if (this._xoffSentToCatchUp && this.writeBufferUtf8.length === bufferOffset) {\n this.handler(C0.DC1);\n this._xoffSentToCatchUp = false;\n }\n\n this._refreshStart = this.buffer.y;\n this._refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n\n this._inputHandler.parseUtf8(data);\n\n this.updateRange(this.buffer.y);\n this.refresh(this._refreshStart, this._refreshEnd);\n\n if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {\n break;\n }\n }\n if (this.writeBufferUtf8.length > bufferOffset) {\n // Allow renderer to catch up before processing the next batch\n // trim already processed chunks if we are above threshold\n if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {\n this.writeBufferUtf8 = this.writeBufferUtf8.slice(bufferOffset);\n bufferOffset = 0;\n }\n setTimeout(() => this._innerWriteUtf8(bufferOffset), 0);\n } else {\n this._writeInProgress = false;\n this.writeBufferUtf8 = [];\n }\n }\n\n /**\n * Writes text to the terminal.\n * @param data The text to write to the terminal.\n */\n public write(data: string): void {\n // Ensure the terminal isn't disposed\n if (this._isDisposed) {\n return;\n }\n\n // Ignore falsy data values (including the empty string)\n if (!data) {\n return;\n }\n\n this.writeBuffer.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this._xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.handler(C0.DC3);\n this._xoffSentToCatchUp = true;\n }\n\n if (!this._writeInProgress && this.writeBuffer.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this._writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n setTimeout(() => {\n this._innerWrite();\n });\n }\n }\n\n protected _innerWrite(bufferOffset: number = 0): void {\n // Ensure the terminal isn't disposed\n if (this._isDisposed) {\n this.writeBuffer = [];\n }\n\n const startTime = Date.now();\n while (this.writeBuffer.length > bufferOffset) {\n const data = this.writeBuffer[bufferOffset];\n bufferOffset++;\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // we reached the end of the writeBuffer to allow more data to come in.\n if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) {\n this.handler(C0.DC1);\n this._xoffSentToCatchUp = false;\n }\n\n this._refreshStart = this.buffer.y;\n this._refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n\n this._inputHandler.parse(data);\n\n this.updateRange(this.buffer.y);\n this.refresh(this._refreshStart, this._refreshEnd);\n\n if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {\n break;\n }\n }\n if (this.writeBuffer.length > bufferOffset) {\n // Allow renderer to catch up before processing the next batch\n // trim already processed chunks if we are above threshold\n if (bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {\n this.writeBuffer = this.writeBuffer.slice(bufferOffset);\n bufferOffset = 0;\n }\n setTimeout(() => this._innerWrite(bufferOffset), 0);\n } else {\n this._writeInProgress = false;\n this.writeBuffer = [];\n }\n }\n\n /**\n * Writes text to the terminal, followed by a break line character (\\n).\n * @param data The text to write to the terminal.\n */\n public writeln(data: string): void {\n this.write(data + '\\r\\n');\n }\n\n /**\n * Attaches a custom key event handler which is run before keys are processed,\n * giving consumers of xterm.js ultimate control as to what keys should be\n * processed by the terminal and what keys should not.\n * @param customKeyEventHandler The custom KeyboardEvent handler to attach.\n * This is a function that takes a KeyboardEvent, allowing consumers to stop\n * propagation and/or prevent the default action. The function returns whether\n * the event should be processed by xterm.js.\n */\n public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {\n this._customKeyEventHandler = customKeyEventHandler;\n }\n\n /** Add handler for CSI escape sequence. See xterm.d.ts for details. */\n public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {\n return this._inputHandler.addCsiHandler(flag, callback);\n }\n /** Add handler for OSC escape sequence. See xterm.d.ts for details. */\n public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {\n return this._inputHandler.addOscHandler(ident, callback);\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param handler The callback when the link is called.\n * @param options Options for the link matcher.\n * @return The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number {\n const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);\n this.refresh(0, this.rows - 1);\n return matcherId;\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param matcherId The link matcher's ID (returned after register)\n */\n public deregisterLinkMatcher(matcherId: number): void {\n if (this.linkifier.deregisterLinkMatcher(matcherId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n\n public registerCharacterJoiner(handler: CharacterJoinerHandler): number {\n const joinerId = this._renderCoordinator.registerCharacterJoiner(handler);\n this.refresh(0, this.rows - 1);\n return joinerId;\n }\n\n public deregisterCharacterJoiner(joinerId: number): void {\n if (this._renderCoordinator.deregisterCharacterJoiner(joinerId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n\n public get markers(): IMarker[] {\n return this.buffer.markers;\n }\n\n public addMarker(cursorYOffset: number): IMarker {\n // Disallow markers on the alt buffer\n if (this.buffer !== this.buffers.normal) {\n return;\n }\n\n return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);\n }\n\n /**\n * Gets whether the terminal has an active selection.\n */\n public hasSelection(): boolean {\n return this.selectionManager ? this.selectionManager.hasSelection : false;\n }\n\n /**\n * Selects text within the terminal.\n * @param column The column the selection starts at..\n * @param row The row the selection starts at.\n * @param length The length of the selection.\n */\n public select(column: number, row: number, length: number): void {\n this.selectionManager.setSelection(column, row, length);\n }\n\n /**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\n public getSelection(): string {\n return this.selectionManager ? this.selectionManager.selectionText : '';\n }\n\n public getSelectionPosition(): ISelectionPosition | undefined {\n if (!this.selectionManager.hasSelection) {\n return undefined;\n }\n\n return {\n startColumn: this.selectionManager.selectionStart[0],\n startRow: this.selectionManager.selectionStart[1],\n endColumn: this.selectionManager.selectionEnd[0],\n endRow: this.selectionManager.selectionEnd[1]\n };\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n }\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n if (this.selectionManager) {\n this.selectionManager.selectAll();\n }\n }\n\n public selectLines(start: number, end: number): void {\n if (this.selectionManager) {\n this.selectionManager.selectLines(start, end);\n }\n }\n\n /**\n * Handle a keydown event\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param ev The keydown event to be handled.\n */\n protected _keyDown(event: KeyboardEvent): boolean {\n if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {\n return false;\n }\n\n if (!this._compositionHelper.keydown(event)) {\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n const result = evaluateKeyboardEvent(event, this.applicationCursor, this.browser.isMac, this.options.macOptionIsMeta);\n\n this.updateCursorStyle(event);\n\n // if (result.key === C0.DC3) { // XOFF\n // this._writeStopped = true;\n // } else if (result.key === C0.DC1) { // XON\n // this._writeStopped = false;\n // }\n\n if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) {\n const scrollCount = this.rows - 1;\n this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount);\n return this.cancel(event, true);\n }\n\n if (result.type === KeyboardResultType.SELECT_ALL) {\n this.selectAll();\n }\n\n if (this._isThirdLevelShift(this.browser, event)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(event, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n this.emit('keydown', event);\n this._onKey.fire({ key: result.key, domEvent: event });\n this.showCursor();\n this.handler(result.key);\n\n return this.cancel(event, true);\n }\n\n private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {\n const thirdLevelKey =\n (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);\n\n if (ev.type === 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n }\n\n /**\n * Set the G level of the terminal\n * @param g\n */\n public setgLevel(g: number): void {\n this.glevel = g;\n this.charset = this.charsets[g];\n }\n\n /**\n * Set the charset for the given G level of the terminal\n * @param g\n * @param charset\n */\n public setgCharset(g: number, charset: ICharset): void {\n this.charsets[g] = charset;\n if (this.glevel === g) {\n this.charset = charset;\n }\n }\n\n protected _keyUp(ev: KeyboardEvent): void {\n this.updateCursorStyle(ev);\n }\n\n /**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param ev The keypress event to be handled.\n */\n protected _keyPress(ev: KeyboardEvent): boolean {\n let key;\n\n if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which === null || ev.which === undefined) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this.emit('keypress', key, ev);\n this._onKey.fire({ key, domEvent: ev });\n this.showCursor();\n this.handler(key);\n\n return true;\n }\n\n /**\n * Ring the bell.\n * Note: We could do sweet things with webaudio here\n */\n public bell(): void {\n this.emit('bell');\n if (this._soundBell()) {\n this.soundManager.playBellSound();\n }\n\n if (this._visualBell()) {\n this.element.classList.add('visual-bell-active');\n clearTimeout(this._visualBellTimer);\n this._visualBellTimer = window.setTimeout(() => {\n this.element.classList.remove('visual-bell-active');\n }, 200);\n }\n }\n\n /**\n * Log the current state to the console.\n */\n public log(text: string, data?: any): void {\n if (!this.options.debug) return;\n if (!this._context.console || !this._context.console.log) return;\n this._context.console.log(text, data);\n }\n\n /**\n * Log the current state as error to the console.\n */\n public error(text: string, data?: any): void {\n if (!this.options.debug) return;\n if (!this._context.console || !this._context.console.error) return;\n this._context.console.error(text, data);\n }\n\n /**\n * Resizes the terminal.\n *\n * @param x The number of columns to resize to.\n * @param y The number of rows to resize to.\n */\n public resize(x: number, y: number): void {\n if (isNaN(x) || isNaN(y)) {\n return;\n }\n\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (this.charMeasure && (!this.charMeasure.width || !this.charMeasure.height)) {\n this.charMeasure.measure(this.options);\n }\n return;\n }\n\n if (x < MINIMUM_COLS) x = MINIMUM_COLS;\n if (y < MINIMUM_ROWS) y = MINIMUM_ROWS;\n\n this.buffers.resize(x, y);\n\n this.cols = x;\n this.rows = y;\n this.buffers.setupTabStops(this.cols);\n\n if (this.charMeasure) {\n this.charMeasure.measure(this.options);\n }\n\n this.refresh(0, this.rows - 1);\n this._onResize.fire({ cols: x, rows: y });\n }\n\n /**\n * Updates the range of rows to refresh\n * @param y The number of rows to refresh next.\n */\n public updateRange(y: number): void {\n if (y < this._refreshStart) this._refreshStart = y;\n if (y > this._refreshEnd) this._refreshEnd = y;\n // if (y > this.refreshEnd) {\n // this.refreshEnd = y;\n // if (y > this.rows - 1) {\n // this.refreshEnd = this.rows - 1;\n // }\n // }\n }\n\n /**\n * Set the range of refreshing to the maximum value\n */\n public maxRange(): void {\n this._refreshStart = 0;\n this._refreshEnd = this.rows - 1;\n }\n\n /**\n * Clear the entire buffer, making the prompt line the new first line.\n */\n public clear(): void {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (let i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));\n }\n this.refresh(0, this.rows - 1);\n this._onScroll.fire(this.buffer.ydisp);\n }\n\n /**\n * Evaluate if the current terminal is the given argument.\n * @param term The terminal name to evaluate\n */\n public is(term: string): boolean {\n return (this.options.termName + '').indexOf(term) === 0;\n }\n\n /**\n * Emit the data event and populate the given data.\n * @param data The data to populate in the event.\n */\n public handler(data: string): void {\n // Prevents all events to pty process if stdin is disabled\n if (this.options.disableStdin) {\n return;\n }\n\n // Clear the selection if the selection manager is available and has an active selection\n if (this.selectionManager && this.selectionManager.hasSelection) {\n this.selectionManager.clearSelection();\n }\n\n // Input is being sent to the terminal, the terminal should focus the prompt.\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n this._onData.fire(data);\n }\n\n /**\n * Emit the 'title' event and populate the given title.\n * @param title The title to populate in the event.\n */\n public handleTitle(title: string): void {\n this._onTitleChange.fire(title);\n }\n\n /**\n * ESC\n */\n\n /**\n * ESC D Index (IND is 0x84).\n */\n public index(): void {\n this.buffer.y++;\n if (this.buffer.y > this.buffer.scrollBottom) {\n this.buffer.y--;\n this.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this.buffer.x >= this.cols) {\n this.buffer.x--;\n }\n }\n\n /**\n * ESC M Reverse Index (RI is 0x8d).\n *\n * Move the cursor up one row, inserting a new blank line if necessary.\n */\n public reverseIndex(): void {\n if (this.buffer.y === this.buffer.scrollTop) {\n // possibly move the code below to term.reverseScroll();\n // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n // blankLine(true) is xterm/linux behavior\n const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop;\n this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1);\n this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttrData()));\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n } else {\n this.buffer.y--;\n }\n }\n\n /**\n * ESC c Full Reset (RIS).\n */\n public reset(): void {\n /**\n * Since _setup handles a full terminal creation, we have to carry forward\n * a few things that should not reset.\n */\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n const customKeyEventHandler = this._customKeyEventHandler;\n const inputHandler = this._inputHandler;\n const cursorState = this.cursorState;\n const writeBuffer = this.writeBuffer;\n const writeBufferUtf8 = this.writeBufferUtf8;\n const writeInProgress = this._writeInProgress;\n const xoffSentToCatchUp = this._xoffSentToCatchUp;\n const userScrolling = this._userScrolling;\n\n this._setup();\n\n // reattach\n this._customKeyEventHandler = customKeyEventHandler;\n this._inputHandler = inputHandler;\n this.cursorState = cursorState;\n this.writeBuffer = writeBuffer;\n this.writeBufferUtf8 = writeBufferUtf8;\n this._writeInProgress = writeInProgress;\n this._xoffSentToCatchUp = xoffSentToCatchUp;\n this._userScrolling = userScrolling;\n\n // do a full screen refresh\n this.refresh(0, this.rows - 1);\n if (this.viewport) {\n this.viewport.syncScrollArea();\n }\n }\n\n\n /**\n * ESC H Tab Set (HTS is 0x88).\n */\n public tabSet(): void {\n this.buffer.tabs[this.buffer.x] = true;\n }\n\n // TODO: Remove cancel function and cancelEvents option\n public cancel(ev: Event, force?: boolean): boolean {\n if (!this.options.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n }\n\n private _visualBell(): boolean {\n return false;\n // return this.options.bellStyle === 'visual' ||\n // this.options.bellStyle === 'both';\n }\n\n private _soundBell(): boolean {\n return this.options.bellStyle === 'sound';\n // return this.options.bellStyle === 'sound' ||\n // this.options.bellStyle === 'both';\n }\n}\n\n/**\n * Helpers\n */\n\nfunction wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nexport let blankLine = 'Blank line';\nexport let promptLabel = 'Terminal input';\nexport let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read';\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, ISoundManager } from './Types';\n\n// Source: https://freesound.org/people/altemark/sounds/45759/\n// This sound is released under the Creative Commons Attribution 3.0 Unported\n// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been\n// made, apart from the conversion to base64.\nexport const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';\n\nexport class SoundManager implements ISoundManager {\n private static _audioContext: AudioContext;\n\n static get audioContext(): AudioContext | null {\n if (!SoundManager._audioContext) {\n const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext;\n if (!audioContextCtor) {\n console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');\n return null;\n }\n SoundManager._audioContext = new audioContextCtor();\n }\n return SoundManager._audioContext;\n }\n\n constructor(\n private _terminal: ITerminal\n ) {\n }\n\n public playBellSound(): void {\n const ctx = SoundManager.audioContext;\n if (!ctx) {\n return;\n }\n const bellAudioSource = ctx.createBufferSource();\n ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {\n bellAudioSource.buffer = buffer;\n bellAudioSource.connect(ctx.destination);\n bellAudioSource.start(0);\n });\n }\n\n private _base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binaryString = window.atob(base64);\n const len = binaryString.length;\n const bytes = new Uint8Array(len);\n\n for (let i = 0; i < len; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n\n return bytes.buffer;\n }\n\n private _removeMimeType(dataURI: string): string {\n // Split the input to get the mime-type and the data itself\n const splitUri = dataURI.split(',');\n\n // Return only the data\n return splitUri[1];\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from './Types';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n /**\n * Whether select all is currently active.\n */\n public isSelectAllActive: boolean;\n\n /**\n * The [x, y] position the selection starts at.\n */\n public selectionStart: [number, number];\n\n /**\n * The minimal length of the selection from the start position. When double\n * clicking on a word, the word will be selected which makes the selection\n * start at the start of the word and makes this variable the length.\n */\n public selectionStartLength: number;\n\n /**\n * The [x, y] position the selection ends at.\n */\n public selectionEnd: [number, number];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this.clearSelection();\n }\n\n /**\n * Clears the current selection.\n */\n public clearSelection(): void {\n this.selectionStart = null;\n this.selectionEnd = null;\n this.isSelectAllActive = false;\n this.selectionStartLength = 0;\n }\n\n /**\n * The final selection start, taking into consideration select all.\n */\n public get finalSelectionStart(): [number, number] {\n if (this.isSelectAllActive) {\n return [0, 0];\n }\n\n if (!this.selectionEnd || !this.selectionStart) {\n return this.selectionStart;\n }\n\n return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n }\n\n /**\n * The final selection end, taking into consideration select all, double click\n * word selection and triple click line selection.\n */\n public get finalSelectionEnd(): [number, number] {\n if (this.isSelectAllActive) {\n return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];\n }\n\n if (!this.selectionStart) {\n return null;\n }\n\n // Use the selection start + length if the end doesn't exist or they're reversed\n if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n const startPlusLength = this.selectionStart[0] + this.selectionStartLength;\n if (startPlusLength > this._terminal.cols) {\n return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)];\n }\n return [startPlusLength, this.selectionStart[1]];\n }\n\n // Ensure the the word/line is selected after a double/triple click\n if (this.selectionStartLength) {\n // Select the larger of the two when start and end are on the same line\n if (this.selectionEnd[1] === this.selectionStart[1]) {\n return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];\n }\n }\n return this.selectionEnd;\n }\n\n /**\n * Returns whether the selection start and end are reversed.\n */\n public areSelectionValuesReversed(): boolean {\n const start = this.selectionStart;\n const end = this.selectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n * @return Whether a refresh is necessary.\n */\n public onTrim(amount: number): boolean {\n // Adjust the selection position based on the trimmed amount.\n if (this.selectionStart) {\n this.selectionStart[1] -= amount;\n }\n if (this.selectionEnd) {\n this.selectionEnd[1] -= amount;\n }\n\n // The selection has moved off the buffer, clear it.\n if (this.selectionEnd && this.selectionEnd[1] < 0) {\n this.clearSelection();\n return true;\n }\n\n // If the selection start is trimmed, ensure the start column is 0.\n if (this.selectionStart && this.selectionStart[1] < 0) {\n this.selectionStart[1] = 0;\n }\n return false;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager, IBuffer, ISelectionRedrawRequestEvent } from './Types';\nimport { IBufferLine } from './core/Types';\nimport { MouseHelper } from './MouseHelper';\nimport * as Browser from './common/Platform';\nimport { CharMeasure } from './CharMeasure';\nimport { SelectionModel } from './SelectionModel';\nimport { AltClickHandler } from './handlers/AltClickHandler';\nimport { CellData } from './core/buffer/BufferLine';\nimport { IDisposable } from 'xterm';\nimport { EventEmitter2, IEvent } from './common/EventEmitter2';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * The maximum amount of time that can have elapsed for an alt click to move the\n * cursor.\n */\nconst ALT_CLICK_MOVE_CURSOR_TIME = 500;\n\n/**\n * A string containing all characters that are considered word separated by the\n * double click to select work logic.\n */\nconst WORD_SEPARATORS = ' ()[]{}\\'\"';\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n start: number;\n length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nexport const enum SelectionMode {\n NORMAL,\n WORD,\n LINE,\n COLUMN\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionManager handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionManager but the onRedrawRequest event is fired\n * when the selection is ready to be redrawn (on an animation frame).\n */\nexport class SelectionManager implements ISelectionManager {\n protected _model: SelectionModel;\n\n /**\n * The amount to scroll every drag scroll update (depends on how far the mouse\n * drag is above or below the terminal).\n */\n private _dragScrollAmount: number;\n\n /**\n * The current selection mode.\n */\n protected _activeSelectionMode: SelectionMode;\n\n /**\n * A setInterval timer that is active while the mouse is down whose callback\n * scrolls the viewport when necessary.\n */\n private _dragScrollIntervalTimer: NodeJS.Timer;\n\n /**\n * The animation frame ID used for refreshing the selection.\n */\n private _refreshAnimationFrame: number;\n\n /**\n * Whether selection is enabled.\n */\n private _enabled = true;\n\n private _mouseMoveListener: EventListener;\n private _mouseUpListener: EventListener;\n private _trimListener: IDisposable;\n private _workCell: CellData = new CellData();\n\n private _mouseDownTimeStamp: number;\n\n private _onLinuxMouseSelection = new EventEmitter2();\n public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; }\n private _onRedrawRequest = new EventEmitter2();\n public get onRedrawRequest(): IEvent { return this._onRedrawRequest.event; }\n private _onSelectionChange = new EventEmitter2();\n public get onSelectionChange(): IEvent { return this._onSelectionChange.event; }\n\n constructor(\n private _terminal: ITerminal,\n private _charMeasure: CharMeasure\n ) {\n this._initListeners();\n this.enable();\n\n this._model = new SelectionModel(_terminal);\n this._activeSelectionMode = SelectionMode.NORMAL;\n }\n\n public dispose(): void {\n this._removeMouseDownListeners();\n }\n\n private get _buffer(): IBuffer {\n return this._terminal.buffers.active;\n }\n\n /**\n * Initializes listener variables.\n */\n private _initListeners(): void {\n this._mouseMoveListener = event => this._onMouseMove(event);\n this._mouseUpListener = event => this._onMouseUp(event);\n\n this.initBuffersListeners();\n }\n\n public initBuffersListeners(): void {\n this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount));\n this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e));\n }\n\n /**\n * Disables the selection manager. This is useful for when terminal mouse\n * are enabled.\n */\n public disable(): void {\n this.clearSelection();\n this._enabled = false;\n }\n\n /**\n * Enable the selection manager.\n */\n public enable(): void {\n this._enabled = true;\n }\n\n public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }\n public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }\n\n /**\n * Gets whether there is an active text selection.\n */\n public get hasSelection(): boolean {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[0] !== end[0] || start[1] !== end[1];\n }\n\n /**\n * Gets the text currently selected.\n */\n public get selectionText(): string {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return '';\n }\n\n const result: string[] = [];\n\n if (this._activeSelectionMode === SelectionMode.COLUMN) {\n // Ignore zero width selections\n if (start[0] === end[0]) {\n return '';\n }\n\n for (let i = start[1]; i <= end[1]; i++) {\n const lineText = this._buffer.translateBufferLineToString(i, true, start[0], end[0]);\n result.push(lineText);\n }\n } else {\n // Get first row\n const startRowEndCol = start[1] === end[1] ? end[0] : undefined;\n result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));\n\n // Get middle rows\n for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n const bufferLine = this._buffer.lines.get(i);\n const lineText = this._buffer.translateBufferLineToString(i, true);\n if (bufferLine.isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Get final row\n if (start[1] !== end[1]) {\n const bufferLine = this._buffer.lines.get(end[1]);\n const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]);\n if (bufferLine.isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n }\n\n // Format string by replacing non-breaking space chars with regular spaces\n // and joining the array into a multi-line string.\n const formattedResult = result.map(line => {\n return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n }).join(Browser.isMSWindows ? '\\r\\n' : '\\n');\n\n return formattedResult;\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this.refresh();\n this._onSelectionChange.fire();\n }\n\n /**\n * Queues a refresh, redrawing the selection on the next opportunity.\n * @param isLinuxMouseSelection Whether the selection should be registered as a new\n * selection on Linux.\n */\n public refresh(isLinuxMouseSelection?: boolean): void {\n // Queue the refresh for the renderer\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());\n }\n\n // If the platform is Linux and the refresh call comes from a mouse event,\n // we need to update the selection for middle click to paste selection.\n if (Browser.isLinux && isLinuxMouseSelection) {\n const selectionText = this.selectionText;\n if (selectionText.length) {\n this._onLinuxMouseSelection.fire(this.selectionText);\n }\n }\n }\n\n /**\n * Fires the refresh event, causing consumers to pick it up and redraw the\n * selection state.\n */\n private _refresh(): void {\n this._refreshAnimationFrame = null;\n this._onRedrawRequest.fire({\n start: this._model.finalSelectionStart,\n end: this._model.finalSelectionEnd,\n columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN\n });\n }\n\n /**\n * Checks if the current click was inside the current selection\n * @param event The mouse event\n */\n public isClickInSelection(event: MouseEvent): boolean {\n const coords = this._getMouseBufferCoords(event);\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n\n if (!start || !end) {\n return false;\n }\n\n return this._areCoordsInSelection(coords, start, end);\n }\n\n protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {\n return (coords[1] > start[1] && coords[1] < end[1]) ||\n (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||\n (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) ||\n (start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]);\n }\n\n /**\n * Selects word at the current mouse event coordinates.\n * @param event The mouse event.\n */\n public selectWordAtCursor(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._selectWordAt(coords, false);\n this._model.selectionEnd = null;\n this.refresh(true);\n }\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._model.isSelectAllActive = true;\n this.refresh();\n this._onSelectionChange.fire();\n }\n\n public selectLines(start: number, end: number): void {\n this._model.clearSelection();\n start = Math.max(start, 0);\n end = Math.min(end, this._terminal.buffer.lines.length - 1);\n this._model.selectionStart = [0, start];\n this._model.selectionEnd = [this._terminal.cols, end];\n this.refresh();\n this._onSelectionChange.fire();\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n */\n private _onTrim(amount: number): void {\n const needsRefresh = this._model.onTrim(amount);\n if (needsRefresh) {\n this.refresh();\n }\n }\n\n /**\n * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n * @param event The mouse event.\n */\n private _getMouseBufferCoords(event: MouseEvent): [number, number] {\n const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.cols, this._terminal.rows, true);\n if (!coords) {\n return null;\n }\n\n // Convert to 0-based\n coords[0]--;\n coords[1]--;\n\n // Convert viewport coords to buffer coords\n coords[1] += this._terminal.buffer.ydisp;\n return coords;\n }\n\n /**\n * Gets the amount the viewport should be scrolled based on how far out of the\n * terminal the mouse is.\n * @param event The mouse event.\n */\n private _getMouseEventScrollAmount(event: MouseEvent): number {\n let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1];\n const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight);\n if (offset >= 0 && offset <= terminalHeight) {\n return 0;\n }\n if (offset > terminalHeight) {\n offset -= terminalHeight;\n }\n\n offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n offset /= DRAG_SCROLL_MAX_THRESHOLD;\n return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n }\n\n /**\n * Returns whether the selection manager should force selection, regardless of\n * whether the terminal is in mouse events mode.\n * @param event The mouse event.\n */\n public shouldForceSelection(event: MouseEvent): boolean {\n if (Browser.isMac) {\n return event.altKey && this._terminal.options.macOptionClickForcesSelection;\n }\n\n return event.shiftKey;\n }\n\n /**\n * Handles te mousedown event, setting up for a new selection.\n * @param event The mousedown event.\n */\n public onMouseDown(event: MouseEvent): void {\n this._mouseDownTimeStamp = event.timeStamp;\n // If we have selection, we want the context menu on right click even if the\n // terminal is in mouse mode.\n if (event.button === 2 && this.hasSelection) {\n return;\n }\n\n // Only action the primary button\n if (event.button !== 0) {\n return;\n }\n\n // Allow selection when using a specific modifier key, even when disabled\n if (!this._enabled) {\n if (!this.shouldForceSelection(event)) {\n return;\n }\n\n // Don't send the mouse down event to the current process, we want to select\n event.stopPropagation();\n }\n\n // Tell the browser not to start a regular selection\n event.preventDefault();\n\n // Reset drag scroll state\n this._dragScrollAmount = 0;\n\n if (this._enabled && event.shiftKey) {\n this._onIncrementalClick(event);\n } else {\n if (event.detail === 1) {\n this._onSingleClick(event);\n } else if (event.detail === 2) {\n this._onDoubleClick(event);\n } else if (event.detail === 3) {\n this._onTripleClick(event);\n }\n }\n\n this._addMouseDownListeners();\n this.refresh(true);\n }\n\n /**\n * Adds listeners when mousedown is triggered.\n */\n private _addMouseDownListeners(): void {\n // Listen on the document so that dragging outside of viewport works\n this._terminal.element.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n }\n\n /**\n * Removes the listeners that are registered when mousedown is triggered.\n */\n private _removeMouseDownListeners(): void {\n if (this._terminal.element.ownerDocument) {\n this._terminal.element.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n }\n clearInterval(this._dragScrollIntervalTimer);\n this._dragScrollIntervalTimer = null;\n }\n\n /**\n * Performs an incremental click, setting the selection end position to the mouse\n * position.\n * @param event The mouse event.\n */\n private _onIncrementalClick(event: MouseEvent): void {\n if (this._model.selectionStart) {\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n }\n }\n\n /**\n * Performs a single click, resetting relevant state and setting the selection\n * start position.\n * @param event The mouse event.\n */\n private _onSingleClick(event: MouseEvent): void {\n this._model.selectionStartLength = 0;\n this._model.isSelectAllActive = false;\n this._activeSelectionMode = this.shouldColumnSelect(event) ? SelectionMode.COLUMN : SelectionMode.NORMAL;\n\n // Initialize the new selection\n this._model.selectionStart = this._getMouseBufferCoords(event);\n if (!this._model.selectionStart) {\n return;\n }\n this._model.selectionEnd = null;\n\n // Ensure the line exists\n const line = this._buffer.lines.get(this._model.selectionStart[1]);\n if (!line) {\n return;\n }\n\n // Return early if the click event is not in the buffer (eg. in scroll bar)\n if (line.length >= this._model.selectionStart[0]) {\n return;\n }\n\n // If the mouse is over the second half of a wide character, adjust the\n // selection to cover the whole character\n if (line.hasWidth(this._model.selectionStart[0]) === 0) {\n this._model.selectionStart[0]++;\n }\n }\n\n /**\n * Performs a double click, selecting the current work.\n * @param event The mouse event.\n */\n private _onDoubleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.WORD;\n this._selectWordAt(coords, true);\n }\n }\n\n /**\n * Performs a triple click, selecting the current line and activating line\n * select mode.\n * @param event The mouse event.\n */\n private _onTripleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.LINE;\n this._selectLineAt(coords[1]);\n }\n }\n\n /**\n * Returns whether the selection manager should operate in column select mode\n * @param event the mouse or keyboard event\n */\n public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {\n return event.altKey && !(Browser.isMac && this._terminal.options.macOptionClickForcesSelection);\n }\n\n /**\n * Handles the mousemove event when the mouse button is down, recording the\n * end of the selection and refreshing the selection.\n * @param event The mousemove event.\n */\n private _onMouseMove(event: MouseEvent): void {\n // If the mousemove listener is active it means that a selection is\n // currently being made, we should stop propagation to prevent mouse events\n // to be sent to the pty.\n event.stopImmediatePropagation();\n\n // Record the previous position so we know whether to redraw the selection\n // at the end.\n const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n // Set the initial selection end based on the mouse coordinates\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n if (!this._model.selectionEnd) {\n this.refresh(true);\n return;\n }\n\n // Select the entire line if line select mode is active.\n if (this._activeSelectionMode === SelectionMode.LINE) {\n if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n this._model.selectionEnd[0] = 0;\n } else {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n } else if (this._activeSelectionMode === SelectionMode.WORD) {\n this._selectToWordAt(this._model.selectionEnd);\n }\n\n // Determine the amount of scrolling that will happen.\n this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively. This should only happen when\n // NOT in column select mode.\n if (this._activeSelectionMode !== SelectionMode.COLUMN) {\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd[0] = this._terminal.cols;\n } else if (this._dragScrollAmount < 0) {\n this._model.selectionEnd[0] = 0;\n }\n }\n\n // If the character is a wide character include the cell to the right in the\n // selection. Note that selections at the very end of the line will never\n // have a character.\n if (this._model.selectionEnd[1] < this._buffer.lines.length) {\n if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) {\n this._model.selectionEnd[0]++;\n }\n }\n\n // Only draw here if the selection changes.\n if (!previousSelectionEnd ||\n previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n this.refresh(true);\n }\n }\n\n /**\n * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n * scrolling of the viewport.\n */\n private _dragScroll(): void {\n if (this._dragScrollAmount) {\n this._terminal.scrollLines(this._dragScrollAmount, false);\n // Re-evaluate selection\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively. This should only happen when\n // NOT in column select mode.\n if (this._dragScrollAmount > 0) {\n if (this._activeSelectionMode !== SelectionMode.COLUMN) {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n this._model.selectionEnd[1] = Math.min(this._terminal.buffer.ydisp + this._terminal.rows, this._terminal.buffer.lines.length - 1);\n } else {\n if (this._activeSelectionMode !== SelectionMode.COLUMN) {\n this._model.selectionEnd[0] = 0;\n }\n this._model.selectionEnd[1] = this._terminal.buffer.ydisp;\n }\n this.refresh();\n }\n }\n\n /**\n * Handles the mouseup event, removing the mousedown listeners.\n * @param event The mouseup event.\n */\n private _onMouseUp(event: MouseEvent): void {\n const timeElapsed = event.timeStamp - this._mouseDownTimeStamp;\n\n this._removeMouseDownListeners();\n\n if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) {\n (new AltClickHandler(event, this._terminal)).move();\n } else if (this.hasSelection) {\n this._onSelectionChange.fire();\n }\n }\n\n private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {\n this.clearSelection();\n // Only adjust the selection on trim, shiftElements is rarely used (only in\n // reverseIndex) and delete in a splice is only ever used when the same\n // number of elements was just added. Given this is could actually be\n // beneficial to leave the selection as is for these cases.\n if (this._trimListener) {\n this._trimListener.dispose();\n }\n this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount));\n }\n\n /**\n * Converts a viewport column to the character index on the buffer line, the\n * latter takes into account wide characters.\n * @param coords The coordinates to find the 2 index for.\n */\n private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number {\n let charIndex = coords[0];\n for (let i = 0; coords[0] >= i; i++) {\n const length = bufferLine.loadCell(i, this._workCell).getChars().length;\n if (this._workCell.getWidth() === 0) {\n // Wide characters aren't included in the line string so decrement the\n // index so the index is back on the wide character.\n charIndex--;\n } else if (length > 1 && coords[0] !== i) {\n // Emojis take up multiple characters, so adjust accordingly. For these\n // we don't want ot include the character at the column as we're\n // returning the start index in the string, not the end index.\n charIndex += length - 1;\n }\n }\n return charIndex;\n }\n\n public setSelection(col: number, row: number, length: number): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this._model.selectionStart = [col, row];\n this._model.selectionStartLength = length;\n this.refresh();\n }\n\n /**\n * Gets positional information for the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition {\n // Ensure coords are within viewport (eg. not within scroll bar)\n if (coords[0] >= this._terminal.cols) {\n return null;\n }\n\n const bufferLine = this._buffer.lines.get(coords[1]);\n if (!bufferLine) {\n return null;\n }\n\n const line = this._buffer.translateBufferLineToString(coords[1], false);\n\n // Get actual index, taking into consideration wide characters\n let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);\n let endIndex = startIndex;\n\n // Record offset to be used later\n const charOffset = coords[0] - startIndex;\n let leftWideCharCount = 0;\n let rightWideCharCount = 0;\n let leftLongCharOffset = 0;\n let rightLongCharOffset = 0;\n\n if (line.charAt(startIndex) === ' ') {\n // Expand until non-whitespace is hit\n while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n startIndex--;\n }\n while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n endIndex++;\n }\n } else {\n // Expand until whitespace is hit. This algorithm works by scanning left\n // and right from the starting position, keeping both the index format\n // (line) and the column format (bufferLine) in sync. When a wide\n // character is hit, it is recorded and the column index is adjusted.\n let startCol = coords[0];\n let endCol = coords[0];\n\n // Consider the initial position, skip it and increment the wide char\n // variable\n if (bufferLine.getWidth(startCol) === 0) {\n leftWideCharCount++;\n startCol--;\n }\n if (bufferLine.getWidth(endCol) === 2) {\n rightWideCharCount++;\n endCol++;\n }\n\n // Adjust the end index for characters whose length are > 1 (emojis)\n const length = bufferLine.getString(endCol).length;\n if (length > 1) {\n rightLongCharOffset += length - 1;\n endIndex += length - 1;\n }\n\n // Expand the string in both directions until a space is hit\n while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) {\n bufferLine.loadCell(startCol - 1, this._workCell);\n const length = this._workCell.getChars().length;\n if (this._workCell.getWidth() === 0) {\n // If the next character is a wide char, record it and skip the column\n leftWideCharCount++;\n startCol--;\n } else if (length > 1) {\n // If the next character's string is longer than 1 char (eg. emoji),\n // adjust the index\n leftLongCharOffset += length - 1;\n startIndex -= length - 1;\n }\n startIndex--;\n startCol--;\n }\n while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) {\n bufferLine.loadCell(endCol + 1, this._workCell);\n const length = this._workCell.getChars().length;\n if (this._workCell.getWidth() === 2) {\n // If the next character is a wide char, record it and skip the column\n rightWideCharCount++;\n endCol++;\n } else if (length > 1) {\n // If the next character's string is longer than 1 char (eg. emoji),\n // adjust the index\n rightLongCharOffset += length - 1;\n endIndex += length - 1;\n }\n endIndex++;\n endCol++;\n }\n }\n\n // Incremenet the end index so it is at the start of the next character\n endIndex++;\n\n // Calculate the start _column_, converting the the string indexes back to\n // column coordinates.\n let start =\n startIndex // The index of the selection's start char in the line string\n + charOffset // The difference between the initial char's column and index\n - leftWideCharCount // The number of wide chars left of the initial char\n + leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)\n\n // Calculate the length in _columns_, converting the the string indexes back\n // to column coordinates.\n let length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols\n endIndex // The index of the selection's end char in the line string\n - startIndex // The index of the selection's start char in the line string\n + leftWideCharCount // The number of wide chars left of the initial char\n + rightWideCharCount // The number of wide chars right of the initial char (inclusive)\n - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)\n - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)\n\n if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {\n return null;\n }\n\n // Recurse upwards if the line is wrapped and the word wraps to the above line\n if (followWrappedLinesAbove) {\n if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) {\n const previousBufferLine = this._buffer.lines.get(coords[1] - 1);\n if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) {\n const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false);\n if (previousLineWordPosition) {\n const offset = this._terminal.cols - previousLineWordPosition.start;\n start -= offset;\n length += offset;\n }\n }\n }\n }\n\n // Recurse downwards if the line is wrapped and the word wraps to the next line\n if (followWrappedLinesBelow) {\n if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) {\n const nextBufferLine = this._buffer.lines.get(coords[1] + 1);\n if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) {\n const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);\n if (nextLineWordPosition) {\n length += nextLineWordPosition.length;\n }\n }\n }\n }\n\n return { start, length };\n }\n\n /**\n * Selects the word at the coordinates specified.\n * @param coords The coordinates to get the word at.\n * @param allowWhitespaceOnlySelection If whitespace should be selected\n */\n protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {\n const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);\n if (wordPosition) {\n // Adjust negative start value\n while (wordPosition.start < 0) {\n wordPosition.start += this._terminal.cols;\n coords[1]--;\n }\n this._model.selectionStart = [wordPosition.start, coords[1]];\n this._model.selectionStartLength = wordPosition.length;\n }\n }\n\n /**\n * Sets the selection end to the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _selectToWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords, true);\n if (wordPosition) {\n let endRow = coords[1];\n\n // Adjust negative start value\n while (wordPosition.start < 0) {\n wordPosition.start += this._terminal.cols;\n endRow--;\n }\n\n // Adjust wrapped length value, this only needs to happen when values are reversed as in that\n // case we're interested in the start of the word, not the end\n if (!this._model.areSelectionValuesReversed()) {\n while (wordPosition.start + wordPosition.length > this._terminal.cols) {\n wordPosition.length -= this._terminal.cols;\n endRow++;\n }\n }\n\n this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow];\n }\n }\n\n /**\n * Gets whether the character is considered a word separator by the select\n * word logic.\n * @param char The character to check.\n */\n private _isCharWordSeparator(cell: CellData): boolean {\n // Zero width characters are never separators as they are always to the\n // right of wide characters\n if (cell.getWidth() === 0) {\n return false;\n }\n return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0;\n }\n\n /**\n * Selects the line specified.\n * @param line The line index.\n */\n protected _selectLineAt(line: number): void {\n const wrappedRange = this._buffer.getWrappedRangeForLine(line);\n this._model.selectionStart = [0, wrappedRange.first];\n this._model.selectionEnd = [this._terminal.cols, wrappedRange.last];\n this._model.selectionStartLength = 0;\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, IMouseZoneManager, IMouseZone } from './Types';\nimport { Disposable } from './common/Lifecycle';\nimport { addDisposableDomListener } from './ui/Lifecycle';\n\nconst HOVER_DURATION = 500;\n\n/**\n * The MouseZoneManager allows components to register zones within the terminal\n * that trigger hover and click callbacks.\n *\n * This class was intentionally made not so robust initially as the only case it\n * needed to support was single-line links which never overlap. Improvements can\n * be made in the future.\n */\nexport class MouseZoneManager extends Disposable implements IMouseZoneManager {\n private _zones: IMouseZone[] = [];\n\n private _areZonesActive: boolean = false;\n private _mouseMoveListener: (e: MouseEvent) => any;\n private _mouseLeaveListener: (e: MouseEvent) => any;\n private _clickListener: (e: MouseEvent) => any;\n\n private _tooltipTimeout: number = null;\n private _currentZone: IMouseZone = null;\n private _lastHoverCoords: [number, number] = [null, null];\n private _initialSelectionLength: number;\n\n constructor(\n private _terminal: ITerminal\n ) {\n super();\n\n this.register(addDisposableDomListener(this._terminal.element, 'mousedown', e => this._onMouseDown(e)));\n\n // These events are expensive, only listen to it when mouse zones are active\n this._mouseMoveListener = e => this._onMouseMove(e);\n this._mouseLeaveListener = e => this._onMouseLeave(e);\n this._clickListener = e => this._onClick(e);\n }\n\n public dispose(): void {\n super.dispose();\n this._deactivate();\n }\n\n public add(zone: IMouseZone): void {\n this._zones.push(zone);\n if (this._zones.length === 1) {\n this._activate();\n }\n }\n\n public clearAll(start?: number, end?: number): void {\n // Exit if there's nothing to clear\n if (this._zones.length === 0) {\n return;\n }\n\n // Clear all if start/end weren't set\n if (!end) {\n start = 0;\n end = this._terminal.rows - 1;\n }\n\n // Iterate through zones and clear them out if they're within the range\n for (let i = 0; i < this._zones.length; i++) {\n const zone = this._zones[i];\n if ((zone.y1 > start && zone.y1 <= end + 1) ||\n (zone.y2 > start && zone.y2 <= end + 1) ||\n (zone.y1 < start && zone.y2 > end + 1)) {\n if (this._currentZone && this._currentZone === zone) {\n this._currentZone.leaveCallback();\n this._currentZone = null;\n }\n this._zones.splice(i--, 1);\n }\n }\n\n // Deactivate the mouse zone manager if all the zones have been removed\n if (this._zones.length === 0) {\n this._deactivate();\n }\n }\n\n private _activate(): void {\n if (!this._areZonesActive) {\n this._areZonesActive = true;\n this._terminal.element.addEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener);\n this._terminal.element.addEventListener('click', this._clickListener);\n }\n }\n\n private _deactivate(): void {\n if (this._areZonesActive) {\n this._areZonesActive = false;\n this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener);\n this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener);\n this._terminal.element.removeEventListener('click', this._clickListener);\n }\n }\n\n private _onMouseMove(e: MouseEvent): void {\n // TODO: Ideally this would only clear the hover state when the mouse moves\n // outside of the mouse zone\n if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) {\n this._onHover(e);\n // Record the current coordinates\n this._lastHoverCoords = [e.pageX, e.pageY];\n }\n }\n\n private _onHover(e: MouseEvent): void {\n const zone = this._findZoneEventAt(e);\n\n // Do nothing if the zone is the same\n if (zone === this._currentZone) {\n return;\n }\n\n // Fire the hover end callback and cancel any existing timer if a new zone\n // is being hovered\n if (this._currentZone) {\n this._currentZone.leaveCallback();\n this._currentZone = null;\n if (this._tooltipTimeout) {\n clearTimeout(this._tooltipTimeout);\n }\n }\n\n // Exit if there is not zone\n if (!zone) {\n return;\n }\n this._currentZone = zone;\n\n // Trigger the hover callback\n if (zone.hoverCallback) {\n zone.hoverCallback(e);\n }\n\n // Restart the tooltip timeout\n this._tooltipTimeout = setTimeout(() => this._onTooltip(e), HOVER_DURATION);\n }\n\n private _onTooltip(e: MouseEvent): void {\n this._tooltipTimeout = null;\n const zone = this._findZoneEventAt(e);\n if (zone && zone.tooltipCallback) {\n zone.tooltipCallback(e);\n }\n }\n\n private _onMouseDown(e: MouseEvent): void {\n // Store current terminal selection length, to check if we're performing\n // a selection operation\n this._initialSelectionLength = this._terminal.getSelection().length;\n\n // Ignore the event if there are no zones active\n if (!this._areZonesActive) {\n return;\n }\n\n // Find the active zone, prevent event propagation if found to prevent other\n // components from handling the mouse event.\n const zone = this._findZoneEventAt(e);\n if (zone) {\n if (zone.willLinkActivate(e)) {\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n }\n }\n\n private _onMouseLeave(e: MouseEvent): void {\n // Fire the hover end callback and cancel any existing timer if the mouse\n // leaves the terminal element\n if (this._currentZone) {\n this._currentZone.leaveCallback();\n this._currentZone = null;\n if (this._tooltipTimeout) {\n clearTimeout(this._tooltipTimeout);\n }\n }\n }\n\n private _onClick(e: MouseEvent): void {\n // Find the active zone and click it if found and no selection was\n // being performed\n const zone = this._findZoneEventAt(e);\n const currentSelectionLength = this._terminal.getSelection().length;\n\n if (zone && currentSelectionLength === this._initialSelectionLength) {\n zone.clickCallback(e);\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n }\n\n private _findZoneEventAt(e: MouseEvent): IMouseZone {\n const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows);\n if (!coords) {\n return null;\n }\n const x = coords[0];\n const y = coords[1];\n for (let i = 0; i < this._zones.length; i++) {\n const zone = this._zones[i];\n if (zone.y1 === zone.y2) {\n // Single line link\n if (y === zone.y1 && x >= zone.x1 && x < zone.x2) {\n return zone;\n }\n } else {\n // Multi-line link\n if ((y === zone.y1 && x >= zone.x1) ||\n (y === zone.y2 && x < zone.x2) ||\n (y > zone.y1 && y < zone.y2)) {\n return zone;\n }\n }\n }\n return null;\n }\n}\n\nexport class MouseZone implements IMouseZone {\n constructor(\n public x1: number,\n public y1: number,\n public x2: number,\n public y2: number,\n public clickCallback: (e: MouseEvent) => any,\n public hoverCallback: (e: MouseEvent) => any,\n public tooltipCallback: (e: MouseEvent) => any,\n public leaveCallback: () => void,\n public willLinkActivate: (e: MouseEvent) => boolean\n ) {\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharMeasure, IMouseHelper } from './Types';\nimport { RenderCoordinator } from './renderer/RenderCoordinator';\n\nexport class MouseHelper implements IMouseHelper {\n constructor(\n private _renderCoordinator: RenderCoordinator\n ) {\n }\n\n public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {\n const rect = element.getBoundingClientRect();\n return [event.clientX - rect.left, event.clientY - rect.top];\n }\n\n /**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param event The mouse event.\n * @param element The terminal's container element.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\n public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {\n // Coordinates cannot be measured if charMeasure has not been initialized\n if (!charMeasure.width || !charMeasure.height) {\n return null;\n }\n\n const coords = MouseHelper.getCoordsRelativeToElement(event, element);\n if (!coords) {\n return null;\n }\n\n coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderCoordinator.dimensions.actualCellWidth / 2 : 0)) / this._renderCoordinator.dimensions.actualCellWidth);\n coords[1] = Math.ceil(coords[1] / this._renderCoordinator.dimensions.actualCellHeight);\n\n // Ensure coordinates are within the terminal viewport. Note that selections\n // need an addition point of precision to cover the end point (as characters\n // cover half of one char and half of the next).\n coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));\n coords[1] = Math.min(Math.max(coords[1], 1), rowCount);\n\n return coords;\n }\n\n /**\n * Gets coordinates within the terminal for a particular mouse event, wrapping\n * them to the bounds of the terminal and adding 32 to both the x and y values\n * as expected by xterm.\n * @param event The mouse event.\n * @param element The terminal's container element.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows in the terminal.\n */\n public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number } {\n const coords = this.getCoords(event, element, charMeasure, colCount, rowCount);\n let x = coords[0];\n let y = coords[1];\n\n // xterm sends raw bytes and starts at 32 (SP) for each.\n x += 32;\n y += 32;\n\n return { x, y };\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult, IMouseZoneManager } from './Types';\nimport { MouseZone } from './MouseZoneManager';\nimport { getStringCellWidth } from './CharWidth';\nimport { EventEmitter2, IEvent } from './common/EventEmitter2';\n\n/**\n * The Linkifier applies links to rows shortly after they have been refreshed.\n */\nexport class Linkifier implements ILinkifier {\n /**\n * The time to wait after a row is changed before it is linkified. This prevents\n * the costly operation of searching every row multiple times, potentially a\n * huge amount of times.\n */\n protected static readonly TIME_BEFORE_LINKIFY = 200;\n\n /**\n * Limit of the unwrapping line expansion (overscan) at the top and bottom\n * of the actual viewport in ASCII characters.\n * A limit of 2000 should match most sane urls.\n */\n protected static readonly OVERSCAN_CHAR_LIMIT = 2000;\n\n protected _linkMatchers: ILinkMatcher[] = [];\n\n private _mouseZoneManager: IMouseZoneManager;\n private _rowsTimeoutId: number;\n private _nextLinkMatcherId = 0;\n private _rowsToLinkify: { start: number, end: number };\n\n private _onLinkHover = new EventEmitter2();\n public get onLinkHover(): IEvent { return this._onLinkHover.event; }\n private _onLinkLeave = new EventEmitter2();\n public get onLinkLeave(): IEvent { return this._onLinkLeave.event; }\n private _onLinkTooltip = new EventEmitter2();\n public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; }\n\n constructor(\n protected _terminal: ITerminal\n ) {\n this._rowsToLinkify = {\n start: null,\n end: null\n };\n }\n\n /**\n * Attaches the linkifier to the DOM, enabling linkification.\n * @param mouseZoneManager The mouse zone manager to register link zones with.\n */\n public attachToDom(mouseZoneManager: IMouseZoneManager): void {\n this._mouseZoneManager = mouseZoneManager;\n }\n\n /**\n * Queue linkification on a set of rows.\n * @param start The row to linkify from (inclusive).\n * @param end The row to linkify to (inclusive).\n */\n public linkifyRows(start: number, end: number): void {\n // Don't attempt linkify if not yet attached to DOM\n if (!this._mouseZoneManager) {\n return;\n }\n\n // Increase range to linkify\n if (this._rowsToLinkify.start === null) {\n this._rowsToLinkify.start = start;\n this._rowsToLinkify.end = end;\n } else {\n this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start);\n this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end);\n }\n\n // Clear out any existing links on this row range\n this._mouseZoneManager.clearAll(start, end);\n\n // Restart timer\n if (this._rowsTimeoutId) {\n clearTimeout(this._rowsTimeoutId);\n }\n this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier.TIME_BEFORE_LINKIFY);\n }\n\n /**\n * Linkifies the rows requested.\n */\n private _linkifyRows(): void {\n this._rowsTimeoutId = null;\n const buffer = this._terminal.buffer;\n\n // Ensure the start row exists\n const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start;\n if (absoluteRowIndexStart >= buffer.lines.length) {\n return;\n }\n\n // Invalidate bad end row values (if a resize happened)\n const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._terminal.rows) + 1;\n\n // Iterate over the range of unwrapped content strings within start..end\n // (excluding).\n // _doLinkifyRow gets full unwrapped lines with the start row as buffer offset\n // for every matcher.\n // The unwrapping is needed to also match content that got wrapped across\n // several buffer lines. To avoid a worst case scenario where the whole buffer\n // contains just a single unwrapped string we limit this line expansion beyond\n // the viewport to +OVERSCAN_CHAR_LIMIT chars (overscan) at top and bottom.\n // This comes with the tradeoff that matches longer than OVERSCAN_CHAR_LIMIT\n // chars will not match anymore at the viewport borders.\n const overscanLineLimit = Math.ceil(Linkifier.OVERSCAN_CHAR_LIMIT / this._terminal.cols);\n const iterator = this._terminal.buffer.iterator(\n false, absoluteRowIndexStart, absoluteRowIndexEnd, overscanLineLimit, overscanLineLimit);\n while (iterator.hasNext()) {\n const lineData: IBufferStringIteratorResult = iterator.next();\n for (let i = 0; i < this._linkMatchers.length; i++) {\n this._doLinkifyRow(lineData.range.first, lineData.content, this._linkMatchers[i]);\n }\n }\n\n this._rowsToLinkify.start = null;\n this._rowsToLinkify.end = null;\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param regex The regular expression to search for. Specifically, this\n * searches the textContent of the rows. You will want to use \\s to match a\n * space ' ' character for example.\n * @param handler The callback when the link is called.\n * @param options Options for the link matcher.\n * @return The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number {\n if (!handler) {\n throw new Error('handler must be defined');\n }\n const matcher: ILinkMatcher = {\n id: this._nextLinkMatcherId++,\n regex,\n handler,\n matchIndex: options.matchIndex,\n validationCallback: options.validationCallback,\n hoverTooltipCallback: options.tooltipCallback,\n hoverLeaveCallback: options.leaveCallback,\n willLinkActivate: options.willLinkActivate,\n priority: options.priority || 0\n };\n this._addLinkMatcherToList(matcher);\n return matcher.id;\n }\n\n /**\n * Inserts a link matcher to the list in the correct position based on the\n * priority of each link matcher. New link matchers of equal priority are\n * considered after older link matchers.\n * @param matcher The link matcher to be added.\n */\n private _addLinkMatcherToList(matcher: ILinkMatcher): void {\n if (this._linkMatchers.length === 0) {\n this._linkMatchers.push(matcher);\n return;\n }\n\n for (let i = this._linkMatchers.length - 1; i >= 0; i--) {\n if (matcher.priority <= this._linkMatchers[i].priority) {\n this._linkMatchers.splice(i + 1, 0, matcher);\n return;\n }\n }\n\n this._linkMatchers.splice(0, 0, matcher);\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param matcherId The link matcher's ID (returned after register)\n * @return Whether a link matcher was found and deregistered.\n */\n public deregisterLinkMatcher(matcherId: number): boolean {\n for (let i = 0; i < this._linkMatchers.length; i++) {\n if (this._linkMatchers[i].id === matcherId) {\n this._linkMatchers.splice(i, 1);\n return true;\n }\n }\n return false;\n }\n\n /**\n * Linkifies a row given a specific handler.\n * @param rowIndex The row index to linkify (absolute index).\n * @param text string content of the unwrapped row.\n * @param matcher The link matcher for this line.\n */\n private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher): void {\n // clone regex to do a global search on text\n const rex = new RegExp(matcher.regex.source, matcher.regex.flags + 'g');\n let match;\n let stringIndex = -1;\n while ((match = rex.exec(text)) !== null) {\n const uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n if (!uri) {\n // something matched but does not comply with the given matchIndex\n // since this is most likely a bug the regex itself we simply do nothing here\n // DEBUG: print match and throw\n if ((this._terminal).debug) {\n console.log({match, matcher});\n throw new Error('match found without corresponding matchIndex');\n }\n break;\n }\n\n // Get index, match.index is for the outer match which includes negated chars\n // therefore we cannot use match.index directly, instead we search the position\n // of the match group in text again\n // also correct regex and string search offsets for the next loop run\n stringIndex = text.indexOf(uri, stringIndex + 1);\n rex.lastIndex = stringIndex + uri.length;\n if (stringIndex < 0) {\n // invalid stringIndex (should not have happened)\n break;\n }\n\n // get the buffer index as [absolute row, col] for the match\n const bufferIndex = this._terminal.buffer.stringIndexToBufferIndex(rowIndex, stringIndex);\n if (bufferIndex[0] < 0) {\n // invalid bufferIndex (should not have happened)\n break;\n }\n\n const line = this._terminal.buffer.lines.get(bufferIndex[0]);\n const attr = line.getFg(bufferIndex[1]);\n let fg: number | undefined;\n if (attr) {\n fg = (attr >> 9) & 0x1ff;\n }\n\n if (matcher.validationCallback) {\n matcher.validationCallback(uri, isValid => {\n // Discard link if the line has already changed\n if (this._rowsTimeoutId) {\n return;\n }\n if (isValid) {\n this._addLink(bufferIndex[1], bufferIndex[0] - this._terminal.buffer.ydisp, uri, matcher, fg);\n }\n });\n } else {\n this._addLink(bufferIndex[1], bufferIndex[0] - this._terminal.buffer.ydisp, uri, matcher, fg);\n }\n }\n }\n\n /**\n * Registers a link to the mouse zone manager.\n * @param x The column the link starts.\n * @param y The row the link is on.\n * @param uri The URI of the link.\n * @param matcher The link matcher for the link.\n * @param fg The link color for hover event.\n */\n private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number): void {\n const width = getStringCellWidth(uri);\n const x1 = x % this._terminal.cols;\n const y1 = y + Math.floor(x / this._terminal.cols);\n let x2 = (x1 + width) % this._terminal.cols;\n let y2 = y1 + Math.floor((x1 + width) / this._terminal.cols);\n if (x2 === 0) {\n x2 = this._terminal.cols;\n y2--;\n }\n\n this._mouseZoneManager.add(new MouseZone(\n x1 + 1,\n y1 + 1,\n x2 + 1,\n y2 + 1,\n e => {\n if (matcher.handler) {\n return matcher.handler(e, uri);\n }\n window.open(uri, '_blank');\n },\n () => {\n this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));\n this._terminal.element.classList.add('xterm-cursor-pointer');\n },\n e => {\n this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));\n if (matcher.hoverTooltipCallback) {\n matcher.hoverTooltipCallback(e, uri);\n }\n },\n () => {\n this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));\n this._terminal.element.classList.remove('xterm-cursor-pointer');\n if (matcher.hoverLeaveCallback) {\n matcher.hoverLeaveCallback();\n }\n },\n e => {\n if (matcher.willLinkActivate) {\n return matcher.willLinkActivate(e, uri);\n }\n return true;\n }\n ));\n }\n\n private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkifierEvent {\n return { x1, y1, x2, y2, cols: this._terminal.cols, fg };\n }\n}\n","/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)\n * @license MIT\n */\n\nimport { IInputHandler, IDcsHandler, IEscapeSequenceParser, IInputHandlingTerminal } from './Types';\nimport { C0, C1 } from './common/data/EscapeSequences';\nimport { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';\nimport { wcwidth } from './CharWidth';\nimport { EscapeSequenceParser } from './EscapeSequenceParser';\nimport { IDisposable } from 'xterm';\nimport { Disposable } from './common/Lifecycle';\nimport { concat } from './common/TypedArrayUtils';\nimport { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from './core/input/TextDecoder';\nimport { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';\nimport { EventEmitter2, IEvent } from './common/EventEmitter2';\n\n/**\n * Map collect to glevel. Used in `selectCharset`.\n */\nconst GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2};\n\n\n/**\n * DCS subparser implementations\n */\n\n/**\n * DCS $ q Pt ST\n * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)\n * Request Status String (DECRQSS), VT420 and up.\n * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)\n */\nclass DECRQSS implements IDcsHandler {\n private _data: Uint32Array = new Uint32Array(0);\n\n constructor(private _terminal: any) { }\n\n hook(collect: string, params: number[], flag: number): void {\n this._data = new Uint32Array(0);\n }\n\n put(data: Uint32Array, start: number, end: number): void {\n this._data = concat(this._data, data.subarray(start, end));\n }\n\n unhook(): void {\n const data = utf32ToString(this._data);\n this._data = new Uint32Array(0);\n switch (data) {\n // valid: DCS 1 $ r Pt ST (xterm)\n case '\"q': // DECSCA\n return this._terminal.handler(`${C0.ESC}P1$r0\"q${C0.ESC}\\\\`);\n case '\"p': // DECSCL\n return this._terminal.handler(`${C0.ESC}P1$r61\"p${C0.ESC}\\\\`);\n case 'r': // DECSTBM\n const pt = '' + (this._terminal.buffer.scrollTop + 1) +\n ';' + (this._terminal.buffer.scrollBottom + 1) + 'r';\n return this._terminal.handler(`${C0.ESC}P1$r${pt}${C0.ESC}\\\\`);\n case 'm': // SGR\n // TODO: report real settings instead of 0m\n return this._terminal.handler(`${C0.ESC}P1$r0m${C0.ESC}\\\\`);\n case ' q': // DECSCUSR\n const STYLES: {[key: string]: number} = {'block': 2, 'underline': 4, 'bar': 6};\n let style = STYLES[this._terminal.getOption('cursorStyle')];\n style -= this._terminal.getOption('cursorBlink');\n return this._terminal.handler(`${C0.ESC}P1$r${style} q${C0.ESC}\\\\`);\n default:\n // invalid: DCS 0 $ r Pt ST (xterm)\n this._terminal.error('Unknown DCS $q %s', data);\n this._terminal.handler(`${C0.ESC}P0$r${C0.ESC}\\\\`);\n }\n }\n}\n\n/**\n * DCS Ps; Ps| Pt ST\n * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html)\n * not supported\n */\n\n/**\n * DCS + q Pt ST (xterm)\n * Request Terminfo String\n * not implemented\n */\n\n/**\n * DCS + p Pt ST (xterm)\n * Set Terminfo Data\n * not supported\n */\n\n\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler extends Disposable implements IInputHandler {\n private _parseBuffer: Uint32Array = new Uint32Array(4096);\n private _stringDecoder: StringToUtf32 = new StringToUtf32();\n private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32();\n private _workCell: CellData = new CellData();\n\n private _onCursorMove = new EventEmitter2();\n public get onCursorMove(): IEvent { return this._onCursorMove.event; }\n private _onData = new EventEmitter2();\n public get onData(): IEvent { return this._onData.event; }\n private _onLineFeed = new EventEmitter2();\n public get onLineFeed(): IEvent { return this._onLineFeed.event; }\n private _onScroll = new EventEmitter2();\n public get onScroll(): IEvent { return this._onScroll.event; }\n\n constructor(\n protected _terminal: IInputHandlingTerminal,\n private _parser: IEscapeSequenceParser = new EscapeSequenceParser())\n {\n super();\n\n this.register(this._parser);\n\n /**\n * custom fallback handlers\n */\n this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => {\n this._terminal.error('Unknown CSI code: ', { collect, params, flag: String.fromCharCode(flag) });\n });\n this._parser.setEscHandlerFallback((collect: string, flag: number) => {\n this._terminal.error('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) });\n });\n this._parser.setExecuteHandlerFallback((code: number) => {\n this._terminal.error('Unknown EXECUTE code: ', { code });\n });\n this._parser.setOscHandlerFallback((identifier: number, data: string) => {\n this._terminal.error('Unknown OSC code: ', { identifier, data });\n });\n\n /**\n * print handler\n */\n this._parser.setPrintHandler((data, start, end): void => this.print(data, start, end));\n\n /**\n * CSI handler\n */\n this._parser.setCsiHandler('@', (params, collect) => this.insertChars(params));\n this._parser.setCsiHandler('A', (params, collect) => this.cursorUp(params));\n this._parser.setCsiHandler('B', (params, collect) => this.cursorDown(params));\n this._parser.setCsiHandler('C', (params, collect) => this.cursorForward(params));\n this._parser.setCsiHandler('D', (params, collect) => this.cursorBackward(params));\n this._parser.setCsiHandler('E', (params, collect) => this.cursorNextLine(params));\n this._parser.setCsiHandler('F', (params, collect) => this.cursorPrecedingLine(params));\n this._parser.setCsiHandler('G', (params, collect) => this.cursorCharAbsolute(params));\n this._parser.setCsiHandler('H', (params, collect) => this.cursorPosition(params));\n this._parser.setCsiHandler('I', (params, collect) => this.cursorForwardTab(params));\n this._parser.setCsiHandler('J', (params, collect) => this.eraseInDisplay(params));\n this._parser.setCsiHandler('K', (params, collect) => this.eraseInLine(params));\n this._parser.setCsiHandler('L', (params, collect) => this.insertLines(params));\n this._parser.setCsiHandler('M', (params, collect) => this.deleteLines(params));\n this._parser.setCsiHandler('P', (params, collect) => this.deleteChars(params));\n this._parser.setCsiHandler('S', (params, collect) => this.scrollUp(params));\n this._parser.setCsiHandler('T', (params, collect) => this.scrollDown(params, collect));\n this._parser.setCsiHandler('X', (params, collect) => this.eraseChars(params));\n this._parser.setCsiHandler('Z', (params, collect) => this.cursorBackwardTab(params));\n this._parser.setCsiHandler('`', (params, collect) => this.charPosAbsolute(params));\n this._parser.setCsiHandler('a', (params, collect) => this.hPositionRelative(params));\n this._parser.setCsiHandler('b', (params, collect) => this.repeatPrecedingCharacter(params));\n this._parser.setCsiHandler('c', (params, collect) => this.sendDeviceAttributes(params, collect));\n this._parser.setCsiHandler('d', (params, collect) => this.linePosAbsolute(params));\n this._parser.setCsiHandler('e', (params, collect) => this.vPositionRelative(params));\n this._parser.setCsiHandler('f', (params, collect) => this.hVPosition(params));\n this._parser.setCsiHandler('g', (params, collect) => this.tabClear(params));\n this._parser.setCsiHandler('h', (params, collect) => this.setMode(params, collect));\n this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect));\n this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params));\n this._parser.setCsiHandler('n', (params, collect) => this.deviceStatus(params, collect));\n this._parser.setCsiHandler('p', (params, collect) => this.softReset(params, collect));\n this._parser.setCsiHandler('q', (params, collect) => this.setCursorStyle(params, collect));\n this._parser.setCsiHandler('r', (params, collect) => this.setScrollRegion(params, collect));\n this._parser.setCsiHandler('s', (params, collect) => this.saveCursor(params));\n this._parser.setCsiHandler('u', (params, collect) => this.restoreCursor(params));\n\n /**\n * execute handler\n */\n this._parser.setExecuteHandler(C0.BEL, () => this.bell());\n this._parser.setExecuteHandler(C0.LF, () => this.lineFeed());\n this._parser.setExecuteHandler(C0.VT, () => this.lineFeed());\n this._parser.setExecuteHandler(C0.FF, () => this.lineFeed());\n this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn());\n this._parser.setExecuteHandler(C0.BS, () => this.backspace());\n this._parser.setExecuteHandler(C0.HT, () => this.tab());\n this._parser.setExecuteHandler(C0.SO, () => this.shiftOut());\n this._parser.setExecuteHandler(C0.SI, () => this.shiftIn());\n // FIXME: What do to with missing? Old code just added those to print.\n\n // some C1 control codes - FIXME: should those be enabled by default?\n this._parser.setExecuteHandler(C1.IND, () => this.index());\n this._parser.setExecuteHandler(C1.NEL, () => this.nextLine());\n this._parser.setExecuteHandler(C1.HTS, () => this.tabSet());\n\n /**\n * OSC handler\n */\n // 0 - icon name + title\n this._parser.setOscHandler(0, (data) => this.setTitle(data));\n // 1 - icon name\n // 2 - title\n this._parser.setOscHandler(2, (data) => this.setTitle(data));\n // 3 - set property X in the form \"prop=value\"\n // 4 - Change Color Number\n // 5 - Change Special Color Number\n // 6 - Enable/disable Special Color Number c\n // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)\n // 10 - Change VT100 text foreground color to Pt.\n // 11 - Change VT100 text background color to Pt.\n // 12 - Change text cursor color to Pt.\n // 13 - Change mouse foreground color to Pt.\n // 14 - Change mouse background color to Pt.\n // 15 - Change Tektronix foreground color to Pt.\n // 16 - Change Tektronix background color to Pt.\n // 17 - Change highlight background color to Pt.\n // 18 - Change Tektronix cursor color to Pt.\n // 19 - Change highlight foreground color to Pt.\n // 46 - Change Log File to Pt.\n // 50 - Set Font to Pt.\n // 51 - reserved for Emacs shell.\n // 52 - Manipulate Selection Data.\n // 104 ; c - Reset Color Number c.\n // 105 ; c - Reset Special Color Number c.\n // 106 ; c; f - Enable/disable Special Color Number c.\n // 110 - Reset VT100 text foreground color.\n // 111 - Reset VT100 text background color.\n // 112 - Reset text cursor color.\n // 113 - Reset mouse foreground color.\n // 114 - Reset mouse background color.\n // 115 - Reset Tektronix foreground color.\n // 116 - Reset Tektronix background color.\n // 117 - Reset highlight color.\n // 118 - Reset Tektronix cursor color.\n // 119 - Reset highlight foreground color.\n\n /**\n * ESC handlers\n */\n this._parser.setEscHandler('7', () => this.saveCursor([]));\n this._parser.setEscHandler('8', () => this.restoreCursor([]));\n this._parser.setEscHandler('D', () => this.index());\n this._parser.setEscHandler('E', () => this.nextLine());\n this._parser.setEscHandler('H', () => this.tabSet());\n this._parser.setEscHandler('M', () => this.reverseIndex());\n this._parser.setEscHandler('=', () => this.keypadApplicationMode());\n this._parser.setEscHandler('>', () => this.keypadNumericMode());\n this._parser.setEscHandler('c', () => this.reset());\n this._parser.setEscHandler('n', () => this.setgLevel(2));\n this._parser.setEscHandler('o', () => this.setgLevel(3));\n this._parser.setEscHandler('|', () => this.setgLevel(3));\n this._parser.setEscHandler('}', () => this.setgLevel(2));\n this._parser.setEscHandler('~', () => this.setgLevel(1));\n this._parser.setEscHandler('%@', () => this.selectDefaultCharset());\n this._parser.setEscHandler('%G', () => this.selectDefaultCharset());\n for (const flag in CHARSETS) {\n this._parser.setEscHandler('(' + flag, () => this.selectCharset('(' + flag));\n this._parser.setEscHandler(')' + flag, () => this.selectCharset(')' + flag));\n this._parser.setEscHandler('*' + flag, () => this.selectCharset('*' + flag));\n this._parser.setEscHandler('+' + flag, () => this.selectCharset('+' + flag));\n this._parser.setEscHandler('-' + flag, () => this.selectCharset('-' + flag));\n this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag));\n this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // TODO: supported?\n }\n\n /**\n * error handler\n */\n this._parser.setErrorHandler((state) => {\n this._terminal.error('Parsing error: ', state);\n return state;\n });\n\n /**\n * DCS handler\n */\n this._parser.setDcsHandler('$q', new DECRQSS(this._terminal));\n }\n\n public dispose(): void {\n super.dispose();\n this._terminal = null;\n }\n\n public parse(data: string): void {\n // Ensure the terminal is not disposed\n if (!this._terminal) {\n return;\n }\n\n let buffer = this._terminal.buffer;\n const cursorStartX = buffer.x;\n const cursorStartY = buffer.y;\n\n // TODO: Consolidate debug/logging #1560\n if ((this._terminal).debug) {\n this._terminal.log('data: ' + data);\n }\n\n if (this._parseBuffer.length < data.length) {\n this._parseBuffer = new Uint32Array(data.length);\n }\n this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));\n\n buffer = this._terminal.buffer;\n if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {\n this._onCursorMove.fire();\n }\n }\n\n public parseUtf8(data: Uint8Array): void {\n // Ensure the terminal is not disposed\n if (!this._terminal) {\n return;\n }\n\n let buffer = this._terminal.buffer;\n const cursorStartX = buffer.x;\n const cursorStartY = buffer.y;\n\n // TODO: Consolidate debug/logging #1560\n if ((this._terminal).debug) {\n this._terminal.log('data: ' + data);\n }\n\n if (this._parseBuffer.length < data.length) {\n this._parseBuffer = new Uint32Array(data.length);\n }\n this._parser.parse(this._parseBuffer, this._utf8Decoder.decode(data, this._parseBuffer));\n\n buffer = this._terminal.buffer;\n if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {\n this._terminal.emit('cursormove');\n }\n }\n\n public print(data: Uint32Array, start: number, end: number): void {\n let code: number;\n let chWidth: number;\n const buffer = this._terminal.buffer;\n const charset = this._terminal.charset;\n const screenReaderMode = this._terminal.options.screenReaderMode;\n const cols = this._terminal.cols;\n const wraparoundMode = this._terminal.wraparoundMode;\n const insertMode = this._terminal.insertMode;\n const curAttr = this._terminal.curAttrData;\n let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);\n\n this._terminal.updateRange(buffer.y);\n for (let pos = start; pos < end; ++pos) {\n code = data[pos];\n\n // calculate print space\n // expensive call, therefore we save width in line buffer\n chWidth = wcwidth(code);\n\n // get charset replacement character\n // charset is only defined for ASCII, therefore we only\n // search for an replacement char if code < 127\n if (code < 127 && charset) {\n const ch = charset[String.fromCharCode(code)];\n if (ch) {\n code = ch.charCodeAt(0);\n }\n }\n\n if (screenReaderMode) {\n this._terminal.emit('a11y.char', stringFromCodePoint(code));\n }\n\n // insert combining char at last cursor position\n // FIXME: needs handling after cursor jumps\n // buffer.x should never be 0 for a combining char\n // since they always follow a cell consuming char\n // therefore we can test for buffer.x to avoid overflow left\n if (!chWidth && buffer.x) {\n if (!bufferRow.getWidth(buffer.x - 1)) {\n // found empty cell after fullwidth, need to go 2 cells back\n // it is save to step 2 cells back here\n // since an empty cell is only set by fullwidth chars\n bufferRow.addCodepointToCell(buffer.x - 2, code);\n } else {\n bufferRow.addCodepointToCell(buffer.x - 1, code);\n }\n continue;\n }\n\n // goto next line if ch would overflow\n // TODO: needs a global min terminal width of 2\n // FIXME: additionally ensure chWidth fits into a line\n // --> maybe forbid cols= cols) {\n // autowrap - DECAWM\n // automatically wraps to the beginning of the next line\n if (wraparoundMode) {\n buffer.x = 0;\n buffer.y++;\n if (buffer.y > buffer.scrollBottom) {\n buffer.y--;\n this._terminal.scroll(true);\n } else {\n // The line already exists (eg. the initial viewport), mark it as a\n // wrapped line\n buffer.lines.get(buffer.y).isWrapped = true;\n }\n // row changed, get it again\n bufferRow = buffer.lines.get(buffer.y + buffer.ybase);\n } else {\n if (chWidth === 2) {\n // FIXME: check for xterm behavior\n // What to do here? We got a wide char that does not fit into last cell\n continue;\n }\n // FIXME: Do we have to set buffer.x to cols - 1, if not wrapping?\n }\n }\n\n // insert mode: move characters to right\n if (insertMode) {\n // right shift cells according to the width\n bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr));\n // test last cell - since the last cell has only room for\n // a halfwidth char any fullwidth shifted there is lost\n // and will be set to empty cell\n if (bufferRow.getWidth(cols - 1) === 2) {\n bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg);\n }\n }\n\n // write current char to buffer and advance cursor\n bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg);\n\n // fullwidth char - also set next cell to placeholder stub and advance cursor\n // for graphemes bigger than fullwidth we can simply loop to zero\n // we already made sure above, that buffer.x + chWidth will not overflow right\n if (chWidth > 0) {\n while (--chWidth) {\n // other than a regular empty cell a cell following a wide char has no width\n bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg);\n }\n }\n }\n this._terminal.updateRange(buffer.y);\n }\n\n /**\n * Forward addCsiHandler from parser.\n */\n public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {\n return this._parser.addCsiHandler(flag, callback);\n }\n\n /**\n * Forward addOscHandler from parser.\n */\n public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {\n return this._parser.addOscHandler(ident, callback);\n }\n\n /**\n * BEL\n * Bell (Ctrl-G).\n */\n public bell(): void {\n this._terminal.bell();\n }\n\n /**\n * LF\n * Line Feed or New Line (NL). (LF is Ctrl-J).\n */\n public lineFeed(): void {\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n\n if (this._terminal.options.convertEol) {\n buffer.x = 0;\n }\n buffer.y++;\n if (buffer.y > buffer.scrollBottom) {\n buffer.y--;\n this._terminal.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (buffer.x >= this._terminal.cols) {\n buffer.x--;\n }\n\n this._onLineFeed.fire();\n }\n\n /**\n * CR\n * Carriage Return (Ctrl-M).\n */\n public carriageReturn(): void {\n this._terminal.buffer.x = 0;\n }\n\n /**\n * BS\n * Backspace (Ctrl-H).\n */\n public backspace(): void {\n if (this._terminal.buffer.x > 0) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * TAB\n * Horizontal Tab (HT) (Ctrl-I).\n */\n public tab(): void {\n const originalX = this._terminal.buffer.x;\n this._terminal.buffer.x = this._terminal.buffer.nextStop();\n if (this._terminal.options.screenReaderMode) {\n this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX);\n }\n }\n\n /**\n * SO\n * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the\n * G1 character set.\n */\n public shiftOut(): void {\n this._terminal.setgLevel(1);\n }\n\n /**\n * SI\n * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0\n * character set (the default).\n */\n public shiftIn(): void {\n this._terminal.setgLevel(0);\n }\n\n /**\n * CSI Ps @\n * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n */\n public insertChars(params: number[]): void {\n this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells(\n this._terminal.buffer.x,\n params[0] || 1,\n this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())\n );\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n\n /**\n * CSI Ps A\n * Cursor Up Ps Times (default = 1) (CUU).\n */\n public cursorUp(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n }\n\n /**\n * CSI Ps B\n * Cursor Down Ps Times (default = 1) (CUD).\n */\n public cursorDown(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps C\n * Cursor Forward Ps Times (default = 1) (CUF).\n */\n public cursorForward(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps D\n * Cursor Backward Ps Times (default = 1) (CUB).\n */\n public cursorBackward(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n this._terminal.buffer.x -= param;\n if (this._terminal.buffer.x < 0) {\n this._terminal.buffer.x = 0;\n }\n }\n\n /**\n * CSI Ps E\n * Cursor Next Line Ps Times (default = 1) (CNL).\n * same as CSI Ps B ?\n */\n public cursorNextLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps F\n * Cursor Preceding Line Ps Times (default = 1) (CNL).\n * reuse CSI Ps A ?\n */\n public cursorPrecedingLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps G\n * Cursor Character Absolute [column] (default = [row,1]) (CHA).\n */\n public cursorCharAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n }\n\n /**\n * CSI Ps ; Ps H\n * Cursor Position [row;column] (default = [1,1]) (CUP).\n */\n public cursorPosition(params: number[]): void {\n let col: number;\n let row: number = params[0] - 1;\n\n if (params.length >= 2) {\n col = params[1] - 1;\n } else {\n col = 0;\n }\n\n if (row < 0) {\n row = 0;\n } else if (row >= this._terminal.rows) {\n row = this._terminal.rows - 1;\n }\n\n if (col < 0) {\n col = 0;\n } else if (col >= this._terminal.cols) {\n col = this._terminal.cols - 1;\n }\n\n this._terminal.buffer.x = col;\n this._terminal.buffer.y = row;\n }\n\n /**\n * CSI Ps I\n * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n */\n public cursorForwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.buffer.nextStop();\n }\n }\n\n /**\n * Helper method to erase cells in a terminal row.\n * The cell gets replaced with the eraseChar of the terminal.\n * @param y row index\n * @param start first cell index to be erased\n * @param end end - 1 is last erased cell\n */\n private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {\n const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);\n line.replaceCells(\n start,\n end,\n this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())\n );\n if (clearWrap) {\n line.isWrapped = false;\n }\n }\n\n /**\n * Helper method to reset cells in a terminal row.\n * The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false.\n * @param y row index\n */\n private _resetBufferLine(y: number): void {\n this._eraseInBufferLine(y, 0, this._terminal.cols, true);\n }\n\n /**\n * CSI Ps J Erase in Display (ED).\n * Ps = 0 -> Erase Below (default).\n * Ps = 1 -> Erase Above.\n * Ps = 2 -> Erase All.\n * Ps = 3 -> Erase Saved Lines (xterm).\n * CSI ? Ps J\n * Erase in Display (DECSED).\n * Ps = 0 -> Selective Erase Below (default).\n * Ps = 1 -> Selective Erase Above.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInDisplay(params: number[]): void {\n let j;\n switch (params[0]) {\n case 0:\n j = this._terminal.buffer.y;\n this._terminal.updateRange(j);\n this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols, this._terminal.buffer.x === 0);\n for (; j < this._terminal.rows; j++) {\n this._resetBufferLine(j);\n }\n this._terminal.updateRange(j);\n break;\n case 1:\n j = this._terminal.buffer.y;\n this._terminal.updateRange(j);\n // Deleted front part of line and everything before. This line will no longer be wrapped.\n this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1, true);\n if (this._terminal.buffer.x + 1 >= this._terminal.cols) {\n // Deleted entire previous line. This next line can no longer be wrapped.\n this._terminal.buffer.lines.get(j + 1).isWrapped = false;\n }\n while (j--) {\n this._resetBufferLine(j);\n }\n this._terminal.updateRange(0);\n break;\n case 2:\n j = this._terminal.rows;\n this._terminal.updateRange(j - 1);\n while (j--) {\n this._resetBufferLine(j);\n }\n this._terminal.updateRange(0);\n break;\n case 3:\n // Clear scrollback (everything not in viewport)\n const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;\n if (scrollBackSize > 0) {\n this._terminal.buffer.lines.trimStart(scrollBackSize);\n this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);\n this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);\n // Force a scroll event to refresh viewport\n this._onScroll.fire(0);\n }\n break;\n }\n }\n\n /**\n * CSI Ps K Erase in Line (EL).\n * Ps = 0 -> Erase to Right (default).\n * Ps = 1 -> Erase to Left.\n * Ps = 2 -> Erase All.\n * CSI ? Ps K\n * Erase in Line (DECSEL).\n * Ps = 0 -> Selective Erase to Right (default).\n * Ps = 1 -> Selective Erase to Left.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInLine(params: number[]): void {\n switch (params[0]) {\n case 0:\n this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols);\n break;\n case 1:\n this._eraseInBufferLine(this._terminal.buffer.y, 0, this._terminal.buffer.x + 1);\n break;\n case 2:\n this._eraseInBufferLine(this._terminal.buffer.y, 0, this._terminal.cols);\n break;\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n\n /**\n * CSI Ps L\n * Insert Ps Line(s) (default = 1) (IL).\n */\n public insertLines(params: number[]): void {\n let param: number = params[0];\n if (param < 1) {\n param = 1;\n }\n\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n\n const row: number = buffer.y + buffer.ybase;\n\n const scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom;\n const scrollBottomAbsolute = this._terminal.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1;\n while (param--) {\n // test: echo -e '\\e[44m\\e[1L\\e[0m'\n // blankLine(true) - xterm/linux behavior\n buffer.lines.splice(scrollBottomAbsolute - 1, 1);\n buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));\n }\n\n // this.maxRange();\n this._terminal.updateRange(buffer.y);\n this._terminal.updateRange(buffer.scrollBottom);\n }\n\n /**\n * CSI Ps M\n * Delete Ps Line(s) (default = 1) (DL).\n */\n public deleteLines(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n\n const row: number = buffer.y + buffer.ybase;\n\n let j: number;\n j = this._terminal.rows - 1 - buffer.scrollBottom;\n j = this._terminal.rows - 1 + buffer.ybase - j;\n while (param--) {\n // test: echo -e '\\e[44m\\e[1M\\e[0m'\n // blankLine(true) - xterm/linux behavior\n buffer.lines.splice(row, 1);\n buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));\n }\n\n // this.maxRange();\n this._terminal.updateRange(buffer.y);\n this._terminal.updateRange(buffer.scrollBottom);\n }\n\n /**\n * CSI Ps P\n * Delete Ps Character(s) (default = 1) (DCH).\n */\n public deleteChars(params: number[]): void {\n this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells(\n this._terminal.buffer.x,\n params[0] || 1,\n this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())\n );\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n\n /**\n * CSI Ps S Scroll up Ps lines (default = 1) (SU).\n */\n public scrollUp(params: number[]): void {\n let param = params[0] || 1;\n\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n\n while (param--) {\n buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);\n buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));\n }\n // this.maxRange();\n this._terminal.updateRange(buffer.scrollTop);\n this._terminal.updateRange(buffer.scrollBottom);\n }\n\n /**\n * CSI Ps T Scroll down Ps lines (default = 1) (SD).\n */\n public scrollDown(params: number[], collect?: string): void {\n if (params.length < 2 && !collect) {\n let param = params[0] || 1;\n\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n\n while (param--) {\n buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);\n buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));\n }\n // this.maxRange();\n this._terminal.updateRange(buffer.scrollTop);\n this._terminal.updateRange(buffer.scrollBottom);\n }\n }\n\n /**\n * CSI Ps X\n * Erase Ps Character(s) (default = 1) (ECH).\n */\n public eraseChars(params: number[]): void {\n this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells(\n this._terminal.buffer.x,\n this._terminal.buffer.x + (params[0] || 1),\n this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())\n );\n }\n\n /**\n * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n */\n public cursorBackwardTab(params: number[]): void {\n let param = params[0] || 1;\n\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n\n while (param--) {\n buffer.x = buffer.prevStop();\n }\n }\n\n /**\n * CSI Pm ` Character Position Absolute\n * [column] (default = [row,1]) (HPA).\n */\n public charPosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Pm a Character Position Relative\n * [columns] (default = [row,col+1]) (HPR)\n * reuse CSI Ps C ?\n */\n public hPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps b Repeat the preceding graphic character Ps times (REP).\n */\n public repeatPrecedingCharacter(params: number[]): void {\n // make buffer local for faster access\n const buffer = this._terminal.buffer;\n const line = buffer.lines.get(buffer.ybase + buffer.y);\n line.loadCell(buffer.x - 1, this._workCell);\n line.replaceCells(buffer.x,\n buffer.x + (params[0] || 1),\n (this._workCell.content !== undefined) ? this._workCell : buffer.getNullCell(DEFAULT_ATTR_DATA)\n );\n // FIXME: no updateRange here?\n }\n\n /**\n * CSI Ps c Send Device Attributes (Primary DA).\n * Ps = 0 or omitted -> request attributes from terminal. The\n * response depends on the decTerminalID resource setting.\n * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')\n * -> CSI ? 1 ; 0 c (``VT101 with No Options'')\n * -> CSI ? 6 c (``VT102'')\n * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')\n * The VT100-style response parameters do not mean anything by\n * themselves. VT220 parameters do, telling the host what fea-\n * tures the terminal supports:\n * Ps = 1 -> 132-columns.\n * Ps = 2 -> Printer.\n * Ps = 6 -> Selective erase.\n * Ps = 8 -> User-defined keys.\n * Ps = 9 -> National replacement character sets.\n * Ps = 1 5 -> Technical characters.\n * Ps = 2 2 -> ANSI color, e.g., VT525.\n * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).\n * CSI > Ps c\n * Send Device Attributes (Secondary DA).\n * Ps = 0 or omitted -> request the terminal's identification\n * code. The response depends on the decTerminalID resource set-\n * ting. It should apply only to VT220 and up, but xterm extends\n * this to VT100.\n * -> CSI > Pp ; Pv ; Pc c\n * where Pp denotes the terminal type\n * Pp = 0 -> ``VT100''.\n * Pp = 1 -> ``VT220''.\n * and Pv is the firmware version (for xterm, this was originally\n * the XFree86 patch number, starting with 95). In a DEC termi-\n * nal, Pc indicates the ROM cartridge registration number and is\n * always zero.\n * More information:\n * xterm/charproc.c - line 2012, for more information.\n * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n */\n public sendDeviceAttributes(params: number[], collect?: string): void {\n if (params[0] > 0) {\n return;\n }\n\n if (!collect) {\n if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {\n this._terminal.handler(C0.ESC + '[?1;2c');\n } else if (this._terminal.is('linux')) {\n this._terminal.handler(C0.ESC + '[?6c');\n }\n } else if (collect === '>') {\n // xterm and urxvt\n // seem to spit this\n // out around ~370 times (?).\n if (this._terminal.is('xterm')) {\n this._terminal.handler(C0.ESC + '[>0;276;0c');\n } else if (this._terminal.is('rxvt-unicode')) {\n this._terminal.handler(C0.ESC + '[>85;95;0c');\n } else if (this._terminal.is('linux')) {\n // not supported by linux console.\n // linux console echoes parameters.\n this._terminal.handler(params[0] + 'c');\n } else if (this._terminal.is('screen')) {\n this._terminal.handler(C0.ESC + '[>83;40003;0c');\n }\n }\n }\n\n /**\n * CSI Pm d Vertical Position Absolute (VPA)\n * [row] (default = [1,column])\n */\n public linePosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y = param - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n }\n\n /**\n * CSI Pm e Vertical Position Relative (VPR)\n * [rows] (default = [row+1,column])\n * reuse CSI Ps B ?\n */\n public vPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps ; Ps f\n * Horizontal and Vertical Position [row;column] (default =\n * [1,1]) (HVP).\n */\n public hVPosition(params: number[]): void {\n if (params[0] < 1) params[0] = 1;\n if (params[1] < 1) params[1] = 1;\n\n this._terminal.buffer.y = params[0] - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n\n this._terminal.buffer.x = params[1] - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps g Tab Clear (TBC).\n * Ps = 0 -> Clear Current Column (default).\n * Ps = 3 -> Clear All.\n * Potentially:\n * Ps = 2 -> Clear Stops on Line.\n * http://vt100.net/annarbor/aaa-ug/section6.html\n */\n public tabClear(params: number[]): void {\n const param = params[0];\n if (param <= 0) {\n delete this._terminal.buffer.tabs[this._terminal.buffer.x];\n } else if (param === 3) {\n this._terminal.buffer.tabs = {};\n }\n }\n\n /**\n * CSI Pm h Set Mode (SM).\n * Ps = 2 -> Keyboard Action Mode (AM).\n * Ps = 4 -> Insert Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Automatic Newline (LNM).\n * CSI ? Pm h\n * DEC Private Mode Set (DECSET).\n * Ps = 1 -> Application Cursor Keys (DECCKM).\n * Ps = 2 -> Designate USASCII for character sets G0-G3\n * (DECANM), and set VT100 mode.\n * Ps = 3 -> 132 Column Mode (DECCOLM).\n * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).\n * Ps = 5 -> Reverse Video (DECSCNM).\n * Ps = 6 -> Origin Mode (DECOM).\n * Ps = 7 -> Wraparound Mode (DECAWM).\n * Ps = 8 -> Auto-repeat Keys (DECARM).\n * Ps = 9 -> Send Mouse X & Y on button press. See the sec-\n * tion Mouse Tracking.\n * Ps = 1 0 -> Show toolbar (rxvt).\n * Ps = 1 2 -> Start Blinking Cursor (att610).\n * Ps = 1 8 -> Print form feed (DECPFF).\n * Ps = 1 9 -> Set print extent to full screen (DECPEX).\n * Ps = 2 5 -> Show Cursor (DECTCEM).\n * Ps = 3 0 -> Show scrollbar (rxvt).\n * Ps = 3 5 -> Enable font-shifting functions (rxvt).\n * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).\n * Ps = 4 0 -> Allow 80 -> 132 Mode.\n * Ps = 4 1 -> more(1) fix (see curses resource).\n * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-\n * RCM).\n * Ps = 4 4 -> Turn On Margin Bell.\n * Ps = 4 5 -> Reverse-wraparound Mode.\n * Ps = 4 6 -> Start Logging. This is normally disabled by a\n * compile-time option.\n * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 6 6 -> Application keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).\n * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).\n * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Interpret \"meta\" key, sets eighth bit.\n * (enables the eightBitInput resource).\n * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-\n * Lock keys. (This enables the numLock resource).\n * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This\n * enables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete\n * key.\n * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This\n * enables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Keep selection even if not highlighted.\n * (This enables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Enable Urgency window manager hint when\n * Control-G is received. (This enables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Enable raising of the window when Control-G\n * is received. (enables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate\n * Screen Buffer, clearing it first. (This may be disabled by\n * the titeInhibit resource). This combines the effects of the 1\n * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based\n * applications rather than the 4 7 mode.\n * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Set Sun function-key mode.\n * Ps = 1 0 5 2 -> Set HP function-key mode.\n * Ps = 1 0 5 3 -> Set SCO function-key mode.\n * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.\n * Ps = 2 0 0 4 -> Set bracketed paste mode.\n * Modes:\n * http: *vt100.net/docs/vt220-rm/chapter4.html\n */\n public setMode(params: number[], collect?: string): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.setMode([params[i]]);\n }\n\n return;\n }\n\n if (!collect) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = true;\n break;\n case 20:\n // this._t.convertEol = true;\n break;\n }\n } else if (collect === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = true;\n break;\n case 2:\n this._terminal.setgCharset(0, DEFAULT_CHARSET);\n this._terminal.setgCharset(1, DEFAULT_CHARSET);\n this._terminal.setgCharset(2, DEFAULT_CHARSET);\n this._terminal.setgCharset(3, DEFAULT_CHARSET);\n // set VT100 mode here\n break;\n case 3: // 132 col mode\n this._terminal.savedCols = this._terminal.cols;\n this._terminal.resize(132, this._terminal.rows);\n break;\n case 6:\n this._terminal.originMode = true;\n break;\n case 7:\n this._terminal.wraparoundMode = true;\n break;\n case 12:\n // this.cursorBlink = true;\n break;\n case 66:\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n break;\n case 9: // X10 Mouse\n // no release, no motion, no wheel, no modifiers.\n case 1000: // vt200 mouse\n // no motion.\n // no modifiers, except control on the wheel.\n case 1002: // button event mouse\n case 1003: // any event mouse\n // any event - sends motion events,\n // even if there is no button held down.\n\n // TODO: Why are params[0] compares nested within a switch for params[0]?\n\n this._terminal.x10Mouse = params[0] === 9;\n this._terminal.vt200Mouse = params[0] === 1000;\n this._terminal.normalMouse = params[0] > 1000;\n this._terminal.mouseEvents = true;\n if (this._terminal.element) {\n this._terminal.element.classList.add('enable-mouse-events');\n }\n if (this._terminal.selectionManager) {\n this._terminal.selectionManager.disable();\n }\n this._terminal.log('Binding to mouse events.');\n break;\n case 1004: // send focusin/focusout events\n // focusin: ^[[I\n // focusout: ^[[O\n this._terminal.sendFocus = true;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = true;\n // for wide terminals\n // simply encodes large values as utf8 characters\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = true;\n // for wide terminals\n // does not add 32 to fields\n // press: ^[[ Keyboard Action Mode (AM).\n * Ps = 4 -> Replace Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Normal Linefeed (LNM).\n * CSI ? Pm l\n * DEC Private Mode Reset (DECRST).\n * Ps = 1 -> Normal Cursor Keys (DECCKM).\n * Ps = 2 -> Designate VT52 mode (DECANM).\n * Ps = 3 -> 80 Column Mode (DECCOLM).\n * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).\n * Ps = 5 -> Normal Video (DECSCNM).\n * Ps = 6 -> Normal Cursor Mode (DECOM).\n * Ps = 7 -> No Wraparound Mode (DECAWM).\n * Ps = 8 -> No Auto-repeat Keys (DECARM).\n * Ps = 9 -> Don't send Mouse X & Y on button press.\n * Ps = 1 0 -> Hide toolbar (rxvt).\n * Ps = 1 2 -> Stop Blinking Cursor (att610).\n * Ps = 1 8 -> Don't print form feed (DECPFF).\n * Ps = 1 9 -> Limit print to scrolling region (DECPEX).\n * Ps = 2 5 -> Hide Cursor (DECTCEM).\n * Ps = 3 0 -> Don't show scrollbar (rxvt).\n * Ps = 3 5 -> Disable font-shifting functions (rxvt).\n * Ps = 4 0 -> Disallow 80 -> 132 Mode.\n * Ps = 4 1 -> No more(1) fix (see curses resource).\n * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-\n * NRCM).\n * Ps = 4 4 -> Turn Off Margin Bell.\n * Ps = 4 5 -> No Reverse-wraparound Mode.\n * Ps = 4 6 -> Stop Logging. (This is normally disabled by a\n * compile-time option).\n * Ps = 4 7 -> Use Normal Screen Buffer.\n * Ps = 6 6 -> Numeric keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends delete (DECBKM).\n * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output\n * (rxvt).\n * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Don't interpret \"meta\" key. (This disables\n * the eightBitInput resource).\n * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-\n * Lock keys. (This disables the numLock resource).\n * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.\n * (This disables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad\n * Delete key.\n * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.\n * (This disables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.\n * (This disables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Disable Urgency window manager hint when\n * Control-G is received. (This disables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Disable raising of the window when Control-\n * G is received. (This disables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen\n * first if in the Alternate Screen. (This may be disabled by\n * the titeInhibit resource).\n * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor\n * as in DECRC. (This may be disabled by the titeInhibit\n * resource). This combines the effects of the 1 0 4 7 and 1 0\n * 4 8 modes. Use this with terminfo-based applications rather\n * than the 4 7 mode.\n * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Reset Sun function-key mode.\n * Ps = 1 0 5 2 -> Reset HP function-key mode.\n * Ps = 1 0 5 3 -> Reset SCO function-key mode.\n * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.\n * Ps = 2 0 0 4 -> Reset bracketed paste mode.\n */\n public resetMode(params: number[], collect?: string): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.resetMode([params[i]]);\n }\n\n return;\n }\n\n if (!collect) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = false;\n break;\n case 20:\n // this._t.convertEol = false;\n break;\n }\n } else if (collect === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = false;\n break;\n case 3:\n if (this._terminal.cols === 132 && this._terminal.savedCols) {\n this._terminal.resize(this._terminal.savedCols, this._terminal.rows);\n }\n delete this._terminal.savedCols;\n break;\n case 6:\n this._terminal.originMode = false;\n break;\n case 7:\n this._terminal.wraparoundMode = false;\n break;\n case 12:\n // this.cursorBlink = false;\n break;\n case 66:\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n break;\n case 9: // X10 Mouse\n case 1000: // vt200 mouse\n case 1002: // button event mouse\n case 1003: // any event mouse\n this._terminal.x10Mouse = false;\n this._terminal.vt200Mouse = false;\n this._terminal.normalMouse = false;\n this._terminal.mouseEvents = false;\n if (this._terminal.element) {\n this._terminal.element.classList.remove('enable-mouse-events');\n }\n if (this._terminal.selectionManager) {\n this._terminal.selectionManager.enable();\n }\n break;\n case 1004: // send focusin/focusout events\n this._terminal.sendFocus = false;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = false;\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = false;\n break;\n case 1015: // urxvt ext mode mouse\n this._terminal.urxvtMouse = false;\n break;\n case 25: // hide cursor\n this._terminal.cursorHidden = true;\n break;\n case 1048: // alt screen cursor\n this.restoreCursor(params);\n break;\n case 1049: // alt screen buffer cursor\n // FALL-THROUGH\n case 47: // normal screen buffer\n case 1047: // normal screen buffer - clearing it first\n // Ensure the selection manager has the correct buffer\n this._terminal.buffers.activateNormalBuffer();\n if (params[0] === 1049) {\n this.restoreCursor(params);\n }\n this._terminal.refresh(0, this._terminal.rows - 1);\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n this._terminal.showCursor();\n break;\n case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)\n this._terminal.bracketedPasteMode = false;\n break;\n }\n }\n }\n\n /**\n * CSI Pm m Character Attributes (SGR).\n * Ps = 0 -> Normal (default).\n * Ps = 1 -> Bold.\n * Ps = 2 -> Faint, decreased intensity (ISO 6429).\n * Ps = 4 -> Underlined.\n * Ps = 5 -> Blink (appears as Bold).\n * Ps = 7 -> Inverse.\n * Ps = 8 -> Invisible, i.e., hidden (VT300).\n * Ps = 2 2 -> Normal (neither bold nor faint).\n * Ps = 2 4 -> Not underlined.\n * Ps = 2 5 -> Steady (not blinking).\n * Ps = 2 7 -> Positive (not inverse).\n * Ps = 2 8 -> Visible, i.e., not hidden (VT300).\n * Ps = 3 0 -> Set foreground color to Black.\n * Ps = 3 1 -> Set foreground color to Red.\n * Ps = 3 2 -> Set foreground color to Green.\n * Ps = 3 3 -> Set foreground color to Yellow.\n * Ps = 3 4 -> Set foreground color to Blue.\n * Ps = 3 5 -> Set foreground color to Magenta.\n * Ps = 3 6 -> Set foreground color to Cyan.\n * Ps = 3 7 -> Set foreground color to White.\n * Ps = 3 9 -> Set foreground color to default (original).\n * Ps = 4 0 -> Set background color to Black.\n * Ps = 4 1 -> Set background color to Red.\n * Ps = 4 2 -> Set background color to Green.\n * Ps = 4 3 -> Set background color to Yellow.\n * Ps = 4 4 -> Set background color to Blue.\n * Ps = 4 5 -> Set background color to Magenta.\n * Ps = 4 6 -> Set background color to Cyan.\n * Ps = 4 7 -> Set background color to White.\n * Ps = 4 9 -> Set background color to default (original).\n *\n * If 16-color support is compiled, the following apply. Assume\n * that xterm's resources are set so that the ISO color codes are\n * the first 8 of a set of 16. Then the aixterm colors are the\n * bright versions of the ISO colors:\n * Ps = 9 0 -> Set foreground color to Black.\n * Ps = 9 1 -> Set foreground color to Red.\n * Ps = 9 2 -> Set foreground color to Green.\n * Ps = 9 3 -> Set foreground color to Yellow.\n * Ps = 9 4 -> Set foreground color to Blue.\n * Ps = 9 5 -> Set foreground color to Magenta.\n * Ps = 9 6 -> Set foreground color to Cyan.\n * Ps = 9 7 -> Set foreground color to White.\n * Ps = 1 0 0 -> Set background color to Black.\n * Ps = 1 0 1 -> Set background color to Red.\n * Ps = 1 0 2 -> Set background color to Green.\n * Ps = 1 0 3 -> Set background color to Yellow.\n * Ps = 1 0 4 -> Set background color to Blue.\n * Ps = 1 0 5 -> Set background color to Magenta.\n * Ps = 1 0 6 -> Set background color to Cyan.\n * Ps = 1 0 7 -> Set background color to White.\n *\n * If xterm is compiled with the 16-color support disabled, it\n * supports the following, from rxvt:\n * Ps = 1 0 0 -> Set foreground and background color to\n * default.\n *\n * If 88- or 256-color support is compiled, the following apply.\n * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second\n * Ps.\n * Ps = 4 8 ; 5 ; Ps -> Set background color to the second\n * Ps.\n */\n public charAttributes(params: number[]): void {\n // Optimize a single SGR0.\n if (params.length === 1 && params[0] === 0) {\n this._terminal.curAttrData.fg = DEFAULT_ATTR_DATA.fg;\n this._terminal.curAttrData.bg = DEFAULT_ATTR_DATA.bg;\n return;\n }\n\n const l = params.length;\n let p;\n const attr = this._terminal.curAttrData;\n\n for (let i = 0; i < l; i++) {\n p = params[i];\n if (p >= 30 && p <= 37) {\n // fg color 8\n attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n attr.fg |= Attributes.CM_P16 | (p - 30);\n } else if (p >= 40 && p <= 47) {\n // bg color 8\n attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n attr.bg |= Attributes.CM_P16 | (p - 40);\n } else if (p >= 90 && p <= 97) {\n // fg color 16\n attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n attr.fg |= Attributes.CM_P16 | (p - 90) | 8;\n } else if (p >= 100 && p <= 107) {\n // bg color 16\n attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n attr.bg |= Attributes.CM_P16 | (p - 100) | 8;\n } else if (p === 0) {\n // default\n attr.fg = DEFAULT_ATTR_DATA.fg;\n attr.bg = DEFAULT_ATTR_DATA.bg;\n } else if (p === 1) {\n // bold text\n attr.fg |= FgFlags.BOLD;\n } else if (p === 3) {\n // italic text\n attr.bg |= BgFlags.ITALIC;\n } else if (p === 4) {\n // underlined text\n attr.fg |= FgFlags.UNDERLINE;\n } else if (p === 5) {\n // blink\n attr.fg |= FgFlags.BLINK;\n } else if (p === 7) {\n // inverse and positive\n // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n attr.fg |= FgFlags.INVERSE;\n } else if (p === 8) {\n // invisible\n attr.fg |= FgFlags.INVISIBLE;\n } else if (p === 2) {\n // dimmed text\n attr.bg |= BgFlags.DIM;\n } else if (p === 22) {\n // not bold nor faint\n attr.fg &= ~FgFlags.BOLD;\n attr.bg &= ~BgFlags.DIM;\n } else if (p === 23) {\n // not italic\n attr.bg &= ~BgFlags.ITALIC;\n } else if (p === 24) {\n // not underlined\n attr.fg &= ~FgFlags.UNDERLINE;\n } else if (p === 25) {\n // not blink\n attr.fg &= ~FgFlags.BLINK;\n } else if (p === 27) {\n // not inverse\n attr.fg &= ~FgFlags.INVERSE;\n } else if (p === 28) {\n // not invisible\n attr.fg &= ~FgFlags.INVISIBLE;\n } else if (p === 39) {\n // reset fg\n attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);\n } else if (p === 49) {\n // reset bg\n attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);\n } else if (p === 38) {\n // fg color 256 and RGB\n if (params[i + 1] === 2) {\n i += 2;\n attr.fg |= Attributes.CM_RGB;\n attr.fg &= ~Attributes.RGB_MASK;\n attr.fg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]);\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n attr.fg |= Attributes.CM_P256 | p;\n }\n } else if (p === 48) {\n // bg color 256 and RGB\n if (params[i + 1] === 2) {\n i += 2;\n attr.bg |= Attributes.CM_RGB;\n attr.bg &= ~Attributes.RGB_MASK;\n attr.bg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]);\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);\n attr.bg |= Attributes.CM_P256 | p;\n }\n } else if (p === 100) {\n // reset fg/bg\n attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);\n attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);\n attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);\n } else {\n this._terminal.error('Unknown SGR attribute: %d.', p);\n }\n }\n }\n\n /**\n * CSI Ps n Device Status Report (DSR).\n * Ps = 5 -> Status Report. Result (``OK'') is\n * CSI 0 n\n * Ps = 6 -> Report Cursor Position (CPR) [row;column].\n * Result is\n * CSI r ; c R\n * CSI ? Ps n\n * Device Status Report (DSR, DEC-specific).\n * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI\n * ? r ; c R (assumes page is zero).\n * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).\n * or CSI ? 1 1 n (not ready).\n * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)\n * or CSI ? 2 1 n (locked).\n * Ps = 2 6 -> Report Keyboard status as\n * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).\n * The last two parameters apply to VT400 & up, and denote key-\n * board ready and LK01 respectively.\n * Ps = 5 3 -> Report Locator status as\n * CSI ? 5 3 n Locator available, if compiled-in, or\n * CSI ? 5 0 n No Locator, if not.\n */\n public deviceStatus(params: number[], collect?: string): void {\n if (!collect) {\n switch (params[0]) {\n case 5:\n // status report\n this._onData.fire(`${C0.ESC}[0n`);\n break;\n case 6:\n // cursor position\n const y = this._terminal.buffer.y + 1;\n const x = this._terminal.buffer.x + 1;\n this._onData.fire(`${C0.ESC}[${y};${x}R`);\n break;\n }\n } else if (collect === '?') {\n // modern xterm doesnt seem to\n // respond to any of these except ?6, 6, and 5\n switch (params[0]) {\n case 6:\n // cursor position\n const y = this._terminal.buffer.y + 1;\n const x = this._terminal.buffer.x + 1;\n this._onData.fire(`${C0.ESC}[?${y};${x}R`);\n break;\n case 15:\n // no printer\n // this.handler(C0.ESC + '[?11n');\n break;\n case 25:\n // dont support user defined keys\n // this.handler(C0.ESC + '[?21n');\n break;\n case 26:\n // north american keyboard\n // this.handler(C0.ESC + '[?27;1;0;0n');\n break;\n case 53:\n // no dec locator/mouse\n // this.handler(C0.ESC + '[?50n');\n break;\n }\n }\n }\n\n /**\n * CSI ! p Soft terminal reset (DECSTR).\n * http://vt100.net/docs/vt220-rm/table4-10.html\n */\n public softReset(params: number[], collect?: string): void {\n if (collect === '!') {\n this._terminal.cursorHidden = false;\n this._terminal.insertMode = false;\n this._terminal.originMode = false;\n this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this._terminal.applicationKeypad = false; // ?\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n this._terminal.applicationCursor = false;\n this._terminal.buffer.scrollTop = 0;\n this._terminal.buffer.scrollBottom = this._terminal.rows - 1;\n this._terminal.curAttrData = DEFAULT_ATTR_DATA.clone();\n this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?\n this._terminal.charset = null;\n this._terminal.glevel = 0; // ??\n this._terminal.charsets = [null]; // ??\n }\n }\n\n /**\n * CSI Ps SP q Set cursor style (DECSCUSR, VT520).\n * Ps = 0 -> blinking block.\n * Ps = 1 -> blinking block (default).\n * Ps = 2 -> steady block.\n * Ps = 3 -> blinking underline.\n * Ps = 4 -> steady underline.\n * Ps = 5 -> blinking bar (xterm).\n * Ps = 6 -> steady bar (xterm).\n */\n public setCursorStyle(params?: number[], collect?: string): void {\n if (collect === ' ') {\n const param = params[0] < 1 ? 1 : params[0];\n switch (param) {\n case 1:\n case 2:\n this._terminal.setOption('cursorStyle', 'block');\n break;\n case 3:\n case 4:\n this._terminal.setOption('cursorStyle', 'underline');\n break;\n case 5:\n case 6:\n this._terminal.setOption('cursorStyle', 'bar');\n break;\n }\n const isBlinking = param % 2 === 1;\n this._terminal.setOption('cursorBlink', isBlinking);\n }\n }\n\n /**\n * CSI Ps ; Ps r\n * Set Scrolling Region [top;bottom] (default = full size of win-\n * dow) (DECSTBM).\n * CSI ? Pm r\n */\n public setScrollRegion(params: number[], collect?: string): void {\n if (collect) {\n return;\n }\n this._terminal.buffer.scrollTop = (params[0] || 1) - 1;\n this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y = 0;\n }\n\n\n /**\n * CSI s\n * ESC 7\n * Save cursor (ANSI.SYS).\n */\n public saveCursor(params: number[]): void {\n this._terminal.buffer.savedX = this._terminal.buffer.x;\n this._terminal.buffer.savedY = this._terminal.buffer.y;\n this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg;\n this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg;\n }\n\n\n /**\n * CSI u\n * ESC 8\n * Restore cursor (ANSI.SYS).\n */\n public restoreCursor(params: number[]): void {\n this._terminal.buffer.x = this._terminal.buffer.savedX || 0;\n this._terminal.buffer.y = this._terminal.buffer.savedY || 0;\n this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg;\n this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg;\n }\n\n\n /**\n * OSC 0; ST (set icon name + window title)\n * OSC 2; ST (set window title)\n * Proxy to set window title. Icon name is not supported.\n */\n public setTitle(data: string): void {\n this._terminal.handleTitle(data);\n }\n\n /**\n * ESC E\n * C1.NEL\n * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL)\n * Moves cursor to first position on next line.\n */\n public nextLine(): void {\n this._terminal.buffer.x = 0;\n this.index();\n }\n\n /**\n * ESC =\n * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html)\n * Enables the numeric keypad to send application sequences to the host.\n */\n public keypadApplicationMode(): void {\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n }\n\n /**\n * ESC >\n * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html)\n * Enables the keypad to send numeric characters to the host.\n */\n public keypadNumericMode(): void {\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n if (this._terminal.viewport) {\n this._terminal.viewport.syncScrollArea();\n }\n }\n\n /**\n * ESC % @\n * ESC % G\n * Select default character set. UTF-8 is not supported (string are unicode anyways)\n * therefore ESC % G does the same.\n */\n public selectDefaultCharset(): void {\n this._terminal.setgLevel(0);\n this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)\n }\n\n /**\n * ESC ( C\n * Designate G0 Character Set, VT100, ISO 2022.\n * ESC ) C\n * Designate G1 Character Set (ISO 2022, VT100).\n * ESC * C\n * Designate G2 Character Set (ISO 2022, VT220).\n * ESC + C\n * Designate G3 Character Set (ISO 2022, VT220).\n * ESC - C\n * Designate G1 Character Set (VT300).\n * ESC . C\n * Designate G2 Character Set (VT300).\n * ESC / C\n * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported?\n */\n public selectCharset(collectAndFlag: string): void {\n if (collectAndFlag.length !== 2) {\n this.selectDefaultCharset();\n return;\n }\n if (collectAndFlag[0] === '/') {\n return; // TODO: Is this supported?\n }\n this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET);\n return;\n }\n\n /**\n * ESC D\n * C1.IND\n * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html)\n * Moves the cursor down one line in the same column.\n */\n public index(): void {\n this._terminal.index(); // TODO: save to move from terminal?\n }\n\n /**\n * ESC H\n * C1.HTS\n * DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html)\n * Sets a horizontal tab stop at the column position indicated by\n * the value of the active column when the terminal receives an HTS.\n */\n public tabSet(): void {\n this._terminal.tabSet(); // TODO: save to move from terminal?\n }\n\n /**\n * ESC M\n * C1.RI\n * DEC mnemonic: HTS\n * Moves the cursor up one line in the same column. If the cursor is at the top margin,\n * the page scrolls down.\n */\n public reverseIndex(): void {\n this._terminal.reverseIndex(); // TODO: save to move from terminal?\n }\n\n /**\n * ESC c\n * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html)\n * Reset to initial state.\n */\n public reset(): void {\n this._parser.reset();\n this._terminal.reset(); // TODO: save to move from terminal?\n }\n\n /**\n * ESC n\n * ESC o\n * ESC |\n * ESC }\n * ESC ~\n * DEC mnemonic: LS (https://vt100.net/docs/vt510-rm/LS.html)\n * When you use a locking shift, the character set remains in GL or GR until\n * you use another locking shift. (partly supported)\n */\n public setgLevel(level: number): void {\n this._terminal.setgLevel(level); // TODO: save to move from terminal?\n }\n}\n","/**\n * Copyright (c) 2018 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';\nimport { IDisposable } from 'xterm';\nimport { Disposable } from './common/Lifecycle';\nimport { utf32ToString } from './core/input/TextDecoder';\n\ninterface IHandlerCollection {\n [key: string]: T[];\n}\n\ntype CsiHandler = (params: number[], collect: string) => boolean | void;\ntype OscHandler = (data: string) => boolean | void;\n\n/**\n * Returns an array filled with numbers between the low and high parameters (right exclusive).\n * @param low The low number.\n * @param high The high number.\n */\nfunction r(low: number, high: number): number[] {\n let c = high - low;\n const arr = new Array(c);\n while (c--) {\n arr[c] = --high;\n }\n return arr;\n}\n\n/**\n * Transition table for EscapeSequenceParser.\n * NOTE: data in the underlying table is packed like this:\n * currentState << 8 | characterCode --> action << 4 | nextState\n */\nexport class TransitionTable {\n public table: Uint8Array | number[];\n\n constructor(length: number) {\n this.table = (typeof Uint8Array === 'undefined')\n ? new Array(length)\n : new Uint8Array(length);\n }\n\n /**\n * Add a transition to the transition table.\n * @param code input character code\n * @param state current parser state\n * @param action parser action to be done\n * @param next next parser state\n */\n add(code: number, state: number, action: number | null, next: number | null): void {\n this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next);\n }\n\n /**\n * Add transitions for multiple input character codes.\n * @param codes input character code array\n * @param state current parser state\n * @param action parser action to be done\n * @param next next parser state\n */\n addMany(codes: number[], state: number, action: number | null, next: number | null): void {\n for (let i = 0; i < codes.length; i++) {\n this.add(codes[i], state, action, next);\n }\n }\n}\n\n\n/**\n * Default definitions for the VT500_TRANSITION_TABLE.\n */\nconst PRINTABLES = r(0x20, 0x7f);\nconst EXECUTABLES = r(0x00, 0x18);\nEXECUTABLES.push(0x19);\nEXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));\n// Pseudo-character placeholder for printable non-ascii characters.\nconst NON_ASCII_PRINTABLE = 0xA0;\n\n/**\n * VT500 compatible transition table.\n * Taken from https://vt100.net/emu/dec_ansi_parser.\n */\nexport const VT500_TRANSITION_TABLE = (function (): TransitionTable {\n const table: TransitionTable = new TransitionTable(4095);\n\n const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);\n let state: any;\n\n // table with default transition\n for (state in states) {\n // NOTE: table lookup is capped at 0xa0 in parse to keep the table small\n for (let code = 0; code <= NON_ASCII_PRINTABLE; ++code) {\n table.add(code, state, ParserAction.ERROR, ParserState.GROUND);\n }\n }\n // printables\n table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);\n // global anywhere rules\n for (state in states) {\n table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);\n table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);\n table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);\n table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator\n table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE); // ESC\n table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING); // OSC\n table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);\n table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY); // CSI\n table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY); // DCS\n }\n // rules for executables and 7f\n table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND);\n table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE);\n table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE);\n table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);\n table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY);\n table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY);\n table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM);\n table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);\n table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE);\n table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE);\n table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE);\n table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE);\n table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);\n // osc\n table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING);\n table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);\n table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);\n table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND);\n table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);\n // sos/pm/apc does nothing\n table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);\n table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);\n table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);\n table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);\n table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);\n // csi entries\n table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);\n table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);\n table.addMany(r(0x30, 0x3a), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);\n table.add(0x3b, ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);\n table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);\n table.addMany(r(0x30, 0x3a), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);\n table.add(0x3b, ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);\n table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);\n table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);\n table.add(0x3a, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);\n table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);\n table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);\n table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND);\n table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);\n // esc_intermediate\n table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);\n table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);\n table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);\n // dcs entry\n table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);\n table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);\n table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);\n table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);\n table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);\n table.add(0x3a, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n table.addMany(r(0x30, 0x3a), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);\n table.add(0x3b, ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);\n table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);\n table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);\n table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);\n table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);\n table.addMany(r(0x30, 0x3a), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);\n table.add(0x3b, ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);\n table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);\n table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);\n table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);\n table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);\n table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);\n table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);\n table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);\n table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);\n table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);\n table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);\n table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);\n table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);\n table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);\n table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);\n return table;\n})();\n\n/**\n * Dummy DCS handler as default fallback.\n */\nclass DcsDummy implements IDcsHandler {\n hook(collect: string, params: number[], flag: number): void { }\n put(data: Uint32Array, start: number, end: number): void { }\n unhook(): void { }\n}\n\n/**\n * EscapeSequenceParser.\n * This class implements the ANSI/DEC compatible parser described by\n * Paul Williams (https://vt100.net/emu/dec_ansi_parser).\n * To implement custom ANSI compliant escape sequences it is not needed to\n * alter this parser, instead consider registering a custom handler.\n * For non ANSI compliant sequences change the transition table with\n * the optional `transitions` contructor argument and\n * reimplement the `parse` method.\n * NOTE: The parameter element notation is currently not supported.\n * TODO: implement error recovery hook via error handler return values\n */\nexport class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {\n public initialState: number;\n public currentState: number;\n\n // buffers over several parse calls\n protected _osc: string;\n protected _params: number[];\n protected _collect: string;\n\n // handler lookup containers\n protected _printHandler: (data: Uint32Array, start: number, end: number) => void;\n protected _executeHandlers: any;\n protected _csiHandlers: IHandlerCollection;\n protected _escHandlers: any;\n protected _oscHandlers: IHandlerCollection;\n protected _dcsHandlers: any;\n protected _activeDcsHandler: IDcsHandler | null;\n protected _errorHandler: (state: IParsingState) => IParsingState;\n\n // fallback handlers\n protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;\n protected _executeHandlerFb: (code: number) => void;\n protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void;\n protected _escHandlerFb: (collect: string, flag: number) => void;\n protected _oscHandlerFb: (identifier: number, data: string) => void;\n protected _dcsHandlerFb: IDcsHandler;\n protected _errorHandlerFb: (state: IParsingState) => IParsingState;\n\n constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {\n super();\n\n this.initialState = ParserState.GROUND;\n this.currentState = this.initialState;\n this._osc = '';\n this._params = [0];\n this._collect = '';\n\n // set default fallback handlers and handler lookup containers\n this._printHandlerFb = (data, start, end): void => { };\n this._executeHandlerFb = (code: number): void => { };\n this._csiHandlerFb = (collect: string, params: number[], flag: number): void => { };\n this._escHandlerFb = (collect: string, flag: number): void => { };\n this._oscHandlerFb = (identifier: number, data: string): void => { };\n this._dcsHandlerFb = new DcsDummy();\n this._errorHandlerFb = (state: IParsingState): IParsingState => state;\n this._printHandler = this._printHandlerFb;\n this._executeHandlers = Object.create(null);\n this._csiHandlers = Object.create(null);\n this._escHandlers = Object.create(null);\n this._oscHandlers = Object.create(null);\n this._dcsHandlers = Object.create(null);\n this._activeDcsHandler = null;\n this._errorHandler = this._errorHandlerFb;\n\n // swallow 7bit ST (ESC+\\)\n this.setEscHandler('\\\\', () => {});\n }\n\n public dispose(): void {\n this._printHandlerFb = null;\n this._executeHandlerFb = null;\n this._csiHandlerFb = null;\n this._escHandlerFb = null;\n this._oscHandlerFb = null;\n this._dcsHandlerFb = null;\n this._errorHandlerFb = null;\n this._printHandler = null;\n this._executeHandlers = null;\n this._escHandlers = null;\n this._csiHandlers = null;\n this._oscHandlers = null;\n this._dcsHandlers = null;\n this._activeDcsHandler = null;\n this._errorHandler = null;\n }\n\n setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {\n this._printHandler = callback;\n }\n clearPrintHandler(): void {\n this._printHandler = this._printHandlerFb;\n }\n\n setExecuteHandler(flag: string, callback: () => void): void {\n this._executeHandlers[flag.charCodeAt(0)] = callback;\n }\n clearExecuteHandler(flag: string): void {\n if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];\n }\n setExecuteHandlerFallback(callback: (code: number) => void): void {\n this._executeHandlerFb = callback;\n }\n\n addCsiHandler(flag: string, callback: CsiHandler): IDisposable {\n const index = flag.charCodeAt(0);\n if (this._csiHandlers[index] === undefined) {\n this._csiHandlers[index] = [];\n }\n const handlerList = this._csiHandlers[index];\n handlerList.push(callback);\n return {\n dispose: () => {\n const handlerIndex = handlerList.indexOf(callback);\n if (handlerIndex !== -1) {\n handlerList.splice(handlerIndex, 1);\n }\n }\n };\n }\n setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {\n this._csiHandlers[flag.charCodeAt(0)] = [callback];\n }\n clearCsiHandler(flag: string): void {\n if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];\n }\n setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void {\n this._csiHandlerFb = callback;\n }\n\n setEscHandler(collectAndFlag: string, callback: () => void): void {\n this._escHandlers[collectAndFlag] = callback;\n }\n clearEscHandler(collectAndFlag: string): void {\n if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag];\n }\n setEscHandlerFallback(callback: (collect: string, flag: number) => void): void {\n this._escHandlerFb = callback;\n }\n\n addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {\n if (this._oscHandlers[ident] === undefined) {\n this._oscHandlers[ident] = [];\n }\n const handlerList = this._oscHandlers[ident];\n handlerList.push(callback);\n return {\n dispose: () => {\n const handlerIndex = handlerList.indexOf(callback);\n if (handlerIndex !== -1) {\n handlerList.splice(handlerIndex, 1);\n }\n }\n };\n }\n setOscHandler(ident: number, callback: (data: string) => void): void {\n this._oscHandlers[ident] = [callback];\n }\n clearOscHandler(ident: number): void {\n if (this._oscHandlers[ident]) delete this._oscHandlers[ident];\n }\n setOscHandlerFallback(callback: (identifier: number, data: string) => void): void {\n this._oscHandlerFb = callback;\n }\n\n setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {\n this._dcsHandlers[collectAndFlag] = handler;\n }\n clearDcsHandler(collectAndFlag: string): void {\n if (this._dcsHandlers[collectAndFlag]) delete this._dcsHandlers[collectAndFlag];\n }\n setDcsHandlerFallback(handler: IDcsHandler): void {\n this._dcsHandlerFb = handler;\n }\n\n setErrorHandler(callback: (state: IParsingState) => IParsingState): void {\n this._errorHandler = callback;\n }\n clearErrorHandler(): void {\n this._errorHandler = this._errorHandlerFb;\n }\n\n reset(): void {\n this.currentState = this.initialState;\n this._osc = '';\n this._params = [0];\n this._collect = '';\n this._activeDcsHandler = null;\n }\n\n parse(data: Uint32Array, length: number): void {\n let code = 0;\n let transition = 0;\n let error = false;\n let currentState = this.currentState;\n let print = -1;\n let dcs = -1;\n let osc = this._osc;\n let collect = this._collect;\n let params = this._params;\n const table: Uint8Array | number[] = this.TRANSITIONS.table;\n let dcsHandler: IDcsHandler | null = this._activeDcsHandler;\n let callback: Function | null = null;\n\n // process input string\n for (let i = 0; i < length; ++i) {\n code = data[i];\n\n // shortcut for most chars (print action)\n if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {\n print = (~print) ? print : i;\n do i++;\n while (i < length && data[i] > 0x1f && data[i] < 0x80);\n i--;\n continue;\n }\n\n // shortcut for CSI params\n if (currentState === ParserState.CSI_PARAM && (code > 0x2f && code < 0x39)) {\n params[params.length - 1] = params[params.length - 1] * 10 + code - 48;\n continue;\n }\n\n // normal transition & action lookup\n transition = table[currentState << 8 | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];\n switch (transition >> 4) {\n case ParserAction.PRINT:\n print = (~print) ? print : i;\n break;\n case ParserAction.EXECUTE:\n if (~print) {\n this._printHandler(data, print, i);\n print = -1;\n }\n callback = this._executeHandlers[code];\n if (callback) callback();\n else this._executeHandlerFb(code);\n break;\n case ParserAction.IGNORE:\n // handle leftover print or dcs chars\n if (~print) {\n this._printHandler(data, print, i);\n print = -1;\n } else if (~dcs) {\n dcsHandler.put(data, dcs, i);\n dcs = -1;\n }\n break;\n case ParserAction.ERROR:\n // chars higher than 0x9f are handled by this action\n // to keep the transition table small\n if (code > 0x9f) {\n switch (currentState) {\n case ParserState.GROUND:\n print = (~print) ? print : i;\n break;\n case ParserState.CSI_IGNORE:\n transition |= ParserState.CSI_IGNORE;\n break;\n case ParserState.DCS_IGNORE:\n transition |= ParserState.DCS_IGNORE;\n break;\n case ParserState.DCS_PASSTHROUGH:\n dcs = (~dcs) ? dcs : i;\n transition |= ParserState.DCS_PASSTHROUGH;\n break;\n default:\n error = true;\n }\n } else {\n error = true;\n }\n // if we end up here a real error happened\n if (error) {\n const inject: IParsingState = this._errorHandler(\n {\n position: i,\n code,\n currentState,\n print,\n dcs,\n osc,\n collect,\n params,\n abort: false\n });\n if (inject.abort) return;\n // TODO: inject return values\n error = false;\n }\n break;\n case ParserAction.CSI_DISPATCH:\n // Trigger CSI Handler\n const handlers = this._csiHandlers[code];\n let j = handlers ? handlers.length - 1 : -1;\n for (; j >= 0; j--) {\n // undefined or true means success and to stop bubbling\n if (handlers[j](params, collect) !== false) {\n break;\n }\n }\n if (j < 0) {\n this._csiHandlerFb(collect, params, code);\n }\n break;\n case ParserAction.PARAM:\n if (code === 0x3b) params.push(0);\n else params[params.length - 1] = params[params.length - 1] * 10 + code - 48;\n break;\n case ParserAction.COLLECT:\n collect += String.fromCharCode(code);\n break;\n case ParserAction.ESC_DISPATCH:\n callback = this._escHandlers[collect + String.fromCharCode(code)];\n if (callback) callback(collect, code);\n else this._escHandlerFb(collect, code);\n break;\n case ParserAction.CLEAR:\n if (~print) {\n this._printHandler(data, print, i);\n print = -1;\n }\n osc = '';\n params = [0];\n collect = '';\n dcs = -1;\n break;\n case ParserAction.DCS_HOOK:\n dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)];\n if (!dcsHandler) dcsHandler = this._dcsHandlerFb;\n dcsHandler.hook(collect, params, code);\n break;\n case ParserAction.DCS_PUT:\n dcs = (~dcs) ? dcs : i;\n break;\n case ParserAction.DCS_UNHOOK:\n if (dcsHandler) {\n if (~dcs) dcsHandler.put(data, dcs, i);\n dcsHandler.unhook();\n dcsHandler = null;\n }\n if (code === 0x1b) transition |= ParserState.ESCAPE;\n osc = '';\n params = [0];\n collect = '';\n dcs = -1;\n break;\n case ParserAction.OSC_START:\n if (~print) {\n this._printHandler(data, print, i);\n print = -1;\n }\n osc = '';\n break;\n case ParserAction.OSC_PUT:\n for (let j = i + 1; ; j++) {\n if (j >= length\n || (code = data[j]) < 0x20\n || (code > 0x7f && code <= 0x9f)) {\n osc += utf32ToString(data, i, j);\n i = j - 1;\n break;\n }\n }\n break;\n case ParserAction.OSC_END:\n if (osc && code !== 0x18 && code !== 0x1a) {\n // NOTE: OSC subparsing is not part of the original parser\n // we do basic identifier parsing here to offer a jump table for OSC as well\n const idx = osc.indexOf(';');\n if (idx === -1) {\n this._oscHandlerFb(-1, osc); // this is an error (malformed OSC)\n } else {\n // Note: NaN is not handled here\n // either catch it with the fallback handler\n // or with an explicit NaN OSC handler\n const identifier = parseInt(osc.substring(0, idx));\n const content = osc.substring(idx + 1);\n // Trigger OSC Handler\n const handlers = this._oscHandlers[identifier];\n let j = handlers ? handlers.length - 1 : -1;\n for (; j >= 0; j--) {\n // undefined or true means success and to stop bubbling\n if (handlers[j](content) !== false) {\n break;\n }\n }\n if (j < 0) {\n this._oscHandlerFb(identifier, content);\n }\n }\n }\n if (code === 0x1b) transition |= ParserState.ESCAPE;\n osc = '';\n params = [0];\n collect = '';\n dcs = -1;\n break;\n }\n currentState = transition & 15;\n }\n\n // push leftover pushable buffers to terminal\n if (currentState === ParserState.GROUND && ~print) {\n this._printHandler(data, print, length);\n } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) {\n dcsHandler.put(data, dcs, length);\n }\n\n // save non pushable buffers\n this._osc = osc;\n this._collect = collect;\n this._params = params;\n\n // save active dcs handler reference\n this._activeDcsHandler = dcsHandler;\n\n // save state\n this.currentState = currentState;\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal } from './Types';\n\ninterface IPosition {\n start: number;\n end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n /**\n * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n * IME. This variable determines whether the compositionText should be displayed on the UI.\n */\n private _isComposing: boolean;\n\n /**\n * The position within the input textarea's value of the current composition.\n */\n private _compositionPosition: IPosition;\n\n /**\n * Whether a composition is in the process of being sent, setting this to false will cancel any\n * in-progress composition.\n */\n private _isSendingComposition: boolean;\n\n /**\n * Creates a new CompositionHelper.\n * @param _textarea The textarea that xterm uses for input.\n * @param _compositionView The element to display the in-progress composition in.\n * @param _terminal The Terminal to forward the finished composition to.\n */\n constructor(\n private _textarea: HTMLTextAreaElement,\n private _compositionView: HTMLElement,\n private _terminal: ITerminal\n ) {\n this._isComposing = false;\n this._isSendingComposition = false;\n this._compositionPosition = { start: null, end: null };\n }\n\n /**\n * Handles the compositionstart event, activating the composition view.\n */\n public compositionstart(): void {\n this._isComposing = true;\n this._compositionPosition.start = this._textarea.value.length;\n this._compositionView.textContent = '';\n this._compositionView.classList.add('active');\n }\n\n /**\n * Handles the compositionupdate event, updating the composition view.\n * @param ev The event.\n */\n public compositionupdate(ev: CompositionEvent): void {\n this._compositionView.textContent = ev.data;\n this.updateCompositionElements();\n setTimeout(() => {\n this._compositionPosition.end = this._textarea.value.length;\n }, 0);\n }\n\n /**\n * Handles the compositionend event, hiding the composition view and sending the composition to\n * the handler.\n */\n public compositionend(): void {\n this._finalizeComposition(true);\n }\n\n /**\n * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n * @param ev The keydown event.\n * @return Whether the Terminal should continue processing the keydown event.\n */\n public keydown(ev: KeyboardEvent): boolean {\n if (this._isComposing || this._isSendingComposition) {\n if (ev.keyCode === 229) {\n // Continue composing if the keyCode is the \"composition character\"\n return false;\n } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n // Continue composing if the keyCode is a modifier key\n return false;\n }\n // Finish composition immediately. This is mainly here for the case where enter is\n // pressed and the handler needs to be triggered before the command is executed.\n this._finalizeComposition(false);\n }\n\n if (ev.keyCode === 229) {\n // If the \"composition character\" is used but gets to this point it means a non-composition\n // character (eg. numbers and punctuation) was pressed when the IME was active.\n this._handleAnyTextareaChanges();\n return false;\n }\n\n return true;\n }\n\n /**\n * Finalizes the composition, resuming regular input actions. This is called when a composition\n * is ending.\n * @param waitForPropagation Whether to wait for events to propagate before sending\n * the input. This should be false if a non-composition keystroke is entered before the\n * compositionend event is triggered, such as enter, so that the composition is sent before\n * the command is executed.\n */\n private _finalizeComposition(waitForPropagation: boolean): void {\n this._compositionView.classList.remove('active');\n this._isComposing = false;\n this._clearTextareaPosition();\n\n if (!waitForPropagation) {\n // Cancel any delayed composition send requests and send the input immediately.\n this._isSendingComposition = false;\n const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);\n this._terminal.handler(input);\n } else {\n // Make a deep copy of the composition position here as a new compositionstart event may\n // fire before the setTimeout executes.\n const currentCompositionPosition = {\n start: this._compositionPosition.start,\n end: this._compositionPosition.end\n };\n\n // Since composition* events happen before the changes take place in the textarea on most\n // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n // complete. This ensures the correct character is retrieved.\n // This solution was used because:\n // - The compositionend event's data property is unreliable, at least on Chromium\n // - The last compositionupdate event's data property does not always accurately describe\n // the character, a counter example being Korean where an ending consonsant can move to\n // the following character if the following input is a vowel.\n this._isSendingComposition = true;\n setTimeout(() => {\n // Ensure that the input has not already been sent\n if (this._isSendingComposition) {\n this._isSendingComposition = false;\n let input;\n if (this._isComposing) {\n // Use the end position to get the string if a new composition has started.\n input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);\n } else {\n // Don't use the end position here in order to pick up any characters after the\n // composition has finished, for example when typing a non-composition character\n // (eg. 2) after a composition character.\n input = this._textarea.value.substring(currentCompositionPosition.start);\n }\n this._terminal.handler(input);\n }\n }, 0);\n }\n }\n\n /**\n * Apply any changes made to the textarea after the current event chain is allowed to complete.\n * This should be called when not currently composing but a keydown event with the \"composition\n * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n * IME is active.\n */\n private _handleAnyTextareaChanges(): void {\n const oldValue = this._textarea.value;\n setTimeout(() => {\n // Ignore if a composition has started since the timeout\n if (!this._isComposing) {\n const newValue = this._textarea.value;\n const diff = newValue.replace(oldValue, '');\n if (diff.length > 0) {\n this._terminal.handler(diff);\n }\n }\n }, 0);\n }\n\n /**\n * Positions the composition view on top of the cursor and the textarea just below it (so the\n * IME helper dialog is positioned correctly).\n * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n * necessary as the IME events across browsers are not consistently triggered.\n */\n public updateCompositionElements(dontRecurse?: boolean): void {\n if (!this._isComposing) {\n return;\n }\n\n if (this._terminal.buffer.isCursorInViewport) {\n const cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight);\n const cursorTop = this._terminal.buffer.y * cellHeight;\n const cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width;\n\n this._compositionView.style.left = cursorLeft + 'px';\n this._compositionView.style.top = cursorTop + 'px';\n this._compositionView.style.height = cellHeight + 'px';\n this._compositionView.style.lineHeight = cellHeight + 'px';\n this._compositionView.style.fontFamily = this._terminal.options.fontFamily;\n this._compositionView.style.fontSize = this._terminal.options.fontSize + 'px';\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n const compositionViewBounds = this._compositionView.getBoundingClientRect();\n this._textarea.style.left = cursorLeft + 'px';\n this._textarea.style.top = cursorTop + 'px';\n this._textarea.style.width = compositionViewBounds.width + 'px';\n this._textarea.style.height = compositionViewBounds.height + 'px';\n this._textarea.style.lineHeight = compositionViewBounds.height + 'px';\n }\n\n if (!dontRecurse) {\n setTimeout(() => this.updateCompositionElements(true), 0);\n }\n }\n\n /**\n * Clears the textarea's position so that the cursor does not blink on IE.\n * @private\n */\n private _clearTextareaPosition(): void {\n this._textarea.style.left = '';\n this._textarea.style.top = '';\n }\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager } from './Types';\n\ninterface IWindow extends Window {\n clipboardData?: {\n getData(format: string): string;\n setData(format: string, data: string): void;\n };\n}\n\ndeclare var window: IWindow;\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string): string {\n return text.replace(/\\r?\\n/g, '\\r');\n}\n\n/**\n * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste\n * @param text The pasted text to bracket\n */\nexport function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {\n if (bracketedPasteMode) {\n return '\\x1b[200~' + text + '\\x1b[201~';\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager): void {\n if (term.browser.isMSIE) {\n window.clipboardData.setData('Text', selectionManager.selectionText);\n } else {\n ev.clipboardData.setData('text/plain', selectionManager.selectionText);\n }\n\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n * @param ev The original paste event to be handled\n * @param term The terminal on which to apply the handled paste event\n */\nexport function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {\n ev.stopPropagation();\n\n let text: string;\n\n const dispatchPaste = function(text: string): void {\n text = prepareTextForTerminal(text);\n text = bracketTextForPaste(text, term.bracketedPasteMode);\n term.handler(text);\n term.textarea.value = '';\n term.emit('paste', text);\n term.cancel(ev);\n };\n\n if (term.browser.isMSIE) {\n if (window.clipboardData) {\n text = window.clipboardData.getData('Text');\n dispatchPaste(text);\n }\n } else {\n if (ev.clipboardData) {\n text = ev.clipboardData.getData('text/plain');\n dispatchPaste(text);\n }\n }\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): void {\n\n // Calculate textarea position relative to the screen element\n const pos = term.screenElement.getBoundingClientRect();\n const left = ev.clientX - pos.left - 10;\n const top = ev.clientY - pos.top - 10;\n\n // Bring textarea at the cursor position\n term.textarea.style.position = 'absolute';\n term.textarea.style.width = '20px';\n term.textarea.style.height = '20px';\n term.textarea.style.left = `${left}px`;\n term.textarea.style.top = `${top}px`;\n term.textarea.style.zIndex = '1000';\n\n term.textarea.focus();\n\n // Reset the terminal textarea's styling\n // Timeout needs to be long enough for click event to be handled.\n setTimeout(() => {\n term.textarea.style.position = null;\n term.textarea.style.width = null;\n term.textarea.style.height = null;\n term.textarea.style.left = null;\n term.textarea.style.top = null;\n term.textarea.style.zIndex = null;\n }, 200);\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n * @param selectionManager The terminal's selection manager.\n * @param shouldSelectWord If true and there is no selection the current word will be selected\n */\nexport function rightClickHandler(ev: MouseEvent, term: ITerminal, selectionManager: ISelectionManager, shouldSelectWord: boolean): void {\n moveTextAreaUnderMouseCursor(ev, term);\n\n if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) {\n selectionManager.selectWordAtCursor(ev);\n }\n\n // Get textarea ready to copy from the context menu\n term.textarea.value = selectionManager.selectionText;\n term.textarea.select();\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { fill } from './common/TypedArrayUtils';\n\nexport const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {\n // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n // combining characters\n const COMBINING_BMP = [\n [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]\n ];\n const COMBINING_HIGH = [\n [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n [0xE0100, 0xE01EF]\n ];\n // binary search\n function bisearch(ucs: number, data: number[][]): boolean {\n let min = 0;\n let max = data.length - 1;\n let mid;\n if (ucs < data[0][0] || ucs > data[max][1]) {\n return false;\n }\n while (max >= min) {\n mid = (min + max) >> 1;\n if (ucs > data[mid][1]) {\n min = mid + 1;\n } else if (ucs < data[mid][0]) {\n max = mid - 1;\n } else {\n return true;\n }\n }\n return false;\n }\n function wcwidthHigh(ucs: number): 0 | 1 | 2 {\n if (bisearch(ucs, COMBINING_HIGH)) {\n return 0;\n }\n if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n return 2;\n }\n return 1;\n }\n const control = opts.control | 0;\n\n // create lookup table for BMP plane\n const table = new Uint8Array(65536);\n fill(table, 1);\n table[0] = opts.nul;\n // control chars\n fill(table, opts.control, 1, 32);\n fill(table, opts.control, 0x7f, 0xa0);\n\n // apply wide char rules first\n // wide chars\n fill(table, 2, 0x1100, 0x1160);\n table[0x2329] = 2;\n table[0x232a] = 2;\n fill(table, 2, 0x2e80, 0xa4d0);\n table[0x303f] = 1; // wrongly in last line\n\n fill(table, 2, 0xac00, 0xd7a4);\n fill(table, 2, 0xf900, 0xfb00);\n fill(table, 2, 0xfe10, 0xfe1a);\n fill(table, 2, 0xfe30, 0xfe70);\n fill(table, 2, 0xff00, 0xff61);\n fill(table, 2, 0xffe0, 0xffe7);\n\n // apply combining last to ensure we overwrite\n // wrongly wide set chars:\n // the original algo evals combining first and falls\n // through to wide check so we simply do here the opposite\n // combining 0\n for (let r = 0; r < COMBINING_BMP.length; ++r) {\n fill(table, 0, COMBINING_BMP[r][0], COMBINING_BMP[r][1] + 1);\n }\n\n return function (num: number): number {\n if (num < 32) {\n return control | 0;\n }\n if (num < 127) {\n return 1;\n }\n if (num < 65536) {\n return table[num];\n }\n // do a full search for high codepoints\n return wcwidthHigh(num);\n };\n})({nul: 0, control: 0}); // configurable options\n\n/**\n * Get the terminal cell width for a string.\n */\nexport function getStringCellWidth(s: string): number {\n let result = 0;\n const length = s.length;\n for (let i = 0; i < length; ++i) {\n let code = s.charCodeAt(i);\n // surrogate pair first\n if (0xD800 <= code && code <= 0xDBFF) {\n if (++i >= length) {\n // this should not happen with strings retrieved from\n // Buffer.translateToString as it converts from UTF-32\n // and therefore always should contain the second part\n // for any other string we still have to handle it somehow:\n // simply treat the lonely surrogate first as a single char (UCS-2 behavior)\n return result + wcwidth(code);\n }\n const second = s.charCodeAt(i);\n // convert surrogate pair to high codepoint only for valid second part (UTF-16)\n // otherwise treat them independently (UCS-2 behavior)\n if (0xDC00 <= second && second <= 0xDFFF) {\n code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;\n } else {\n result += wcwidth(second);\n }\n }\n result += wcwidth(code);\n }\n return result;\n}\n","/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ICharMeasure, ITerminalOptions } from './Types';\nimport { EventEmitter2, IEvent } from './common/EventEmitter2';\n\n/**\n * Utility class that measures the size of a character. Measurements are done in\n * the DOM rather than with a canvas context because support for extracting the\n * height of characters is patchy across browsers.\n */\nexport class CharMeasure implements ICharMeasure {\n private _document: Document;\n private _parentElement: HTMLElement;\n private _measureElement: HTMLElement;\n private _width: number;\n private _height: number;\n\n private _onCharSizeChanged = new EventEmitter2();\n public get onCharSizeChanged(): IEvent { return this._onCharSizeChanged.event; }\n\n constructor(document: Document, parentElement: HTMLElement) {\n this._document = document;\n this._parentElement = parentElement;\n this._measureElement = this._document.createElement('span');\n this._measureElement.classList.add('xterm-char-measure-element');\n this._measureElement.textContent = 'W';\n this._measureElement.setAttribute('aria-hidden', 'true');\n this._parentElement.appendChild(this._measureElement);\n }\n\n public get width(): number {\n return this._width;\n }\n\n public get height(): number {\n return this._height;\n }\n\n public measure(options: ITerminalOptions): void {\n this._measureElement.style.fontFamily = options.fontFamily;\n this._measureElement.style.fontSize = `${options.fontSize}px`;\n const geometry = this._measureElement.getBoundingClientRect();\n // The element is likely currently display:none, we should retain the\n // previous value.\n if (geometry.width === 0 || geometry.height === 0) {\n return;\n }\n const adjustedHeight = Math.ceil(geometry.height);\n if (this._width !== geometry.width || this._height !== adjustedHeight) {\n this._width = geometry.width;\n this._height = adjustedHeight;\n this._onCharSizeChanged.fire();\n }\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ITerminal, IBufferSet, IBuffer } from './Types';\nimport { IAttributeData } from './core/Types';\nimport { Buffer } from './Buffer';\nimport { EventEmitter2, IEvent } from './common/EventEmitter2';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet implements IBufferSet {\n private _normal: Buffer;\n private _alt: Buffer;\n private _activeBuffer: Buffer;\n\n\n private _onBufferActivate = new EventEmitter2<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>();\n public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }\n\n /**\n * Create a new BufferSet for the given terminal.\n * @param _terminal - The terminal the BufferSet will belong to\n */\n constructor(private _terminal: ITerminal) {\n this._normal = new Buffer(this._terminal, true);\n this._normal.fillViewportRows();\n\n // The alt buffer should never have scrollback.\n // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer\n this._alt = new Buffer(this._terminal, false);\n this._activeBuffer = this._normal;\n\n this.setupTabStops();\n }\n\n /**\n * Returns the alt Buffer of the BufferSet\n */\n public get alt(): Buffer {\n return this._alt;\n }\n\n /**\n * Returns the normal Buffer of the BufferSet\n */\n public get active(): Buffer {\n return this._activeBuffer;\n }\n\n /**\n * Returns the currently active Buffer of the BufferSet\n */\n public get normal(): Buffer {\n return this._normal;\n }\n\n /**\n * Sets the normal Buffer of the BufferSet as its currently active Buffer\n */\n public activateNormalBuffer(): void {\n if (this._activeBuffer === this._normal) {\n return;\n }\n this._normal.x = this._alt.x;\n this._normal.y = this._alt.y;\n // The alt buffer should always be cleared when we switch to the normal\n // buffer. This frees up memory since the alt buffer should always be new\n // when activated.\n this._alt.clear();\n this._activeBuffer = this._normal;\n this._onBufferActivate.fire({\n activeBuffer: this._normal,\n inactiveBuffer: this._alt\n });\n }\n\n /**\n * Sets the alt Buffer of the BufferSet as its currently active Buffer\n */\n public activateAltBuffer(fillAttr?: IAttributeData): void {\n if (this._activeBuffer === this._alt) {\n return;\n }\n // Since the alt buffer is always cleared when the normal buffer is\n // activated, we want to fill it when switching to it.\n this._alt.fillViewportRows(fillAttr);\n this._alt.x = this._normal.x;\n this._alt.y = this._normal.y;\n this._activeBuffer = this._alt;\n this._onBufferActivate.fire({\n activeBuffer: this._alt,\n inactiveBuffer: this._normal\n });\n }\n\n /**\n * Resizes both normal and alt buffers, adjusting their data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n this._normal.resize(newCols, newRows);\n this._alt.resize(newCols, newRows);\n }\n\n /**\n * Setup the tab stops.\n * @param i The index to start setting up tab stops from.\n */\n public setupTabStops(i?: number): void {\n this._normal.setupTabStops(i);\n this._alt.setupTabStops(i);\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { CircularList, IInsertEvent } from './common/CircularList';\nimport { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types';\nimport { IBufferLine, ICellData, IAttributeData } from './core/Types';\nimport { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';\nimport { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './core/buffer/BufferReflow';\nimport { Marker } from './core/buffer/Marker';\n\nexport const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n * - text content of this particular buffer\n * - cursor position\n * - scroll position\n */\nexport class Buffer implements IBuffer {\n public lines: CircularList;\n public ydisp: number;\n public ybase: number;\n public y: number;\n public x: number;\n public scrollBottom: number;\n public scrollTop: number;\n public tabs: any;\n public savedY: number;\n public savedX: number;\n public savedCurAttrData = DEFAULT_ATTR_DATA.clone();\n public markers: Marker[] = [];\n private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);\n private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);\n private _cols: number;\n private _rows: number;\n\n /**\n * Create a new Buffer.\n * @param _terminal The terminal the Buffer will belong to.\n * @param _hasScrollback Whether the buffer should respect the scrollback of\n * the terminal.\n */\n constructor(\n private _terminal: ITerminal,\n private _hasScrollback: boolean\n ) {\n this._cols = this._terminal.cols;\n this._rows = this._terminal.rows;\n this.clear();\n }\n\n public getNullCell(attr?: IAttributeData): ICellData {\n if (attr) {\n this._nullCell.fg = attr.fg;\n this._nullCell.bg = attr.bg;\n } else {\n this._nullCell.fg = 0;\n this._nullCell.bg = 0;\n }\n return this._nullCell;\n }\n\n public getWhitespaceCell(attr?: IAttributeData): ICellData {\n if (attr) {\n this._whitespaceCell.fg = attr.fg;\n this._whitespaceCell.bg = attr.bg;\n } else {\n this._whitespaceCell.fg = 0;\n this._whitespaceCell.bg = 0;\n }\n return this._whitespaceCell;\n }\n\n public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {\n return new BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped);\n }\n\n public get hasScrollback(): boolean {\n return this._hasScrollback && this.lines.maxLength > this._rows;\n }\n\n public get isCursorInViewport(): boolean {\n const absoluteY = this.ybase + this.y;\n const relativeY = absoluteY - this.ydisp;\n return (relativeY >= 0 && relativeY < this._rows);\n }\n\n /**\n * Gets the correct buffer length based on the rows provided, the terminal's\n * scrollback and whether this buffer is flagged to have scrollback or not.\n * @param rows The terminal rows to use in the calculation.\n */\n private _getCorrectBufferLength(rows: number): number {\n if (!this._hasScrollback) {\n return rows;\n }\n\n const correctBufferLength = rows + this._terminal.options.scrollback;\n\n return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength;\n }\n\n /**\n * Fills the buffer's viewport with blank lines.\n */\n public fillViewportRows(fillAttr?: IAttributeData): void {\n if (this.lines.length === 0) {\n if (fillAttr === undefined) {\n fillAttr = DEFAULT_ATTR_DATA;\n }\n let i = this._rows;\n while (i--) {\n this.lines.push(this.getBlankLine(fillAttr));\n }\n }\n }\n\n /**\n * Clears the buffer to it's initial state, discarding all previous data.\n */\n public clear(): void {\n this.ydisp = 0;\n this.ybase = 0;\n this.y = 0;\n this.x = 0;\n this.lines = new CircularList(this._getCorrectBufferLength(this._rows));\n this.scrollTop = 0;\n this.scrollBottom = this._rows - 1;\n this.setupTabStops();\n }\n\n /**\n * Resizes the buffer, adjusting its data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n // store reference to null cell with default attrs\n const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);\n\n // Increase max length if needed before adjustments to allow space to fill\n // as required.\n const newMaxLength = this._getCorrectBufferLength(newRows);\n if (newMaxLength > this.lines.maxLength) {\n this.lines.maxLength = newMaxLength;\n }\n\n // The following adjustments should only happen if the buffer has been\n // initialized/filled.\n if (this.lines.length > 0) {\n // Deal with columns increasing (reducing needs to happen after reflow)\n if (this._cols < newCols) {\n for (let i = 0; i < this.lines.length; i++) {\n this.lines.get(i).resize(newCols, nullCell);\n }\n }\n\n // Resize rows in both directions as needed\n let addToY = 0;\n if (this._rows < newRows) {\n for (let y = this._rows; y < newRows; y++) {\n if (this.lines.length < newRows + this.ybase) {\n if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {\n // There is room above the buffer and there are no empty elements below the line,\n // scroll up\n this.ybase--;\n addToY++;\n if (this.ydisp > 0) {\n // Viewport is at the top of the buffer, must increase downwards\n this.ydisp--;\n }\n } else {\n // Add a blank line if there is no buffer left at the top to scroll to, or if there\n // are blank lines after the cursor\n this.lines.push(new BufferLine(newCols, nullCell));\n }\n }\n }\n } else { // (this._rows >= newRows)\n for (let y = this._rows; y > newRows; y--) {\n if (this.lines.length > newRows + this.ybase) {\n if (this.lines.length > this.ybase + this.y + 1) {\n // The line is a blank line below the cursor, remove it\n this.lines.pop();\n } else {\n // The line is the cursor, scroll down\n this.ybase++;\n this.ydisp++;\n }\n }\n }\n }\n\n // Reduce max length if needed after adjustments, this is done after as it\n // would otherwise cut data from the bottom of the buffer.\n if (newMaxLength < this.lines.maxLength) {\n // Trim from the top of the buffer and adjust ybase and ydisp.\n const amountToTrim = this.lines.length - newMaxLength;\n if (amountToTrim > 0) {\n this.lines.trimStart(amountToTrim);\n this.ybase = Math.max(this.ybase - amountToTrim, 0);\n this.ydisp = Math.max(this.ydisp - amountToTrim, 0);\n }\n this.lines.maxLength = newMaxLength;\n }\n\n // Make sure that the cursor stays on screen\n this.x = Math.min(this.x, newCols - 1);\n this.y = Math.min(this.y, newRows - 1);\n if (addToY) {\n this.y += addToY;\n }\n this.savedY = Math.min(this.savedY, newRows - 1);\n this.savedX = Math.min(this.savedX, newCols - 1);\n\n this.scrollTop = 0;\n }\n\n this.scrollBottom = newRows - 1;\n\n if (this._isReflowEnabled) {\n this._reflow(newCols, newRows);\n\n // Trim the end of the line off if cols shrunk\n if (this._cols > newCols) {\n for (let i = 0; i < this.lines.length; i++) {\n this.lines.get(i).resize(newCols, nullCell);\n }\n }\n }\n\n this._cols = newCols;\n this._rows = newRows;\n }\n\n private get _isReflowEnabled(): boolean {\n return this._hasScrollback && !this._terminal.options.windowsMode;\n }\n\n private _reflow(newCols: number, newRows: number): void {\n if (this._cols === newCols) {\n return;\n }\n\n // Iterate through rows, ignore the last one as it cannot be wrapped\n if (newCols > this._cols) {\n this._reflowLarger(newCols, newRows);\n } else {\n this._reflowSmaller(newCols, newRows);\n }\n }\n\n private _reflowLarger(newCols: number, newRows: number): void {\n const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA));\n if (toRemove.length > 0) {\n const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);\n reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);\n this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);\n }\n }\n\n private _reflowLargerAdjustViewport(newCols: number, newRows: number, countRemoved: number): void {\n const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);\n // Adjust viewport based on number of items removed\n let viewportAdjustments = countRemoved;\n while (viewportAdjustments-- > 0) {\n if (this.ybase === 0) {\n if (this.y > 0) {\n this.y--;\n }\n if (this.lines.length < newRows) {\n // Add an extra row at the bottom of the viewport\n this.lines.push(new BufferLine(newCols, nullCell));\n }\n } else {\n if (this.ydisp === this.ybase) {\n this.ydisp--;\n }\n this.ybase--;\n }\n }\n }\n\n private _reflowSmaller(newCols: number, newRows: number): void {\n const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);\n // Gather all BufferLines that need to be inserted into the Buffer here so that they can be\n // batched up and only committed once\n const toInsert = [];\n let countToInsert = 0;\n // Go backwards as many lines may be trimmed and this will avoid considering them\n for (let y = this.lines.length - 1; y >= 0; y--) {\n // Check whether this line is a problem\n let nextLine = this.lines.get(y) as BufferLine;\n if (!nextLine || !nextLine.isWrapped && nextLine.getTrimmedLength() <= newCols) {\n continue;\n }\n\n // Gather wrapped lines and adjust y to be the starting line\n const wrappedLines: BufferLine[] = [nextLine];\n while (nextLine.isWrapped && y > 0) {\n nextLine = this.lines.get(--y) as BufferLine;\n wrappedLines.unshift(nextLine);\n }\n\n // If these lines contain the cursor don't touch them, the program will handle fixing up\n // wrapped lines with the cursor\n const absoluteY = this.ybase + this.y;\n if (absoluteY >= y && absoluteY < y + wrappedLines.length) {\n continue;\n }\n\n const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();\n const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);\n const linesToAdd = destLineLengths.length - wrappedLines.length;\n let trimmedLines: number;\n if (this.ybase === 0 && this.y !== this.lines.length - 1) {\n // If the top section of the buffer is not yet filled\n trimmedLines = Math.max(0, this.y - this.lines.maxLength + linesToAdd);\n } else {\n trimmedLines = Math.max(0, this.lines.length - this.lines.maxLength + linesToAdd);\n }\n\n // Add the new lines\n const newLines: BufferLine[] = [];\n for (let i = 0; i < linesToAdd; i++) {\n const newLine = this.getBlankLine(DEFAULT_ATTR_DATA, true) as BufferLine;\n newLines.push(newLine);\n }\n if (newLines.length > 0) {\n toInsert.push({\n // countToInsert here gets the actual index, taking into account other inserted items.\n // using this we can iterate through the list forwards\n start: y + wrappedLines.length + countToInsert,\n newLines\n });\n countToInsert += newLines.length;\n }\n wrappedLines.push(...newLines);\n\n // Copy buffer data to new locations, this needs to happen backwards to do in-place\n let destLineIndex = destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);\n let destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;\n if (destCol === 0) {\n destLineIndex--;\n destCol = destLineLengths[destLineIndex];\n }\n let srcLineIndex = wrappedLines.length - linesToAdd - 1;\n let srcCol = lastLineLength;\n while (srcLineIndex >= 0) {\n const cellsToCopy = Math.min(srcCol, destCol);\n wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy, true);\n destCol -= cellsToCopy;\n if (destCol === 0) {\n destLineIndex--;\n destCol = destLineLengths[destLineIndex];\n }\n srcCol -= cellsToCopy;\n if (srcCol === 0) {\n srcLineIndex--;\n const wrappedLinesIndex = Math.max(srcLineIndex, 0);\n srcCol = getWrappedLineTrimmedLength(wrappedLines, wrappedLinesIndex, this._cols);\n }\n }\n\n // Null out the end of the line ends if a wide character wrapped to the following line\n for (let i = 0; i < wrappedLines.length; i++) {\n if (destLineLengths[i] < newCols) {\n wrappedLines[i].setCell(destLineLengths[i], nullCell);\n }\n }\n\n // Adjust viewport as needed\n let viewportAdjustments = linesToAdd - trimmedLines;\n while (viewportAdjustments-- > 0) {\n if (this.ybase === 0) {\n if (this.y < newRows - 1) {\n this.y++;\n this.lines.pop();\n } else {\n this.ybase++;\n this.ydisp++;\n }\n } else {\n // Ensure ybase does not exceed its maximum value\n if (this.ybase < Math.min(this.lines.maxLength, this.lines.length + countToInsert) - newRows) {\n if (this.ybase === this.ydisp) {\n this.ydisp++;\n }\n this.ybase++;\n }\n }\n }\n }\n\n // Rearrange lines in the buffer if there are any insertions, this is done at the end rather\n // than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many\n // costly calls to CircularList.splice.\n if (toInsert.length > 0) {\n // Record buffer insert events and then play them back backwards so that the indexes are\n // correct\n const insertEvents: IInsertEvent[] = [];\n\n // Record original lines so they don't get overridden when we rearrange the list\n const originalLines: BufferLine[] = [];\n for (let i = 0; i < this.lines.length; i++) {\n originalLines.push(this.lines.get(i) as BufferLine);\n }\n const originalLinesLength = this.lines.length;\n\n let originalLineIndex = originalLinesLength - 1;\n let nextToInsertIndex = 0;\n let nextToInsert = toInsert[nextToInsertIndex];\n this.lines.length = Math.min(this.lines.maxLength, this.lines.length + countToInsert);\n let countInsertedSoFar = 0;\n for (let i = Math.min(this.lines.maxLength - 1, originalLinesLength + countToInsert - 1); i >= 0; i--) {\n if (nextToInsert && nextToInsert.start > originalLineIndex + countInsertedSoFar) {\n // Insert extra lines here, adjusting i as needed\n for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) {\n this.lines.set(i--, nextToInsert.newLines[nextI]);\n }\n i++;\n\n // Create insert events for later\n insertEvents.push({\n index: originalLineIndex + 1,\n amount: nextToInsert.newLines.length\n });\n\n countInsertedSoFar += nextToInsert.newLines.length;\n nextToInsert = toInsert[++nextToInsertIndex];\n } else {\n this.lines.set(i, originalLines[originalLineIndex--]);\n }\n }\n\n // Update markers\n let insertCountEmitted = 0;\n for (let i = insertEvents.length - 1; i >= 0; i--) {\n insertEvents[i].index += insertCountEmitted;\n this.lines.onInsertEmitter.fire(insertEvents[i]);\n insertCountEmitted += insertEvents[i].amount;\n }\n const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength);\n if (amountToTrim > 0) {\n this.lines.onTrimEmitter.fire(amountToTrim);\n }\n }\n }\n\n // private _reflowSmallerGetLinesNeeded()\n\n /**\n * Translates a string index back to a BufferIndex.\n * To get the correct buffer position the string must start at `startCol` 0\n * (default in translateBufferLineToString).\n * The method also works on wrapped line strings given rows were not trimmed.\n * The method operates on the CharData string length, there are no\n * additional content or boundary checks. Therefore the string and the buffer\n * should not be altered in between.\n * TODO: respect trim flag after fixing #1685\n * @param lineIndex line index the string was retrieved from\n * @param stringIndex index within the string\n * @param startCol column offset the string was retrieved from\n */\n public stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight: boolean = false): BufferIndex {\n while (stringIndex) {\n const line = this.lines.get(lineIndex);\n if (!line) {\n return [-1, -1];\n }\n const length = (trimRight) ? line.getTrimmedLength() : line.length;\n for (let i = 0; i < length; ++i) {\n if (line.get(i)[CHAR_DATA_WIDTH_INDEX]) {\n // empty cells report a string length of 0, but get replaced\n // with a whitespace in translateToString, thus replace with 1\n stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length || 1;\n }\n if (stringIndex < 0) {\n return [lineIndex, i];\n }\n }\n lineIndex++;\n }\n return [lineIndex, 0];\n }\n\n /**\n * Translates a buffer line to a string, with optional start and end columns.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n * @param startCol The column to start at.\n * @param endCol The column to end at.\n */\n public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol?: number): string {\n const line = this.lines.get(lineIndex);\n if (!line) {\n return '';\n }\n return line.translateToString(trimRight, startCol, endCol);\n }\n\n public getWrappedRangeForLine(y: number): { first: number, last: number } {\n let first = y;\n let last = y;\n // Scan upwards for wrapped lines\n while (first > 0 && this.lines.get(first).isWrapped) {\n first--;\n }\n // Scan downwards for wrapped lines\n while (last + 1 < this.lines.length && this.lines.get(last + 1).isWrapped) {\n last++;\n }\n return { first, last };\n }\n\n /**\n * Setup the tab stops.\n * @param i The index to start setting up tab stops from.\n */\n public setupTabStops(i?: number): void {\n if (i !== null && i !== undefined) {\n if (!this.tabs[i]) {\n i = this.prevStop(i);\n }\n } else {\n this.tabs = {};\n i = 0;\n }\n\n for (; i < this._cols; i += this._terminal.options.tabStopWidth) {\n this.tabs[i] = true;\n }\n }\n\n /**\n * Move the cursor to the previous tab stop from the given position (default is current).\n * @param x The position to move the cursor to the previous tab stop.\n */\n public prevStop(x?: number): number {\n if (x === null || x === undefined) {\n x = this.x;\n }\n while (!this.tabs[--x] && x > 0);\n return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;\n }\n\n /**\n * Move the cursor one tab stop forward from the given position (default is current).\n * @param x The position to move the cursor one tab stop forward.\n */\n public nextStop(x?: number): number {\n if (x === null || x === undefined) {\n x = this.x;\n }\n while (!this.tabs[++x] && x < this._cols);\n return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;\n }\n\n public addMarker(y: number): Marker {\n const marker = new Marker(y);\n this.markers.push(marker);\n marker.register(this.lines.onTrim(amount => {\n marker.line -= amount;\n // The marker should be disposed when the line is trimmed from the buffer\n if (marker.line < 0) {\n marker.dispose();\n }\n }));\n marker.register(this.lines.onInsert(event => {\n if (marker.line >= event.index) {\n marker.line += event.amount;\n }\n }));\n marker.register(this.lines.onDelete(event => {\n // Delete the marker if it's within the range\n if (marker.line >= event.index && marker.line < event.index + event.amount) {\n marker.dispose();\n }\n\n // Shift the marker if it's after the deleted range\n if (marker.line > event.index) {\n marker.line -= event.amount;\n }\n }));\n marker.register(marker.onDispose(() => this._removeMarker(marker)));\n return marker;\n }\n\n private _removeMarker(marker: Marker): void {\n this.markers.splice(this.markers.indexOf(marker), 1);\n }\n\n public iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator {\n return new BufferStringIterator(this, trimRight, startIndex, endIndex, startOverscan, endOverscan);\n }\n}\n\n/**\n * Iterator to get unwrapped content strings from the buffer.\n * The iterator returns at least the string data between the borders\n * `startIndex` and `endIndex` (exclusive) and will expand the lines\n * by `startOverscan` to the top and by `endOverscan` to the bottom,\n * if no new line was found in between.\n * It will never read/return string data beyond `startIndex - startOverscan`\n * or `endIndex + endOverscan`. Therefore the first and last line might be truncated.\n * It is possible to always get the full string for the first and last line as well\n * by setting the overscan values to the actual buffer length. This not recommended\n * since it might return the whole buffer within a single string in a worst case scenario.\n */\nexport class BufferStringIterator implements IBufferStringIterator {\n private _current: number;\n\n constructor (\n private _buffer: IBuffer,\n private _trimRight: boolean,\n private _startIndex: number = 0,\n private _endIndex: number = _buffer.lines.length,\n private _startOverscan: number = 0,\n private _endOverscan: number = 0\n ) {\n if (this._startIndex < 0) {\n this._startIndex = 0;\n }\n if (this._endIndex > this._buffer.lines.length) {\n this._endIndex = this._buffer.lines.length;\n }\n this._current = this._startIndex;\n }\n\n public hasNext(): boolean {\n return this._current < this._endIndex;\n }\n\n public next(): IBufferStringIteratorResult {\n const range = this._buffer.getWrappedRangeForLine(this._current);\n // limit search window to overscan value at both borders\n if (range.first < this._startIndex - this._startOverscan) {\n range.first = this._startIndex - this._startOverscan;\n }\n if (range.last > this._endIndex + this._endOverscan) {\n range.last = this._endIndex + this._endOverscan;\n }\n // limit to current buffer length\n range.first = Math.max(range.first, 0);\n range.last = Math.min(range.last, this._buffer.lines.length);\n let result = '';\n for (let i = range.first; i <= range.last; ++i) {\n result += this._buffer.translateBufferLineToString(i, this._trimRight);\n }\n this._current = range.last + 1;\n return {range: range, content: result};\n }\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport * as Strings from './Strings';\nimport { ITerminal, IBuffer } from './Types';\nimport { isMac } from './common/Platform';\nimport { RenderDebouncer } from './ui/RenderDebouncer';\nimport { addDisposableDomListener } from './ui/Lifecycle';\nimport { Disposable } from './common/Lifecycle';\nimport { ScreenDprMonitor } from './ui/ScreenDprMonitor';\nimport { IRenderDimensions } from './renderer/Types';\n\nconst MAX_ROWS_TO_READ = 20;\n\nconst enum BoundaryPosition {\n TOP,\n BOTTOM\n}\n\nexport class AccessibilityManager extends Disposable {\n private _accessibilityTreeRoot: HTMLElement;\n private _rowContainer: HTMLElement;\n private _rowElements: HTMLElement[];\n private _liveRegion: HTMLElement;\n private _liveRegionLineCount: number = 0;\n\n private _renderRowsDebouncer: RenderDebouncer;\n private _screenDprMonitor: ScreenDprMonitor;\n\n private _topBoundaryFocusListener: (e: FocusEvent) => void;\n private _bottomBoundaryFocusListener: (e: FocusEvent) => void;\n\n /**\n * This queue has a character pushed to it for keys that are pressed, if the\n * next character added to the terminal is equal to the key char then it is\n * not announced (added to live region) because it has already been announced\n * by the textarea event (which cannot be canceled). There are some race\n * condition cases if there is typing while data is streaming, but this covers\n * the main case of typing into the prompt and inputting the answer to a\n * question (Y/N, etc.).\n */\n private _charsToConsume: string[] = [];\n\n private _charsToAnnounce: string = '';\n\n constructor(\n private _terminal: ITerminal,\n private _dimensions: IRenderDimensions\n ) {\n super();\n this._accessibilityTreeRoot = document.createElement('div');\n this._accessibilityTreeRoot.classList.add('xterm-accessibility');\n\n this._rowContainer = document.createElement('div');\n this._rowContainer.classList.add('xterm-accessibility-tree');\n this._rowElements = [];\n for (let i = 0; i < this._terminal.rows; i++) {\n this._rowElements[i] = this._createAccessibilityTreeNode();\n this._rowContainer.appendChild(this._rowElements[i]);\n }\n\n this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.TOP);\n this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.BOTTOM);\n this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);\n this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n this._refreshRowsDimensions();\n this._accessibilityTreeRoot.appendChild(this._rowContainer);\n\n this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this));\n this._refreshRows();\n\n this._liveRegion = document.createElement('div');\n this._liveRegion.classList.add('live-region');\n this._liveRegion.setAttribute('aria-live', 'assertive');\n this._accessibilityTreeRoot.appendChild(this._liveRegion);\n\n this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);\n\n this.register(this._renderRowsDebouncer);\n this.register(this._terminal.onResize(e => this._onResize(e.rows)));\n this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));\n this.register(this._terminal.onScroll(() => this._refreshRows()));\n // Line feed is an issue as the prompt won't be read out after a command is run\n this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char)));\n this.register(this._terminal.onLineFeed(() => this._onChar('\\n')));\n this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount)));\n this.register(this._terminal.onKey(e => this._onKey(e.key)));\n this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()));\n\n this._screenDprMonitor = new ScreenDprMonitor();\n this.register(this._screenDprMonitor);\n this._screenDprMonitor.setListener(() => this._refreshRowsDimensions());\n // This shouldn't be needed on modern browsers but is present in case the\n // media query that drives the ScreenDprMonitor isn't supported\n this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions()));\n }\n\n public dispose(): void {\n super.dispose();\n this._terminal.element.removeChild(this._accessibilityTreeRoot);\n this._rowElements.length = 0;\n }\n\n private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {\n const boundaryElement = e.target;\n const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];\n\n // Don't scroll if the buffer top has reached the end in that direction\n const posInSet = boundaryElement.getAttribute('aria-posinset');\n const lastRowPos = position === BoundaryPosition.TOP ? '1' : `${this._terminal.buffer.lines.length}`;\n if (posInSet === lastRowPos) {\n return;\n }\n\n // Don't scroll when the last focused item was not the second row (focus is going the other\n // direction)\n if (e.relatedTarget !== beforeBoundaryElement) {\n return;\n }\n\n // Remove old boundary element from array\n let topBoundaryElement: HTMLElement;\n let bottomBoundaryElement: HTMLElement;\n if (position === BoundaryPosition.TOP) {\n topBoundaryElement = boundaryElement;\n bottomBoundaryElement = this._rowElements.pop()!;\n this._rowContainer.removeChild(bottomBoundaryElement);\n } else {\n topBoundaryElement = this._rowElements.shift()!;\n bottomBoundaryElement = boundaryElement;\n this._rowContainer.removeChild(topBoundaryElement);\n }\n\n // Remove listeners from old boundary elements\n topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener);\n bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);\n\n // Add new element to array/DOM\n if (position === BoundaryPosition.TOP) {\n const newElement = this._createAccessibilityTreeNode();\n this._rowElements.unshift(newElement);\n this._rowContainer.insertAdjacentElement('afterbegin', newElement);\n } else {\n const newElement = this._createAccessibilityTreeNode();\n this._rowElements.push(newElement);\n this._rowContainer.appendChild(newElement);\n }\n\n // Add listeners to new boundary elements\n this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);\n this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n // Scroll up\n this._terminal.scrollLines(position === BoundaryPosition.TOP ? -1 : 1);\n\n // Focus new boundary before element\n this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2].focus();\n\n // Prevent the standard behavior\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n\n private _onResize(rows: number): void {\n // Remove bottom boundary listener\n this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);\n\n // Grow rows as required\n for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {\n this._rowElements[i] = this._createAccessibilityTreeNode();\n this._rowContainer.appendChild(this._rowElements[i]);\n }\n // Shrink rows as required\n while (this._rowElements.length > rows) {\n this._rowContainer.removeChild(this._rowElements.pop()!);\n }\n\n // Add bottom boundary listener\n this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);\n\n this._refreshRowsDimensions();\n }\n\n private _createAccessibilityTreeNode(): HTMLElement {\n const element = document.createElement('div');\n element.setAttribute('role', 'listitem');\n element.tabIndex = -1;\n this._refreshRowDimensions(element);\n return element;\n }\n\n private _onTab(spaceCount: number): void {\n for (let i = 0; i < spaceCount; i++) {\n this._onChar(' ');\n }\n }\n\n private _onChar(char: string): void {\n if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {\n if (this._charsToConsume.length > 0) {\n // Have the screen reader ignore the char if it was just input\n const shiftedChar = this._charsToConsume.shift();\n if (shiftedChar !== char) {\n this._charsToAnnounce += char;\n }\n } else {\n this._charsToAnnounce += char;\n }\n\n if (char === '\\n') {\n this._liveRegionLineCount++;\n if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {\n this._liveRegion.textContent += Strings.tooMuchOutput;\n }\n }\n\n // Only detach/attach on mac as otherwise messages can go unaccounced\n if (isMac) {\n if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {\n setTimeout(() => {\n this._accessibilityTreeRoot.appendChild(this._liveRegion);\n }, 0);\n }\n }\n }\n }\n\n private _clearLiveRegion(): void {\n this._liveRegion.textContent = '';\n this._liveRegionLineCount = 0;\n\n // Only detach/attach on mac as otherwise messages can go unaccounced\n if (isMac) {\n if (this._liveRegion.parentNode) {\n this._accessibilityTreeRoot.removeChild(this._liveRegion);\n }\n }\n }\n\n private _onKey(keyChar: string): void {\n this._clearLiveRegion();\n this._charsToConsume.push(keyChar);\n }\n\n private _refreshRows(start?: number, end?: number): void {\n this._renderRowsDebouncer.refresh(start, end, this._terminal.rows);\n }\n\n private _renderRows(start: number, end: number): void {\n const buffer: IBuffer = this._terminal.buffer;\n const setSize = buffer.lines.length.toString();\n for (let i = start; i <= end; i++) {\n const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true);\n const posInSet = (buffer.ydisp + i + 1).toString();\n const element = this._rowElements[i];\n if (element) {\n element.textContent = lineData.length === 0 ? Strings.blankLine : lineData;\n element.setAttribute('aria-posinset', posInSet);\n element.setAttribute('aria-setsize', setSize);\n }\n }\n this._announceCharacters();\n }\n\n private _refreshRowsDimensions(): void {\n if (!this._dimensions.actualCellHeight) {\n return;\n }\n if (this._rowElements.length !== this._terminal.rows) {\n this._onResize(this._terminal.rows);\n }\n for (let i = 0; i < this._terminal.rows; i++) {\n this._refreshRowDimensions(this._rowElements[i]);\n }\n }\n\n public setDimensions(dimensions: IRenderDimensions): void {\n this._dimensions = dimensions;\n this._refreshRowsDimensions();\n }\n\n private _refreshRowDimensions(element: HTMLElement): void {\n element.style.height = `${this._dimensions.actualCellHeight}px`;\n }\n\n private _announceCharacters(): void {\n if (this._charsToAnnounce.length === 0) {\n return;\n }\n this._liveRegion.textContent += this._charsToAnnounce;\n this._charsToAnnounce = '';\n }\n}\n",null],"names":[],"mappings":"A8DAA;;;;;;;;;;;;;;;;ADKA;AAEA;AACA;AACA;AACA;AACA;AAGA;AAOA;AAAA;AA0BA;AAAA;AACA;AACA;AAvBA;AAiBA;AAEA;AAOA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAGA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAlRa;;;;;ADhBb;AAGA;AACA;AACA;AAEa;AASb;AAwBA;AACA;AACA;AAfA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;;;AAAA;AAOA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AAIA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAiBA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AApkBa;AAklBb;AAGA;AAGA;AACA;AACA;AACA;AALA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3Ca;;;;;ADhmBb;AACA;AAMA;AAaA;AAAA;AAPA;AAQA;AACA;AAIA;AACA;AAEA;AACA;AAhBA;AAAA;;;AAAA;AAqBA;AAAA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAvGa;;;;;ADRb;AAOA;AAUA;AAHA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAVA;AAAA;;;AAAA;AAYA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA5Ca;;;;;ADRb;AAEa;AAGb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3BA;;;;;AD3HA;AACA;AACA;AAFA;AAQA;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AATA;AAgBA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AAgCA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3BA;AAoCA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAVA;;;;;AD1GA;AAwBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAGA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAUA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AArNa;;;;;;;;;;;;;;;;;;ADVb;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhCa;AAsCb;AACA;AACA;AACA;AAEA;AAMa;AACb;AAEA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AAIA;AAHA;AACA;AACA;AACA;AAAA;AAcA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AAIA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAGA;AACA;AACA;AAAA;AAzZa;;;;;;;;;;;;;;;;;;ADvNb;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAKA;AAaA;AAGA;AAAA;AAFA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AA6BA;AAAA;AAeA;AAEA;AAFA;AACA;AACA;AAhBA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AASA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AAMA;AAGA;AAqCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAPA;AAAA;AAQA;AAKA;AACA;AACA;AACA;AAKA;;AACA;AAlLA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AA8KA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAOA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AACA;AAOA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AAAA;AACA;AAGA;AACA;AAEA;AACA;AAGA;AAEA;AAIA;AACA;AACA;AACA;AAGA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AAMA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AAKA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AASA;AAAA;AACA;AACA;AAKA;AACA;AACA;AACA;AAOA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAKA;AACA;AAKA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AAKA;AAKA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAEA;AACA;AACA;AACA;AAKA;AAuCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAAA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAwFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAmEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAyBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAQA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAkBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AASA;AACA;AACA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AAAA;AAl5Da;;;;;ADjGb;AACA;AACA;AAKA;AA6BA;AACA;AAfA;AAIA;AAGA;AAEA;AAEA;AAMA;AACA;AACA;AACA;AACA;AAbA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAeA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AAYA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AAEA;AACA;AACA;;AAEA;AACA;AAIA;AACA;AACA;AACA;;AAEA;AAMA;AACA;AACA;;AAGA;AAGA;AACA;;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;;;AAlDA;;;;AAmDA;AACA;AAUA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AA3SA;AAOA;AAqSA;AAAA;AAlTa;;;;;ADLb;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAeA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAKA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAAA;AApEa;;;;;;;;;;;;;;;;;;ADFb;AACA;AAEA;AAUA;AAAA;AAaA;AAAA;AACA;AAbA;AAEA;AAKA;AACA;AACA;AAQA;AAGA;AACA;AACA;;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAlNa;AAoNb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAba;;;;;ADhOb;AACA;AAEA;AACA;AACA;AAEA;AAMA;AAKA;AAKA;AAMA;AAMA;AAEA;AACA;AA4BA;AA4CA;AACA;AACA;AAlBA;AAKA;AAIA;AAEA;AAEA;AAOA;AACA;AAEA;AACA;AACA;AAfA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAaA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAAA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AAIA;AAIA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AAKA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAKA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAKA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAIA;AAEA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAp1Ba;;;;;AD9Db;AAuBA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AA5Ha;;;;;ADAA;AAEb;AAeA;AACA;AAEA;AAfA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AAAA;AApDa;;;;;ADRF;AACA;AACA;;;;;;;;;;;;;;;;;;ADkBX;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AAOA;AAQA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AA0IA;AACA;AADA;AAxHA;AAoFA;AAKA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAmBA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA;AAhDA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AAKA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAMA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AAGA;AAGA;AAIA;AAEA;AAEA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AAKA;AACA;AACA;AAGA;AAGA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AAIA;AACA;AACA;AACA;AAAA;AAIA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AAIA;AACA;AAKA;AACA;AACA;AAGA;AAIA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAGA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AAGA;AACA;AAEA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAQA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AAAA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAQA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAKA;AACA;AACA;AAQA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAMA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AAOA;AAKA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAKA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAEA;AACA;AAGA;AACA;AAAA;AAn6Da;AAy6Db;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;AD7hEA;AACA;AAIA;AAMA;AAAA;AAwBA;AAAA;AACA;AACA;AACA;AACA;AACA;AA5BA;AACA;AACA;AACA;AACA;AAEA;AAKA;AAEA;AACA;AAqBA;AACA;AAGA;;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAKA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AAEA;AAIA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhOa;;;;;ADXb;AAEA;AAWA;AACA;AACA;AAEA;AACA;AACA;AACA;AAlBA;;;;;ADHA;AAgBA;AAYA;AACA;AARA;AAEA;AAEA;AAMA;AACA;AACA;AACA;AAZA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAUA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAjBA;AAmBA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AATA;AAmBA;AACA;AACA;AAUA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;;;AAAA;AAMA;AACA;AACA;AAWA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAAA;AApNa;;;;;ADdb;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AAEA;AACA;AAEA;AACA;AAnBA;;;;;;;;;;;;;;;;;;ADFA;AAEA;AAAA;AAGA;AAAA;AAIA;;AACA;AAEA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAzFa;;;;;ADOb;AAAA;AACA;AAgCA;AA7BA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAjCa;;;;;ADJb;AAIA;AAHA;AACA;AAGA;AAKA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnCsB;;;;;ADItB;AACA;AACA;AAEa;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAOb;AACA;AACA;;;;;ADzBA;AAEA;AACA;AACA;AACA;AACA;AANA;AAQA;AAAA;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAlBA;AAwBA;AACA;AACA;AACA;AACA;AACA;AALA;;;;;ADtCa;;;;;ADCb;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAMA;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;;;;;;;;;;;;;;;;;;ADhJA;AACA;AAEa;AAEA;AACA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAYb;AAmHA;AAAA;AAoBA;AACA;AAsCA;AA1DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3Da;AA6DA;AAKb;AAAA;AAAA;AAAA;AAUA;AACA;AACA;AACA;;AAuEA;AAjFA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AApFa;AAsGb;AAKA;AAAA;AAAA;AAHA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAEA;AACA;AAAA;AACA;AAIA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAlVa;;;;;ADlTb;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAlFA;AAyFA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AA3BA;AAmCA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAZA;AA4BA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AA/BA;AAiCA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAdA;;;;;;;;;;;;;;;;;;ADxMA;AACA;AAGA;AAAA;AAWA;AAAA;AACA;AATA;AACA;AAIA;;AAOA;AATA;AAAA;;;AAAA;AAGA;AAAA;;;AAAA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAvBA;AAwBA;AAAA;AAzBa;;;;;ADGA;AAKA;AAYb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;ADtPA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AArUA;;;;;AD1BA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAaA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAuBA;AAAA;AACA;AA8DA;AAzDA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA/Da;AAoEb;AAAA;AACA;AAgOA;AA3NA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAeA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AAGA;AACA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAjOa;;;;;AD5Gb;AASA;AAOA;;AACA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AAOA;AACA;AACA;AACA;AACA;AAIA;AAMA;AACA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAWA;AAOA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAMA;AACA;AACA;AACA;AACA;AAAA;AA5Oa;AAoPb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;AD/PA;AAGA;AAFA;AAGA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA1Ca;;;;;ADLb;AACA;AAEA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;;;AAAA;AACA;AAAA;AA/Ka;AAiLb;AACA;AAAA;AAAA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAAA;AAEA;AAAA;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AACA;AAAA;;;;;AD1NA;AACA;AAEA;AACA;AAGA;AAyBA;AACA;AAGA;AACA;AA3BA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AAWA;AACA;AAKA;AAQA;AAAA;AACA;AAKA;AAQA;AACA;AAKA;AAQA;AACA;AACA;AAKA;AAKA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AAKA;AAAA;AACA;AACA;AAKA;AACA;AAYA;AACA;AACA;AACA;AACA;AAIA;AAgBA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AAIA;AACA;AAOA;AACA;AACA;AAKA;AACA;AAOA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAhXsB;;;;;;;;;;;;;;;;;;ADPtB;AAEA;AAAA;AASA;AAAA;AALA;AAGA;AAIA;AACA;AACA;AACA;;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3Ca;AA6Cb;AAMA;AAAA;AAJA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAGA;AAGA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAOA;AACA;AACA;AACA;AAEA;AACA;AAUA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AACA;AACA;AAMA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAIA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AACA;AAAA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AA9Qa;;;;;;;;;;;;;;;;;;ADjDb;AAGA;AAcA;AAEA;AAAA;AAMA;AAAA;AAFA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AArMa;AAuMb;AAcA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAEA;AACA;AACA;AAMA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;;;;;AD5VA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3Ba;;;;;;;;;;;;;;;;;;ADEb;AACA;AACA;AAGA;AAAA;AAGA;AAAA;AAFA;AAIA;AACA;;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA1Da;;;;;;;;;;;;;;;;;;ADNb;AACA;AACA;AACA;AACA;AAIA;AAAA;AAkBA;AAAA;AACA;AACA;AAhBA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAWA;AACA;AAEA;AACA;AACA;AAIA;AAIA;AACA;AACA;AACA;AACA;;AACA;AAhCA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AAEA;AAAA;;;AAAA;AA4BA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAnJa;;;;;;;;;;;;;;;;;;ADTb;AACA;AACA;AAGA;AACA;AACA;AAGA;AAAA;AAOA;AAAA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAEA;AAGA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AAAA;AACA;AACA;AAKA;AAEA;AACA;AACA;AAMA;AAKA;AAMA;AAIA;AAGA;AAIA;AAIA;AACA;AAOA;AACA;AAQA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAvLa;;;;;;;;;;;;;;;;;;ADRb;AAUA;AAAA;AAGA;AAAA;AAEA;;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAtGa;;;;;;;;;;;;;;;;;;ADTb;AACA;AACA;AACA;AAUA;AAAA;AAQA;AAAA;AAJA;AAEA;AAIA;AACA;;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAIA;AACA;AACA;AAKA;AACA;AACA;AAKA;AAOA;AACA;AAMA;AAQA;AAEA;AACA;AAMA;AACA;AAEA;AAMA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAGA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AACA;AAKA;AAGA;AAGA;AACA;AACA;AAgBA;AAAA;AAlSa;;;;;ADbb;AAAA;AACA;AA8CA;AA5CA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AASA;AAkBA;AAAA;;;;;;ADjDA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAUA;AAQA;AAMA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AA/CA;AAqDA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAfA;;;;;ADnFA;AACA;AASA;AACA;AACA;AACA;AAIA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AAEA;AACA;AAEA;AAGA;AAEA;AACA;AAvFA;AA6FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAfA;AAiBA;AACA;AACA;;;;;ADzHA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAxBA;AA0BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAmBA;AACA;AACA;AAFA;;;;;;;;;;;;;;;;;;ADlDA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AAQA;AAMA;AAQA;AAUA;AACA;AAXA;AAaA;AAAA;AA2BA;AAAA;AAAA;AAXA;AAGA;AAGA;AAGA;AAIA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AACA;AACA;AACA;AACA;AACA;AAWA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAIA;AACA;AAEA;AAGA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAIA;AAGA;AACA;AACA;AACA;AAGA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AAKA;AACA;AACA;AAGA;AAGA;AACA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;;;;;;AD7TA;AAOA;AAAA;AANA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;;;;;;;;;;;;;;;;;;;AD/HA;AAEA;AAAA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAAA;;;;;;;;;;;;;;;;;;;ADlBA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAGA;AAAA;AAAA;AAAA;AAIA;AACA;AACA;AACA;AAKA;AACA;;AAXA;AAaA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AAEA;AAYA;AAEA;AACA;AACA;AAAA;;;;;;AD5Fa;AACA;AAEA;;;;;;;;;;;;;;;;;;ADJb;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAUA;AAAA;AAYA;AAAA;AACA;AACA;AAZA;AAKA;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AAGA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AA3Va;;;;;ADtBb;AACA;AAEa;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEb;AAGA;AACA;AACA;AAJA;AAMA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AA9Ga;;;;;ADbb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIa;AACb;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAiBA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AA1Ga;;;;;ADtEb;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAhBA;;;;;ADFA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAAA;AApDa;;;;;;;;;;;;;;;;;;ADLb;AAcA;AAAA;AAAA;AAAA;AACA;;AAgDA;AA3CA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAjDa;;;;;ADZb;AAEA;"} \ No newline at end of file diff --git a/handlers/www/bypass.html b/handlers/www/bypass.html new file mode 100644 index 0000000000000000000000000000000000000000..69504266e95d3a01dea6002a859b754b04c3d8fc --- /dev/null +++ b/handlers/www/bypass.html @@ -0,0 +1,51 @@ + + + + Docker Playground + + + + +
+

Welcome!

+

We're bypassing the Captcha and redirecting you now..

+
+ + + + +
+
+ + + + + + + + + + diff --git a/handlers/www/default/index.html b/handlers/www/default/index.html new file mode 100644 index 0000000000000000000000000000000000000000..26697233128bb31a5ca2cad5b4ee55370fdfaa86 --- /dev/null +++ b/handlers/www/default/index.html @@ -0,0 +1,341 @@ + + + + Docker Playground + + + + + + + + + + + +
+
+ +
+

+ Your session has expired. +

+
+
+
+
+ +
+

No connection to server. Reconnecting...

+ +
+ +
+ + + + {{ttl}} + Close session +
+

Instances

+ +
+
+
+ + Windows containers {{windows}} + +
+
+ + {{newInstanceBtnText}} + + + + +
+

{{instance.ip}}

+

{{instance.hostname}}

+
+ +
+
+
+
+ +
+

Add instances to your playground.

+

Sessions and all their instances are deleted after {{ttl}} hours.

+
+ +
+
+ + + + + {{instance.name}} + + + +
+ + + + + + Open Port + + + + {{$chip}} + + + + + {{$chip}} + + +
+
+ + + + + + + + +
+
+ + + + content_copy + Copy! + + +
+
+
+ + {{deleteInstanceBtnText}} + + insert_drive_file Editor + + +
+ +
+ +
+ {{uploadMessage}} +
+
+
Connection has been lost. Sometimes this happens when a windows instance is joining a swarm. Trying to reconnect terminal...
+ +
+
+
+
+
+
+ +
+
+ + +
+

Session stack builder

+ +
+
+ +
+ We are building your stack. This might take a while.
+
+
+
+
+
+ Your session is ready! +
+
+
+ + + + Close + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/handlers/www/default/landing.html b/handlers/www/default/landing.html new file mode 100644 index 0000000000000000000000000000000000000000..49f9fa8cfd54a36e5d21b7ffbadee9d31ce8de0a --- /dev/null +++ b/handlers/www/default/landing.html @@ -0,0 +1,153 @@ + + + + + + + + + + + + Play with Docker + + + + + + + + + + +
+
+ +
+ +
+ +

Play with Docker

+

A simple, interactive and fun playground to learn Docker

+
+ + +
+
+

Start

+ + + +
+
+ +
+
+

Play with Docker (PWD) is a project hacked by Marcos Liljedhal and Jonathan Leibiusky and sponsored by Docker Inc.

+

PWD is a Docker playground which allows users to run Docker commands in a matter of seconds. It gives the experience of having a free Alpine Linux Virtual Machine in browser, where you can build and run Docker containers and even create clusters in Docker Swarm Mode. Under the hood Docker-in-Docker (DinD) is used to give the effect of multiple VMs/PCs. In addition to the playground, PWD also includes a training site composed of a large set of Docker labs and quizzes from beginner to advanced level available at training.play-with-docker.com.

+
+
+ +
+

© Play with Docker 2017 - 2025

+
+ +
+ + + + + + + + diff --git a/handlers/www/editor.html b/handlers/www/editor.html new file mode 100644 index 0000000000000000000000000000000000000000..fb6abe879b93e30cb33941ddcd4211d8792088ef --- /dev/null +++ b/handlers/www/editor.html @@ -0,0 +1,177 @@ + + + + Editor + + + + + + + +
+ +
+
+
+ + +
+
+
+
+ + + +
+
+
+
+
+ + + + + + + + + diff --git a/handlers/www/k8s/index.html b/handlers/www/k8s/index.html new file mode 100644 index 0000000000000000000000000000000000000000..21e6c0ab126324ad9a51526ce50561bc9ab6174e --- /dev/null +++ b/handlers/www/k8s/index.html @@ -0,0 +1,263 @@ + + + + Docker Playground + + + + + + + + + + + +
+
+ +
+

+ Your session has expired. +

+
+
+
+
+ +
+

No connection to server. Reconnecting...

+ +
+ +
+ + + + {{ttl}} + Close session +
+

Instances

+
+
+
+ + Windows containers {{windows}} + +
+
+ + {{newInstanceBtnText}} + + + + +
+

{{instance.ip}}

+

{{instance.hostname}}

+
+ +
+
+
+
+ +
+

Add instances to your playground.

+

Sessions and all their instances are deleted after {{ttl}} hours.

+
+ +
+
+ + + + + {{instance.name}} + + + +
+ + + + + + + {{$chip}} + + + + + {{$chip}} + + +
+
+ + + + + + + + +
+
+ + + + +
+
+
+ + {{deleteInstanceBtnText}} + +
+ +
+ +
+ {{uploadMessage}} +
+
+
Connection has been lost. Sometimes this happens when a windows instance is joining a swarm. Trying to reconnect terminal...
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/handlers/www/k8s/landing.html b/handlers/www/k8s/landing.html new file mode 100644 index 0000000000000000000000000000000000000000..4a41524576760dec2cf34c077f64f3522dff0764 --- /dev/null +++ b/handlers/www/k8s/landing.html @@ -0,0 +1,231 @@ + + + + + + + + + + + Play with Kubernetes + + + + + + + + + +
+
+ +
+ +
+ +

Play with Kubernetes

+

+ A simple, interactive and fun playground to learn Kubernetes +

+
+ + +
+
+

+ Start +

+ + + +
+
+ +
+
+

+ Play with Kubernetes is a labs site provided by + Docker and created by Tutorius. + Play with Kubernetes is a playground which allows users to run K8s + clusters in a matter of seconds. It gives the experience of having a + free Alpine Linux Virtual Machine in browser. Under the hood + Docker-in-Docker (DinD) is used to give the effect of multiple + VMs/PCs. +

+

+ If you want to learn more about Kubernetes, consider the + Play with Kubernetes Classroom + which provides more directed learning using an integrated Play with + Kubernetes commandline. +

+
+
+ +
+

© Play with Kubernetes 2017 - 2023

+
+
+ + + + + + + diff --git a/handlers/www/ooc.html b/handlers/www/ooc.html new file mode 100644 index 0000000000000000000000000000000000000000..61201d7244d0fd8095ccc9114a94e4a3108ddddd --- /dev/null +++ b/handlers/www/ooc.html @@ -0,0 +1,24 @@ + + + + Docker Playground + + + + + + + + + +
+ We are really sorry but we are out of capacity and cannot create your session at the moment. Please try again later. +
+ + diff --git a/handlers/www/robots.txt b/handlers/www/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..1f53798bb4fe33c86020be7f10c44f29486fd190 --- /dev/null +++ b/handlers/www/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/haproxy/haproxy.cfg b/haproxy/haproxy.cfg new file mode 100644 index 0000000000000000000000000000000000000000..678c7258959207f0b61a5f82e5f81c76cf744ae8 --- /dev/null +++ b/haproxy/haproxy.cfg @@ -0,0 +1,23 @@ +defaults + mode http + timeout connect 5000ms + default-server init-addr none + option http-server-close + +resolvers docker_resolver + nameserver dns 127.0.0.11:53 + +frontend http-in + bind *:8080 + + acl host_direct hdr_reg(host) -i ^.*\.direct\..*?:?.*$ + + use_backend l2 if host_direct + + default_backend pwd + +backend pwd + server node1 pwd:3000 check inter 10s resolvers docker_resolver resolve-prefer ipv4 + +backend l2 + server node2 l2:443 check inter 10s resolvers docker_resolver resolve-prefer ipv4 diff --git a/id/generator.go b/id/generator.go new file mode 100644 index 0000000000000000000000000000000000000000..44727bca8243e644c348dcdb7f66419cf00d7088 --- /dev/null +++ b/id/generator.go @@ -0,0 +1,5 @@ +package id + +type Generator interface { + NewId() string +} diff --git a/id/mock.go b/id/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..a854fe38b3ba322e3c18d39bd03b3106893c5da2 --- /dev/null +++ b/id/mock.go @@ -0,0 +1,12 @@ +package id + +import "github.com/stretchr/testify/mock" + +type MockGenerator struct { + mock.Mock +} + +func (m *MockGenerator) NewId() string { + args := m.Called() + return args.String(0) +} diff --git a/id/xid.go b/id/xid.go new file mode 100644 index 0000000000000000000000000000000000000000..c87ac690681026e2245019416513701994b37835 --- /dev/null +++ b/id/xid.go @@ -0,0 +1,10 @@ +package id + +import "github.com/rs/xid" + +type XIDGenerator struct { +} + +func (x XIDGenerator) NewId() string { + return xid.New().String() +} diff --git a/internal/addgenheader/addgenheader.go b/internal/addgenheader/addgenheader.go new file mode 100644 index 0000000000000000000000000000000000000000..0b7b84ce36d38bb656086619c30986c4ed9cd34c --- /dev/null +++ b/internal/addgenheader/addgenheader.go @@ -0,0 +1,26 @@ +// addgenheader is a simple program that adds a DO NOT EDIT style +// comment at the top of a file. Because some generators do not do +// this, e.g. go-bindata +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" +) + +func main() { + var buf bytes.Buffer + fmt.Fprintf(&buf, "// %v DO NOT EDIT\n", strings.TrimSpace(os.Args[2])) + fmt.Fprintf(&buf, "\n") + byts, err := ioutil.ReadFile(os.Args[1]) + if err != nil { + panic(err) + } + fmt.Fprintf(&buf, "%s", byts) + if err := ioutil.WriteFile(os.Args[1], buf.Bytes(), 0666); err != nil { + panic(err) + } +} diff --git a/k8s/factory.go b/k8s/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..5170edc7bd397becba4299747257adfb67c5cb61 --- /dev/null +++ b/k8s/factory.go @@ -0,0 +1,140 @@ +package k8s + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net" + "net/http" + "net/url" + "time" + + "github.com/docker/go-connections/tlsconfig" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/router" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" +) + +type FactoryApi interface { + GetForInstance(instance *types.Instance) (*kubernetes.Clientset, error) + GetKubeletForInstance(instance *types.Instance) (*KubeletClient, error) +} + +func NewClient(instance *types.Instance, proxyHost string) (*kubernetes.Clientset, error) { + var durl string + + host := router.EncodeHost(instance.SessionId, instance.RoutableIP, router.HostOpts{EncodedPort: 6443}) + + var tlsConfig *tls.Config + tlsConfig = tlsconfig.ClientDefault() + tlsConfig.InsecureSkipVerify = true + tlsConfig.ServerName = host + + var transport http.RoundTripper + transport = &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 1 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + TLSClientConfig: tlsConfig, + MaxIdleConnsPerHost: 5, + } + + durl = fmt.Sprintf("https://%s", proxyHost) + + cc := rest.ContentConfig{ + ContentType: "application/json", + GroupVersion: &schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}, + } + restConfig := &rest.Config{ + Host: durl, + APIPath: "/api/", + BearerToken: "31ada4fd-adec-460c-809a-9e56ceb75269", + ContentConfig: cc, + } + + transport, err := rest.HTTPWrappersForConfig(restConfig, transport) + if err != nil { + return nil, fmt.Errorf("Error wrapping transport %v", err) + } + cli := &http.Client{ + Transport: transport, + } + + rc, err := rest.RESTClientFor(restConfig) + rc.Client = cli + if err != nil { + return nil, fmt.Errorf("Error creating K8s client %v", err) + } + + return kubernetes.New(rc), nil +} + +func NewKubeletClient(instance *types.Instance, proxyHost string) (*KubeletClient, error) { + var durl string + + host := router.EncodeHost(instance.SessionId, instance.RoutableIP, router.HostOpts{EncodedPort: 10255}) + + transport := &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 1 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConnsPerHost: 5, + } + + durl = fmt.Sprintf("http://%s", host) + transport.Proxy = http.ProxyURL(&url.URL{Host: proxyHost}) + + cli := &http.Client{ + Transport: transport, + } + kc := &KubeletClient{client: cli, baseURL: durl} + return kc, nil +} + +type KubeletClient struct { + client *http.Client + baseURL string +} + +func (c *KubeletClient) Get(path string) (*http.Response, error) { + return c.client.Get(c.baseURL + path) +} + +type metadata struct { + Labels map[string]string +} + +type item struct { + Metadata metadata +} + +type kubeletPodsResponse struct { + Items []item +} + +func (c *KubeletClient) IsManager() (bool, error) { + res, err := c.client.Get(c.baseURL + "/pods") + if err != nil { + return false, err + } + podsData := &kubeletPodsResponse{} + + json.NewDecoder(res.Body).Decode(podsData) + + for _, i := range podsData.Items { + for _, v := range i.Metadata.Labels { + if v == "kube-apiserver" { + return true, nil + } + } + } + + return false, nil +} diff --git a/k8s/factory_mock.go b/k8s/factory_mock.go new file mode 100644 index 0000000000000000000000000000000000000000..46a4f7f80e52d76fedd465c94069e1cc57298404 --- /dev/null +++ b/k8s/factory_mock.go @@ -0,0 +1,21 @@ +package k8s + +import ( + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/mock" + "k8s.io/client-go/kubernetes" +) + +type FactoryMock struct { + mock.Mock +} + +func (m *FactoryMock) GetKubeletForInstance(i *types.Instance) (*KubeletClient, error) { + args := m.Called(i) + return args.Get(0).(*KubeletClient), args.Error(1) +} + +func (m *FactoryMock) GetForInstance(instance *types.Instance) (*kubernetes.Clientset, error) { + args := m.Called(instance) + return args.Get(0).(*kubernetes.Clientset), args.Error(1) +} diff --git a/k8s/local_cached_factory.go b/k8s/local_cached_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..809396c7b2413f3ffe2ec6fb7292f1db88e18614 --- /dev/null +++ b/k8s/local_cached_factory.go @@ -0,0 +1,124 @@ +package k8s + +import ( + "fmt" + "log" + "sync" + "time" + + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +type localCachedFactory struct { + rw sync.Mutex + irw sync.Mutex + sessionClient *kubernetes.Clientset + instanceClients map[string]*instanceEntry + storage storage.StorageApi +} + +type instanceEntry struct { + rw sync.Mutex + client *kubernetes.Clientset + kubeletClient *KubeletClient +} + +func (f *localCachedFactory) GetForInstance(instance *types.Instance) (*kubernetes.Clientset, error) { + key := instance.Name + + f.irw.Lock() + c, found := f.instanceClients[key] + if !found { + c := &instanceEntry{} + f.instanceClients[key] = c + } + c = f.instanceClients[key] + f.irw.Unlock() + + c.rw.Lock() + defer c.rw.Unlock() + + if c.client == nil { + kc, err := NewClient(instance, "l2:443") + if err != nil { + return nil, err + } + c.client = kc + } + + err := f.check(func() error { + _, err := c.client.CoreV1().Pods("").List(metav1.ListOptions{}) + return err + }) + if err != nil { + return nil, err + } + + return c.client, nil +} + +func (f *localCachedFactory) GetKubeletForInstance(instance *types.Instance) (*KubeletClient, error) { + key := instance.Name + + f.irw.Lock() + c, found := f.instanceClients[key] + if !found { + c := &instanceEntry{} + f.instanceClients[key] = c + } + c = f.instanceClients[key] + f.irw.Unlock() + + c.rw.Lock() + defer c.rw.Unlock() + + if c.kubeletClient == nil { + kc, err := NewKubeletClient(instance, "l2:443") + if err != nil { + return nil, err + } + c.kubeletClient = kc + } + + err := f.check(func() error { + r, err := c.kubeletClient.Get("/pods") + if err != nil { + return err + } + defer r.Body.Close() + return nil + }) + if err != nil { + return nil, err + } + + return c.kubeletClient, nil +} + +func (f *localCachedFactory) check(fn func() error) error { + ok := false + for i := 0; i < 5; i++ { + err := fn() + if err != nil { + log.Printf("Connection to k8s api has failed, maybe instance is not ready yet, sleeping and retrying in 1 second. Try #%d. Got: %v\n", i+1, err) + time.Sleep(time.Second) + continue + } + ok = true + break + } + if !ok { + return fmt.Errorf("Connection to k8s api was not established.") + } + return nil +} + +func NewLocalCachedFactory(s storage.StorageApi) *localCachedFactory { + return &localCachedFactory{ + instanceClients: make(map[string]*instanceEntry), + storage: s, + } +} diff --git a/prometheus.yml b/prometheus.yml new file mode 100644 index 0000000000000000000000000000000000000000..9af86d3bfdd8c0d076996e15f3e80a4fce46913c --- /dev/null +++ b/prometheus.yml @@ -0,0 +1,24 @@ +# my global config +global: + scrape_interval: 5s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 5s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: + # - "first.rules" + # - "second.rules" + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'pwd' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ['pwd1:3000', 'pwd2:3000'] diff --git a/provisioner/cert.go b/provisioner/cert.go new file mode 100644 index 0000000000000000000000000000000000000000..1dd03f3a30138c0a896b8149b34bd13dc657b48f --- /dev/null +++ b/provisioner/cert.go @@ -0,0 +1,152 @@ +package provisioner + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "time" +) + +func newCertificate(org string) (*x509.Certificate, error) { + now := time.Now() + // need to set notBefore slightly in the past to account for time + // skew in the VMs otherwise the certs sometimes are not yet valid + notBefore := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()-5, 0, 0, time.Local) + notAfter := notBefore.Add(time.Hour * 24 * 1080) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{org}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement, + BasicConstraintsValid: true, + }, nil + +} + +func GenerateCACertificate(org string) ([]byte, []byte, error) { + template, err := newCertificate(org) + if err != nil { + return nil, nil, err + } + + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + template.KeyUsage |= x509.KeyUsageKeyEncipherment + template.KeyUsage |= x509.KeyUsageKeyAgreement + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) + if err != nil { + return nil, nil, err + } + + cert := new(bytes.Buffer) + key := new(bytes.Buffer) + pem.Encode(cert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + pem.Encode(key, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + + return cert.Bytes(), key.Bytes(), nil +} + +func GenerateClientCertificate(org string, caCert, caKey []byte) ([]byte, []byte, error) { + template, err := newCertificate(org) + if err != nil { + return nil, nil, err + } + + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + template.KeyUsage = x509.KeyUsageDigitalSignature + + tlsCert, err := tls.X509KeyPair(caCert, caKey) + if err != nil { + return nil, nil, err + } + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + return nil, nil, err + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, x509Cert, &priv.PublicKey, tlsCert.PrivateKey) + if err != nil { + return nil, nil, err + } + + cert := new(bytes.Buffer) + key := new(bytes.Buffer) + + pem.Encode(cert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + pem.Encode(key, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + + return cert.Bytes(), key.Bytes(), nil +} + +func GenerateServerCertificate(org string, caCert, caKey []byte, hosts []string) ([]byte, []byte, error) { + template, err := newCertificate(org) + if err != nil { + return nil, nil, err + } + + template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + tlsCert, err := tls.X509KeyPair(caCert, caKey) + if err != nil { + return nil, nil, err + } + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + return nil, nil, err + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, x509Cert, &priv.PublicKey, tlsCert.PrivateKey) + if err != nil { + return nil, nil, err + } + + cert := new(bytes.Buffer) + key := new(bytes.Buffer) + + pem.Encode(cert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + pem.Encode(key, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + + return cert.Bytes(), key.Bytes(), nil +} diff --git a/provisioner/dind.go b/provisioner/dind.go new file mode 100644 index 0000000000000000000000000000000000000000..d7b99948b453e6a49ef6654dda857d2167c53da4 --- /dev/null +++ b/provisioner/dind.go @@ -0,0 +1,297 @@ +package provisioner + +import ( + "bytes" + "fmt" + "io" + "log" + "net" + "net/http" + "path/filepath" + "strings" + + lru "github.com/hashicorp/golang-lru" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/router" + "github.com/play-with-docker/play-with-docker/storage" +) + +type DinD struct { + factory docker.FactoryApi + storage storage.StorageApi + generator id.Generator + cache *lru.Cache +} + +func NewDinD(generator id.Generator, f docker.FactoryApi, s storage.StorageApi) *DinD { + c, _ := lru.New(5000) + return &DinD{generator: generator, factory: f, storage: s, cache: c} +} + +func checkHostnameExists(sessionId, hostname string, instances []*types.Instance) bool { + exists := false + for _, instance := range instances { + if instance.Hostname == hostname { + exists = true + break + } + } + return exists +} + +func (d *DinD) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) { + if conf.ImageName == "" { + playground, err := d.storage.PlaygroundGet(session.PlaygroundId) + if err != nil { + return nil, err + } + conf.ImageName = playground.DefaultDinDInstanceImage + } + log.Printf("NewInstance - using image: [%s]\n", conf.ImageName) + if conf.Hostname == "" { + instances, err := d.storage.InstanceFindBySessionId(session.Id) + if err != nil { + return nil, err + } + var nodeName string + for i := 1; ; i++ { + nodeName = fmt.Sprintf("node%d", i) + exists := checkHostnameExists(session.Id, nodeName, instances) + if !exists { + break + } + } + conf.Hostname = nodeName + } + + networks := []string{session.Id} + if config.Unsafe { + networks = append(networks, conf.Networks...) + } + + containerName := fmt.Sprintf("%s_%s", session.Id[:8], d.generator.NewId()) + opts := docker.CreateContainerOpts{ + Image: conf.ImageName, + SessionId: session.Id, + ContainerName: containerName, + Hostname: conf.Hostname, + ServerCert: conf.ServerCert, + ServerKey: conf.ServerKey, + CACert: conf.CACert, + HostFQDN: conf.PlaygroundFQDN, + Privileged: conf.Privileged, + Networks: networks, + DindVolumeSize: conf.DindVolumeSize, + Envs: conf.Envs, + } + + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return nil, err + } + if err := dockerClient.ContainerCreate(opts); err != nil { + return nil, err + } + + ips, err := dockerClient.ContainerIPs(containerName) + if err != nil { + return nil, err + } + + instance := &types.Instance{} + instance.Image = opts.Image + instance.IP = ips[session.Id] + instance.RoutableIP = instance.IP + instance.SessionId = session.Id + instance.Name = containerName + instance.Hostname = conf.Hostname + instance.Cert = conf.Cert + instance.Key = conf.Key + instance.ServerCert = conf.ServerCert + instance.ServerKey = conf.ServerKey + instance.CACert = conf.CACert + instance.Tls = conf.Tls + instance.ProxyHost = router.EncodeHost(session.Id, instance.RoutableIP, router.HostOpts{}) + instance.SessionHost = session.Host + + return instance, nil +} + +func (d *DinD) getSession(sessionId string) (*types.Session, error) { + var session *types.Session + if s, found := d.cache.Get(sessionId); !found { + s, err := d.storage.SessionGet(sessionId) + if err != nil { + return nil, err + } + session = s + d.cache.Add(sessionId, s) + } else { + session = s.(*types.Session) + } + return session, nil +} + +func (d *DinD) InstanceDelete(session *types.Session, instance *types.Instance) error { + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return err + } + err = dockerClient.ContainerDelete(instance.Name) + if err != nil && !strings.Contains(err.Error(), "No such container") { + return err + } + return nil +} + +func (d *DinD) InstanceExec(instance *types.Instance, cmd []string) (int, error) { + session, err := d.getSession(instance.SessionId) + if err != nil { + return -1, err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return -1, err + } + return dockerClient.Exec(instance.Name, cmd) +} + +func (d *DinD) InstanceFSTree(instance *types.Instance) (io.Reader, error) { + session, err := d.getSession(instance.SessionId) + if err != nil { + return nil, err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return nil, err + } + b := bytes.NewBuffer([]byte{}) + + if c, err := dockerClient.ExecAttach(instance.Name, []string{"bash", "-c", `tree --noreport -J $HOME`}, b); c > 0 { + log.Println(b.String()) + return nil, fmt.Errorf("Error %d trying list directories", c) + } else if err != nil { + return nil, err + } + + return b, nil +} + +func (d *DinD) InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) { + session, err := d.getSession(instance.SessionId) + if err != nil { + return nil, err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return nil, err + } + + return dockerClient.CopyFromContainer(instance.Name, filePath) +} + +func (d *DinD) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error { + session, err := d.getSession(instance.SessionId) + if err != nil { + return err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return err + } + return dockerClient.ContainerResize(instance.Name, rows, cols) +} + +func (d *DinD) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) { + session, err := d.getSession(instance.SessionId) + if err != nil { + return nil, err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return nil, err + } + return dockerClient.CreateAttachConnection(instance.Name) +} + +func (d *DinD) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, url string) error { + log.Printf("Downloading file [%s]\n", url) + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("Could not download file [%s]. Error: %s\n", url, err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("Could not download file [%s]. Status code: %d\n", url, resp.StatusCode) + } + session, err := d.getSession(instance.SessionId) + if err != nil { + return err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return err + } + + copyErr := dockerClient.CopyToContainer(instance.Name, dest, fileName, resp.Body) + + if copyErr != nil { + return fmt.Errorf("Error while downloading file [%s]. Error: %s\n", url, copyErr) + } + + return nil +} + +func (d *DinD) getInstanceCWD(instance *types.Instance) (string, error) { + session, err := d.getSession(instance.SessionId) + if err != nil { + return "", err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return "", err + } + b := bytes.NewBufferString("") + + if c, err := dockerClient.ExecAttach(instance.Name, []string{"bash", "-c", `pwdx $( 0 { + return "", fmt.Errorf("Error %d trying to get CWD", c) + } else if err != nil { + return "", err + } + + cwd := strings.TrimSpace(strings.Split(b.String(), ":")[1]) + + return cwd, nil +} + +func (d *DinD) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error { + session, err := d.getSession(instance.SessionId) + if err != nil { + return err + } + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return err + } + var finalDest string + if filepath.IsAbs(dest) { + finalDest = dest + } else { + if cwd, err := d.getInstanceCWD(instance); err != nil { + return err + } else { + finalDest = fmt.Sprintf("%s/%s", cwd, dest) + } + } + + copyErr := dockerClient.CopyToContainer(instance.Name, finalDest, fileName, reader) + + if copyErr != nil { + return fmt.Errorf("Error while uploading file [%s]. Error: %s\n", fileName, copyErr) + } + + return nil +} diff --git a/provisioner/factory.go b/provisioner/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..278aa1d156e3d3dea0d96c564c93847518a55a0b --- /dev/null +++ b/provisioner/factory.go @@ -0,0 +1,18 @@ +package provisioner + +type instanceProvisionerFactory struct { + windows InstanceProvisionerApi + dind InstanceProvisionerApi +} + +func NewInstanceProvisionerFactory(w InstanceProvisionerApi, d InstanceProvisionerApi) InstanceProvisionerFactoryApi { + return &instanceProvisionerFactory{windows: w, dind: d} +} + +func (p *instanceProvisionerFactory) GetProvisioner(instanceType string) (InstanceProvisionerApi, error) { + if instanceType == "windows" { + return p.windows, nil + } else { + return p.dind, nil + } +} diff --git a/provisioner/overlay.go b/provisioner/overlay.go new file mode 100644 index 0000000000000000000000000000000000000000..84c80d08f696f1541ed86cfa79ee3787985d443d --- /dev/null +++ b/provisioner/overlay.go @@ -0,0 +1,76 @@ +package provisioner + +import ( + "context" + "fmt" + "log" + "net/url" + "strings" + + dtypes "github.com/docker/docker/api/types" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type overlaySessionProvisioner struct { + dockerFactory docker.FactoryApi +} + +func NewOverlaySessionProvisioner(df docker.FactoryApi) SessionProvisionerApi { + return &overlaySessionProvisioner{dockerFactory: df} +} + +func (p *overlaySessionProvisioner) SessionNew(ctx context.Context, s *types.Session) error { + dockerClient, err := p.dockerFactory.GetForSession(s) + if err != nil { + // We assume we are out of capacity + return fmt.Errorf("Out of capacity") + } + u, _ := url.Parse(dockerClient.DaemonHost()) + if u.Host == "" { + s.Host = "localhost" + } else { + chunks := strings.Split(u.Host, ":") + s.Host = chunks[0] + } + + opts := dtypes.NetworkCreate{Driver: "overlay", Attachable: true} + if err := dockerClient.NetworkCreate(s.Id, opts); err != nil { + log.Println("ERROR NETWORKING", err) + return err + } + log.Printf("Network [%s] created for session [%s]\n", s.Id, s.Id) + + ip, err := dockerClient.NetworkConnect(config.L2ContainerName, s.Id, s.PwdIpAddress) + if err != nil { + log.Println(err) + return err + } + s.PwdIpAddress = ip + log.Printf("Connected %s to network [%s]\n", config.PWDContainerName, s.Id) + return nil +} +func (p *overlaySessionProvisioner) SessionClose(s *types.Session) error { + // Disconnect L2 router from the network + dockerClient, err := p.dockerFactory.GetForSession(s) + if err != nil { + log.Println(err) + return err + } + if err := dockerClient.NetworkDisconnect(config.L2ContainerName, s.Id); err != nil { + if !strings.Contains(err.Error(), "is not connected to the network") { + log.Println("ERROR NETWORKING", err) + return err + } + } + log.Printf("Disconnected l2 from network [%s]\n", s.Id) + if err := dockerClient.NetworkDelete(s.Id); err != nil { + if !strings.Contains(err.Error(), "not found") { + log.Println(err) + return err + } + } + + return nil +} diff --git a/provisioner/provisioner.go b/provisioner/provisioner.go new file mode 100644 index 0000000000000000000000000000000000000000..400ed1174a4f93d7df267671a0e7ac13980b7f06 --- /dev/null +++ b/provisioner/provisioner.go @@ -0,0 +1,39 @@ +package provisioner + +import ( + "context" + "errors" + "io" + "net" + + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +var OutOfCapacityError = errors.New("OutOfCapacity") + +func OutOfCapacity(e error) bool { + return e == OutOfCapacityError +} + +type InstanceProvisionerApi interface { + InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) + InstanceDelete(session *types.Session, instance *types.Instance) error + InstanceExec(instance *types.Instance, cmd []string) (int, error) + InstanceFSTree(instance *types.Instance) (io.Reader, error) + InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) + + InstanceResizeTerminal(instance *types.Instance, cols, rows uint) error + InstanceGetTerminal(instance *types.Instance) (net.Conn, error) + + InstanceUploadFromUrl(instance *types.Instance, fileName, dest, url string) error + InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error +} + +type SessionProvisionerApi interface { + SessionNew(ctx context.Context, session *types.Session) error + SessionClose(session *types.Session) error +} + +type InstanceProvisionerFactoryApi interface { + GetProvisioner(instanceType string) (InstanceProvisionerApi, error) +} diff --git a/provisioner/windows.go b/provisioner/windows.go new file mode 100644 index 0000000000000000000000000000000000000000..19c1cdad68636fb8ef18c110466aaaad2d0d5436 --- /dev/null +++ b/provisioner/windows.go @@ -0,0 +1,320 @@ +package provisioner + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net" + "net/http" + "net/url" + "sort" + + "golang.org/x/net/websocket" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/router" + "github.com/play-with-docker/play-with-docker/storage" +) + +var asgService *autoscaling.AutoScaling +var ec2Service *ec2.EC2 + +func init() { + // Create a session to share configuration, and load external configuration. + sess := session.Must(session.NewSession()) + // + // // Create the service's client with the session. + asgService = autoscaling.New(sess) + ec2Service = ec2.New(sess) +} + +type windows struct { + factory docker.FactoryApi + storage storage.StorageApi +} + +type instanceInfo struct { + publicIP string + privateIP string + id string +} + +func NewWindowsASG(f docker.FactoryApi, st storage.StorageApi) *windows { + return &windows{factory: f, storage: st} +} + +func (d *windows) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) { + winfo, err := d.getWindowsInstanceInfo(session.Id) + + if err != nil { + return nil, err + } + labels := map[string]string{ + "io.tutorius.networkid": session.Id, + "io.tutorius.networking.remote.ip": winfo.privateIP, + } + instanceName := fmt.Sprintf("%s_%s", session.Id[:8], winfo.id) + + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + d.releaseInstance(winfo.id) + return nil, err + } + if err = dockerClient.ConfigCreate(instanceName, labels, []byte(instanceName)); err != nil { + d.releaseInstance(winfo.id) + return nil, err + } + + instance := &types.Instance{} + instance.Name = instanceName + instance.Image = "" + instance.IP = winfo.privateIP + instance.RoutableIP = instance.IP + instance.SessionId = session.Id + instance.WindowsId = winfo.id + instance.Cert = conf.Cert + instance.Key = conf.Key + instance.Type = conf.Type + instance.ServerCert = conf.ServerCert + instance.ServerKey = conf.ServerKey + instance.CACert = conf.CACert + instance.Tls = conf.Tls + instance.ProxyHost = router.EncodeHost(session.Id, instance.RoutableIP, router.HostOpts{}) + instance.SessionHost = session.Host + + return instance, nil + +} + +func (d *windows) InstanceDelete(session *types.Session, instance *types.Instance) error { + dockerClient, err := d.factory.GetForSession(session) + if err != nil { + return err + } + + _, err = asgService.DetachInstances(&autoscaling.DetachInstancesInput{ + AutoScalingGroupName: aws.String("pwd-windows"), + InstanceIds: []*string{aws.String(instance.WindowsId)}, + ShouldDecrementDesiredCapacity: aws.Bool(false), + }) + + if err != nil { + return err + } + + //return error and don't do anything else + if _, err := ec2Service.TerminateInstances(&ec2.TerminateInstancesInput{InstanceIds: []*string{aws.String(instance.WindowsId)}}); err != nil { + return err + } + + err = dockerClient.ConfigDelete(instance.Name) + if err != nil { + return err + } + + return d.releaseInstance(instance.WindowsId) +} + +type execRes struct { + ExitCode int `json:"exit_code"` + Error string `json:"error"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` +} + +func (d *windows) InstanceExec(instance *types.Instance, cmd []string) (int, error) { + execBody := struct { + Cmd []string `json:"cmd"` + }{Cmd: cmd} + + b, err := json.Marshal(execBody) + if err != nil { + return -1, err + } + resp, err := http.Post(fmt.Sprintf("http://%s:222/exec", instance.IP), "application/json", bytes.NewReader(b)) + if err != nil { + log.Println(err) + return -1, err + } + if resp.StatusCode != 200 { + log.Printf("Error exec on instance %s. Got %d\n", instance.Name, resp.StatusCode) + return -1, fmt.Errorf("Error exec on instance %s. Got %d\n", instance.Name, resp.StatusCode) + } + var ex execRes + err = json.NewDecoder(resp.Body).Decode(&ex) + if err != nil { + return -1, err + } + return ex.ExitCode, nil +} + +func (d *windows) InstanceFSTree(instance *types.Instance) (io.Reader, error) { + //TODO implement + return nil, nil +} +func (d *windows) InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) { + //TODO implement + return nil, nil +} + +func (d *windows) releaseInstance(instanceId string) error { + return d.storage.WindowsInstanceDelete(instanceId) +} + +func (d *windows) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error { + resp, err := http.Post(fmt.Sprintf("http://%s:222/terminals/1/size?cols=%d&rows=%d", instance.IP, cols, rows), "application/json", nil) + if err != nil { + log.Println(err) + return err + } + if resp.StatusCode != 200 { + log.Printf("Error resizing terminal of instance %s. Got %d\n", instance.Name, resp.StatusCode) + return fmt.Errorf("Error resizing terminal got %d\n", resp.StatusCode) + } + return nil +} + +func (d *windows) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) { + resp, err := http.Post(fmt.Sprintf("http://%s:222/terminals/1", instance.IP), "application/json", nil) + if err != nil { + log.Printf("Error creating terminal for instance %s. Got %v\n", instance.Name, err) + return nil, err + } + if resp.StatusCode != 200 { + log.Printf("Error creating terminal for instance %s. Got %d\n", instance.Name, resp.StatusCode) + return nil, fmt.Errorf("Creating terminal got %d\n", resp.StatusCode) + } + url := fmt.Sprintf("ws://%s:222/terminals/1", instance.IP) + ws, err := websocket.Dial(url, "", url) + if err != nil { + log.Println(err) + return nil, err + } + return ws, nil +} + +func (d *windows) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, u string) error { + log.Printf("Downloading file [%s]\n", u) + resp, err := http.Get(u) + if err != nil { + return fmt.Errorf("Could not download file [%s]. Error: %s\n", u, err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("Could not download file [%s]. Status code: %d\n", u, resp.StatusCode) + } + uploadResp, err := http.Post(fmt.Sprintf("http://%s:222/terminals/1/uploads?dest=%s&file_name=%s", instance.IP, url.QueryEscape(dest), url.QueryEscape(fileName)), "", resp.Body) + if err != nil { + return err + } + if uploadResp.StatusCode != 200 { + return fmt.Errorf("Could not upload file [%s]. Status code: %d\n", fileName, uploadResp.StatusCode) + } + + return nil +} + +func (d *windows) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error { + uploadResp, err := http.Post(fmt.Sprintf("http://%s:222/terminals/1/uploads?dest=%s&file_name=%s", instance.IP, url.QueryEscape(dest), url.QueryEscape(fileName)), "", reader) + if err != nil { + return err + } + if uploadResp.StatusCode != 200 { + return fmt.Errorf("Could not upload file [%s]. Status code: %d\n", fileName, uploadResp.StatusCode) + } + + return nil +} + +func (d *windows) getWindowsInstanceInfo(sessionId string) (*instanceInfo, error) { + + input := &autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: []*string{aws.String("pwd-windows")}, + } + out, err := asgService.DescribeAutoScalingGroups(input) + + if err != nil { + return nil, err + } + + // there should always be one asg + instances := out.AutoScalingGroups[0].Instances + availInstances := make([]string, len(instances)) + + // reverse order so older instances are first served + sort.Sort(sort.Reverse(sort.StringSlice(availInstances))) + + for i, inst := range instances { + if *inst.LifecycleState == "InService" { + availInstances[i] = *inst.InstanceId + } + } + + assignedInstances, err := d.storage.WindowsInstanceGetAll() + assignedInstancesIds := []string{} + for _, ai := range assignedInstances { + assignedInstancesIds = append(assignedInstancesIds, ai.Id) + } + + if err != nil { + return nil, err + } + + avInstanceId := d.pickFreeInstance(sessionId, availInstances, assignedInstancesIds) + + if len(avInstanceId) == 0 { + return nil, OutOfCapacityError + } + + iout, err := ec2Service.DescribeInstances(&ec2.DescribeInstancesInput{ + InstanceIds: []*string{aws.String(avInstanceId)}, + }) + if err != nil { + // TODO retry x times and free the instance that was picked? + d.releaseInstance(avInstanceId) + return nil, err + } + + instance := iout.Reservations[0].Instances[0] + + instanceInfo := &instanceInfo{ + publicIP: *instance.PublicIpAddress, + privateIP: *instance.PrivateIpAddress, + id: avInstanceId, + } + + //TODO check for free instance, ASG capacity and return + + return instanceInfo, nil +} + +// select free instance and lock it into db. +// additionally check if ASG needs to be resized +func (d *windows) pickFreeInstance(sessionId string, availInstances, assignedInstances []string) string { + for _, av := range availInstances { + found := false + for _, as := range assignedInstances { + if av == as { + found = true + break + } + } + + if !found { + err := d.storage.WindowsInstancePut(&types.WindowsInstance{SessionId: sessionId, Id: av}) + if err != nil { + // TODO either storage error or instance is already assigned (race condition) + } + return av + } + } + // all availalbe instances are assigned + return "" +} diff --git a/pwd/client.go b/pwd/client.go new file mode 100644 index 0000000000000000000000000000000000000000..d01ad944c995a5ffb62ad52bf82388205a3d56b3 --- /dev/null +++ b/pwd/client.go @@ -0,0 +1,67 @@ +package pwd + +import ( + "log" + "time" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +func (p *pwd) ClientNew(id string, session *types.Session) *types.Client { + defer observeAction("ClientNew", time.Now()) + c := &types.Client{Id: id, SessionId: session.Id} + if err := p.storage.ClientPut(c); err != nil { + log.Println("Error saving client", err) + } + return c +} + +func (p *pwd) ClientResizeViewPort(c *types.Client, cols, rows uint) { + defer observeAction("ClientResizeViewPort", time.Now()) + c.ViewPort.Rows = rows + c.ViewPort.Cols = cols + + if err := p.storage.ClientPut(c); err != nil { + log.Println("Error saving client", err) + return + } + p.notifyClientSmallestViewPort(c.SessionId) +} + +func (p *pwd) ClientClose(client *types.Client) { + defer observeAction("ClientClose", time.Now()) + // Client has disconnected. Remove from session and recheck terminal sizes. + if err := p.storage.ClientDelete(client.Id); err != nil { + log.Println("Error deleting client", err) + return + } + p.notifyClientSmallestViewPort(client.SessionId) +} + +func (p *pwd) ClientCount() int { + count, err := p.storage.ClientCount() + if err != nil { + log.Println("Error counting clients", err) + return 0 + } + return count +} + +func (p *pwd) notifyClientSmallestViewPort(sessionId string) { + instances, err := p.storage.InstanceFindBySessionId(sessionId) + if err != nil { + log.Printf("Error finding instances for session [%s]. Got: %v\n", sessionId, err) + return + } + + vp := p.SessionGetSmallestViewPort(sessionId) + // Resize all terminals in the session + for _, instance := range instances { + err := p.InstanceResizeTerminal(instance, vp.Rows, vp.Cols) + if err != nil { + log.Println("Error resizing terminal", err) + } + } + p.event.Emit(event.INSTANCE_VIEWPORT_RESIZE, sessionId, vp.Cols, vp.Rows) +} diff --git a/pwd/client_test.go b/pwd/client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..20ef831c5427fd42547f35f4481172c9c7e8490e --- /dev/null +++ b/pwd/client_test.go @@ -0,0 +1,148 @@ +package pwd + +import ( + "context" + "testing" + "time" + + dtypes "github.com/docker/docker/api/types" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestClientNew(t *testing.T) { + _s := &storage.Mock{} + _f := &docker.FactoryMock{} + _g := &id.MockGenerator{} + _d := &docker.Mock{} + _e := &event.Mock{} + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("ClientCount").Return(1, nil) + _s.On("ClientPut", mock.AnythingOfType("*types.Client")).Return(nil) + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + playground := &types.Playground{Id: "foobar"} + + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, err) + + client := p.ClientNew("foobar", session) + + assert.Equal(t, types.Client{Id: "foobar", SessionId: session.Id, ViewPort: types.ViewPort{Cols: 0, Rows: 0}}, *client) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestClientCount(t *testing.T) { + _s := &storage.Mock{} + _f := &docker.FactoryMock{} + _g := &id.MockGenerator{} + _d := &docker.Mock{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("ClientPut", mock.AnythingOfType("*types.Client")).Return(nil) + _s.On("ClientCount").Return(1, nil) + _s.On("SessionCount").Return(1, nil) + _s.On("InstanceCount").Return(-1, nil) + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + playground := &types.Playground{Id: "foobar"} + + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, err) + + p.ClientNew("foobar", session) + + assert.Equal(t, 1, p.ClientCount()) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestClientResizeViewPort(t *testing.T) { + _s := &storage.Mock{} + _f := &docker.FactoryMock{} + _g := &id.MockGenerator{} + _d := &docker.Mock{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("InstanceFindBySessionId", "aaaabbbbcccc").Return([]*types.Instance{}, nil) + _s.On("ClientPut", mock.AnythingOfType("*types.Client")).Return(nil) + _s.On("ClientCount").Return(1, nil) + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + _e.M.On("Emit", event.INSTANCE_VIEWPORT_RESIZE, "aaaabbbbcccc", []interface{}{uint(80), uint(24)}).Return() + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + playground := &types.Playground{Id: "foobar"} + + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, err) + client := p.ClientNew("foobar", session) + _s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil) + + p.ClientResizeViewPort(client, 80, 24) + + assert.Equal(t, types.ViewPort{Cols: 80, Rows: 24}, client.ViewPort) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} diff --git a/pwd/instance.go b/pwd/instance.go new file mode 100644 index 0000000000000000000000000000000000000000..ba807e27c60b45134315e41f61d3cab4984f3b1a --- /dev/null +++ b/pwd/instance.go @@ -0,0 +1,161 @@ +package pwd + +import ( + "io" + "log" + "net" + "time" + + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +func (p *pwd) InstanceResizeTerminal(instance *types.Instance, rows, cols uint) error { + defer observeAction("InstanceResizeTerminal", time.Now()) + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return err + } + return prov.InstanceResizeTerminal(instance, rows, cols) +} + +func (p *pwd) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) { + defer observeAction("InstanceGetTerminal", time.Now()) + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return nil, err + } + return prov.InstanceGetTerminal(instance) +} + +func (p *pwd) InstanceUploadFromUrl(instance *types.Instance, fileName, dest string, url string) error { + defer observeAction("InstanceUploadFromUrl", time.Now()) + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return err + } + + return prov.InstanceUploadFromUrl(instance, fileName, dest, url) +} + +func (p *pwd) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error { + defer observeAction("InstanceUploadFromReader", time.Now()) + + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return err + } + + return prov.InstanceUploadFromReader(instance, fileName, dest, reader) +} + +func (p *pwd) InstanceGet(session *types.Session, name string) *types.Instance { + defer observeAction("InstanceGet", time.Now()) + instance, err := p.storage.InstanceGet(name) + if err != nil { + log.Println(err) + return nil + } + return instance +} + +func (p *pwd) InstanceFindBySession(session *types.Session) ([]*types.Instance, error) { + defer observeAction("InstanceFindBySession", time.Now()) + instances, err := p.storage.InstanceFindBySessionId(session.Id) + if err != nil { + log.Println(err) + return nil, err + } + return instances, nil +} + +func (p *pwd) InstanceDelete(session *types.Session, instance *types.Instance) error { + defer observeAction("InstanceDelete", time.Now()) + + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return err + } + + err = prov.InstanceDelete(session, instance) + if err != nil { + log.Println(err) + return err + } + + if err := p.storage.InstanceDelete(instance.Name); err != nil { + return err + } + + p.event.Emit(event.INSTANCE_DELETE, session.Id, instance.Name) + + p.setGauges() + + return nil +} + +func (p *pwd) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) { + defer observeAction("InstanceNew", time.Now()) + + prov, err := p.getProvisioner(conf.Type) + if err != nil { + return nil, err + } + + if config.ForceTLS { + conf.Tls = true + } + + instance, err := prov.InstanceNew(session, conf) + if err != nil { + log.Println(err) + return nil, err + } + + err = p.storage.InstancePut(instance) + if err != nil { + return nil, err + } + + p.event.Emit(event.INSTANCE_NEW, session.Id, instance.Name, instance.IP, instance.Hostname, instance.ProxyHost) + + p.setGauges() + + return instance, nil +} + +func (p *pwd) InstanceExec(instance *types.Instance, cmd []string) (int, error) { + defer observeAction("InstanceExec", time.Now()) + + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return -1, err + } + exitCode, err := prov.InstanceExec(instance, cmd) + if err != nil { + log.Println(err) + return -1, err + } + return exitCode, nil +} + +func (p *pwd) InstanceFSTree(instance *types.Instance) (io.Reader, error) { + defer observeAction("InstanceFSTree", time.Now()) + + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return nil, err + } + return prov.InstanceFSTree(instance) +} + +func (p *pwd) InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) { + defer observeAction("InstanceFile", time.Now()) + + prov, err := p.getProvisioner(instance.Type) + if err != nil { + return nil, err + } + return prov.InstanceFile(instance, filePath) +} diff --git a/pwd/instance_test.go b/pwd/instance_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d52fda1b8bddeb69677b2e0b73c1a5bf21bfa908 --- /dev/null +++ b/pwd/instance_test.go @@ -0,0 +1,350 @@ +package pwd + +import ( + "context" + "fmt" + "testing" + "time" + + dtypes "github.com/docker/docker/api/types" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/router" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestInstanceResizeTerminal(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + s := &types.Session{Id: "aaaabbbbcccc"} + _d.On("ContainerResize", "foobar", uint(24), uint(80)).Return(nil) + _s.On("SessionGet", "aaaabbbbcccc").Return(s, nil) + _f.On("GetForSession", s).Return(_d, nil) + + p := NewPWD(_f, _e, _s, sp, ipf) + + err := p.InstanceResizeTerminal(&types.Instance{Name: "foobar", SessionId: "aaaabbbbcccc"}, 24, 80) + assert.Nil(t, err) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestInstanceNew(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("ClientCount").Return(0, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("InstanceFindBySessionId", "aaaabbbbcccc").Return([]*types.Instance{}, nil) + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + playground := &types.Playground{Id: "foobar", DefaultDinDInstanceImage: "franela/dind"} + + _s.On("PlaygroundGet", "foobar").Return(playground, nil) + + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, err) + + expectedInstance := types.Instance{ + Name: fmt.Sprintf("%s_aaaabbbbcccc", session.Id[:8]), + Hostname: "node1", + IP: "10.0.0.1", + RoutableIP: "10.0.0.1", + Image: "franela/dind", + SessionId: session.Id, + SessionHost: session.Host, + ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}), + } + expectedContainerOpts := docker.CreateContainerOpts{ + Image: expectedInstance.Image, + SessionId: session.Id, + ContainerName: expectedInstance.Name, + Hostname: expectedInstance.Hostname, + ServerCert: nil, + ServerKey: nil, + CACert: nil, + Privileged: false, + HostFQDN: "something.play-with-docker.com", + Networks: []string{session.Id}, + } + _d.On("ContainerCreate", expectedContainerOpts).Return(nil) + _d.On("ContainerIPs", expectedInstance.Name).Return(map[string]string{session.Id: "10.0.0.1"}, nil) + _s.On("InstancePut", mock.AnythingOfType("*types.Instance")).Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_aaaabbbbcccc", "10.0.0.1", "node1", "ip10-0-0-1-aaaabbbbcccc"}).Return() + + instance, err := p.InstanceNew(session, types.InstanceConfig{PlaygroundFQDN: "something.play-with-docker.com"}) + assert.Nil(t, err) + + assert.Equal(t, expectedInstance, *instance) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestInstanceNew_Privileged(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("ClientCount").Return(0, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("InstanceFindBySessionId", "aaaabbbbcccc").Return([]*types.Instance{}, nil) + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + playground := &types.Playground{Id: "foobar"} + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + + assert.Nil(t, err) + + // Switch to unsafe mode in order to test custom networks below + // + // TODO: move config away from being a global in order that we don't + // have to hack setting the context in this way. + config.Unsafe = true + defer func() { + config.Unsafe = false + }() + + expectedInstance := types.Instance{ + Name: fmt.Sprintf("%s_aaaabbbbcccc", session.Id[:8]), + Hostname: "node1", + IP: "10.0.0.1", + RoutableIP: "10.0.0.1", + Image: "redis", + SessionId: session.Id, + SessionHost: session.Host, + ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}), + } + expectedContainerOpts := docker.CreateContainerOpts{ + Image: expectedInstance.Image, + SessionId: session.Id, + ContainerName: expectedInstance.Name, + Hostname: expectedInstance.Hostname, + ServerCert: nil, + ServerKey: nil, + CACert: nil, + Privileged: true, + Envs: []string{"HELLO=WORLD"}, + Networks: []string{session.Id, "arpanet"}, + } + _d.On("ContainerCreate", expectedContainerOpts).Return(nil) + _d.On("ContainerIPs", expectedInstance.Name).Return(map[string]string{session.Id: "10.0.0.1"}, nil) + _s.On("InstancePut", mock.AnythingOfType("*types.Instance")).Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_aaaabbbbcccc", "10.0.0.1", "node1", "ip10-0-0-1-aaaabbbbcccc"}).Return() + + instance, err := p.InstanceNew(session, types.InstanceConfig{ImageName: "redis", Envs: []string{"HELLO=WORLD"}, Networks: []string{"arpanet"}, Privileged: true}) + assert.Nil(t, err) + + assert.Equal(t, expectedInstance, *instance) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestInstanceNew_WithNotAllowedImage(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("ClientCount").Return(0, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("InstanceFindBySessionId", "aaaabbbbcccc").Return([]*types.Instance{}, nil) + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + playground := &types.Playground{Id: "foobar"} + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + + assert.Nil(t, err) + + // Switch to unsafe mode in order to test custom networks below + // + // TODO: move config away from being a global in order that we don't + // have to hack setting the context in this way. + config.Unsafe = true + defer func() { + config.Unsafe = false + }() + + expectedInstance := types.Instance{ + Name: fmt.Sprintf("%s_aaaabbbbcccc", session.Id[:8]), + Hostname: "node1", + IP: "10.0.0.1", + RoutableIP: "10.0.0.1", + Image: "redis", + SessionId: session.Id, + SessionHost: session.Host, + ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}), + } + expectedContainerOpts := docker.CreateContainerOpts{ + Image: expectedInstance.Image, + SessionId: session.Id, + ContainerName: expectedInstance.Name, + Hostname: expectedInstance.Hostname, + ServerCert: nil, + ServerKey: nil, + CACert: nil, + Privileged: false, + Envs: []string{"HELLO=WORLD"}, + Networks: []string{session.Id, "arpanet"}, + } + _d.On("ContainerCreate", expectedContainerOpts).Return(nil) + _d.On("ContainerIPs", expectedInstance.Name).Return(map[string]string{session.Id: "10.0.0.1"}, nil) + _s.On("InstancePut", mock.AnythingOfType("*types.Instance")).Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_aaaabbbbcccc", "10.0.0.1", "node1", "ip10-0-0-1-aaaabbbbcccc"}).Return() + + instance, err := p.InstanceNew(session, types.InstanceConfig{ImageName: "redis", Envs: []string{"HELLO=WORLD"}, Networks: []string{"arpanet"}}) + assert.Nil(t, err) + + assert.Equal(t, expectedInstance, *instance) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestInstanceNew_WithCustomHostname(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("ClientCount").Return(0, nil) + _s.On("InstanceCount").Return(0, nil) + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + playground := &types.Playground{Id: "foobar"} + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + session, err := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, err) + + expectedInstance := types.Instance{ + Name: fmt.Sprintf("%s_aaaabbbbcccc", session.Id[:8]), + Hostname: "redis-master", + IP: "10.0.0.1", + RoutableIP: "10.0.0.1", + Image: "redis", + SessionHost: session.Host, + SessionId: session.Id, + ProxyHost: router.EncodeHost(session.Id, "10.0.0.1", router.HostOpts{}), + } + expectedContainerOpts := docker.CreateContainerOpts{ + Image: expectedInstance.Image, + SessionId: session.Id, + ContainerName: expectedInstance.Name, + Hostname: expectedInstance.Hostname, + ServerCert: nil, + ServerKey: nil, + CACert: nil, + Privileged: false, + Networks: []string{session.Id}, + } + + _d.On("ContainerCreate", expectedContainerOpts).Return(nil) + _d.On("ContainerIPs", expectedInstance.Name).Return(map[string]string{session.Id: "10.0.0.1"}, nil) + _s.On("InstancePut", mock.AnythingOfType("*types.Instance")).Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_aaaabbbbcccc", "10.0.0.1", "redis-master", "ip10-0-0-1-aaaabbbbcccc"}).Return() + + instance, err := p.InstanceNew(session, types.InstanceConfig{ImageName: "redis", Hostname: "redis-master"}) + + assert.Nil(t, err) + + assert.Equal(t, expectedInstance, *instance) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} diff --git a/pwd/mock.go b/pwd/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..e39c9010022780f8dae19476a84a5d30a003a17f --- /dev/null +++ b/pwd/mock.go @@ -0,0 +1,155 @@ +package pwd + +import ( + "context" + "io" + "net" + + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/mock" +) + +type Mock struct { + mock.Mock +} + +func (m *Mock) SessionNew(ctx context.Context, config types.SessionConfig) (*types.Session, error) { + args := m.Called(ctx, config) + return args.Get(0).(*types.Session), args.Error(1) +} + +func (m *Mock) SessionClose(session *types.Session) error { + args := m.Called(session) + return args.Error(0) +} + +func (m *Mock) SessionGetSmallestViewPort(sessionId string) types.ViewPort { + args := m.Called(sessionId) + return args.Get(0).(types.ViewPort) +} + +func (m *Mock) SessionDeployStack(session *types.Session) error { + args := m.Called(session) + return args.Error(0) +} + +func (m *Mock) SessionGet(id string) (*types.Session, error) { + args := m.Called(id) + return args.Get(0).(*types.Session), args.Error(1) +} + +func (m *Mock) SessionSetup(session *types.Session, conf SessionSetupConf) error { + args := m.Called(session, conf) + return args.Error(0) +} + +func (m *Mock) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) { + args := m.Called(session, conf) + return args.Get(0).(*types.Instance), args.Error(1) +} + +func (m *Mock) InstanceResizeTerminal(instance *types.Instance, cols, rows uint) error { + args := m.Called(instance, cols, rows) + return args.Error(0) +} + +func (m *Mock) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) { + args := m.Called(instance) + return args.Get(0).(net.Conn), args.Error(1) +} + +func (m *Mock) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, url string) error { + args := m.Called(instance, fileName, dest, url) + return args.Error(0) +} + +func (m *Mock) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error { + args := m.Called(instance, fileName, dest, reader) + return args.Error(0) +} + +func (m *Mock) InstanceGet(session *types.Session, name string) *types.Instance { + args := m.Called(session, name) + return args.Get(0).(*types.Instance) +} +func (m *Mock) InstanceFindBySession(session *types.Session) ([]*types.Instance, error) { + args := m.Called(session) + return args.Get(0).([]*types.Instance), args.Error(1) +} + +func (m *Mock) InstanceDelete(session *types.Session, instance *types.Instance) error { + args := m.Called(session, instance) + return args.Error(0) +} + +func (m *Mock) InstanceExec(instance *types.Instance, cmd []string) (int, error) { + args := m.Called(instance, cmd) + return args.Int(0), args.Error(1) +} + +func (m *Mock) InstanceFSTree(instance *types.Instance) (io.Reader, error) { + args := m.Called(instance) + return args.Get(0).(io.Reader), args.Error(1) +} + +func (m *Mock) InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) { + args := m.Called(instance, filePath) + return args.Get(0).(io.Reader), args.Error(1) +} + +func (m *Mock) ClientNew(id string, session *types.Session) *types.Client { + args := m.Called(id, session) + return args.Get(0).(*types.Client) +} + +func (m *Mock) ClientResizeViewPort(client *types.Client, cols, rows uint) { + m.Called(client, cols, rows) +} + +func (m *Mock) ClientClose(client *types.Client) { + m.Called(client) +} + +func (m *Mock) ClientCount() int { + args := m.Called() + return args.Int(0) +} + +func (m *Mock) UserNewLoginRequest(providerName string) (*types.LoginRequest, error) { + args := m.Called(providerName) + return args.Get(0).(*types.LoginRequest), args.Error(1) +} + +func (m *Mock) UserGetLoginRequest(id string) (*types.LoginRequest, error) { + args := m.Called(id) + return args.Get(0).(*types.LoginRequest), args.Error(1) +} + +func (m *Mock) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error) { + args := m.Called(loginRequest, user) + return args.Get(0).(*types.User), args.Error(1) +} + +func (m *Mock) UserGet(id string) (*types.User, error) { + args := m.Called(id) + return args.Get(0).(*types.User), args.Error(1) +} + +func (m *Mock) PlaygroundNew(playground types.Playground) (*types.Playground, error) { + args := m.Called(playground) + return args.Get(0).(*types.Playground), args.Error(1) +} + +func (m *Mock) PlaygroundGet(id string) *types.Playground { + args := m.Called(id) + return args.Get(0).(*types.Playground) +} + +func (m *Mock) PlaygroundFindByDomain(domain string) *types.Playground { + args := m.Called(domain) + return args.Get(0).(*types.Playground) +} +func (m *Mock) PlaygroundList() ([]*types.Playground, error) { + args := m.Called() + return args.Get(0).([]*types.Playground), args.Error(1) +} diff --git a/pwd/playground.go b/pwd/playground.go new file mode 100644 index 0000000000000000000000000000000000000000..2cd0f27f8287b4d1c83c3ca1335c0000c35171c4 --- /dev/null +++ b/pwd/playground.go @@ -0,0 +1,38 @@ +package pwd + +import ( + "log" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/satori/go.uuid" +) + +func (p *pwd) PlaygroundNew(playground types.Playground) (*types.Playground, error) { + playground.Id = uuid.NewV5(uuid.NamespaceOID, playground.Domain).String() + if err := p.storage.PlaygroundPut(&playground); err != nil { + log.Printf("Error saving playground %s. Got: %v\n", playground.Id, err) + return nil, err + } + + p.event.Emit(event.PLAYGROUND_NEW, playground.Id) + return &playground, nil +} + +func (p *pwd) PlaygroundGet(id string) *types.Playground { + if playground, err := p.storage.PlaygroundGet(id); err != nil { + log.Printf("Error retrieving playground %s. Got: %v\n", id, err) + return nil + } else { + return playground + } +} + +func (p *pwd) PlaygroundFindByDomain(domain string) *types.Playground { + id := uuid.NewV5(uuid.NamespaceOID, domain).String() + return p.PlaygroundGet(id) +} + +func (p *pwd) PlaygroundList() ([]*types.Playground, error) { + return p.storage.PlaygroundGetAll() +} diff --git a/pwd/playground_test.go b/pwd/playground_test.go new file mode 100644 index 0000000000000000000000000000000000000000..26cb5d8a7a74b3c2aed5ba83dced0aeed995dbf5 --- /dev/null +++ b/pwd/playground_test.go @@ -0,0 +1,161 @@ +package pwd + +import ( + "testing" + "time" + + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestPlaygroundNew(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + var nilArgs []interface{} + _e.M.On("Emit", event.PLAYGROUND_NEW, uuid.NewV5(uuid.NamespaceOID, "localhost").String(), nilArgs).Return() + _s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil) + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}} + playground, e := p.PlaygroundNew(expectedPlayground) + assert.Nil(t, e) + assert.NotNil(t, playground) + + expectedPlayground.Id = uuid.NewV5(uuid.NamespaceOID, "localhost").String() + assert.Equal(t, expectedPlayground, *playground) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestPlaygroundGet(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + var nilArgs []interface{} + _e.M.On("Emit", event.PLAYGROUND_NEW, uuid.NewV5(uuid.NamespaceOID, "localhost").String(), nilArgs).Return() + _s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil) + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}} + playground, e := p.PlaygroundNew(expectedPlayground) + assert.Nil(t, e) + assert.NotNil(t, playground) + + _s.On("PlaygroundGet", playground.Id).Return(playground, nil) + + playground2 := p.PlaygroundGet(playground.Id) + assert.NotNil(t, playground2) + + assert.Equal(t, *playground, *playground2) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestPlaygroundFindByDomain(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + var nilArgs []interface{} + _e.M.On("Emit", event.PLAYGROUND_NEW, uuid.NewV5(uuid.NamespaceOID, "localhost").String(), nilArgs).Return() + _s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil) + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + expectedPlayground := types.Playground{Domain: "localhost", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}} + playground, e := p.PlaygroundNew(expectedPlayground) + assert.Nil(t, e) + assert.NotNil(t, playground) + + _s.On("PlaygroundGet", playground.Id).Return(playground, nil) + + playground2 := p.PlaygroundFindByDomain("localhost") + assert.NotNil(t, playground2) + + assert.Equal(t, *playground, *playground2) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestPlaygroundList(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + var nilArgs []interface{} + _e.M.On("Emit", event.PLAYGROUND_NEW, uuid.NewV5(uuid.NamespaceOID, "localhost1").String(), nilArgs).Return() + _e.M.On("Emit", event.PLAYGROUND_NEW, uuid.NewV5(uuid.NamespaceOID, "localhost2").String(), nilArgs).Return() + _s.On("PlaygroundPut", mock.AnythingOfType("*types.Playground")).Return(nil) + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + pd := types.Playground{Domain: "localhost1", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}} + p1, e := p.PlaygroundNew(pd) + assert.Nil(t, e) + assert.NotNil(t, p1) + + pd = types.Playground{Domain: "localhost2", DefaultDinDInstanceImage: "franela/dind", AllowWindowsInstances: false, DefaultSessionDuration: time.Hour * 3, Extras: types.PlaygroundExtras{"foo": "bar"}} + p2, e := p.PlaygroundNew(pd) + assert.Nil(t, e) + assert.NotNil(t, p2) + + _s.On("PlaygroundGetAll").Return([]*types.Playground{p1, p2}, nil) + + received, err := p.PlaygroundList() + assert.Nil(t, err) + assert.NotNil(t, received) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} diff --git a/pwd/pwd.go b/pwd/pwd.go new file mode 100644 index 0000000000000000000000000000000000000000..703fccda058b40016372bf9f21e33c6f913a7985 --- /dev/null +++ b/pwd/pwd.go @@ -0,0 +1,125 @@ +package pwd + +import ( + "context" + "errors" + "io" + "net" + "time" + + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + sessionsGauge = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "sessions", + Help: "Sessions", + }) + clientsGauge = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "clients", + Help: "Clients", + }) + instancesGauge = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "instances", + Help: "Instances", + }) + + latencyHistogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "pwd_action_duration_ms", + Help: "How long it took to process a specific action, in a specific host", + Buckets: []float64{300, 1200, 5000}, + }, []string{"action"}) +) + +func observeAction(action string, start time.Time) { + latencyHistogramVec.WithLabelValues(action).Observe(float64(time.Since(start).Nanoseconds()) / 1000000) +} + +func init() { + prometheus.MustRegister(sessionsGauge) + prometheus.MustRegister(clientsGauge) + prometheus.MustRegister(instancesGauge) + prometheus.MustRegister(latencyHistogramVec) +} + +type pwd struct { + dockerFactory docker.FactoryApi + event event.EventApi + storage storage.StorageApi + generator id.Generator + clientCount int32 + sessionProvisioner provisioner.SessionProvisionerApi + instanceProvisionerFactory provisioner.InstanceProvisionerFactoryApi + windowsProvisioner provisioner.InstanceProvisionerApi + dindProvisioner provisioner.InstanceProvisionerApi +} + +var sessionNotEmpty = errors.New("Session is not empty") + +func SessionNotEmpty(e error) bool { + return e == sessionNotEmpty +} + +type PWDApi interface { + SessionNew(ctx context.Context, config types.SessionConfig) (*types.Session, error) + SessionClose(session *types.Session) error + SessionGetSmallestViewPort(sessionId string) types.ViewPort + SessionDeployStack(session *types.Session) error + SessionGet(id string) (*types.Session, error) + SessionSetup(session *types.Session, conf SessionSetupConf) error + + InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) + InstanceResizeTerminal(instance *types.Instance, cols, rows uint) error + InstanceGetTerminal(instance *types.Instance) (net.Conn, error) + InstanceUploadFromUrl(instance *types.Instance, fileName, dest, url string) error + InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error + InstanceGet(session *types.Session, name string) *types.Instance + InstanceFindBySession(session *types.Session) ([]*types.Instance, error) + InstanceDelete(session *types.Session, instance *types.Instance) error + InstanceExec(instance *types.Instance, cmd []string) (int, error) + InstanceFSTree(instance *types.Instance) (io.Reader, error) + InstanceFile(instance *types.Instance, filePath string) (io.Reader, error) + + ClientNew(id string, session *types.Session) *types.Client + ClientResizeViewPort(client *types.Client, cols, rows uint) + ClientClose(client *types.Client) + ClientCount() int + + UserNewLoginRequest(providerName string) (*types.LoginRequest, error) + UserGetLoginRequest(id string) (*types.LoginRequest, error) + UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error) + UserGet(id string) (*types.User, error) + + PlaygroundNew(playground types.Playground) (*types.Playground, error) + PlaygroundGet(id string) *types.Playground + PlaygroundFindByDomain(domain string) *types.Playground + PlaygroundList() ([]*types.Playground, error) +} + +func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi, sp provisioner.SessionProvisionerApi, ipf provisioner.InstanceProvisionerFactoryApi) *pwd { + // windowsProvisioner: provisioner.NewWindowsASG(f, s), dindProvisioner: provisioner.NewDinD(f) + return &pwd{dockerFactory: f, event: e, storage: s, generator: id.XIDGenerator{}, sessionProvisioner: sp, instanceProvisionerFactory: ipf} +} + +func (p *pwd) getProvisioner(t string) (provisioner.InstanceProvisionerApi, error) { + return p.instanceProvisionerFactory.GetProvisioner(t) +} + +func (p *pwd) setGauges() { + s, _ := p.storage.SessionCount() + ses := float64(s) + i, _ := p.storage.InstanceCount() + ins := float64(i) + c := p.ClientCount() + cli := float64(c) + + clientsGauge.Set(cli) + instancesGauge.Set(ins) + sessionsGauge.Set(ses) +} diff --git a/pwd/session.go b/pwd/session.go new file mode 100644 index 0000000000000000000000000000000000000000..2f031dc806abe76d259fcff3229c57e8ba3a5461 --- /dev/null +++ b/pwd/session.go @@ -0,0 +1,355 @@ +package pwd + +import ( + "context" + "errors" + "fmt" + "log" + "math" + "path" + "path/filepath" + "sync" + "time" + + "golang.org/x/sync/errgroup" + + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +var preparedSessions = map[string]bool{} + +type AccessDeniedError struct { + Err error +} + +func (u *AccessDeniedError) Error() string { + return fmt.Sprintf("Acess denied error: %s", u.Err.Error()) +} + +func (u *AccessDeniedError) Unwrap() error { + return u.Err +} + +type sessionBuilderWriter struct { + sessionId string + event event.EventApi +} + +func (s *sessionBuilderWriter) Write(p []byte) (n int, err error) { + s.event.Emit(event.SESSION_BUILDER_OUT, s.sessionId, string(p)) + return len(p), nil +} + +type SessionSetupConf struct { + Instances []SessionSetupInstanceConf `json:"instances"` + PlaygroundFQDN string + DindVolumeSize string + Privileged bool +} + +type SessionSetupInstanceConf struct { + Image string `json:"image"` + Hostname string `json:"hostname"` + IsSwarmManager bool `json:"is_swarm_manager"` + IsSwarmWorker bool `json:"is_swarm_worker"` + Type string `json:"type"` + Run [][]string `json:"run"` + Tls bool `json:"tls"` +} + +func (p *pwd) SessionNew(ctx context.Context, config types.SessionConfig) (*types.Session, error) { + defer observeAction("SessionNew", time.Now()) + + // Annonymous users should be also allowed to login + if config.UserId != "" { + if _, err := p.UserGet(config.UserId); errors.Is(err, userBannedError) { + return nil, &AccessDeniedError{err} + } else if err != nil { + return nil, err + } + } + + s := &types.Session{} + s.Id = p.generator.NewId() + s.CreatedAt = time.Now() + s.ExpiresAt = s.CreatedAt.Add(config.Duration) + s.Ready = true + s.Stack = config.Stack + s.UserId = config.UserId + s.PlaygroundId = config.Playground.Id + + if s.Stack != "" { + s.Ready = false + } + stackName := config.StackName + if stackName == "" { + stackName = "pwd" + } + s.StackName = stackName + s.ImageName = config.ImageName + + log.Printf("NewSession id=[%s]\n", s.Id) + if err := p.sessionProvisioner.SessionNew(ctx, s); err != nil { + log.Println(err) + return nil, err + } + + if err := p.storage.SessionPut(s); err != nil { + log.Println(err) + return nil, err + } + + p.setGauges() + p.event.Emit(event.SESSION_NEW, s.Id) + + return s, nil +} + +func (p *pwd) SessionClose(s *types.Session) error { + defer observeAction("SessionClose", time.Now()) + + log.Printf("Starting clean up of session [%s]\n", s.Id) + g, _ := errgroup.WithContext(context.Background()) + instances, err := p.storage.InstanceFindBySessionId(s.Id) + if err != nil { + log.Printf("Could not find instances in session %s. Got %v\n", s.Id, err) + return err + } + for _, i := range instances { + i := i + g.Go(func() error { + return p.InstanceDelete(s, i) + }) + } + err = g.Wait() + if err != nil { + log.Println(err) + return err + } + + if err := p.sessionProvisioner.SessionClose(s); err != nil { + log.Println(err) + return err + } + + err = p.storage.SessionDelete(s.Id) + if err != nil { + return err + } + + log.Printf("Cleaned up session [%s]\n", s.Id) + p.setGauges() + p.event.Emit(event.SESSION_END, s.Id) + return nil +} + +func (p *pwd) SessionGetSmallestViewPort(sessionId string) types.ViewPort { + defer observeAction("SessionGetSmallestViewPort", time.Now()) + + clients, err := p.storage.ClientFindBySessionId(sessionId) + if err != nil { + log.Printf("Error finding clients for session [%s]. Got: %v\n", sessionId, err) + return types.ViewPort{Rows: 24, Cols: 80} + } + if len(clients) == 0 { + log.Printf("Session [%s] doesn't have clients. Returning default viewport\n", sessionId) + return types.ViewPort{Rows: 24, Cols: 80} + } + var minRows uint + var minCols uint + + for _, c := range clients { + if c.ViewPort.Rows > 0 && c.ViewPort.Cols > 0 { + minRows = uint(c.ViewPort.Rows) + minCols = uint(c.ViewPort.Cols) + break + } + } + + for _, c := range clients { + if c.ViewPort.Rows > 0 && c.ViewPort.Cols > 0 { + minRows = uint(math.Min(float64(minRows), float64(c.ViewPort.Rows))) + minCols = uint(math.Min(float64(minCols), float64(c.ViewPort.Cols))) + } + } + + return types.ViewPort{Rows: minRows, Cols: minCols} +} + +func (p *pwd) SessionDeployStack(s *types.Session) error { + defer observeAction("SessionDeployStack", time.Now()) + + if s.Ready { + // a stack was already deployed on this session, just ignore + return nil + } + + s.Ready = false + p.event.Emit(event.SESSION_READY, s.Id, false) + i, err := p.InstanceNew(s, types.InstanceConfig{ImageName: s.ImageName, PlaygroundFQDN: s.Host, DindVolumeSize: "5G", Privileged: true}) + if err != nil { + log.Printf("Error creating instance for stack [%s]: %s\n", s.Stack, err) + return err + } + + _, fileName := filepath.Split(s.Stack) + err = p.InstanceUploadFromUrl(i, fileName, "/var/run/pwd/uploads", s.Stack) + if err != nil { + log.Printf("Error uploading stack file [%s]: %s\n", s.Stack, err) + return err + } + + fileName = path.Base(s.Stack) + file := fmt.Sprintf("/var/run/pwd/uploads/%s", fileName) + cmd := fmt.Sprintf("docker swarm init --advertise-addr eth0 && docker-compose -f %s pull && docker stack deploy -c %s %s", file, file, s.StackName) + + w := sessionBuilderWriter{sessionId: s.Id, event: p.event} + + dockerClient, err := p.dockerFactory.GetForSession(s) + if err != nil { + log.Println(err) + return err + } + + if _, err := p.dockerFactory.GetForInstance(i); err != nil { + log.Printf("error retrieving docker client for new instance %v", err) + return err + } + + code, err := dockerClient.ExecAttach(i.Name, []string{"sh", "-c", cmd}, &w) + if err != nil { + log.Printf("Error executing stack [%s]: %s\n", s.Stack, err) + return err + } + + log.Printf("Stack execution finished with code %d\n", code) + s.Ready = true + p.event.Emit(event.SESSION_READY, s.Id, true) + if err := p.storage.SessionPut(s); err != nil { + return err + } + return nil +} + +func (p *pwd) SessionGet(sessionId string) (*types.Session, error) { + defer observeAction("SessionGet", time.Now()) + + s, err := p.storage.SessionGet(sessionId) + if err != nil { + log.Println(err) + return nil, err + } + + return s, nil +} + +func (p *pwd) SessionSetup(session *types.Session, sconf SessionSetupConf) error { + defer observeAction("SessionSetup", time.Now()) + + c := sync.NewCond(&sync.Mutex{}) + + var tokens *docker.SwarmTokens = nil + var firstSwarmManager *types.Instance = nil + + instances, err := p.storage.InstanceFindBySessionId(session.Id) + if err != nil { + log.Println(err) + return err + } + if len(instances) > 0 { + return sessionNotEmpty + } + + g, ctx := errgroup.WithContext(context.Background()) + + for _, conf := range sconf.Instances { + conf := conf + g.Go(func() error { + instanceConf := types.InstanceConfig{ + ImageName: conf.Image, + Hostname: conf.Hostname, + PlaygroundFQDN: sconf.PlaygroundFQDN, + Type: conf.Type, + Tls: conf.Tls, + DindVolumeSize: sconf.DindVolumeSize, + Privileged: sconf.Privileged, + } + i, err := p.InstanceNew(session, instanceConf) + if err != nil { + return err + } + + if conf.IsSwarmManager || conf.IsSwarmWorker { + dockerClient, err := p.dockerFactory.GetForInstance(i) + if err != nil { + return err + } + if conf.IsSwarmManager { + c.L.Lock() + if firstSwarmManager == nil { + tkns, err := dockerClient.SwarmInit(i.IP) + if err != nil { + log.Printf("Cannot initialize swarm on instance %s. Got: %v\n", i.Name, err) + return err + } + tokens = tkns + firstSwarmManager = i + c.Broadcast() + c.L.Unlock() + } else { + c.L.Unlock() + if err := dockerClient.SwarmJoin(fmt.Sprintf("%s:2377", firstSwarmManager.IP), tokens.Manager); err != nil { + log.Printf("Cannot join manager %s to swarm. Got: %v\n", i.Name, err) + return err + } + } + } else if conf.IsSwarmWorker { + c.L.Lock() + if firstSwarmManager == nil { + c.Wait() + } + c.L.Unlock() + err = dockerClient.SwarmJoin(fmt.Sprintf("%s:2377", firstSwarmManager.IP), tokens.Worker) + if err != nil { + log.Printf("Cannot join worker %s to swarm. Got: %v\n", i.Name, err) + return err + } + } + } + + for _, cmd := range conf.Run { + errch := make(chan error) + go func() { + exitCode, err := p.InstanceExec(i, cmd) + fmt.Printf("Finished execuing command [%s] on instance %s with code [%d] and err [%v]\n", cmd, i.Name, exitCode, err) + + if err != nil { + errch <- err + } + if exitCode != 0 { + errch <- fmt.Errorf("Command returned %d on instance %s", exitCode, i.IP) + } + errch <- nil + }() + + // ctx.Done() could be called if the errgroup is cancelled due to a previous error. In that case, return immediately + select { + case err = <-errch: + return err + case <-ctx.Done(): + return ctx.Err() + } + } + return nil + }) + } + + if err := g.Wait(); err != nil { + log.Println(err) + return err + } + + return nil +} diff --git a/pwd/session_test.go b/pwd/session_test.go new file mode 100644 index 0000000000000000000000000000000000000000..81188bafebf7bb2661116115816547b3df916582 --- /dev/null +++ b/pwd/session_test.go @@ -0,0 +1,209 @@ +package pwd + +import ( + "context" + "errors" + "testing" + "time" + + dtypes "github.com/docker/docker/api/types" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/id" + "github.com/play-with-docker/play-with-docker/provisioner" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestSessionNew(t *testing.T) { + config.PWDContainerName = "pwd" + + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", mock.AnythingOfType("*types.Session")).Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("ClientCount").Return(0, nil) + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + before := time.Now() + + playground := &types.Playground{Id: "foobar"} + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + s, e := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, e) + assert.NotNil(t, s) + + assert.Equal(t, "pwd", s.StackName) + + assert.NotEmpty(t, s.Id) + assert.WithinDuration(t, s.CreatedAt, before, time.Since(before)) + assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second) + assert.True(t, s.Ready) + + sConfig = types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "stackPath", StackName: "stackName", ImageName: "imageName"} + s, _ = p.SessionNew(context.Background(), sConfig) + + assert.Equal(t, "stackPath", s.Stack) + assert.Equal(t, "stackName", s.StackName) + assert.Equal(t, "imageName", s.ImageName) + assert.Equal(t, "localhost", s.Host) + assert.Equal(t, playground.Id, s.PlaygroundId) + assert.False(t, s.Ready) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +func TestSessionFailWhenUserIsBanned(t *testing.T) { + config.PWDContainerName = "pwd" + + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &id.MockGenerator{} + _e := &event.Mock{} + + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_g, _f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _s.On("UserGet", mock.Anything).Return(&types.User{IsBanned: true}, nil) + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + + playground := &types.Playground{Id: "foobar"} + sConfig := types.SessionConfig{Playground: playground, UserId: "some_user", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + s, e := p.SessionNew(context.Background(), sConfig) + assert.NotNil(t, e) + assert.Nil(t, s) + assert.True(t, errors.Is(e, userBannedError)) + assert.Contains(t, e.Error(), "banned") + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} + +/* + +************************** Not sure how to test this as it can pick any manager as the first node in the swarm cluster. + + +func TestSessionSetup(t *testing.T) { + _d := &docker.Mock{} + _f := &docker.FactoryMock{} + _s := &storage.Mock{} + _g := &mockGenerator{} + _e := &event.Mock{} + ipf := provisioner.NewInstanceProvisionerFactory(provisioner.NewWindowsASG(_f, _s), provisioner.NewDinD(_f, _s)) + sp := provisioner.NewOverlaySessionProvisioner(_f) + + _g.On("NewId").Return("aaaabbbbcccc") + _f.On("GetForSession", "aaaabbbbcccc").Return(_d, nil) + _d.On("NetworkCreate", "aaaabbbbcccc", dtypes.NetworkCreate{Attachable: true, Driver: "overlay"}).Return(nil) + _d.On("DaemonHost").Return("localhost") + _d.On("NetworkConnect", config.L2ContainerName, "aaaabbbbcccc", "").Return("10.0.0.1", nil) + _s.On("SessionPut", mock.AnythingOfType("*types.Session")).Return(nil) + _s.On("InstancePut", mock.AnythingOfType("*types.Instance")).Return(nil) + _s.On("SessionCount").Return(1, nil) + _s.On("ClientCount").Return(1, nil) + _s.On("InstanceCount").Return(0, nil) + _s.On("InstanceFindBySessionId", "aaaabbbbcccc").Return([]*types.Instance{}, nil) + + _d.On("CreateContainer", docker.CreateContainerOpts{Image: "franela/dind", SessionId: "aaaabbbbcccc", ContainerName: "aaaabbbb_manager1", Hostname: "manager1", Privileged: true, HostFQDN: "localhost", Networks: []string{"aaaabbbbcccc"}}).Return(nil) + _d.On("ContainerIPs", "aaaabbbb_manager1").Return(map[string]string{"aaaabbbbcccc": "10.0.0.2"}, nil) + _f.On("GetForInstance", mock.AnythingOfType("*types.Instance")).Return(_d, nil) + _d.On("SwarmInit").Return(&docker.SwarmTokens{Manager: "managerToken", Worker: "workerToken"}, nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_manager1", "10.0.0.2", "manager1", "ip10-0-0-2-aaaabbbbcccc"}).Return() + + _d.On("CreateContainer", docker.CreateContainerOpts{Image: "franela/dind", SessionId: "aaaabbbbcccc", ContainerName: "aaaabbbb_manager2", Hostname: "manager2", Privileged: true, HostFQDN: "localhost", Networks: []string{"aaaabbbbcccc"}}).Return(nil) + _d.On("ContainerIPs", "aaaabbbb_manager2").Return(map[string]string{"aaaabbbbcccc": "10.0.0.3"}, nil) + _f.On("GetForInstance", mock.AnythingOfType("*types.Instance")).Return(_d, nil) + _d.On("SwarmJoin", "10.0.0.2:2377", "managerToken").Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_manager2", "10.0.0.3", "manager2", "ip10-0-0-3-aaaabbbbcccc"}).Return() + + _d.On("CreateContainer", docker.CreateContainerOpts{Image: "franela/dind:overlay2-dev", SessionId: "aaaabbbbcccc", ContainerName: "aaaabbbb_manager3", Hostname: "manager3", Privileged: true, HostFQDN: "localhost", Networks: []string{"aaaabbbbcccc"}}).Return(nil) + _d.On("ContainerIPs", "aaaabbbb_manager3").Return(map[string]string{"aaaabbbbcccc": "10.0.0.4"}, nil) + _f.On("GetForInstance", mock.AnythingOfType("*types.Instance")).Return(_d, nil) + _d.On("SwarmJoin", "10.0.0.2:2377", "managerToken").Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_manager3", "10.0.0.4", "manager3", "ip10-0-0-4-aaaabbbbcccc"}).Return() + + _d.On("CreateContainer", docker.CreateContainerOpts{Image: "franela/dind", SessionId: "aaaabbbbcccc", ContainerName: "aaaabbbb_worker1", Hostname: "worker1", Privileged: true, HostFQDN: "localhost", Networks: []string{"aaaabbbbcccc"}}).Return(nil) + _d.On("ContainerIPs", "aaaabbbb_worker1").Return(map[string]string{"aaaabbbbcccc": "10.0.0.5"}, nil) + _f.On("GetForInstance", mock.AnythingOfType("*types.Instance")).Return(_d, nil) + _d.On("SwarmJoin", "10.0.0.2:2377", "workerToken").Return(nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_worker1", "10.0.0.5", "worker1", "ip10-0-0-5-aaaabbbbcccc"}).Return() + + _d.On("CreateContainer", docker.CreateContainerOpts{Image: "franela/dind", SessionId: "aaaabbbbcccc", ContainerName: "aaaabbbb_other", Hostname: "other", Privileged: true, HostFQDN: "localhost", Networks: []string{"aaaabbbbcccc"}}).Return(nil) + _d.On("ContainerIPs", "aaaabbbb_other").Return(map[string]string{"aaaabbbbcccc": "10.0.0.6"}, nil) + _e.M.On("Emit", event.INSTANCE_NEW, "aaaabbbbcccc", []interface{}{"aaaabbbb_other", "10.0.0.6", "other", "ip10-0-0-6-aaaabbbbcccc"}).Return() + + var nilArgs []interface{} + _e.M.On("Emit", event.SESSION_NEW, "aaaabbbbcccc", nilArgs).Return() + + p := NewPWD(_f, _e, _s, sp, ipf) + p.generator = _g + sConfig := types.SessionConfig{Playground: playground, UserId: "", Duration: time.Hour, Stack: "", StackName: "", ImageName: ""} + s, e := p.SessionNew(context.Background(), sConfig) + assert.Nil(t, e) + + err := p.SessionSetup(s, SessionSetupConf{ + Instances: []SessionSetupInstanceConf{ + { + Image: "franela/dind", + IsSwarmManager: true, + Hostname: "manager1", + }, + { + IsSwarmManager: true, + Hostname: "manager2", + }, + { + Image: "franela/dind:overlay2-dev", + IsSwarmManager: true, + Hostname: "manager3", + }, + { + IsSwarmWorker: true, + Hostname: "worker1", + }, + { + Hostname: "other", + }, + }, + }) + assert.Nil(t, err) + + _d.AssertExpectations(t) + _f.AssertExpectations(t) + _s.AssertExpectations(t) + _g.AssertExpectations(t) + _e.M.AssertExpectations(t) +} +*/ diff --git a/pwd/types/client.go b/pwd/types/client.go new file mode 100644 index 0000000000000000000000000000000000000000..11dcfec53d76237033380585f429e8712896f39d --- /dev/null +++ b/pwd/types/client.go @@ -0,0 +1,12 @@ +package types + +type Client struct { + Id string `json:"id" bson:"id"` + SessionId string `json:"session_id" bson:"session_id"` + ViewPort ViewPort `json:"viewport"` +} + +type ViewPort struct { + Rows uint `json:"rows"` + Cols uint `json:"cols"` +} diff --git a/pwd/types/instance.go b/pwd/types/instance.go new file mode 100644 index 0000000000000000000000000000000000000000..cae5b249988e0253cf21c7ab1c922f09b0f67f70 --- /dev/null +++ b/pwd/types/instance.go @@ -0,0 +1,45 @@ +package types + +import "context" + +type Instance struct { + Name string `json:"name" bson:"name"` + Image string `json:"image" bson:"image"` + Hostname string `json:"hostname" bson:"hostname"` + IP string `json:"ip" bson:"ip"` + RoutableIP string `json:"routable_ip" bson:"routable_id"` + ServerCert []byte `json:"server_cert" bson:"server_cert"` + ServerKey []byte `json:"server_key" bson:"server_key"` + CACert []byte `json:"ca_cert" bson:"ca_cert"` + Cert []byte `json:"cert" bson:"cert"` + Key []byte `json:"key" bson:"key"` + Tls bool `json:"tls" bson:"tls"` + SessionId string `json:"session_id" bson:"session_id"` + ProxyHost string `json:"proxy_host" bson:"proxy_host"` + SessionHost string `json:"session_host" bson:"session_host"` + Type string `json:"type" bson:"type"` + WindowsId string `json:"-" bson:"windows_id"` + ctx context.Context `json:"-" bson:"-"` +} + +type WindowsInstance struct { + Id string `bson:"id"` + SessionId string `bson:"session_id"` +} + +type InstanceConfig struct { + ImageName string + Privileged bool + Hostname string + ServerCert []byte + ServerKey []byte + CACert []byte + Cert []byte + Key []byte + Tls bool + PlaygroundFQDN string + Type string + DindVolumeSize string + Envs []string + Networks []string +} diff --git a/pwd/types/playground.go b/pwd/types/playground.go new file mode 100644 index 0000000000000000000000000000000000000000..271c1df4a4182b5740092d7c0a67ff9ec2cd7e7b --- /dev/null +++ b/pwd/types/playground.go @@ -0,0 +1,95 @@ +package types + +import ( + "strconv" + "time" +) + +type PlaygroundExtras map[string]interface{} + +func (e PlaygroundExtras) Get(name string) (interface{}, bool) { + v, f := e[name] + return v, f +} +func (e PlaygroundExtras) GetInt(name string) (int, bool) { + v, f := e[name] + if f { + if r, ok := v.(int); ok { + return r, ok + } else if r, ok := v.(float64); ok { + return int(r), ok + } else if r, ok := v.(string); ok { + if v, err := strconv.Atoi(r); err != nil { + return 0, false + } else { + return v, true + } + } + return v.(int), f + } else { + return 0, f + } +} + +func (e PlaygroundExtras) GetString(name string) (string, bool) { + v, f := e[name] + if f { + if r, ok := v.(int); ok { + return strconv.Itoa(r), ok + } else if r, ok := v.(float64); ok { + return strconv.FormatFloat(r, 'g', -1, 64), ok + } else if r, ok := v.(bool); ok { + return strconv.FormatBool(r), ok + } else if r, ok := v.(string); ok { + return r, ok + } else { + return "", false + } + } else { + return "", f + } +} + +func (e PlaygroundExtras) GetDuration(name string) (time.Duration, bool) { + v, f := e[name] + if f { + if r, ok := v.(int); ok { + return time.Duration(r), ok + } else if r, ok := v.(float64); ok { + return time.Duration(r), ok + } else if r, ok := v.(string); ok { + if d, err := time.ParseDuration(r); err != nil { + return time.Duration(0), false + } else { + return d, true + } + } else { + return time.Duration(0), false + } + } else { + return time.Duration(0), f + } +} + +type Playground struct { + Id string `json:"id" bson:"id"` + Domain string `json:"domain" bson:"domain"` + DefaultDinDInstanceImage string `json:"default_dind_instance_image" bson:"default_dind_instance_image"` + AvailableDinDInstanceImages []string `json:"available_dind_instance_images" bson:"available_dind_instance_images"` + AllowWindowsInstances bool `json:"allow_windows_instances" bson:"allow_windows_instances"` + DefaultSessionDuration time.Duration `json:"default_session_duration" bson:"default_session_duration"` + DindVolumeSize string `json:"dind_volume_size" bson:"dind_volume_size"` + Extras PlaygroundExtras `json:"extras" bson:"extras"` + AssetsDir string `json:"assets_dir" bson:"assets_dir"` + Tasks []string `json:"tasks" bson:"tasks"` + GithubClientID string `json:"github_client_id" bson:"github_client_id"` + GithubClientSecret string `json:"github_client_secret" bson:"github_client_secret"` + GoogleClientID string `json:"google_client_id" bson:"google_client_id"` + GoogleClientSecret string `json:"google_client_secret" bson:"google_client_secret"` + DockerClientID string `json:"docker_client_id" bson:"docker_client_id"` + DockerClientSecret string `json:"docker_client_secret" bson:"docker_client_secret"` + AuthRedirectBase string `json:"auth_redirect_base" bson:"auth_redirect_base"` + DockerHost string `json:"docker_host" bson:"docker_host"` + MaxInstances int `json:"max_instances" bson:"max_instances"` + Privileged bool `json:"privileged" bson:"privileged"` +} diff --git a/pwd/types/playground_test.go b/pwd/types/playground_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e4108888b2e5d66a7c783ba1f2d5a882f773dfb4 --- /dev/null +++ b/pwd/types/playground_test.go @@ -0,0 +1,119 @@ +package types + +import ( + "encoding/json" + "testing" + "time" + + "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" +) + +func TestPlayground_Extras_GetInt(t *testing.T) { + p := Playground{ + Id: uuid.NewV4().String(), + Domain: "localhost", + DefaultDinDInstanceImage: "franel/dind", + AllowWindowsInstances: false, + DefaultSessionDuration: time.Hour * 4, + Extras: PlaygroundExtras{ + "intFromInt": 10, + "intFromFloat": 32.0, + "intFromString": "15", + }, + } + + b, err := json.Marshal(p) + assert.Nil(t, err) + + var p2 Playground + json.Unmarshal(b, &p2) + + v, found := p2.Extras.GetInt("intFromInt") + assert.True(t, found) + assert.Equal(t, 10, v) + + v, found = p2.Extras.GetInt("intFromFloat") + assert.True(t, found) + assert.Equal(t, 32, v) + + v, found = p2.Extras.GetInt("intFromString") + assert.True(t, found) + assert.Equal(t, 15, v) +} + +func TestPlayground_Extras_GetString(t *testing.T) { + p := Playground{ + Id: uuid.NewV4().String(), + Domain: "localhost", + DefaultDinDInstanceImage: "franel/dind", + AllowWindowsInstances: false, + DefaultSessionDuration: time.Hour * 4, + Extras: PlaygroundExtras{ + "stringFromInt": 10, + "stringFromFloat": 32.3, + "stringFromString": "15", + "stringFromBool": false, + }, + } + + b, err := json.Marshal(p) + assert.Nil(t, err) + + var p2 Playground + json.Unmarshal(b, &p2) + + v, found := p2.Extras.GetString("stringFromInt") + assert.True(t, found) + assert.Equal(t, "10", v) + + v, found = p2.Extras.GetString("stringFromFloat") + assert.True(t, found) + assert.Equal(t, "32.3", v) + + v, found = p2.Extras.GetString("stringFromString") + assert.True(t, found) + assert.Equal(t, "15", v) + + v, found = p2.Extras.GetString("stringFromBool") + assert.True(t, found) + assert.Equal(t, "false", v) +} + +func TestPlayground_Extras_GetDuration(t *testing.T) { + p := Playground{ + Id: uuid.NewV4().String(), + Domain: "localhost", + DefaultDinDInstanceImage: "franel/dind", + AllowWindowsInstances: false, + DefaultSessionDuration: time.Hour * 4, + Extras: PlaygroundExtras{ + "durationFromInt": 10, + "durationFromFloat": 32.3, + "durationFromString": "4h", + "durationFromDuration": time.Hour * 3, + }, + } + + b, err := json.Marshal(p) + assert.Nil(t, err) + + var p2 Playground + json.Unmarshal(b, &p2) + + v, found := p2.Extras.GetDuration("durationFromInt") + assert.True(t, found) + assert.Equal(t, time.Duration(10), v) + + v, found = p2.Extras.GetDuration("durationFromFloat") + assert.True(t, found) + assert.Equal(t, time.Duration(32), v) + + v, found = p2.Extras.GetDuration("durationFromString") + assert.True(t, found) + assert.Equal(t, time.Hour*4, v) + + v, found = p2.Extras.GetDuration("durationFromDuration") + assert.True(t, found) + assert.Equal(t, time.Hour*3, v) +} diff --git a/pwd/types/session.go b/pwd/types/session.go new file mode 100644 index 0000000000000000000000000000000000000000..9816718c346c5dd160c9ece81336505bc727669d --- /dev/null +++ b/pwd/types/session.go @@ -0,0 +1,28 @@ +package types + +import ( + "time" +) + +type SessionConfig struct { + Playground *Playground + UserId string + Duration time.Duration + Stack string + StackName string + ImageName string +} + +type Session struct { + Id string `json:"id" bson:"id"` + CreatedAt time.Time `json:"created_at" bson:"created_at"` + ExpiresAt time.Time `json:"expires_at" bson:"expires_at"` + PwdIpAddress string `json:"pwd_ip_address" bson:"pwd_ip_address"` + Ready bool `json:"ready" bson:"ready"` + Stack string `json:"stack" bson:"stack"` + StackName string `json:"stack_name" bson:"stack_name"` + ImageName string `json:"image_name" bson:"image_name"` + Host string `json:"host" bson:"host"` + UserId string `json:"user_id" bson:"user_id"` + PlaygroundId string `json:"playground_id" bson:"playground_id"` +} diff --git a/pwd/types/user.go b/pwd/types/user.go new file mode 100644 index 0000000000000000000000000000000000000000..9516ddad5437d2e4da539a7ff0999a007acdc55c --- /dev/null +++ b/pwd/types/user.go @@ -0,0 +1,16 @@ +package types + +type User struct { + Id string `json:"id" bson:"id"` + Name string `json:"name" bson:"name"` + ProviderUserId string `json:"provider_user_id" bson:"provider_user_id"` + Avatar string `json:"avatar" bson:"avatar"` + Provider string `json:"provider" bson:"provider"` + Email string `json:"email" bson:"email"` + IsBanned bool `json:"banned" bson:"banned"` +} + +type LoginRequest struct { + Id string `json:"id" bson:"id"` + Provider string `json:"provider" bson:"provider"` +} diff --git a/pwd/user.go b/pwd/user.go new file mode 100644 index 0000000000000000000000000000000000000000..c7bc1beeb5a15a58d2cbe531d82909905f14a60e --- /dev/null +++ b/pwd/user.go @@ -0,0 +1,55 @@ +package pwd + +import ( + "errors" + + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" +) + +var userBannedError = errors.New("User is banned") + +func (p *pwd) UserNewLoginRequest(providerName string) (*types.LoginRequest, error) { + req := &types.LoginRequest{Id: p.generator.NewId(), Provider: providerName} + if err := p.storage.LoginRequestPut(req); err != nil { + return nil, err + } + return req, nil +} + +func (p *pwd) UserGetLoginRequest(id string) (*types.LoginRequest, error) { + if req, err := p.storage.LoginRequestGet(id); err != nil { + return nil, err + } else { + return req, nil + } +} + +func (p *pwd) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error) { + if err := p.storage.LoginRequestDelete(loginRequest.Id); err != nil { + return nil, err + } + u, err := p.storage.UserFindByProvider(user.Provider, user.ProviderUserId) + + if err != nil { + if storage.NotFound(err) { + user.Id = p.generator.NewId() + if err := p.storage.UserPut(user); err != nil { + return nil, err + } + return user, nil + } + return nil, err + } + return u, nil +} +func (p *pwd) UserGet(id string) (*types.User, error) { + var user *types.User + var err error + if user, err = p.storage.UserGet(id); err != nil { + return nil, err + } else if user.IsBanned { + return user, userBannedError + } + return user, nil +} diff --git a/router/host.go b/router/host.go new file mode 100644 index 0000000000000000000000000000000000000000..4cc079cbadd29fb5316952d5050c466f69ff12cc --- /dev/null +++ b/router/host.go @@ -0,0 +1,71 @@ +package router + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +const hostPattern = "^.*ip([0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3}-[0-9]{1,3})-([0-9|a-z]+)(?:-?([0-9]{1,5}))?(?:\\.([a-z|A-Z|0-9|_|\\-\\.]+))?(?:\\:([0-9]{1,5}))?$" + +var hostRegex *regexp.Regexp + +func init() { + hostRegex = regexp.MustCompile(hostPattern) +} + +type HostOpts struct { + TLD string + EncodedPort int + Port int +} + +type HostInfo struct { + SessionId string + InstanceIP string + TLD string + EncodedPort int + Port int +} + +func EncodeHost(sessionId, instanceIP string, opts HostOpts) string { + encodedIP := strings.Replace(instanceIP, ".", "-", -1) + + sub := fmt.Sprintf("ip%s-%s", encodedIP, sessionId) + if opts.EncodedPort > 0 { + sub = fmt.Sprintf("%s-%d", sub, opts.EncodedPort) + } + if opts.TLD != "" { + sub = fmt.Sprintf("%s.%s", sub, opts.TLD) + } + if opts.Port > 0 { + sub = fmt.Sprintf("%s:%d", sub, opts.Port) + } + + return sub +} + +func DecodeHost(host string) (HostInfo, error) { + info := HostInfo{} + + matches := hostRegex.FindStringSubmatch(host) + if len(matches) != 6 { + return HostInfo{}, fmt.Errorf("Couldn't find host in string") + } + + info.InstanceIP = strings.Replace(matches[1], "-", ".", -1) + info.SessionId = matches[2] + info.TLD = matches[4] + + if matches[3] != "" { + i, _ := strconv.Atoi(matches[3]) + info.EncodedPort = i + } + if matches[5] != "" { + i, _ := strconv.Atoi(matches[5]) + info.Port = i + } + + return info, nil +} diff --git a/router/host_test.go b/router/host_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6deea3e58bc3f9d8f2287aa02af3970330ff94a7 --- /dev/null +++ b/router/host_test.go @@ -0,0 +1,45 @@ +package router + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEncodeHostInfo(t *testing.T) { + host := EncodeHost("aaabbbcccddd", "10.0.0.1", HostOpts{}) + assert.Equal(t, "ip10-0-0-1-aaabbbcccddd", host) + + opts := HostOpts{EncodedPort: 8080} + host = EncodeHost("aaabbbcccddd", "10.0.0.1", opts) + assert.Equal(t, "ip10-0-0-1-aaabbbcccddd-8080", host) + + opts = HostOpts{TLD: "foo.bar"} + host = EncodeHost("aaabbbcccddd", "10.0.0.1", opts) + assert.Equal(t, "ip10-0-0-1-aaabbbcccddd.foo.bar", host) + + opts = HostOpts{TLD: "foo.bar", EncodedPort: 8080, Port: 443} + host = EncodeHost("aaabbbcccddd", "10.0.0.1", opts) + assert.Equal(t, "ip10-0-0-1-aaabbbcccddd-8080.foo.bar:443", host) +} + +func TestDecodeHostInfo(t *testing.T) { + info, err := DecodeHost("ip10-0-0-1-aaabbbcccddd") + assert.Nil(t, err) + assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd"}, info) + + info, err = DecodeHost("ip10-0-0-1-aaabbbcccddd-8080") + assert.Nil(t, err) + assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd", EncodedPort: 8080}, info) + + info, err = DecodeHost("ip10-0-0-1-aaabbbcccddd-8080.foo.bar") + assert.Nil(t, err) + assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd", EncodedPort: 8080, TLD: "foo.bar"}, info) + + info, err = DecodeHost("ip10-0-0-1-aaabbbcccddd-8080.foo.bar:443") + assert.Nil(t, err) + assert.Equal(t, HostInfo{InstanceIP: "10.0.0.1", SessionId: "aaabbbcccddd", EncodedPort: 8080, TLD: "foo.bar", Port: 443}, info) + + _, err = DecodeHost("ip10-0-0-1") + assert.NotNil(t, err) +} diff --git a/router/l2/l2.go b/router/l2/l2.go new file mode 100644 index 0000000000000000000000000000000000000000..6f1e885cf67a20149f9650807452f6333b878990 --- /dev/null +++ b/router/l2/l2.go @@ -0,0 +1,174 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "os" + "time" + + "golang.org/x/crypto/ssh" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/config" + "github.com/play-with-docker/play-with-docker/router" + "github.com/shirou/gopsutil/load" + "github.com/urfave/negroni" +) + +func director(protocol router.Protocol, host string) (*router.DirectorInfo, error) { + info, err := router.DecodeHost(host) + if err != nil { + return nil, err + } + + port := info.Port + + if info.EncodedPort > 0 { + port = info.EncodedPort + } + + i := router.DirectorInfo{} + if port == 0 { + if protocol == router.ProtocolHTTP { + port = 80 + } else if protocol == router.ProtocolHTTPS { + port = 443 + } else if protocol == router.ProtocolSSH { + port = 22 + i.SSHUser = "root" + i.SSHAuthMethods = []ssh.AuthMethod{ssh.Password("root")} + } else if protocol == router.ProtocolDNS { + port = 53 + } + } + + t, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", info.InstanceIP, port)) + if err != nil { + return nil, err + } + i.Dst = t + return &i, nil +} + +func connectNetworks() error { + ctx := context.Background() + c, err := client.NewClientWithOpts() + if err != nil { + log.Fatal(err) + } + + defer c.Close() + + f, err := os.Open(config.SessionsFile) + if err != nil { + return err + } + defer f.Close() + + networks := map[string]*network.EndpointSettings{} + + err = json.NewDecoder(f).Decode(&networks) + if err != nil { + return err + } + + for netId, opts := range networks { + settings := &network.EndpointSettings{} + settings.IPAddress = opts.IPAddress + log.Printf("Connected to network [%s] with ip [%s]\n", netId, opts.IPAddress) + c.NetworkConnect(ctx, netId, config.PWDContainerName, settings) + } + + return nil +} + +func monitorNetworks() { + c, err := client.NewClientWithOpts() + if err != nil { + log.Fatal(err) + } + + defer c.Close() + + ctx := context.Background() + + args := filters.NewArgs() + + cmsg, _ := c.Events(ctx, types.EventsOptions{Filters: args}) + for { + select { + case m := <-cmsg: + if m.Type == "network" { + // Router has been connected to a new network. Let's get all connections and store them in case of restart. + container, err := c.ContainerInspect(ctx, config.PWDContainerName) + if err != nil { + log.Println(err) + return + } + + f, err := os.Create(config.SessionsFile) + if err != nil { + log.Println(err) + return + } + err = json.NewEncoder(f).Encode(container.NetworkSettings.Networks) + if err != nil { + log.Println(err) + return + } + log.Println("Saved networks") + } + } + } +} + +func main() { + config.ParseFlags() + + err := connectNetworks() + if err != nil && !os.IsNotExist(err) { + log.Fatal("connect networks:", err) + } + go monitorNetworks() + + ro := mux.NewRouter() + ro.HandleFunc("/ping", ping).Methods("GET") + n := negroni.Classic() + n.UseHandler(ro) + + httpServer := http.Server{ + Addr: "0.0.0.0:8080", + Handler: n, + IdleTimeout: 30 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + } + go httpServer.ListenAndServe() + + r := router.NewRouter(director, config.SSHKeyPath) + r.ListenAndWait(":443", ":53", ":22") + defer r.Close() +} + +func ping(rw http.ResponseWriter, req *http.Request) { + // Get system load average of the last 5 minutes and compare it against a threashold. + + a, err := load.Avg() + if err != nil { + log.Println("Cannot get system load average!", err) + } else { + if a.Load5 > config.MaxLoadAvg { + log.Printf("System load average is too high [%f]\n", a.Load5) + rw.WriteHeader(http.StatusInsufficientStorage) + } + } + + fmt.Fprintf(rw, `{"ip": "%s"}`, config.L2RouterIP) +} diff --git a/router/l2/l2_test.go b/router/l2/l2_test.go new file mode 100644 index 0000000000000000000000000000000000000000..68da5e5d336344ae8bdde2b70bb958f7cbbd08e9 --- /dev/null +++ b/router/l2/l2_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "testing" + + "github.com/play-with-docker/play-with-docker/router" + "github.com/stretchr/testify/assert" +) + +func TestDirector(t *testing.T) { + info, err := director(router.ProtocolHTTP, "ip10-0-0-1-aabb-8080.foo.bar") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:8080", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "ip10-0-0-1-aabb.foo.bar") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:80", info.Dst.String()) + + info, err = director(router.ProtocolHTTPS, "ip10-0-0-1-aabb.foo.bar") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:443", info.Dst.String()) + + info, err = director(router.ProtocolSSH, "ip10-0-0-1-aabb.foo.bar") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:22", info.Dst.String()) + assert.Equal(t, "root", info.SSHUser) + + info, err = director(router.ProtocolDNS, "ip10-0-0-1-aabb.foo.bar") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:53", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "ip10-0-0-1-aabb.foo.bar:9090") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:9090", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "ip10-0-0-1-aabb-2222.foo.bar:9090") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:2222", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "lala.ip10-0-0-1-aabb-2222.foo.bar") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:2222", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "lala.ip10-0-0-1-aabb-2222") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:2222", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "ip10-0-0-1-aabb-2222") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:2222", info.Dst.String()) + + info, err = director(router.ProtocolHTTP, "ip10-0-0-1-aabb") + assert.Nil(t, err) + assert.Equal(t, "10.0.0.1:80", info.Dst.String()) + + _, err = director(router.ProtocolHTTP, "lala10-0-0-1-aabb.foo.bar") + assert.NotNil(t, err) +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000000000000000000000000000000000000..d226719d80d9c223ee56658dada6efc7a637ffd6 --- /dev/null +++ b/router/router.go @@ -0,0 +1,489 @@ +package router + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "strings" + "sync" + "time" + + "golang.org/x/crypto/ssh" + + vhost "github.com/inconshreveable/go-vhost" + "github.com/miekg/dns" +) + +type Protocol int + +const ( + ProtocolHTTP Protocol = iota + ProtocolHTTPS + ProtocolSSH + ProtocolDNS +) + +type DirectorInfo struct { + Dst *net.TCPAddr + SSHUser string + SSHAuthMethods []ssh.AuthMethod +} + +type Director func(protocol Protocol, host string) (*DirectorInfo, error) + +type proxyRouter struct { + sync.Mutex + + keyPath string + director Director + closed bool + httpListener *net.TCPListener + udpDnsServer *dns.Server + tcpDnsServer *dns.Server + sshListener net.Listener + sshConfig *ssh.ServerConfig + dialer *net.Dialer +} + +func (r *proxyRouter) Listen(httpAddr, dnsAddr, sshAddr string) { + r.listen(&sync.WaitGroup{}, httpAddr, dnsAddr, sshAddr) +} + +func (r *proxyRouter) ListenAndWait(httpAddr, dnsAddr, sshAddr string) { + wg := sync.WaitGroup{} + r.listen(&wg, httpAddr, dnsAddr, sshAddr) + wg.Wait() +} + +func (r *proxyRouter) listen(wg *sync.WaitGroup, httpAddr, dnsAddr, sshAddr string) { + tcpAddr, err := net.ResolveTCPAddr("tcp", httpAddr) + if err != nil { + log.Fatal(err) + } + l, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + log.Fatal(err) + } + r.httpListener = l + wg.Add(1) + go func() { + for !r.closed { + conn, err := r.httpListener.AcceptTCP() + if err != nil { + continue + } + conn.SetKeepAlive(true) + conn.SetKeepAlivePeriod(3 * time.Minute) + go r.handleConnection(conn) + } + wg.Done() + }() + + dnsMux := dns.NewServeMux() + dnsMux.HandleFunc(".", r.dnsRequest) + r.udpDnsServer = &dns.Server{Addr: dnsAddr, Net: "udp", Handler: dnsMux} + r.tcpDnsServer = &dns.Server{Addr: dnsAddr, Net: "tcp", Handler: dnsMux} + + wgStarted := sync.WaitGroup{} + wgStarted.Add(2) + + r.udpDnsServer.NotifyStartedFunc = func() { + wgStarted.Done() + } + r.tcpDnsServer.NotifyStartedFunc = func() { + wgStarted.Done() + } + go r.udpDnsServer.ListenAndServe() + go r.tcpDnsServer.ListenAndServe() + wgStarted.Wait() + + lssh, err := net.Listen("tcp", sshAddr) + if err != nil { + log.Fatal("failed to listen for connection: ", err) + } + r.sshListener = lssh + wg.Add(1) + go func() { + for { + nConn, err := lssh.Accept() + if err != nil { + log.Fatal("failed to accept incoming connection: ", err) + } + + go r.sshHandle(nConn) + } + wg.Done() + }() +} + +func (r *proxyRouter) sshHandle(nConn net.Conn) { + sshCon, chans, reqs, err := ssh.NewServerConn(nConn, r.sshConfig) + if err != nil { + nConn.Close() + return + } + + info, err := r.director(ProtocolSSH, sshCon.User()) + if err != nil { + nConn.Close() + return + } + + // The incoming Request channel must be serviced. + go ssh.DiscardRequests(reqs) + + newChannel := <-chans + if newChannel == nil { + sshCon.Close() + return + } + + if newChannel.ChannelType() != "session" { + newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") + return + } + + channel, requests, err := newChannel.Accept() + if err != nil { + log.Fatalf("Could not accept channel: %v", err) + } + + stderr := channel.Stderr() + + fmt.Fprintf(stderr, "Connecting to %s\r\n", info.Dst.String()) + + /* + Auth: []ssh.AuthMethod{ + ssh.Password("root"), + }, + */ + clientConfig := &ssh.ClientConfig{ + User: info.SSHUser, + Auth: info.SSHAuthMethods, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + } + + client, err := ssh.Dial("tcp", info.Dst.String(), clientConfig) + if err != nil { + fmt.Fprintf(stderr, "Connect failed: %v\r\n", err) + channel.Close() + return + } + + go func() { + for newChannel = range chans { + if newChannel == nil { + return + } + + channel2, reqs2, err := client.OpenChannel(newChannel.ChannelType(), newChannel.ExtraData()) + if err != nil { + x, ok := err.(*ssh.OpenChannelError) + if ok { + newChannel.Reject(x.Reason, x.Message) + } else { + newChannel.Reject(ssh.Prohibited, "remote server denied channel request") + } + continue + } + + channel, reqs, err := newChannel.Accept() + if err != nil { + channel2.Close() + continue + } + go proxySsh(reqs, reqs2, channel, channel2) + } + }() + + // Forward the session channel + channel2, reqs2, err := client.OpenChannel("session", []byte{}) + if err != nil { + fmt.Fprintf(stderr, "Remote session setup failed: %v\r\n", err) + channel.Close() + return + } + + maskedReqs := make(chan *ssh.Request, 1) + go func() { + for req := range requests { + if req.Type == "auth-agent-req@openssh.com" { + continue + } + maskedReqs <- req + } + }() + proxySsh(maskedReqs, reqs2, channel, channel2) +} + +func (r *proxyRouter) dnsRequest(w dns.ResponseWriter, req *dns.Msg) { + if len(req.Question) > 0 { + question := req.Question[0].Name + + if question == "localhost." { + log.Printf("Asked for [localhost.] returning automatically [127.0.0.1]\n") + m := new(dns.Msg) + m.SetReply(req) + m.Authoritative = true + m.RecursionAvailable = true + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A 127.0.0.1", question)) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + w.WriteMsg(m) + return + } + + info, err := r.director(ProtocolDNS, strings.TrimSuffix(question, ".")) + if err != nil { + // Director couldn't resolve it, try to lookup in the system's DNS + ips, err := net.LookupIP(question) + if err != nil { + // we have no information about this and we are not a recursive dns server, so we just fail so the client can fallback to the next dns server it has configured + w.Close() + // dns.HandleFailed(w, r) + return + } + m := new(dns.Msg) + m.SetReply(req) + m.Authoritative = true + m.RecursionAvailable = true + for _, ip := range ips { + ipv4 := ip.To4() + if ipv4 == nil { + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN AAAA %s", question, ip.String())) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + } else { + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A %s", question, ipv4.String())) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + } + } + w.WriteMsg(m) + return + } + dstHost := info.Dst + + m := new(dns.Msg) + m.SetReply(req) + m.Authoritative = true + m.RecursionAvailable = true + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A %s", question, dstHost.IP)) + if err != nil { + log.Println(err) + w.Close() + // dns.HandleFailed(w, r) + return + } + m.Answer = append(m.Answer, a) + w.WriteMsg(m) + return + } +} + +func (r *proxyRouter) Close() { + r.Lock() + defer r.Unlock() + + if r.httpListener != nil { + r.httpListener.Close() + } + if r.udpDnsServer != nil { + r.udpDnsServer.Shutdown() + } + r.closed = true +} + +func (r *proxyRouter) ListenHttpAddress() string { + if r.httpListener != nil { + return r.httpListener.Addr().String() + } + return "" +} + +func (r *proxyRouter) ListenDnsUdpAddress() string { + if r.udpDnsServer != nil && r.udpDnsServer.PacketConn != nil { + return r.udpDnsServer.PacketConn.LocalAddr().String() + } + + return "" +} + +func (r *proxyRouter) ListenDnsTcpAddress() string { + if r.tcpDnsServer != nil && r.tcpDnsServer.Listener != nil { + return r.tcpDnsServer.Listener.Addr().String() + } + + return "" +} + +func (r *proxyRouter) ListenSshAddress() string { + if r.sshListener != nil { + return r.sshListener.Addr().String() + } + + return "" +} + +func (r *proxyRouter) handleConnection(c net.Conn) { + defer c.Close() + // first try tls + start := time.Now() + vhostConn, err := vhost.TLS(c) + discoverElapsed := time.Since(start) + + if err == nil { + // It is a TLS connection + defer vhostConn.Close() + host := vhostConn.ClientHelloMsg.ServerName + log.Printf("Proxying TLS connection to %s. Discover took %s\n", host, discoverElapsed) + info, err := r.director(ProtocolHTTPS, host) + if err != nil { + log.Printf("Error directing request: %v\n", err) + return + } + dstHost := info.Dst + d, err := r.dialer.Dial("tcp", dstHost.String()) + if err != nil { + log.Printf("Error dialing backend %s: %v\n", dstHost.String(), err) + return + } + + proxyConn(vhostConn, d) + } else { + // it is not TLS + // treat it as an http connection + + start := time.Now() + req, err := http.ReadRequest(bufio.NewReader(vhostConn)) + httpReadElapsed := time.Since(start) + if err != nil { + // It is not http neither. So just close the connection. + return + } + host := req.Header.Get("X-Forwarded-Host") + if host == "" { + host = req.Host + } + log.Printf("Proxying http connection to %s. Discover took %s. Http read took %s\n", host, discoverElapsed, httpReadElapsed) + info, err := r.director(ProtocolHTTP, host) + if err != nil { + log.Printf("Error directing request: %v\n", err) + return + } + dstHost := info.Dst + d, err := r.dialer.Dial("tcp", dstHost.String()) + if err != nil { + log.Printf("Error dialing backend %s: %v\n", dstHost.String(), err) + return + } + defer d.Close() + err = req.Write(d) + if err != nil { + log.Printf("Error requesting backend %s: %v\n", dstHost.String(), err) + return + } + proxyConn(c, d) + } +} + +func proxySsh(reqs1, reqs2 <-chan *ssh.Request, channel1, channel2 ssh.Channel) { + var closer sync.Once + closeFunc := func() { + channel1.Close() + channel2.Close() + } + + defer closer.Do(closeFunc) + + closerChan := make(chan bool, 1) + + go func() { + io.Copy(channel1, channel2) + closerChan <- true + }() + + go func() { + io.Copy(channel2, channel1) + closerChan <- true + }() + + for { + select { + case req := <-reqs1: + if req == nil { + return + } + b, err := channel2.SendRequest(req.Type, req.WantReply, req.Payload) + if err != nil { + return + } + req.Reply(b, nil) + + case req := <-reqs2: + if req == nil { + return + } + b, err := channel1.SendRequest(req.Type, req.WantReply, req.Payload) + if err != nil { + return + } + req.Reply(b, nil) + case <-closerChan: + return + } + } +} + +func proxyConn(src, dst net.Conn) { + errc := make(chan error, 2) + cp := func(dst net.Conn, src net.Conn) { + _, err := io.Copy(dst, src) + errc <- err + } + + go cp(src, dst) + go cp(dst, src) + <-errc +} + +func NewRouter(director Director, keyPath string) *proxyRouter { + sshConfig := &ssh.ServerConfig{ + PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { + return nil, nil + }, + } + privateBytes, err := ioutil.ReadFile(keyPath) + if err != nil { + log.Fatal("Failed to load private key: ", err) + } + + private, err := ssh.ParsePrivateKey(privateBytes) + if err != nil { + log.Fatal("Failed to parse private key: ", err) + } + + sshConfig.AddHostKey(private) + + return &proxyRouter{ + director: director, + sshConfig: sshConfig, + dialer: &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }, + } +} diff --git a/router/router_test.go b/router/router_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4efa4fd37ee0d94463fa58936431188dbe4d46f1 --- /dev/null +++ b/router/router_test.go @@ -0,0 +1,544 @@ +package router + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "sort" + "strings" + "sync" + "testing" + + "golang.org/x/crypto/ssh" + + "github.com/gorilla/websocket" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func testSshClient(user string, r *proxyRouter) error { + reader := rand.Reader + bitSize := 2048 + + key, err := rsa.GenerateKey(reader, bitSize) + if err != nil { + return err + } + signer, err := ssh.NewSignerFromKey(key) + if err != nil { + return err + } + config := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + } + chunks := strings.Split(r.ListenSshAddress(), ":") + l := fmt.Sprintf("127.0.0.1:%s", chunks[len(chunks)-1]) + client, err := ssh.Dial("tcp", l, config) + if err != nil { + return err + } + session, err := client.NewSession() + if err != nil { + return err + } + defer session.Close() + + return nil +} + +func testSshServer(f func(user, pass, ctype string)) (string, error) { + reader := rand.Reader + bitSize := 2048 + + key, err := rsa.GenerateKey(reader, bitSize) + if err != nil { + return "", err + } + signer, err := ssh.NewSignerFromKey(key) + if err != nil { + return "", err + } + + var receivedUser string + var receivedPass string + var receivedChannelType string + + config := &ssh.ServerConfig{ + PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { + receivedUser = conn.User() + receivedPass = string(password) + + return nil, nil + }, + } + config.AddHostKey(signer) + listener, err := net.Listen("tcp", "0.0.0.0:0") + if err != nil { + return "", err + } + + go func() { + defer listener.Close() + nConn, err := listener.Accept() + if err != nil { + log.Println(err) + return + } + conn, chans, reqs, err := ssh.NewServerConn(nConn, config) + if err != nil { + log.Println(err) + return + } + go ssh.DiscardRequests(reqs) + defer nConn.Close() + defer conn.Close() + + ch := <-chans + + receivedChannelType = ch.ChannelType() + + f(receivedUser, receivedPass, receivedChannelType) + }() + + return listener.Addr().String(), nil +} + +func generateKeys() (string, string, string, error) { + dir, err := ioutil.TempDir("", "pwd") + if err != nil { + return "", "", "", err + } + + reader := rand.Reader + bitSize := 2048 + + key, err := rsa.GenerateKey(reader, bitSize) + if err != nil { + return "", "", "", err + } + + privateFile, err := os.OpenFile(fmt.Sprintf("%s/id_rsa", dir), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return "", "", "", err + } + defer privateFile.Close() + + var privateKey = &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + + err = pem.Encode(privateFile, privateKey) + if err != nil { + return "", "", "", err + } + + publicFile, err := os.OpenFile(fmt.Sprintf("%s/id_rsa.pub", dir), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return "", "", "", err + } + defer publicFile.Close() + + asn1Bytes, err := asn1.Marshal(key.PublicKey) + if err != nil { + return "", "", "", err + } + + var publicKey = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: asn1Bytes, + } + + err = pem.Encode(publicFile, publicKey) + if err != nil { + return "", "", "", err + } + return dir, privateFile.Name(), publicFile.Name(), nil +} + +func getRouterUrl(scheme string, r *proxyRouter) string { + chunks := strings.Split(r.ListenHttpAddress(), ":") + return fmt.Sprintf("%s://localhost:%s", scheme, chunks[len(chunks)-1]) +} + +func routerLookup(protocol, domain string, r *proxyRouter) ([]string, error) { + c := dns.Client{Net: protocol} + m := dns.Msg{} + + m.SetQuestion(fmt.Sprintf("%s.", domain), dns.TypeA) + var l string + if protocol == "udp" { + chunks := strings.Split(r.ListenDnsUdpAddress(), ":") + l = fmt.Sprintf("127.0.0.1:%s", chunks[len(chunks)-1]) + } else if protocol == "tcp" { + chunks := strings.Split(r.ListenDnsTcpAddress(), ":") + l = fmt.Sprintf("127.0.0.1:%s", chunks[len(chunks)-1]) + } + res, _, err := c.Exchange(&m, l) + + if err != nil { + return nil, err + } + + if len(res.Answer) == 0 { + return nil, fmt.Errorf("Didn't receive any answer") + } + addrs := []string{} + for _, a := range res.Answer { + if b, ok := a.(*dns.A); ok { + addrs = append(addrs, b.A.String()) + } else if b, ok := a.(*dns.AAAA); ok { + addrs = append(addrs, b.AAAA.String()) + } + } + + return addrs, nil +} + +func TestProxy_TLS(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + + const msg = "It works!" + + var receivedHost string + var receivedProtocol Protocol + + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, msg) + })) + defer ts.Close() + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + receivedHost = host + receivedProtocol = protocol + u, _ := url.Parse(ts.URL) + a, _ := net.ResolveTCPAddr("tcp", u.Host) + return &DirectorInfo{Dst: a}, nil + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + req, err := http.NewRequest("GET", getRouterUrl("https", r), nil) + assert.Nil(t, err) + + resp, err := client.Do(req) + assert.Nil(t, err) + + body, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, msg, string(body)) + assert.Equal(t, "localhost", receivedHost) + assert.Equal(t, ProtocolHTTPS, receivedProtocol) +} + +func TestProxy_Http(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + const msg = "It works!" + + var receivedHost string + var receivedProtocol Protocol + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, msg) + })) + defer ts.Close() + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + receivedHost = host + receivedProtocol = protocol + u, _ := url.Parse(ts.URL) + a, _ := net.ResolveTCPAddr("tcp", u.Host) + return &DirectorInfo{Dst: a}, nil + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + req, err := http.NewRequest("GET", getRouterUrl("http", r), nil) + assert.Nil(t, err) + + resp, err := http.DefaultClient.Do(req) + assert.Nil(t, err) + + body, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, msg, string(body)) + + u, _ := url.Parse(getRouterUrl("http", r)) + assert.Equal(t, u.Host, receivedHost) + assert.Equal(t, ProtocolHTTP, receivedProtocol) +} + +func TestProxy_WS(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + const msg = "It works!" + + var serverReceivedMessage string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var upgrader = websocket.Upgrader{} + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + return + } + serverReceivedMessage = string(message) + err = c.WriteMessage(mt, message) + if err != nil { + log.Println("write:", err) + return + } + })) + defer ts.Close() + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + u, _ := url.Parse(ts.URL) + a, _ := net.ResolveTCPAddr("tcp", u.Host) + return &DirectorInfo{Dst: a}, nil + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + c, _, err := websocket.DefaultDialer.Dial(getRouterUrl("ws", r), nil) + assert.Nil(t, err) + defer c.Close() + + var clientReceivedMessage []byte + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + _, clientReceivedMessage, _ = c.ReadMessage() + wg.Done() + }() + err = c.WriteMessage(websocket.TextMessage, []byte(msg)) + assert.Nil(t, err) + + wg.Wait() + + assert.Equal(t, msg, string(clientReceivedMessage)) + assert.Equal(t, msg, serverReceivedMessage) +} + +func TestProxy_WSS(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + const msg = "It works!" + + var serverReceivedMessage string + + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var upgrader = websocket.Upgrader{} + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + defer c.Close() + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + return + } + serverReceivedMessage = string(message) + err = c.WriteMessage(mt, message) + if err != nil { + log.Println("write:", err) + return + } + })) + defer ts.Close() + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + u, _ := url.Parse(ts.URL) + a, _ := net.ResolveTCPAddr("tcp", u.Host) + return &DirectorInfo{Dst: a}, nil + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + d := websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + c, _, err := d.Dial(getRouterUrl("wss", r), nil) + assert.Nil(t, err) + defer c.Close() + + var clientReceivedMessage []byte + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + _, clientReceivedMessage, _ = c.ReadMessage() + wg.Done() + }() + + err = c.WriteMessage(websocket.TextMessage, []byte(msg)) + assert.Nil(t, err) + + wg.Wait() + + assert.Equal(t, msg, string(clientReceivedMessage)) + assert.Equal(t, msg, serverReceivedMessage) +} + +func TestProxy_DNS_UDP(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + var receivedHost string + var receivedProtocol Protocol + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + receivedHost = host + receivedProtocol = protocol + if host == "10_0_0_1.foo.bar" { + a, _ := net.ResolveTCPAddr("tcp", "10.0.0.1:0") + return &DirectorInfo{Dst: a}, nil + } else { + return nil, fmt.Errorf("Not recognized") + } + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + ips, err := routerLookup("udp", "10_0_0_1.foo.bar", r) + assert.Nil(t, err) + assert.Equal(t, "10_0_0_1.foo.bar", receivedHost) + assert.Equal(t, ProtocolDNS, receivedProtocol) + assert.Equal(t, []string{"10.0.0.1"}, ips) + + ips, err = routerLookup("udp", "www.google.com", r) + assert.Nil(t, err) + assert.Equal(t, "www.google.com", receivedHost) + assert.Equal(t, ProtocolDNS, receivedProtocol) + + expectedIps, err := net.LookupHost("www.google.com") + assert.Nil(t, err) + + sort.Strings(expectedIps) + sort.Strings(ips) + assert.Equal(t, expectedIps, ips) + + ips, err = routerLookup("udp", "localhost", r) + assert.Nil(t, err) + assert.NotEqual(t, "localhost", receivedHost) + assert.Equal(t, ProtocolDNS, receivedProtocol) + assert.Equal(t, []string{"127.0.0.1"}, ips) +} + +func TestProxy_DNS_TCP(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + var receivedHost string + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + receivedHost = host + if host == "10_0_0_1.foo.bar" { + a, _ := net.ResolveTCPAddr("tcp", "10.0.0.1:0") + return &DirectorInfo{Dst: a}, nil + } else { + return nil, fmt.Errorf("Not recognized") + } + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + ips, err := routerLookup("tcp", "10_0_0_1.foo.bar", r) + assert.Nil(t, err) + assert.Equal(t, "10_0_0_1.foo.bar", receivedHost) + assert.Equal(t, []string{"10.0.0.1"}, ips) + + ips, err = routerLookup("tcp", "www.google.com", r) + assert.Nil(t, err) + assert.Equal(t, "www.google.com", receivedHost) + + expectedIps, err := net.LookupHost("www.google.com") + assert.Nil(t, err) + + sort.Strings(expectedIps) + sort.Strings(ips) + assert.Equal(t, expectedIps, ips) + + ips, err = routerLookup("tcp", "localhost", r) + assert.Nil(t, err) + assert.NotEqual(t, "localhost", receivedHost) + assert.Equal(t, []string{"127.0.0.1"}, ips) +} + +func TestProxy_SSH(t *testing.T) { + dir, private, _, _ := generateKeys() + defer os.RemoveAll(dir) + + var receivedUser string + var receivedPass string + var receivedChannelType string + var receivedHost string + var receivedProtocol Protocol + + wg := sync.WaitGroup{} + wg.Add(1) + laddr, err := testSshServer(func(user, pass, ctype string) { + receivedUser = user + receivedPass = pass + receivedChannelType = ctype + wg.Done() + }) + assert.Nil(t, err) + + r := NewRouter(func(protocol Protocol, host string) (*DirectorInfo, error) { + receivedHost = host + receivedProtocol = protocol + if host == "10-0-0-1-aaaabbbb" { + chunks := strings.Split(laddr, ":") + a, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%s", chunks[len(chunks)-1])) + return &DirectorInfo{Dst: a, SSHUser: "root", SSHAuthMethods: []ssh.AuthMethod{ssh.Password("root")}}, nil + } else { + return nil, fmt.Errorf("Not recognized") + } + }, private) + r.Listen(":0", ":0", ":0") + defer r.Close() + + err = testSshClient("10-0-0-1-aaaabbbb", r) + assert.Nil(t, err) + + wg.Wait() + + assert.Equal(t, "root", receivedUser) + assert.Equal(t, "root", receivedPass) + assert.Equal(t, "session", receivedChannelType) + assert.Equal(t, "10-0-0-1-aaaabbbb", receivedHost) + assert.Equal(t, ProtocolSSH, receivedProtocol) +} diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go new file mode 100644 index 0000000000000000000000000000000000000000..18f89d8153ada4aefaefb6ec7bf373a9f56cf624 --- /dev/null +++ b/scheduler/scheduler.go @@ -0,0 +1,324 @@ +package scheduler + +import ( + "context" + "fmt" + "log" + "regexp" + "sync" + "time" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" +) + +type Task interface { + Name() string + Run(ctx context.Context, instance *types.Instance) error +} + +type SchedulerApi interface { + Start() error + Stop() +} + +type scheduledSession struct { + session *types.Session + cancel context.CancelFunc +} + +type scheduledInstance struct { + instance *types.Instance + playgroundId string + ticker *time.Ticker + cancel context.CancelFunc + fails int +} + +type scheduler struct { + scheduledSessions map[string]*scheduledSession + scheduledInstances map[string]*scheduledInstance + tasks map[string]Task + playgrounds map[string]*types.Playground + playgroundTasks map[string][]Task + started bool + ticker *time.Ticker + + storage storage.StorageApi + event event.EventApi + pwd pwd.PWDApi + mx sync.Mutex +} + +func NewScheduler(tasks []Task, s storage.StorageApi, e event.EventApi, p pwd.PWDApi) (*scheduler, error) { + sch := &scheduler{storage: s, event: e, pwd: p} + + sch.tasks = make(map[string]Task) + sch.scheduledSessions = make(map[string]*scheduledSession) + sch.scheduledInstances = make(map[string]*scheduledInstance) + sch.playgrounds = make(map[string]*types.Playground) + sch.playgroundTasks = make(map[string][]Task) + + for _, task := range tasks { + if err := sch.addTask(task); err != nil { + return nil, err + } + } + + return sch, nil +} + +func (s *scheduler) updatePlaygrounds() { + s.mx.Lock() + defer s.mx.Unlock() + + log.Printf("Updating playgrounds configuration\n") + for playgroundId, _ := range s.playgrounds { + playground, err := s.storage.PlaygroundGet(playgroundId) + if err != nil { + log.Printf("Could not find playground %s\n", playgroundId) + continue + } + s.playgrounds[playgroundId] = playground + matchedTasks := s.getMatchedTasks(playground) + s.playgroundTasks[playground.Id] = matchedTasks + } +} + +func (s *scheduler) schedulePlaygroundsUpdate() { + s.updatePlaygrounds() + s.ticker = time.NewTicker(time.Minute * 5) + go func() { + for range s.ticker.C { + s.updatePlaygrounds() + } + }() +} + +func (s *scheduler) getMatchedTasks(playground *types.Playground) []Task { + matchedTasks := []Task{} + for _, expr := range playground.Tasks { + for _, task := range s.tasks { + if expr == task.Name() { + matchedTasks = append(matchedTasks, task) + continue + } + matched, err := regexp.MatchString(expr, task.Name()) + if err != nil { + continue + } + if matched { + matchedTasks = append(matchedTasks, task) + continue + } + } + } + return matchedTasks +} + +func (s *scheduler) getTasks(playgroundId string) []Task { + s.mx.Lock() + defer s.mx.Unlock() + + return s.playgroundTasks[playgroundId] +} + +func (s *scheduler) processSession(ctx context.Context, ss *scheduledSession) { + defer s.unscheduleSession(ss.session) + select { + case <-time.After(time.Until(ss.session.ExpiresAt)): + // Session has expired. Need to close the session. + s.pwd.SessionClose(ss.session) + return + case <-ctx.Done(): + return + } +} +func (s *scheduler) processInstance(ctx context.Context, si *scheduledInstance) { + defer s.unscheduleInstance(si.instance) + for { + select { + case <-ctx.Done(): + log.Printf("Processing tasks for instance %s has been canceled.\n", si.instance.Name) + return + default: + select { + case <-si.ticker.C: + // First check if instance still exists + _, err := s.storage.InstanceGet(si.instance.Name) + if err != nil { + if storage.NotFound(err) { + // Instance doesn't exists anymore. Unschedule. + log.Printf("Instance %s doesn't exists in storage.\n", si.instance.Name) + return + } + log.Printf("Error retrieving instance %s from storage. Got: %v\n", si.instance.Name, err) + continue + } + for _, task := range s.getTasks(si.playgroundId) { + err := task.Run(ctx, si.instance) + if err != nil { + log.Printf("Error running task %s on instance %s. Got: %v\n", task.Name(), si.instance.Name, err) + } + } + } + } + } +} + +func (s *scheduler) addTask(task Task) error { + if _, found := s.tasks[task.Name()]; found { + return fmt.Errorf("Task [%s] was already added", task.Name()) + } + s.tasks[task.Name()] = task + + return nil +} + +func (s *scheduler) unscheduleSession(session *types.Session) { + ss, found := s.scheduledSessions[session.Id] + if !found { + return + } + + ss.cancel() + delete(s.scheduledSessions, ss.session.Id) + log.Printf("Unscheduled session %s\n", session.Id) +} +func (s *scheduler) scheduleSession(session *types.Session) { + if _, found := s.scheduledSessions[session.Id]; found { + log.Printf("Session %s is already scheduled. Ignoring.\n", session.Id) + return + } + ss := &scheduledSession{session: session} + s.scheduledSessions[session.Id] = ss + ctx, cancel := context.WithCancel(context.Background()) + ss.cancel = cancel + go s.processSession(ctx, ss) + log.Printf("Scheduled session %s\n", session.Id) +} +func (s *scheduler) unscheduleInstance(instance *types.Instance) { + si, found := s.scheduledInstances[instance.Name] + if !found { + return + } + si.cancel() + si.ticker.Stop() + delete(s.scheduledInstances, si.instance.Name) + log.Printf("Unscheduled instance %s\n", instance.Name) +} +func (s *scheduler) scheduleInstance(instance *types.Instance, playgroundId string) { + if _, found := s.scheduledInstances[instance.Name]; found { + log.Printf("Instance %s is already scheduled. Ignoring.\n", instance.Name) + return + } + si := &scheduledInstance{instance: instance, playgroundId: playgroundId} + s.scheduledInstances[instance.Name] = si + ctx, cancel := context.WithCancel(context.Background()) + si.cancel = cancel + si.ticker = time.NewTicker(time.Second) + go s.processInstance(ctx, si) + log.Printf("Scheduled instance %s\n", instance.Name) +} + +func (s *scheduler) Stop() { + s.ticker.Stop() + for _, ss := range s.scheduledSessions { + s.unscheduleSession(ss.session) + } + for _, si := range s.scheduledInstances { + s.unscheduleInstance(si.instance) + } + s.started = false +} + +func (s *scheduler) Start() error { + sessions, err := s.storage.SessionGetAll() + if err != nil { + return err + } + for _, session := range sessions { + s.scheduleSession(session) + if _, found := s.playgrounds[session.PlaygroundId]; !found { + playground, err := s.storage.PlaygroundGet(session.PlaygroundId) + if err != nil { + return err + } + s.playgrounds[playground.Id] = playground + } + + instances, err := s.storage.InstanceFindBySessionId(session.Id) + if err != nil { + return err + } + + for _, instance := range instances { + s.scheduleInstance(instance, session.PlaygroundId) + } + } + + // Refresh playground conf every 5 minutes + s.schedulePlaygroundsUpdate() + + s.event.On(event.SESSION_NEW, func(sessionId string, args ...interface{}) { + s.mx.Lock() + defer s.mx.Unlock() + + log.Printf("EVENT: Session New %s\n", sessionId) + session, err := s.storage.SessionGet(sessionId) + if err != nil { + log.Printf("Session [%s] was not found in storage. Got %s\n", sessionId, err) + return + } + if _, found := s.playgrounds[session.PlaygroundId]; !found { + playground, err := s.storage.PlaygroundGet(session.PlaygroundId) + if err != nil { + log.Printf("Could not find playground %s\n", session.PlaygroundId) + return + } + s.playgrounds[playground.Id] = playground + } + s.scheduleSession(session) + }) + s.event.On(event.SESSION_END, func(sessionId string, args ...interface{}) { + log.Printf("EVENT: Session End %s\n", sessionId) + session := &types.Session{Id: sessionId} + s.unscheduleSession(session) + }) + s.event.On(event.INSTANCE_NEW, func(sessionId string, args ...interface{}) { + instanceName := args[0].(string) + log.Printf("EVENT: Instance New %s\n", instanceName) + instance, err := s.storage.InstanceGet(instanceName) + if err != nil { + log.Printf("Instance [%s] was not found in storage. Got %s\n", instanceName, err) + return + } + session, err := s.storage.SessionGet(instance.SessionId) + if err != nil { + log.Printf("Session [%s] was not found in storage. Got %s\n", instance.SessionId, err) + return + } + s.scheduleInstance(instance, session.PlaygroundId) + }) + s.event.On(event.INSTANCE_DELETE, func(sessionId string, args ...interface{}) { + instanceName := args[0].(string) + log.Printf("EVENT: Instance Delete %s\n", instanceName) + instance := &types.Instance{Name: instanceName} + s.unscheduleInstance(instance) + }) + s.event.On(event.PLAYGROUND_NEW, func(playgroundId string, args ...interface{}) { + s.mx.Lock() + + log.Printf("EVENT: Playground New %s\n", playgroundId) + + // Don't defer lock as updatePlaygrounds will lock again + s.mx.Unlock() + // We just update all playgrounds we manage to be safe. This is pretty fast anyway and this event should be fairly rare + s.updatePlaygrounds() + }) + s.started = true + + return nil +} diff --git a/scheduler/scheduler_test.go b/scheduler/scheduler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a5fff51917c202dac6d6e7c93954adc6c6bd9385 --- /dev/null +++ b/scheduler/scheduler_test.go @@ -0,0 +1,58 @@ +package scheduler + +import ( + "context" + "testing" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/stretchr/testify/assert" +) + +type fakeTask struct { + name string +} + +func (f fakeTask) Name() string { + return f.name +} +func (f fakeTask) Run(ctx context.Context, instance *types.Instance) error { + return nil +} + +func TestScheduler_getMatchedTasks(t *testing.T) { + tasks := []Task{ + fakeTask{name: "docker_task1"}, + fakeTask{name: "docker_task2"}, + fakeTask{name: "k8s_task1"}, + fakeTask{name: "k8s_task2"}, + } + + _s := &storage.Mock{} + _e := &event.Mock{} + _p := &pwd.Mock{} + + s, err := NewScheduler(tasks, _s, _e, _p) + assert.Nil(t, err) + + // No matches + matched := s.getMatchedTasks(&types.Playground{Tasks: []string{}}) + assert.Empty(t, matched) + + // Match everything + matched = s.getMatchedTasks(&types.Playground{Tasks: []string{".*"}}) + assert.Subset(t, tasks, matched) + assert.Len(t, matched, len(tasks)) + + // Match some + matched = s.getMatchedTasks(&types.Playground{Tasks: []string{"docker_.*"}}) + assert.Subset(t, []Task{fakeTask{name: "docker_task1"}, fakeTask{name: "docker_task2"}}, matched) + assert.Len(t, matched, 2) + + // Match exactly + matched = s.getMatchedTasks(&types.Playground{Tasks: []string{"docker_task1", "docker_task3"}}) + assert.Subset(t, []Task{fakeTask{name: "docker_task1"}}, matched) + assert.Len(t, matched, 1) +} diff --git a/scheduler/task/check_k8s_cluster_exposed_ports.go b/scheduler/task/check_k8s_cluster_exposed_ports.go new file mode 100644 index 0000000000000000000000000000000000000000..8f22ca6448101d4d1af7f185edb147e260d2ae3a --- /dev/null +++ b/scheduler/task/check_k8s_cluster_exposed_ports.go @@ -0,0 +1,77 @@ +package task + +import ( + "context" + "log" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/k8s" + "github.com/play-with-docker/play-with-docker/pwd/types" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type checkK8sClusterExposedPortsTask struct { + event event.EventApi + factory k8s.FactoryApi +} + +var CheckK8sClusterExpoedPortsEvent event.EventType + +func init() { + CheckK8sClusterExpoedPortsEvent = event.EventType("instance k8s cluster ports") +} + +func (t *checkK8sClusterExposedPortsTask) Name() string { + return "CheckK8sClusterPorts" +} + +func NewCheckK8sClusterExposedPorts(e event.EventApi, f k8s.FactoryApi) *checkK8sClusterExposedPortsTask { + return &checkK8sClusterExposedPortsTask{event: e, factory: f} +} + +func (c checkK8sClusterExposedPortsTask) Run(ctx context.Context, i *types.Instance) error { + + kc, err := c.factory.GetKubeletForInstance(i) + if err != nil { + return err + } + + if isManager, err := kc.IsManager(); err != nil { + log.Println(err) + return err + } else if !isManager { + return nil + } + + k8s, err := c.factory.GetForInstance(i) + if err != nil { + log.Println(err) + return err + } + + list, err := k8s.CoreV1().Services("").List(meta_v1.ListOptions{}) + if err != nil { + return err + } + exposedPorts := []int{} + + for _, svc := range list.Items { + for _, p := range svc.Spec.Ports { + if p.NodePort > 0 { + exposedPorts = append(exposedPorts, int(p.NodePort)) + } + } + } + + nodeList, err := k8s.CoreV1().Nodes().List(meta_v1.ListOptions{}) + if err != nil { + return err + } + instances := []string{} + for _, node := range nodeList.Items { + instances = append(instances, node.Name) + } + + c.event.Emit(CheckSwarmPortsEvent, i.SessionId, ClusterPorts{Manager: i.Name, Instances: instances, Ports: exposedPorts}) + return nil +} diff --git a/scheduler/task/check_k8s_cluster_status_task.go b/scheduler/task/check_k8s_cluster_status_task.go new file mode 100644 index 0000000000000000000000000000000000000000..a72eb2247900862a813eaab90dd75aceacccc9aa --- /dev/null +++ b/scheduler/task/check_k8s_cluster_status_task.go @@ -0,0 +1,54 @@ +package task + +import ( + "context" + "log" + + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/k8s" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type checkK8sClusterStatusTask struct { + event event.EventApi + factory k8s.FactoryApi +} + +var CheckK8sStatusEvent event.EventType + +func init() { + CheckK8sStatusEvent = event.EventType("instance k8s status") +} + +func NewCheckK8sClusterStatus(e event.EventApi, f k8s.FactoryApi) *checkK8sClusterStatusTask { + return &checkK8sClusterStatusTask{event: e, factory: f} +} + +func (c *checkK8sClusterStatusTask) Name() string { + return "CheckK8sClusterStatus" +} + +func (c checkK8sClusterStatusTask) Run(ctx context.Context, i *types.Instance) error { + status := ClusterStatus{Instance: i.Name} + + kc, err := c.factory.GetKubeletForInstance(i) + if err != nil { + log.Println(err) + c.event.Emit(CheckSwarmStatusEvent, i.SessionId, status) + return err + } + + if isManager, err := kc.IsManager(); err != nil { + c.event.Emit(CheckSwarmStatusEvent, i.SessionId, status) + return err + } else if !isManager { + // Not a manager node, nothing to do for this task + status.IsWorker = true + } else { + status.IsManager = true + } + + c.event.Emit(CheckK8sStatusEvent, i.SessionId, status) + + return nil +} diff --git a/scheduler/task/check_ports.go b/scheduler/task/check_ports.go new file mode 100644 index 0000000000000000000000000000000000000000..470a38e97a98ce496c7fa7a1ee445fffdd3f4fed --- /dev/null +++ b/scheduler/task/check_ports.go @@ -0,0 +1,55 @@ +package task + +import ( + "context" + "log" + + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type DockerPorts struct { + Instance string `json:"instance"` + Ports []int `json:"ports"` +} + +type checkPorts struct { + event event.EventApi + factory docker.FactoryApi +} + +var CheckPortsEvent event.EventType + +func init() { + CheckPortsEvent = event.EventType("instance docker ports") +} + +func (t *checkPorts) Name() string { + return "CheckPorts" +} + +func (t *checkPorts) Run(ctx context.Context, instance *types.Instance) error { + dockerClient, err := t.factory.GetForInstance(instance) + if err != nil { + log.Println(err) + return err + } + + ps, err := dockerClient.GetPorts() + if err != nil { + log.Println(err) + return err + } + ports := make([]int, len(ps)) + for i, port := range ps { + ports[i] = int(port) + } + + t.event.Emit(CheckPortsEvent, instance.SessionId, DockerPorts{Instance: instance.Name, Ports: ports}) + return nil +} + +func NewCheckPorts(e event.EventApi, f docker.FactoryApi) *checkPorts { + return &checkPorts{event: e, factory: f} +} diff --git a/scheduler/task/check_ports_test.go b/scheduler/task/check_ports_test.go new file mode 100644 index 0000000000000000000000000000000000000000..391efbe4948057715a231841b5dc52dc05e8718f --- /dev/null +++ b/scheduler/task/check_ports_test.go @@ -0,0 +1,48 @@ +package task + +import ( + "context" + "testing" + + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/assert" +) + +func TestCheckPorts_Name(t *testing.T) { + e := &event.Mock{} + f := &docker.FactoryMock{} + + task := NewCheckPorts(e, f) + + assert.Equal(t, "CheckPorts", task.Name()) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCheckPorts_Run(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + + i := &types.Instance{ + IP: "10.0.0.1", + Name: "aaaabbbb_node1", + SessionId: "aaaabbbbcccc", + } + + d.On("GetPorts").Return([]uint16{8080, 9090}, nil) + f.On("GetForInstance", i).Return(d, nil) + e.M.On("Emit", CheckPortsEvent, "aaaabbbbcccc", []interface{}{DockerPorts{Instance: "aaaabbbb_node1", Ports: []int{8080, 9090}}}).Return() + + task := NewCheckPorts(e, f) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} diff --git a/scheduler/task/check_swarm_ports.go b/scheduler/task/check_swarm_ports.go new file mode 100644 index 0000000000000000000000000000000000000000..63c73a902241256da05a0e74d93f5f3df380cfdc --- /dev/null +++ b/scheduler/task/check_swarm_ports.go @@ -0,0 +1,60 @@ +package task + +import ( + "context" + "log" + + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type checkSwarmPorts struct { + event event.EventApi + factory docker.FactoryApi +} + +var CheckSwarmPortsEvent event.EventType + +func init() { + CheckSwarmPortsEvent = event.EventType("instance docker swarm ports") +} + +func (t *checkSwarmPorts) Name() string { + return "CheckSwarmPorts" +} + +func (t *checkSwarmPorts) Run(ctx context.Context, instance *types.Instance) error { + dockerClient, err := t.factory.GetForInstance(instance) + if err != nil { + log.Println(err) + return err + } + + status, err := getDockerSwarmStatus(ctx, dockerClient) + if err != nil { + log.Println(err) + return err + } + + if !status.IsManager { + return nil + } + + hosts, ps, err := dockerClient.GetSwarmPorts() + if err != nil { + log.Println(err) + return err + } + ports := make([]int, len(ps)) + for i, port := range ps { + ports[i] = int(port) + } + + t.event.Emit(CheckSwarmPortsEvent, instance.SessionId, ClusterPorts{Manager: instance.Name, Instances: hosts, Ports: ports}) + return nil +} + +func NewCheckSwarmPorts(e event.EventApi, f docker.FactoryApi) *checkSwarmPorts { + return &checkSwarmPorts{event: e, factory: f} +} diff --git a/scheduler/task/check_swarm_ports_test.go b/scheduler/task/check_swarm_ports_test.go new file mode 100644 index 0000000000000000000000000000000000000000..88d5d681001c98a51c9288983c1d24753ae04b7f --- /dev/null +++ b/scheduler/task/check_swarm_ports_test.go @@ -0,0 +1,58 @@ +package task + +import ( + "context" + "testing" + + dockerTypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/assert" +) + +func TestCheckSwarmPorts_Name(t *testing.T) { + e := &event.Mock{} + f := &docker.FactoryMock{} + + task := NewCheckSwarmPorts(e, f) + + assert.Equal(t, "CheckSwarmPorts", task.Name()) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCheckSwarmPorts_RunWhenManager(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + + i := &types.Instance{ + IP: "10.0.0.1", + Name: "aaaabbbb_node1", + SessionId: "aaaabbbbcccc", + Hostname: "node1", + } + info := dockerTypes.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateActive, + ControlAvailable: true, + }, + } + + f.On("GetForInstance", i).Return(d, nil) + d.On("DaemonInfo").Return(info, nil) + d.On("GetSwarmPorts").Return([]string{"aaaabbbb_node1", "aaaabbbb_node2"}, []uint16{8080, 9090}, nil) + e.M.On("Emit", CheckSwarmPortsEvent, "aaaabbbbcccc", []interface{}{ClusterPorts{Manager: i.Name, Instances: []string{i.Name, "aaaabbbb_node2"}, Ports: []int{8080, 9090}}}).Return() + + task := NewCheckSwarmPorts(e, f) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} diff --git a/scheduler/task/check_swarm_status.go b/scheduler/task/check_swarm_status.go new file mode 100644 index 0000000000000000000000000000000000000000..45f6e5615abdd7c6c3a297be5ecd430488bd9bee --- /dev/null +++ b/scheduler/task/check_swarm_status.go @@ -0,0 +1,63 @@ +package task + +import ( + "context" + "log" + + "github.com/docker/docker/api/types/swarm" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type checkSwarmStatus struct { + event event.EventApi + factory docker.FactoryApi +} + +var CheckSwarmStatusEvent event.EventType + +func init() { + CheckSwarmStatusEvent = event.EventType("instance docker swarm status") +} + +func (t *checkSwarmStatus) Name() string { + return "CheckSwarmStatus" +} + +func (t *checkSwarmStatus) Run(ctx context.Context, instance *types.Instance) error { + dockerClient, err := t.factory.GetForInstance(instance) + if err != nil { + log.Println(err) + return err + } + + status, err := getDockerSwarmStatus(ctx, dockerClient) + if err != nil { + log.Println(err) + return err + } + status.Instance = instance.Name + + t.event.Emit(CheckSwarmStatusEvent, instance.SessionId, status) + return nil +} + +func NewCheckSwarmStatus(e event.EventApi, f docker.FactoryApi) *checkSwarmStatus { + return &checkSwarmStatus{event: e, factory: f} +} + +func getDockerSwarmStatus(ctx context.Context, client docker.DockerApi) (ClusterStatus, error) { + status := ClusterStatus{} + info, err := client.DaemonInfo() + if err != nil { + return status, err + } + + if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked { + status.IsManager = info.Swarm.ControlAvailable + status.IsWorker = !info.Swarm.ControlAvailable + } + + return status, nil +} diff --git a/scheduler/task/check_swarm_status_test.go b/scheduler/task/check_swarm_status_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d350ef926a04013b1018c1c3c4373dcfdb918170 --- /dev/null +++ b/scheduler/task/check_swarm_status_test.go @@ -0,0 +1,150 @@ +package task + +import ( + "context" + "testing" + + dockerTypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/assert" +) + +func TestCheckSwarmStatus_Name(t *testing.T) { + e := &event.Mock{} + f := &docker.FactoryMock{} + + task := NewCheckSwarmStatus(e, f) + + assert.Equal(t, "CheckSwarmStatus", task.Name()) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCheckSwarmStatus_RunWhenInactive(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + + i := &types.Instance{ + IP: "10.0.0.1", + Name: "node1", + SessionId: "aaabbbccc", + } + infoInactive := dockerTypes.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateInactive, + }, + } + + f.On("GetForInstance", i).Return(d, nil) + d.On("DaemonInfo").Return(infoInactive, nil) + e.M.On("Emit", CheckSwarmStatusEvent, "aaabbbccc", []interface{}{ClusterStatus{IsManager: false, IsWorker: false, Instance: "node1"}}).Return() + + task := NewCheckSwarmStatus(e, f) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCheckSwarmStatus_RunWhenLocked(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + + i := &types.Instance{ + IP: "10.0.0.1", + Name: "node1", + SessionId: "aaabbbccc", + } + infoLocked := dockerTypes.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateLocked, + }, + } + + f.On("GetForInstance", i).Return(d, nil) + d.On("DaemonInfo").Return(infoLocked, nil) + e.M.On("Emit", CheckSwarmStatusEvent, "aaabbbccc", []interface{}{ClusterStatus{IsManager: false, IsWorker: false, Instance: "node1"}}).Return() + + task := NewCheckSwarmStatus(e, f) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCheckSwarmStatus_RunWhenManager(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + + i := &types.Instance{ + IP: "10.0.0.1", + Name: "node1", + SessionId: "aaabbbccc", + } + infoLocked := dockerTypes.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateActive, + ControlAvailable: true, + }, + } + + f.On("GetForInstance", i).Return(d, nil) + d.On("DaemonInfo").Return(infoLocked, nil) + e.M.On("Emit", CheckSwarmStatusEvent, "aaabbbccc", []interface{}{ClusterStatus{IsManager: true, IsWorker: false, Instance: "node1"}}).Return() + + task := NewCheckSwarmStatus(e, f) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCheckSwarmStatus_RunWhenWorker(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + + i := &types.Instance{ + IP: "10.0.0.1", + Name: "node1", + SessionId: "aaabbbccc", + } + infoLocked := dockerTypes.Info{ + Swarm: swarm.Info{ + LocalNodeState: swarm.LocalNodeStateActive, + ControlAvailable: false, + }, + } + + f.On("GetForInstance", i).Return(d, nil) + d.On("DaemonInfo").Return(infoLocked, nil) + e.M.On("Emit", CheckSwarmStatusEvent, "aaabbbccc", []interface{}{ClusterStatus{IsManager: false, IsWorker: true, Instance: "node1"}}).Return() + + task := NewCheckSwarmStatus(e, f) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} diff --git a/scheduler/task/collect_stats.go b/scheduler/task/collect_stats.go new file mode 100644 index 0000000000000000000000000000000000000000..bd11139690e0fa2307915e8d258f4a2a011e1c71 --- /dev/null +++ b/scheduler/task/collect_stats.go @@ -0,0 +1,166 @@ +package task + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "net/url" + "time" + + dockerTypes "github.com/docker/docker/api/types" + units "github.com/docker/go-units" + lru "github.com/hashicorp/golang-lru" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/router" + "github.com/play-with-docker/play-with-docker/storage" +) + +type InstanceStats struct { + Instance string `json:"instance"` + Mem string `json:"mem"` + Cpu string `json:"cpu"` +} + +type collectStats struct { + event event.EventApi + factory docker.FactoryApi + cli *http.Client + cache *lru.Cache + storage storage.StorageApi +} + +var CollectStatsEvent event.EventType + +func init() { + CollectStatsEvent = event.EventType("instance stats") +} + +func (t *collectStats) Name() string { + return "CollectStats" +} + +func (t *collectStats) Run(ctx context.Context, instance *types.Instance) error { + if instance.Type == "windows" { + host := router.EncodeHost(instance.SessionId, instance.IP, router.HostOpts{EncodedPort: 222}) + req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/stats", host), nil) + if err != nil { + log.Printf("Could not create request to get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) + return fmt.Errorf("Could not create request to get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) + } + req.Header.Set("X-Proxy-Host", instance.SessionHost) + resp, err := t.cli.Do(req) + if err != nil { + log.Printf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) + return fmt.Errorf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) + } + if resp.StatusCode != 200 { + log.Printf("Could not get stats of windows instance with IP %s. Got status code: %d\n", instance.IP, resp.StatusCode) + return fmt.Errorf("Could not get stats of windows instance with IP %s. Got status code: %d\n", instance.IP, resp.StatusCode) + } + var info map[string]float64 + err = json.NewDecoder(resp.Body).Decode(&info) + if err != nil { + log.Printf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) + return fmt.Errorf("Could not get stats of windows instance with IP %s. Got: %v\n", instance.IP, err) + } + stats := InstanceStats{Instance: instance.Name} + + stats.Mem = fmt.Sprintf("%.2f%% (%s / %s)", ((info["mem_used"] / info["mem_total"]) * 100), units.BytesSize(info["mem_used"]), units.BytesSize(info["mem_total"])) + stats.Cpu = fmt.Sprintf("%.2f%%", info["cpu"]*100) + t.event.Emit(CollectStatsEvent, instance.SessionId, stats) + return nil + } + var session *types.Session + if sess, found := t.cache.Get(instance.SessionId); !found { + s, err := t.storage.SessionGet(instance.SessionId) + if err != nil { + return err + } + t.cache.Add(s.Id, s) + session = s + } else { + session = sess.(*types.Session) + } + dockerClient, err := t.factory.GetForSession(session) + if err != nil { + log.Println(err) + return err + } + reader, err := dockerClient.ContainerStats(instance.Name) + if err != nil { + log.Println("Error while trying to collect instance stats", err) + return err + } + dec := json.NewDecoder(reader) + var v *dockerTypes.StatsJSON + e := dec.Decode(&v) + if e != nil { + log.Println("Error while trying to collect instance stats", e) + return err + } + stats := InstanceStats{Instance: instance.Name} + // Memory + var memPercent float64 = 0 + if v.MemoryStats.Limit != 0 { + memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 + } + mem := float64(v.MemoryStats.Usage) + memLimit := float64(v.MemoryStats.Limit) + + stats.Mem = fmt.Sprintf("%.2f%% (%s / %s)", memPercent, units.BytesSize(mem), units.BytesSize(memLimit)) + + // cpu + previousCPU := v.PreCPUStats.CPUUsage.TotalUsage + previousSystem := v.PreCPUStats.SystemUsage + cpuPercent := calculateCPUPercentUnix(previousCPU, previousSystem, v) + stats.Cpu = fmt.Sprintf("%.2f%%", cpuPercent) + + t.event.Emit(CollectStatsEvent, instance.SessionId, stats) + return nil +} + +func proxyHost(r *http.Request) (*url.URL, error) { + if r.Header.Get("X-Proxy-Host") == "" { + return nil, nil + } + u := new(url.URL) + *u = *r.URL + u.Host = fmt.Sprintf("%s:8443", r.Header.Get("X-Proxy-Host")) + return u, nil +} + +func NewCollectStats(e event.EventApi, f docker.FactoryApi, s storage.StorageApi) *collectStats { + transport := &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 1 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConnsPerHost: 5, + Proxy: proxyHost, + } + cli := &http.Client{ + Transport: transport, + } + c, _ := lru.New(5000) + return &collectStats{event: e, factory: f, cli: cli, cache: c, storage: s} +} + +func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *dockerTypes.StatsJSON) float64 { + var ( + cpuPercent = 0.0 + // calculate the change for the cpu usage of the container in between readings + cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) + // calculate the change for the entire system between readings + systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem) + ) + + if systemDelta > 0.0 && cpuDelta > 0.0 { + cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0 + } + return cpuPercent +} diff --git a/scheduler/task/collect_stats_test.go b/scheduler/task/collect_stats_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6de4edba3cd3844fafc8cb841826c0c196db030e --- /dev/null +++ b/scheduler/task/collect_stats_test.go @@ -0,0 +1,80 @@ +package task + +import ( + "bytes" + "context" + "encoding/json" + "io" + "testing" + + dockerTypes "github.com/docker/docker/api/types" + "github.com/play-with-docker/play-with-docker/docker" + "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type mockSessionProvider struct { + mock.Mock +} + +func (m *mockSessionProvider) GetDocker(session *types.Session) (docker.DockerApi, error) { + args := m.Called(session) + + return args.Get(0).(docker.DockerApi), args.Error(1) +} + +type nopCloser struct { + io.Reader +} + +func (nopCloser) Close() error { return nil } + +func TestCollectStats_Name(t *testing.T) { + e := &event.Mock{} + f := &docker.FactoryMock{} + s := &storage.Mock{} + + task := NewCollectStats(e, f, s) + + assert.Equal(t, "CollectStats", task.Name()) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} + +func TestCollectStats_Run(t *testing.T) { + d := &docker.Mock{} + e := &event.Mock{} + f := &docker.FactoryMock{} + s := &storage.Mock{} + + stats := dockerTypes.StatsJSON{} + b, _ := json.Marshal(stats) + i := &types.Instance{ + IP: "10.0.0.1", + Name: "aaaabbbb_node1", + SessionId: "aaaabbbbcccc", + Hostname: "node1", + } + + sess := &types.Session{ + Id: "aaaabbbbcccc", + } + + s.On("SessionGet", i.SessionId).Return(sess, nil) + f.On("GetForSession", sess).Return(d, nil) + d.On("ContainerStats", i.Name).Return(nopCloser{bytes.NewReader(b)}, nil) + e.M.On("Emit", CollectStatsEvent, "aaaabbbbcccc", []interface{}{InstanceStats{Instance: i.Name, Mem: "0.00% (0B / 0B)", Cpu: "0.00%"}}).Return() + + task := NewCollectStats(e, f, s) + ctx := context.Background() + + err := task.Run(ctx, i) + + assert.Nil(t, err) + d.AssertExpectations(t) + e.M.AssertExpectations(t) + f.AssertExpectations(t) +} diff --git a/scheduler/task/types.go b/scheduler/task/types.go new file mode 100644 index 0000000000000000000000000000000000000000..651920778ab14545f131c9ccdcb9bb9cf6aba0a2 --- /dev/null +++ b/scheduler/task/types.go @@ -0,0 +1,13 @@ +package task + +type ClusterStatus struct { + IsManager bool `json:"is_manager"` + IsWorker bool `json:"is_worker"` + Instance string `json:"instance"` +} + +type ClusterPorts struct { + Manager string `json:"manager"` + Instances []string `json:"instances"` + Ports []int `json:"ports"` +} diff --git a/storage/file.go b/storage/file.go new file mode 100644 index 0000000000000000000000000000000000000000..adf1ae9ba95202976f2b80ededc6078f99559642 --- /dev/null +++ b/storage/file.go @@ -0,0 +1,451 @@ +package storage + +import ( + "encoding/json" + "fmt" + "os" + "sync" + + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +type storage struct { + rw sync.Mutex + path string + db *DB +} + +type DB struct { + Sessions map[string]*types.Session `json:"sessions"` + Instances map[string]*types.Instance `json:"instances"` + Clients map[string]*types.Client `json:"clients"` + WindowsInstances map[string]*types.WindowsInstance `json:"windows_instances"` + LoginRequests map[string]*types.LoginRequest `json:"login_requests"` + Users map[string]*types.User `json:"user"` + Playgrounds map[string]*types.Playground `json:"playgrounds"` + + WindowsInstancesBySessionId map[string][]string `json:"windows_instances_by_session_id"` + InstancesBySessionId map[string][]string `json:"instances_by_session_id"` + ClientsBySessionId map[string][]string `json:"clients_by_session_id"` + UsersByProvider map[string]string `json:"users_by_providers"` +} + +func (store *storage) SessionGet(id string) (*types.Session, error) { + store.rw.Lock() + defer store.rw.Unlock() + + s, found := store.db.Sessions[id] + if !found { + return nil, NotFoundError + } + + return s, nil +} + +func (store *storage) SessionGetAll() ([]*types.Session, error) { + store.rw.Lock() + defer store.rw.Unlock() + + sessions := make([]*types.Session, len(store.db.Sessions)) + i := 0 + for _, s := range store.db.Sessions { + sessions[i] = s + i++ + } + + return sessions, nil +} + +func (store *storage) SessionPut(session *types.Session) error { + store.rw.Lock() + defer store.rw.Unlock() + + store.db.Sessions[session.Id] = session + + return store.save() +} + +func (store *storage) SessionDelete(id string) error { + store.rw.Lock() + defer store.rw.Unlock() + + _, found := store.db.Sessions[id] + if !found { + return nil + } + for _, i := range store.db.WindowsInstancesBySessionId[id] { + delete(store.db.WindowsInstances, i) + } + store.db.WindowsInstancesBySessionId[id] = []string{} + for _, i := range store.db.InstancesBySessionId[id] { + delete(store.db.Instances, i) + } + store.db.InstancesBySessionId[id] = []string{} + for _, i := range store.db.ClientsBySessionId[id] { + delete(store.db.Clients, i) + } + store.db.ClientsBySessionId[id] = []string{} + delete(store.db.Sessions, id) + + return store.save() +} + +func (store *storage) SessionCount() (int, error) { + store.rw.Lock() + defer store.rw.Unlock() + + return len(store.db.Sessions), nil +} + +func (store *storage) InstanceGet(name string) (*types.Instance, error) { + store.rw.Lock() + defer store.rw.Unlock() + + i := store.db.Instances[name] + if i == nil { + return nil, NotFoundError + } + return i, nil +} + +func (store *storage) InstancePut(instance *types.Instance) error { + store.rw.Lock() + defer store.rw.Unlock() + + _, found := store.db.Sessions[string(instance.SessionId)] + if !found { + return NotFoundError + } + + store.db.Instances[instance.Name] = instance + found = false + for _, i := range store.db.InstancesBySessionId[string(instance.SessionId)] { + if i == instance.Name { + found = true + break + } + } + if !found { + store.db.InstancesBySessionId[string(instance.SessionId)] = append(store.db.InstancesBySessionId[string(instance.SessionId)], instance.Name) + } + + return store.save() +} + +func (store *storage) InstanceDelete(name string) error { + store.rw.Lock() + defer store.rw.Unlock() + + instance, found := store.db.Instances[name] + if !found { + return nil + } + + instances := store.db.InstancesBySessionId[string(instance.SessionId)] + for n, i := range instances { + if i == name { + instances = append(instances[:n], instances[n+1:]...) + break + } + } + store.db.InstancesBySessionId[string(instance.SessionId)] = instances + delete(store.db.Instances, name) + + return store.save() +} + +func (store *storage) InstanceCount() (int, error) { + store.rw.Lock() + defer store.rw.Unlock() + + return len(store.db.Instances), nil +} + +func (store *storage) InstanceFindBySessionId(sessionId string) ([]*types.Instance, error) { + store.rw.Lock() + defer store.rw.Unlock() + + instanceIds := store.db.InstancesBySessionId[sessionId] + instances := make([]*types.Instance, len(instanceIds)) + for i, id := range instanceIds { + instances[i] = store.db.Instances[id] + } + + return instances, nil +} + +func (store *storage) WindowsInstanceGetAll() ([]*types.WindowsInstance, error) { + store.rw.Lock() + defer store.rw.Unlock() + + instances := []*types.WindowsInstance{} + + for _, s := range store.db.WindowsInstances { + instances = append(instances, s) + } + + return instances, nil +} + +func (store *storage) WindowsInstancePut(instance *types.WindowsInstance) error { + store.rw.Lock() + defer store.rw.Unlock() + + _, found := store.db.Sessions[string(instance.SessionId)] + if !found { + return NotFoundError + } + store.db.WindowsInstances[instance.Id] = instance + found = false + for _, i := range store.db.WindowsInstancesBySessionId[string(instance.SessionId)] { + if i == instance.Id { + found = true + break + } + } + if !found { + store.db.WindowsInstancesBySessionId[string(instance.SessionId)] = append(store.db.WindowsInstancesBySessionId[string(instance.SessionId)], instance.Id) + } + + return store.save() +} + +func (store *storage) WindowsInstanceDelete(id string) error { + store.rw.Lock() + defer store.rw.Unlock() + + instance, found := store.db.WindowsInstances[id] + if !found { + return nil + } + + instances := store.db.WindowsInstancesBySessionId[string(instance.SessionId)] + for n, i := range instances { + if i == id { + instances = append(instances[:n], instances[n+1:]...) + break + } + } + store.db.WindowsInstancesBySessionId[string(instance.SessionId)] = instances + delete(store.db.WindowsInstances, id) + + return store.save() +} + +func (store *storage) ClientGet(id string) (*types.Client, error) { + store.rw.Lock() + defer store.rw.Unlock() + + i := store.db.Clients[id] + if i == nil { + return nil, NotFoundError + } + return i, nil +} +func (store *storage) ClientPut(client *types.Client) error { + store.rw.Lock() + defer store.rw.Unlock() + + _, found := store.db.Sessions[string(client.SessionId)] + if !found { + return NotFoundError + } + + store.db.Clients[client.Id] = client + found = false + for _, i := range store.db.ClientsBySessionId[string(client.SessionId)] { + if i == client.Id { + found = true + break + } + } + if !found { + store.db.ClientsBySessionId[string(client.SessionId)] = append(store.db.ClientsBySessionId[string(client.SessionId)], client.Id) + } + + return store.save() +} +func (store *storage) ClientDelete(id string) error { + store.rw.Lock() + defer store.rw.Unlock() + + client, found := store.db.Clients[id] + if !found { + return nil + } + + clients := store.db.ClientsBySessionId[string(client.SessionId)] + for n, i := range clients { + if i == client.Id { + clients = append(clients[:n], clients[n+1:]...) + break + } + } + store.db.ClientsBySessionId[string(client.SessionId)] = clients + delete(store.db.Clients, id) + + return store.save() +} +func (store *storage) ClientCount() (int, error) { + store.rw.Lock() + defer store.rw.Unlock() + + return len(store.db.Clients), nil +} +func (store *storage) ClientFindBySessionId(sessionId string) ([]*types.Client, error) { + store.rw.Lock() + defer store.rw.Unlock() + + clientIds := store.db.ClientsBySessionId[sessionId] + clients := make([]*types.Client, len(clientIds)) + for i, id := range clientIds { + clients[i] = store.db.Clients[id] + } + + return clients, nil +} + +func (store *storage) LoginRequestPut(loginRequest *types.LoginRequest) error { + store.rw.Lock() + defer store.rw.Unlock() + + store.db.LoginRequests[loginRequest.Id] = loginRequest + return nil +} +func (store *storage) LoginRequestGet(id string) (*types.LoginRequest, error) { + store.rw.Lock() + defer store.rw.Unlock() + + if lr, found := store.db.LoginRequests[id]; !found { + return nil, NotFoundError + } else { + return lr, nil + } +} +func (store *storage) LoginRequestDelete(id string) error { + store.rw.Lock() + defer store.rw.Unlock() + + delete(store.db.LoginRequests, id) + return nil +} + +func (store *storage) UserFindByProvider(providerName, providerUserId string) (*types.User, error) { + store.rw.Lock() + defer store.rw.Unlock() + + if userId, found := store.db.UsersByProvider[fmt.Sprintf("%s_%s", providerName, providerUserId)]; !found { + return nil, NotFoundError + } else { + if user, found := store.db.Users[userId]; !found { + return nil, NotFoundError + } else { + return user, nil + } + } +} + +func (store *storage) UserPut(user *types.User) error { + store.rw.Lock() + defer store.rw.Unlock() + + store.db.UsersByProvider[fmt.Sprintf("%s_%s", user.Provider, user.ProviderUserId)] = user.Id + store.db.Users[user.Id] = user + + return store.save() +} +func (store *storage) UserGet(id string) (*types.User, error) { + store.rw.Lock() + defer store.rw.Unlock() + + if user, found := store.db.Users[id]; !found { + return nil, NotFoundError + } else { + return user, nil + } +} + +func (store *storage) PlaygroundPut(playground *types.Playground) error { + store.rw.Lock() + defer store.rw.Unlock() + + store.db.Playgrounds[playground.Id] = playground + + return store.save() +} +func (store *storage) PlaygroundGet(id string) (*types.Playground, error) { + store.rw.Lock() + defer store.rw.Unlock() + + playground, found := store.db.Playgrounds[id] + if !found { + return nil, NotFoundError + } + + return playground, nil +} + +func (store *storage) load() error { + file, err := os.Open(store.path) + + if err == nil { + decoder := json.NewDecoder(file) + err = decoder.Decode(&store.db) + + if err != nil { + return err + } + } else { + store.db = &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + } + + file.Close() + return nil +} +func (store *storage) PlaygroundGetAll() ([]*types.Playground, error) { + store.rw.Lock() + defer store.rw.Unlock() + + playgrounds := make([]*types.Playground, len(store.db.Playgrounds)) + i := 0 + for _, p := range store.db.Playgrounds { + playgrounds[i] = p + i++ + } + + return playgrounds, nil +} + +func (store *storage) save() error { + file, err := os.Create(store.path) + if err != nil { + return err + } + defer file.Close() + encoder := json.NewEncoder(file) + err = encoder.Encode(&store.db) + return err +} + +func NewFileStorage(path string) (StorageApi, error) { + s := &storage{path: path} + + err := s.load() + if err != nil { + return nil, err + } + + return s, nil +} diff --git a/storage/file_test.go b/storage/file_test.go new file mode 100644 index 0000000000000000000000000000000000000000..de987e06e8562c120a7c5a50706ff13fb746ae54 --- /dev/null +++ b/storage/file_test.go @@ -0,0 +1,712 @@ +package storage + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "testing" + + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/assert" +) + +func TestSessionPut(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "a session"} + err = storage.SessionPut(s) + + assert.Nil(t, err) + + expectedDB := &DB{ + Sessions: map[string]*types.Session{s.Id: s}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + var loadedDB *DB + + file, err := os.Open(tmpfile.Name()) + + assert.Nil(t, err) + defer file.Close() + + decoder := json.NewDecoder(file) + err = decoder.Decode(&loadedDB) + + assert.Nil(t, err) + + assert.EqualValues(t, expectedDB, loadedDB) +} + +func TestSessionGet(t *testing.T) { + expectedSession := &types.Session{Id: "aaabbbccc"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{expectedSession.Id: expectedSession}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + _, err = storage.SessionGet("foobar") + assert.True(t, NotFound(err)) + + loadedSession, err := storage.SessionGet("aaabbbccc") + assert.Nil(t, err) + + assert.Equal(t, expectedSession, loadedSession) +} + +func TestSessionGetAll(t *testing.T) { + s1 := &types.Session{Id: "aaabbbccc"} + s2 := &types.Session{Id: "dddeeefff"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + sessions, err := storage.SessionGetAll() + assert.Nil(t, err) + + assert.Subset(t, sessions, []*types.Session{s1, s2}) + assert.Len(t, sessions, 2) +} + +func TestSessionDelete(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s1 := &types.Session{Id: "session1"} + err = storage.SessionPut(s1) + assert.Nil(t, err) + + found, err := storage.SessionGet(s1.Id) + assert.Nil(t, err) + assert.Equal(t, s1, found) + + err = storage.SessionDelete(s1.Id) + assert.Nil(t, err) + + found, err = storage.SessionGet(s1.Id) + assert.True(t, NotFound(err)) + assert.Nil(t, found) +} + +func TestInstanceGet(t *testing.T) { + expectedInstance := &types.Instance{SessionId: "aaabbbccc", Name: "i1", IP: "10.0.0.1"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{expectedInstance.SessionId: []string{expectedInstance.Name}}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + foundInstance, err := storage.InstanceGet("i1") + assert.Nil(t, err) + assert.Equal(t, expectedInstance, foundInstance) +} + +func TestInstancePut(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "aaabbbccc"} + i := &types.Instance{Name: "i1", IP: "10.0.0.1", SessionId: s.Id} + + err = storage.SessionPut(s) + assert.Nil(t, err) + + err = storage.InstancePut(i) + assert.Nil(t, err) + + expectedDB := &DB{ + Sessions: map[string]*types.Session{s.Id: s}, + Instances: map[string]*types.Instance{i.Name: i}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{i.SessionId: []string{i.Name}}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + var loadedDB *DB + + file, err := os.Open(tmpfile.Name()) + + assert.Nil(t, err) + defer file.Close() + + decoder := json.NewDecoder(file) + err = decoder.Decode(&loadedDB) + + assert.Nil(t, err) + + assert.EqualValues(t, expectedDB, loadedDB) +} + +func TestInstanceDelete(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "session1"} + err = storage.SessionPut(s) + assert.Nil(t, err) + + i := &types.Instance{Name: "i1", IP: "10.0.0.1", SessionId: s.Id} + err = storage.InstancePut(i) + assert.Nil(t, err) + + found, err := storage.InstanceGet(i.Name) + assert.Nil(t, err) + assert.Equal(t, i, found) + + err = storage.InstanceDelete(i.Name) + assert.Nil(t, err) + + found, err = storage.InstanceGet(i.Name) + assert.True(t, NotFound(err)) + assert.Nil(t, found) +} + +func TestInstanceFindBySessionId(t *testing.T) { + i1 := &types.Instance{SessionId: "aaabbbccc", Name: "c1"} + i2 := &types.Instance{SessionId: "aaabbbccc", Name: "c2"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Name, i2.Name}}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + instances, err := storage.InstanceFindBySessionId("aaabbbccc") + assert.Nil(t, err) + assert.Subset(t, instances, []*types.Instance{i1, i2}) + assert.Len(t, instances, 2) +} + +func TestWindowsInstanceGetAll(t *testing.T) { + i1 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i1"} + i2 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i2"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Id, i2.Id}}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + instances, err := storage.WindowsInstanceGetAll() + assert.Nil(t, err) + assert.Subset(t, instances, []*types.WindowsInstance{i1, i2}) + assert.Len(t, instances, 2) +} + +func TestWindowsInstancePut(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "aaabbbccc"} + i := &types.WindowsInstance{Id: "i1", SessionId: s.Id} + + err = storage.SessionPut(s) + assert.Nil(t, err) + + err = storage.WindowsInstancePut(i) + assert.Nil(t, err) + + expectedDB := &DB{ + Sessions: map[string]*types.Session{s.Id: s}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{i.Id: i}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{i.SessionId: []string{i.Id}}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + var loadedDB *DB + + file, err := os.Open(tmpfile.Name()) + + assert.Nil(t, err) + defer file.Close() + + decoder := json.NewDecoder(file) + err = decoder.Decode(&loadedDB) + + assert.Nil(t, err) + + assert.EqualValues(t, expectedDB, loadedDB) +} + +func TestWindowsInstanceDelete(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "session1"} + err = storage.SessionPut(s) + assert.Nil(t, err) + + i := &types.WindowsInstance{Id: "i1", SessionId: s.Id} + err = storage.WindowsInstancePut(i) + assert.Nil(t, err) + + found, err := storage.WindowsInstanceGetAll() + assert.Nil(t, err) + assert.Equal(t, []*types.WindowsInstance{i}, found) + + err = storage.WindowsInstanceDelete(i.Id) + assert.Nil(t, err) + + found, err = storage.WindowsInstanceGetAll() + assert.Nil(t, err) + assert.Empty(t, found) +} + +func TestClientGet(t *testing.T) { + c := &types.Client{SessionId: "aaabbbccc", Id: "c1"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{c.Id: c}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + found, err := storage.ClientGet("c1") + assert.Nil(t, err) + assert.Equal(t, c, found) +} + +func TestClientPut(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "aaabbbccc"} + c := &types.Client{Id: "c1", SessionId: s.Id} + + err = storage.SessionPut(s) + assert.Nil(t, err) + + err = storage.ClientPut(c) + assert.Nil(t, err) + + expectedDB := &DB{ + Sessions: map[string]*types.Session{s.Id: s}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{c.Id: c}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}}, + UsersByProvider: map[string]string{}, + } + var loadedDB *DB + + file, err := os.Open(tmpfile.Name()) + + assert.Nil(t, err) + defer file.Close() + + decoder := json.NewDecoder(file) + err = decoder.Decode(&loadedDB) + + assert.Nil(t, err) + + assert.EqualValues(t, expectedDB, loadedDB) +} + +func TestClientDelete(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + s := &types.Session{Id: "session1"} + err = storage.SessionPut(s) + assert.Nil(t, err) + + c := &types.Client{Id: "c1", SessionId: s.Id} + err = storage.ClientPut(c) + assert.Nil(t, err) + + found, err := storage.ClientGet(c.Id) + assert.Nil(t, err) + assert.Equal(t, c, found) + + err = storage.ClientDelete(c.Id) + assert.Nil(t, err) + + found, err = storage.ClientGet(c.Id) + assert.True(t, NotFound(err)) + assert.Nil(t, found) +} + +func TestClientFindBySessionId(t *testing.T) { + c1 := &types.Client{SessionId: "aaabbbccc", Id: "c1"} + c2 := &types.Client{SessionId: "aaabbbccc", Id: "c2"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{c1.SessionId: []string{c1.Id, c2.Id}}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + clients, err := storage.ClientFindBySessionId("aaabbbccc") + assert.Nil(t, err) + assert.Subset(t, clients, []*types.Client{c1, c2}) + assert.Len(t, clients, 2) +} + +func TestPlaygroundGet(t *testing.T) { + p := &types.Playground{Id: "aaabbbccc"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{p.Id: p}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + found, err := storage.PlaygroundGet("aaabbbccc") + assert.Nil(t, err) + assert.Equal(t, p, found) +} + +func TestPlaygroundPut(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + tmpfile.Close() + os.Remove(tmpfile.Name()) + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + p := &types.Playground{Id: "aaabbbccc"} + + err = storage.PlaygroundPut(p) + assert.Nil(t, err) + + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{p.Id: p}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + var loadedDB *DB + + file, err := os.Open(tmpfile.Name()) + + assert.Nil(t, err) + defer file.Close() + + decoder := json.NewDecoder(file) + err = decoder.Decode(&loadedDB) + + assert.Nil(t, err) + + assert.EqualValues(t, expectedDB, loadedDB) +} + +func TestPlaygroundGetAll(t *testing.T) { + p1 := &types.Playground{Id: "aaabbbccc"} + p2 := &types.Playground{Id: "dddeeefff"} + expectedDB := &DB{ + Sessions: map[string]*types.Session{}, + Instances: map[string]*types.Instance{}, + Clients: map[string]*types.Client{}, + WindowsInstances: map[string]*types.WindowsInstance{}, + LoginRequests: map[string]*types.LoginRequest{}, + Users: map[string]*types.User{}, + Playgrounds: map[string]*types.Playground{p1.Id: p1, p2.Id: p2}, + WindowsInstancesBySessionId: map[string][]string{}, + InstancesBySessionId: map[string][]string{}, + ClientsBySessionId: map[string][]string{}, + UsersByProvider: map[string]string{}, + } + + tmpfile, err := ioutil.TempFile("", "pwd") + if err != nil { + log.Fatal(err) + } + encoder := json.NewEncoder(tmpfile) + err = encoder.Encode(&expectedDB) + assert.Nil(t, err) + tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + storage, err := NewFileStorage(tmpfile.Name()) + + assert.Nil(t, err) + + found, err := storage.PlaygroundGetAll() + assert.Nil(t, err) + assert.Subset(t, []*types.Playground{p1, p2}, found) + assert.Len(t, found, 2) +} diff --git a/storage/mock.go b/storage/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..3d6d21ab9263a957e0270f7250231dd84d16236b --- /dev/null +++ b/storage/mock.go @@ -0,0 +1,120 @@ +package storage + +import ( + "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/stretchr/testify/mock" +) + +type Mock struct { + mock.Mock +} + +func (m *Mock) SessionGet(id string) (*types.Session, error) { + args := m.Called(id) + return args.Get(0).(*types.Session), args.Error(1) +} +func (m *Mock) SessionGetAll() ([]*types.Session, error) { + args := m.Called() + return args.Get(0).([]*types.Session), args.Error(1) +} +func (m *Mock) SessionPut(session *types.Session) error { + args := m.Called(session) + return args.Error(0) +} +func (m *Mock) SessionDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} +func (m *Mock) SessionCount() (int, error) { + args := m.Called() + return args.Int(0), args.Error(1) +} +func (m *Mock) InstanceGet(name string) (*types.Instance, error) { + args := m.Called(name) + return args.Get(0).(*types.Instance), args.Error(1) +} +func (m *Mock) InstancePut(instance *types.Instance) error { + args := m.Called(instance) + return args.Error(0) +} +func (m *Mock) InstanceDelete(name string) error { + args := m.Called(name) + return args.Error(0) +} +func (m *Mock) InstanceCount() (int, error) { + args := m.Called() + return args.Int(0), args.Error(1) +} +func (m *Mock) InstanceFindBySessionId(sessionId string) ([]*types.Instance, error) { + args := m.Called(sessionId) + return args.Get(0).([]*types.Instance), args.Error(1) +} + +func (m *Mock) WindowsInstanceGetAll() ([]*types.WindowsInstance, error) { + args := m.Called() + return args.Get(0).([]*types.WindowsInstance), args.Error(1) +} +func (m *Mock) WindowsInstancePut(instance *types.WindowsInstance) error { + args := m.Called(instance) + return args.Error(0) +} +func (m *Mock) WindowsInstanceDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} +func (m *Mock) ClientGet(id string) (*types.Client, error) { + args := m.Called(id) + return args.Get(0).(*types.Client), args.Error(1) +} +func (m *Mock) ClientPut(client *types.Client) error { + args := m.Called(client) + return args.Error(0) +} +func (m *Mock) ClientDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} +func (m *Mock) ClientCount() (int, error) { + args := m.Called() + return args.Int(0), args.Error(1) +} +func (m *Mock) ClientFindBySessionId(sessionId string) ([]*types.Client, error) { + args := m.Called(sessionId) + return args.Get(0).([]*types.Client), args.Error(1) +} +func (m *Mock) LoginRequestPut(loginRequest *types.LoginRequest) error { + args := m.Called(loginRequest) + return args.Error(0) +} +func (m *Mock) LoginRequestGet(id string) (*types.LoginRequest, error) { + args := m.Called(id) + return args.Get(0).(*types.LoginRequest), args.Error(1) +} +func (m *Mock) LoginRequestDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} +func (m *Mock) UserFindByProvider(providerName, providerUserId string) (*types.User, error) { + args := m.Called(providerName, providerUserId) + return args.Get(0).(*types.User), args.Error(1) +} +func (m *Mock) UserPut(user *types.User) error { + args := m.Called(user) + return args.Error(0) +} +func (m *Mock) UserGet(id string) (*types.User, error) { + args := m.Called(id) + return args.Get(0).(*types.User), args.Error(1) +} +func (m *Mock) PlaygroundPut(playground *types.Playground) error { + args := m.Called(playground) + return args.Error(0) +} +func (m *Mock) PlaygroundGet(id string) (*types.Playground, error) { + args := m.Called(id) + return args.Get(0).(*types.Playground), args.Error(1) +} +func (m *Mock) PlaygroundGetAll() ([]*types.Playground, error) { + args := m.Called() + return args.Get(0).([]*types.Playground), args.Error(1) +} diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000000000000000000000000000000000000..bc40d569467e03cc2b76a5bcc6b8ca1791a18984 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,49 @@ +package storage + +import ( + "errors" + + "github.com/play-with-docker/play-with-docker/pwd/types" +) + +var NotFoundError = errors.New("NotFound") + +func NotFound(e error) bool { + return e == NotFoundError +} + +type StorageApi interface { + SessionGet(id string) (*types.Session, error) + SessionGetAll() ([]*types.Session, error) + SessionPut(session *types.Session) error + SessionDelete(id string) error + SessionCount() (int, error) + + InstanceGet(name string) (*types.Instance, error) + InstancePut(instance *types.Instance) error + InstanceDelete(name string) error + InstanceCount() (int, error) + InstanceFindBySessionId(sessionId string) ([]*types.Instance, error) + + WindowsInstanceGetAll() ([]*types.WindowsInstance, error) + WindowsInstancePut(instance *types.WindowsInstance) error + WindowsInstanceDelete(id string) error + + ClientGet(id string) (*types.Client, error) + ClientPut(client *types.Client) error + ClientDelete(id string) error + ClientCount() (int, error) + ClientFindBySessionId(sessionId string) ([]*types.Client, error) + + LoginRequestPut(loginRequest *types.LoginRequest) error + LoginRequestGet(id string) (*types.LoginRequest, error) + LoginRequestDelete(id string) error + + UserFindByProvider(providerName, providerUserId string) (*types.User, error) + UserPut(user *types.User) error + UserGet(id string) (*types.User, error) + + PlaygroundPut(playground *types.Playground) error + PlaygroundGet(id string) (*types.Playground, error) + PlaygroundGetAll() ([]*types.Playground, error) +}