From ba387764ca1590625d349e74eb8a8a64d1849b67 Mon Sep 17 00:00:00 2001
From: Tulir Asokan <tulir@maunium.net>
Date: Wed, 14 Nov 2018 00:00:35 +0200
Subject: Fix things

---
 vendor/golang.org/x/image/bmp/reader.go            |  30 +-
 vendor/golang.org/x/image/tiff/reader.go           |   2 +-
 vendor/golang.org/x/image/vp8/decode.go            |   4 +-
 vendor/golang.org/x/image/webp/decode.go           |   2 -
 vendor/golang.org/x/image/webp/doc.go              |   9 +
 vendor/golang.org/x/image/webp/webp.go             |  30 -
 vendor/golang.org/x/net/html/const.go              |  10 +-
 vendor/golang.org/x/net/html/parse.go              |  29 +-
 .../gopkg.in/russross/blackfriday.v2/.travis.yml   |  27 +-
 vendor/gopkg.in/russross/blackfriday.v2/README.md  |  20 +-
 vendor/gopkg.in/russross/blackfriday.v2/block.go   | 101 ++-
 vendor/gopkg.in/russross/blackfriday.v2/go.mod     |   1 +
 vendor/gopkg.in/russross/blackfriday.v2/html.go    |  21 +-
 vendor/gopkg.in/russross/blackfriday.v2/inline.go  |  18 +-
 .../gopkg.in/russross/blackfriday.v2/markdown.go   |  94 +--
 vendor/maunium.net/go/gomatrix/.gitignore          |  24 -
 vendor/maunium.net/go/gomatrix/.travis.yml         |   9 -
 vendor/maunium.net/go/gomatrix/LICENSE             | 201 ------
 vendor/maunium.net/go/gomatrix/README.md           |   6 -
 vendor/maunium.net/go/gomatrix/client.go           | 794 --------------------
 vendor/maunium.net/go/gomatrix/events.go           | 413 -----------
 vendor/maunium.net/go/gomatrix/filter.go           |  90 ---
 vendor/maunium.net/go/gomatrix/reply.go            |  96 ---
 vendor/maunium.net/go/gomatrix/requests.go         |  82 ---
 vendor/maunium.net/go/gomatrix/responses.go        | 182 -----
 vendor/maunium.net/go/gomatrix/room.go             |  44 --
 vendor/maunium.net/go/gomatrix/store.go            |  65 --
 vendor/maunium.net/go/gomatrix/sync.go             | 159 ----
 vendor/maunium.net/go/gomatrix/userids.go          | 130 ----
 vendor/maunium.net/go/maulogger/LICENSE            |  21 -
 vendor/maunium.net/go/maulogger/README.md          |   6 -
 vendor/maunium.net/go/maulogger/logger.go          | 219 ------
 vendor/maunium.net/go/mautrix/.gitignore           |   2 +
 vendor/maunium.net/go/mautrix/LICENSE              | 201 ++++++
 vendor/maunium.net/go/mautrix/README.md            |   4 +
 vendor/maunium.net/go/mautrix/client.go            | 796 +++++++++++++++++++++
 vendor/maunium.net/go/mautrix/events.go            | 448 ++++++++++++
 vendor/maunium.net/go/mautrix/filter.go            |  90 +++
 vendor/maunium.net/go/mautrix/reply.go             |  97 +++
 vendor/maunium.net/go/mautrix/requests.go          |  82 +++
 vendor/maunium.net/go/mautrix/responses.go         | 182 +++++
 vendor/maunium.net/go/mautrix/room.go              |  44 ++
 vendor/maunium.net/go/mautrix/store.go             |  65 ++
 vendor/maunium.net/go/mautrix/sync.go              | 159 ++++
 vendor/maunium.net/go/mautrix/userids.go           | 130 ++++
 vendor/maunium.net/go/tcell/README.adoc            | 270 +++++++
 vendor/maunium.net/go/tcell/cell.go                |   7 +-
 vendor/maunium.net/go/tcell/console_win.go         |   1 -
 vendor/maunium.net/go/tcell/tcell.png              | Bin 5336 -> 0 bytes
 vendor/maunium.net/go/tcell/tcell.svg              |  93 ---
 .../maunium.net/go/tcell/terminfo/term_termite.go  | 152 ++++
 vendor/maunium.net/go/tcell/tscreen.go             |   6 +-
 vendor/maunium.net/go/tview/LICENSE.txt            |   2 +-
 vendor/maunium.net/go/tview/README.md              |  11 +-
 vendor/maunium.net/go/tview/ansi.go                | 237 ++++++
 vendor/maunium.net/go/tview/ansii.go               | 237 ------
 vendor/maunium.net/go/tview/application.go         | 376 +++++++---
 vendor/maunium.net/go/tview/borders.go             |  45 ++
 vendor/maunium.net/go/tview/box.go                 |  88 +--
 vendor/maunium.net/go/tview/checkbox.go            |   6 +-
 vendor/maunium.net/go/tview/doc.go                 |  31 +-
 vendor/maunium.net/go/tview/dropdown.go            |   5 +
 vendor/maunium.net/go/tview/flex.go                |  17 +-
 vendor/maunium.net/go/tview/form.go                |  53 +-
 vendor/maunium.net/go/tview/grid.go                |  16 +-
 vendor/maunium.net/go/tview/inputfield.go          | 205 ++++--
 vendor/maunium.net/go/tview/list.go                |  15 +-
 vendor/maunium.net/go/tview/modal.go               |  15 +
 vendor/maunium.net/go/tview/semigraphics.go        | 296 ++++++++
 vendor/maunium.net/go/tview/table.go               | 119 ++-
 vendor/maunium.net/go/tview/textview.go            | 266 ++++---
 vendor/maunium.net/go/tview/treeview.go            | 684 ++++++++++++++++++
 vendor/maunium.net/go/tview/util.go                | 658 ++++++++---------
 73 files changed, 5413 insertions(+), 3737 deletions(-)
 create mode 100644 vendor/golang.org/x/image/webp/doc.go
 delete mode 100644 vendor/golang.org/x/image/webp/webp.go
 create mode 100644 vendor/gopkg.in/russross/blackfriday.v2/go.mod
 delete mode 100644 vendor/maunium.net/go/gomatrix/.gitignore
 delete mode 100644 vendor/maunium.net/go/gomatrix/.travis.yml
 delete mode 100644 vendor/maunium.net/go/gomatrix/LICENSE
 delete mode 100644 vendor/maunium.net/go/gomatrix/README.md
 delete mode 100644 vendor/maunium.net/go/gomatrix/client.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/events.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/filter.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/reply.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/requests.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/responses.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/room.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/store.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/sync.go
 delete mode 100644 vendor/maunium.net/go/gomatrix/userids.go
 delete mode 100644 vendor/maunium.net/go/maulogger/LICENSE
 delete mode 100644 vendor/maunium.net/go/maulogger/README.md
 delete mode 100644 vendor/maunium.net/go/maulogger/logger.go
 create mode 100644 vendor/maunium.net/go/mautrix/.gitignore
 create mode 100644 vendor/maunium.net/go/mautrix/LICENSE
 create mode 100644 vendor/maunium.net/go/mautrix/README.md
 create mode 100644 vendor/maunium.net/go/mautrix/client.go
 create mode 100644 vendor/maunium.net/go/mautrix/events.go
 create mode 100644 vendor/maunium.net/go/mautrix/filter.go
 create mode 100644 vendor/maunium.net/go/mautrix/reply.go
 create mode 100644 vendor/maunium.net/go/mautrix/requests.go
 create mode 100644 vendor/maunium.net/go/mautrix/responses.go
 create mode 100644 vendor/maunium.net/go/mautrix/room.go
 create mode 100644 vendor/maunium.net/go/mautrix/store.go
 create mode 100644 vendor/maunium.net/go/mautrix/sync.go
 create mode 100644 vendor/maunium.net/go/mautrix/userids.go
 create mode 100644 vendor/maunium.net/go/tcell/README.adoc
 delete mode 100644 vendor/maunium.net/go/tcell/tcell.png
 delete mode 100644 vendor/maunium.net/go/tcell/tcell.svg
 create mode 100644 vendor/maunium.net/go/tcell/terminfo/term_termite.go
 create mode 100644 vendor/maunium.net/go/tview/ansi.go
 delete mode 100644 vendor/maunium.net/go/tview/ansii.go
 create mode 100644 vendor/maunium.net/go/tview/borders.go
 create mode 100644 vendor/maunium.net/go/tview/semigraphics.go
 create mode 100644 vendor/maunium.net/go/tview/treeview.go

(limited to 'vendor')

diff --git a/vendor/golang.org/x/image/bmp/reader.go b/vendor/golang.org/x/image/bmp/reader.go
index a0f2715..c10a022 100644
--- a/vendor/golang.org/x/image/bmp/reader.go
+++ b/vendor/golang.org/x/image/bmp/reader.go
@@ -137,20 +137,26 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
 	// We only support those BMP images that are a BITMAPFILEHEADER
 	// immediately followed by a BITMAPINFOHEADER.
 	const (
-		fileHeaderLen = 14
-		infoHeaderLen = 40
+		fileHeaderLen   = 14
+		infoHeaderLen   = 40
+		v4InfoHeaderLen = 108
+		v5InfoHeaderLen = 124
 	)
 	var b [1024]byte
-	if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
+	if _, err := io.ReadFull(r, b[:fileHeaderLen+4]); err != nil {
 		return image.Config{}, 0, false, err
 	}
 	if string(b[:2]) != "BM" {
 		return image.Config{}, 0, false, errors.New("bmp: invalid format")
 	}
 	offset := readUint32(b[10:14])
-	if readUint32(b[14:18]) != infoHeaderLen {
+	infoLen := readUint32(b[14:18])
+	if infoLen != infoHeaderLen && infoLen != v4InfoHeaderLen && infoLen != v5InfoHeaderLen {
 		return image.Config{}, 0, false, ErrUnsupported
 	}
+	if _, err := io.ReadFull(r, b[fileHeaderLen+4:fileHeaderLen+infoLen]); err != nil {
+		return image.Config{}, 0, false, err
+	}
 	width := int(int32(readUint32(b[18:22])))
 	height := int(int32(readUint32(b[22:26])))
 	if height < 0 {
@@ -159,14 +165,22 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
 	if width < 0 || height < 0 {
 		return image.Config{}, 0, false, ErrUnsupported
 	}
-	// We only support 1 plane, 8 or 24 bits per pixel and no compression.
+	// We only support 1 plane and 8, 24 or 32 bits per pixel and no
+	// compression.
 	planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
+	// if compression is set to BITFIELDS, but the bitmask is set to the default bitmask
+	// that would be used if compression was set to 0, we can continue as if compression was 0
+	if compression == 3 && infoLen > infoHeaderLen &&
+		readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 &&
+		readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 {
+		compression = 0
+	}
 	if planes != 1 || compression != 0 {
 		return image.Config{}, 0, false, ErrUnsupported
 	}
 	switch bpp {
 	case 8:
-		if offset != fileHeaderLen+infoHeaderLen+256*4 {
+		if offset != fileHeaderLen+infoLen+256*4 {
 			return image.Config{}, 0, false, ErrUnsupported
 		}
 		_, err = io.ReadFull(r, b[:256*4])
@@ -181,12 +195,12 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
 		}
 		return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
 	case 24:
-		if offset != fileHeaderLen+infoHeaderLen {
+		if offset != fileHeaderLen+infoLen {
 			return image.Config{}, 0, false, ErrUnsupported
 		}
 		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
 	case 32:
-		if offset != fileHeaderLen+infoHeaderLen {
+		if offset != fileHeaderLen+infoLen {
 			return image.Config{}, 0, false, ErrUnsupported
 		}
 		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
diff --git a/vendor/golang.org/x/image/tiff/reader.go b/vendor/golang.org/x/image/tiff/reader.go
index 8a941c1..ce2ef71 100644
--- a/vendor/golang.org/x/image/tiff/reader.go
+++ b/vendor/golang.org/x/image/tiff/reader.go
@@ -110,7 +110,7 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
 	return u, nil
 }
 
-// parseIFD decides whether the the IFD entry in p is "interesting" and
+// parseIFD decides whether the IFD entry in p is "interesting" and
 // stows away the data in the decoder. It returns the tag number of the
 // entry and an error, if any.
 func (d *decoder) parseIFD(p []byte) (int, error) {
diff --git a/vendor/golang.org/x/image/vp8/decode.go b/vendor/golang.org/x/image/vp8/decode.go
index 1bb5028..2aa9fee 100644
--- a/vendor/golang.org/x/image/vp8/decode.go
+++ b/vendor/golang.org/x/image/vp8/decode.go
@@ -82,7 +82,7 @@ type mb struct {
 	pred [4]uint8
 	// nzMask is a mask of 8 bits: 4 for the bottom or right 4x4 luma regions,
 	// and 2 + 2 for the bottom or right 4x4 chroma regions. A 1 bit indicates
-	// that that region has non-zero coefficients.
+	// that region has non-zero coefficients.
 	nzMask uint8
 	// nzY16 is a 0/1 value that is 1 if the macroblock used Y16 prediction and
 	// had non-zero coefficients.
@@ -274,7 +274,7 @@ func (d *Decoder) parseOtherPartitions() error {
 	var partLens [maxNOP]int
 	d.nOP = 1 << d.fp.readUint(uniformProb, 2)
 
-	// The final partition length is implied by the the remaining chunk data
+	// The final partition length is implied by the remaining chunk data
 	// (d.r.n) and the other d.nOP-1 partition lengths. Those d.nOP-1 partition
 	// lengths are stored as 24-bit uints, i.e. up to 16 MiB per partition.
 	n := 3 * (d.nOP - 1)
diff --git a/vendor/golang.org/x/image/webp/decode.go b/vendor/golang.org/x/image/webp/decode.go
index 111f358..f77a4eb 100644
--- a/vendor/golang.org/x/image/webp/decode.go
+++ b/vendor/golang.org/x/image/webp/decode.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build go1.6
-
 package webp
 
 import (
diff --git a/vendor/golang.org/x/image/webp/doc.go b/vendor/golang.org/x/image/webp/doc.go
new file mode 100644
index 0000000..e321c85
--- /dev/null
+++ b/vendor/golang.org/x/image/webp/doc.go
@@ -0,0 +1,9 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package webp implements a decoder for WEBP images.
+//
+// WEBP is defined at:
+// https://developers.google.com/speed/webp/docs/riff_container
+package webp // import "golang.org/x/image/webp"
diff --git a/vendor/golang.org/x/image/webp/webp.go b/vendor/golang.org/x/image/webp/webp.go
deleted file mode 100644
index 850cdc8..0000000
--- a/vendor/golang.org/x/image/webp/webp.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package webp implements a decoder for WEBP images.
-//
-// WEBP is defined at:
-// https://developers.google.com/speed/webp/docs/riff_container
-//
-// It requires Go 1.6 or later.
-package webp // import "golang.org/x/image/webp"
-
-// This blank Go file, other than the package clause, exists so that this
-// package can be built for Go 1.5 and earlier. (The other files in this
-// package are all marked "+build go1.6" for the NYCbCrA types introduced in Go
-// 1.6). There is no functionality in a blank package, but some image
-// manipulation programs might still underscore import this package for the
-// side effect of registering the WEBP format with the standard library's
-// image.RegisterFormat and image.Decode functions. For example, that program
-// might contain:
-//
-//	// Underscore imports to register some formats for image.Decode.
-//	import _ "image/gif"
-//	import _ "image/jpeg"
-//	import _ "image/png"
-//	import _ "golang.org/x/image/webp"
-//
-// Such a program will still compile for Go 1.5 (due to this placeholder Go
-// file). It will simply not be able to recognize and decode WEBP (but still
-// handle GIF, JPEG and PNG).
diff --git a/vendor/golang.org/x/net/html/const.go b/vendor/golang.org/x/net/html/const.go
index 5eb7c5a..a3a918f 100644
--- a/vendor/golang.org/x/net/html/const.go
+++ b/vendor/golang.org/x/net/html/const.go
@@ -97,8 +97,16 @@ func isSpecialElement(element *Node) bool {
 	switch element.Namespace {
 	case "", "html":
 		return isSpecialElementMap[element.Data]
+	case "math":
+		switch element.Data {
+		case "mi", "mo", "mn", "ms", "mtext", "annotation-xml":
+			return true
+		}
 	case "svg":
-		return element.Data == "foreignObject"
+		switch element.Data {
+		case "foreignObject", "desc", "title":
+			return true
+		}
 	}
 	return false
 }
diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go
index 4b1fa42..64a5793 100644
--- a/vendor/golang.org/x/net/html/parse.go
+++ b/vendor/golang.org/x/net/html/parse.go
@@ -470,6 +470,10 @@ func (p *parser) resetInsertionMode() {
 		case a.Table:
 			p.im = inTableIM
 		case a.Template:
+			// TODO: remove this divergence from the HTML5 spec.
+			if n.Namespace != "" {
+				continue
+			}
 			p.im = p.templateStack.top()
 		case a.Head:
 			// TODO: remove this divergence from the HTML5 spec.
@@ -984,6 +988,14 @@ func inBodyIM(p *parser) bool {
 			p.acknowledgeSelfClosingTag()
 			p.popUntil(buttonScope, a.P)
 			p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+			if p.form == nil {
+				// NOTE: The 'isindex' element has been removed,
+				// and the 'template' element has not been designed to be
+				// collaborative with the index element.
+				//
+				// Ignore the token.
+				return true
+			}
 			if action != "" {
 				p.form.Attr = []Attribute{{Key: "action", Val: action}}
 			}
@@ -1252,12 +1264,6 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
 		switch commonAncestor.DataAtom {
 		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
 			p.fosterParent(lastNode)
-		case a.Template:
-			// TODO: remove namespace checking
-			if commonAncestor.Namespace == "html" {
-				commonAncestor = commonAncestor.LastChild
-			}
-			fallthrough
 		default:
 			commonAncestor.AppendChild(lastNode)
 		}
@@ -2209,6 +2215,15 @@ func (p *parser) parse() error {
 }
 
 // Parse returns the parse tree for the HTML from the given Reader.
+//
+// It implements the HTML5 parsing algorithm
+// (https://html.spec.whatwg.org/multipage/syntax.html#tree-construction),
+// which is very complicated. The resultant tree can contain implicitly created
+// nodes that have no explicit <tag> listed in r's data, and nodes' parents can
+// differ from the nesting implied by a naive processing of start and end
+// <tag>s. Conversely, explicit <tag>s in r's data can be silently dropped,
+// with no corresponding node in the resulting tree.
+//
 // The input is assumed to be UTF-8 encoded.
 func Parse(r io.Reader) (*Node, error) {
 	p := &parser{
@@ -2230,6 +2245,8 @@ func Parse(r io.Reader) (*Node, error) {
 // ParseFragment parses a fragment of HTML and returns the nodes that were
 // found. If the fragment is the InnerHTML for an existing element, pass that
 // element in context.
+//
+// It has the same intricacies as Parse.
 func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
 	contextTag := ""
 	if context != nil {
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml b/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
index a4eb257..b0b525a 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
+++ b/vendor/gopkg.in/russross/blackfriday.v2/.travis.yml
@@ -1,18 +1,17 @@
-# Travis CI (http://travis-ci.org/) is a continuous integration service for
-# open source projects. This file configures it to run unit tests for
-# blackfriday.
-
+sudo: false
 language: go
-
 go:
-    - 1.5
-    - 1.6
-    - 1.7
-
+  - "1.10.x"
+  - "1.11.x"
+  - tip
+matrix:
+  fast_finish: true
+  allow_failures:
+    - go: tip
 install:
-    - go get -d -t -v ./...
-    - go build -v ./...
-
+  - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
 script:
-    - go test -v ./...
-    - go test -run=^$ -bench=BenchmarkReference -benchmem
+  - go get -t -v ./...
+  - diff -u <(echo -n) <(gofmt -d -s .)
+  - go tool vet .
+  - go test -v ./...
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/README.md b/vendor/gopkg.in/russross/blackfriday.v2/README.md
index 2e0db35..d5a8649 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/README.md
+++ b/vendor/gopkg.in/russross/blackfriday.v2/README.md
@@ -34,9 +34,15 @@ Versions
 --------
 
 Currently maintained and recommended version of Blackfriday is `v2`. It's being
-developed on its own branch: https://github.com/russross/blackfriday/v2. You
-should install and import it via [gopkg.in][6] at
-`gopkg.in/russross/blackfriday.v2`.
+developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
+documentation is available at
+https://godoc.org/gopkg.in/russross/blackfriday.v2.
+
+It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
+but we highly recommend using package management tool like [dep][7] or
+[Glide][8] and make use of semantic versioning. With package management you
+should import `github.com/russross/blackfriday` and specify that you're using
+version 2.0.0.
 
 Version 2 offers a number of improvements over v1:
 
@@ -198,7 +204,7 @@ implements the following extensions:
 
         Cat
         : Fluffy animal everyone likes
-        
+
         Internet
         : Vector of transmission for pictures of cats
 
@@ -209,7 +215,7 @@ implements the following extensions:
     end of the document. A footnote looks like this:
 
         This is a footnote.[^1]
-        
+
         [^1]: the footnote text.
 
 *   **Autolinking**. Blackfriday can find URLs that have not been
@@ -255,9 +261,11 @@ are a few of note:
 *   [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
     but for markdown.
 
-*   [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
+*   [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
     renders output as LaTeX.
 
+*   [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
+
 
 Todo
 ----
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/block.go b/vendor/gopkg.in/russross/blackfriday.v2/block.go
index d7da33f..b860747 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/block.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/block.go
@@ -17,6 +17,7 @@ import (
 	"bytes"
 	"html"
 	"regexp"
+	"strings"
 
 	"github.com/shurcooL/sanitized_anchor_name"
 )
@@ -568,8 +569,8 @@ func (*Markdown) isHRule(data []byte) bool {
 
 // isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
 // and returns the end index if so, or 0 otherwise. It also returns the marker found.
-// If syntax is not nil, it gets set to the syntax specified in the fence line.
-func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) {
+// If info is not nil, it gets set to the syntax specified in the fence line.
+func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
 	i, size := 0, 0
 
 	// skip up to three spaces
@@ -605,9 +606,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
 	}
 
 	// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
-	// into one, always get the syntax, and discard it if the caller doesn't care.
-	if syntax != nil {
-		syn := 0
+	// into one, always get the info string, and discard it if the caller doesn't care.
+	if info != nil {
+		infoLength := 0
 		i = skipChar(data, i, ' ')
 
 		if i >= len(data) {
@@ -617,14 +618,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
 			return 0, ""
 		}
 
-		syntaxStart := i
+		infoStart := i
 
 		if data[i] == '{' {
 			i++
-			syntaxStart++
+			infoStart++
 
 			for i < len(data) && data[i] != '}' && data[i] != '\n' {
-				syn++
+				infoLength++
 				i++
 			}
 
@@ -634,31 +635,30 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
 
 			// strip all whitespace at the beginning and the end
 			// of the {} block
-			for syn > 0 && isspace(data[syntaxStart]) {
-				syntaxStart++
-				syn--
+			for infoLength > 0 && isspace(data[infoStart]) {
+				infoStart++
+				infoLength--
 			}
 
-			for syn > 0 && isspace(data[syntaxStart+syn-1]) {
-				syn--
+			for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
+				infoLength--
 			}
-
 			i++
+			i = skipChar(data, i, ' ')
 		} else {
-			for i < len(data) && !isspace(data[i]) {
-				syn++
+			for i < len(data) && !isverticalspace(data[i]) {
+				infoLength++
 				i++
 			}
 		}
 
-		*syntax = string(data[syntaxStart : syntaxStart+syn])
+		*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
 	}
 
-	i = skipChar(data, i, ' ')
-	if i >= len(data) || data[i] != '\n' {
-		if i == len(data) {
-			return i, marker
-		}
+	if i == len(data) {
+		return i, marker
+	}
+	if i > len(data) || data[i] != '\n' {
 		return 0, ""
 	}
 	return i + 1, marker // Take newline into account.
@@ -668,14 +668,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
 // or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
 // If doRender is true, a final newline is mandatory to recognize the fenced code block.
 func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
-	var syntax string
-	beg, marker := isFenceLine(data, &syntax, "")
+	var info string
+	beg, marker := isFenceLine(data, &info, "")
 	if beg == 0 || beg >= len(data) {
 		return 0
 	}
 
 	var work bytes.Buffer
-	work.Write([]byte(syntax))
+	work.Write([]byte(info))
 	work.WriteByte('\n')
 
 	for {
@@ -1148,6 +1148,18 @@ func (p *Markdown) list(data []byte, flags ListType) int {
 	return i
 }
 
+// Returns true if the list item is not the same type as its parent list
+func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
+	if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
+		return true
+	} else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
+		return true
+	} else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
+		return true
+	}
+	return false
+}
+
 // Returns true if block ends with a blank line, descending if needed
 // into lists and sublists.
 func endsWithBlankLine(block *Node) bool {
@@ -1246,6 +1258,7 @@ func (p *Markdown) listItem(data []byte, flags *ListType) int {
 	// process the following lines
 	containsBlankLine := false
 	sublist := 0
+	codeBlockMarker := ""
 
 gatherlines:
 	for line < len(data) {
@@ -1279,6 +1292,27 @@ gatherlines:
 
 		chunk := data[line+indentIndex : i]
 
+		if p.extensions&FencedCode != 0 {
+			// determine if in or out of codeblock
+			// if in codeblock, ignore normal list processing
+			_, marker := isFenceLine(chunk, nil, codeBlockMarker)
+			if marker != "" {
+				if codeBlockMarker == "" {
+					// start of codeblock
+					codeBlockMarker = marker
+				} else {
+					// end of codeblock.
+					codeBlockMarker = ""
+				}
+			}
+			// we are in a codeblock, write line, and continue
+			if codeBlockMarker != "" || marker != "" {
+				raw.Write(data[line+indentIndex : i])
+				line = i
+				continue gatherlines
+			}
+		}
+
 		// evaluate how this line fits in
 		switch {
 		// is this a nested list item?
@@ -1286,16 +1320,23 @@ gatherlines:
 			p.oliPrefix(chunk) > 0 ||
 			p.dliPrefix(chunk) > 0:
 
-			if containsBlankLine {
-				*flags |= ListItemContainsBlock
-			}
-
 			// to be a nested list, it must be indented more
-			// if not, it is the next item in the same list
+			// if not, it is either a different kind of list
+			// or the next item in the same list
 			if indent <= itemIndent {
+				if p.listTypeChanged(chunk, flags) {
+					*flags |= ListItemEndOfList
+				} else if containsBlankLine {
+					*flags |= ListItemContainsBlock
+				}
+
 				break gatherlines
 			}
 
+			if containsBlankLine {
+				*flags |= ListItemContainsBlock
+			}
+
 			// is this the first item in the nested list?
 			if sublist == 0 {
 				sublist = raw.Len()
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/go.mod b/vendor/gopkg.in/russross/blackfriday.v2/go.mod
new file mode 100644
index 0000000..620b74e
--- /dev/null
+++ b/vendor/gopkg.in/russross/blackfriday.v2/go.mod
@@ -0,0 +1 @@
+module github.com/russross/blackfriday/v2
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/html.go b/vendor/gopkg.in/russross/blackfriday.v2/html.go
index 25fb185..284c871 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/html.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/html.go
@@ -35,6 +35,7 @@ const (
 	Safelink                                      // Only link to trusted protocols
 	NofollowLinks                                 // Only link with rel="nofollow"
 	NoreferrerLinks                               // Only link with rel="noreferrer"
+	NoopenerLinks                                 // Only link with rel="noopener"
 	HrefTargetBlank                               // Add a blank target
 	CompletePage                                  // Generate a complete HTML page
 	UseXHTML                                      // Generate XHTML output instead of HTML
@@ -87,6 +88,10 @@ type HTMLRendererParameters struct {
 	HeadingIDPrefix string
 	// If set, add this text to the back of each Heading ID, to ensure uniqueness.
 	HeadingIDSuffix string
+	// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
+	// Negative offset is also valid.
+	// Resulting levels are clipped between 1 and 6.
+	HeadingLevelOffset int
 
 	Title string // Document title (used if CompletePage is set)
 	CSS   string // Optional CSS file URL (used if CompletePage is set)
@@ -282,6 +287,9 @@ func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
 	if flags&NoreferrerLinks != 0 {
 		val = append(val, "noreferrer")
 	}
+	if flags&NoopenerLinks != 0 {
+		val = append(val, "noopener")
+	}
 	if flags&HrefTargetBlank != 0 {
 		attrs = append(attrs, "target=\"_blank\"")
 	}
@@ -331,7 +339,7 @@ func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
 
 func footnoteRef(prefix string, node *Node) []byte {
 	urlFrag := prefix + string(slugify(node.Destination))
-	anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
+	anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
 	return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
 }
 
@@ -460,9 +468,10 @@ var (
 )
 
 func headingTagsFromLevel(level int) ([]byte, []byte) {
-	switch level {
-	case 1:
+	if level <= 1 {
 		return h1Tag, h1CloseTag
+	}
+	switch level {
 	case 2:
 		return h2Tag, h2CloseTag
 	case 3:
@@ -471,9 +480,8 @@ func headingTagsFromLevel(level int) ([]byte, []byte) {
 		return h4Tag, h4CloseTag
 	case 5:
 		return h5Tag, h5CloseTag
-	default:
-		return h6Tag, h6CloseTag
 	}
+	return h6Tag, h6CloseTag
 }
 
 func (r *HTMLRenderer) outHRTag(w io.Writer) {
@@ -651,7 +659,8 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
 		r.out(w, node.Literal)
 		r.cr(w)
 	case Heading:
-		openTag, closeTag := headingTagsFromLevel(node.Level)
+		headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
+		openTag, closeTag := headingTagsFromLevel(headingLevel)
 		if entering {
 			if node.IsTitleblock {
 				attrs = append(attrs, `class="title"`)
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/inline.go b/vendor/gopkg.in/russross/blackfriday.v2/inline.go
index 3d63310..4ed2907 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/inline.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/inline.go
@@ -23,8 +23,22 @@ var (
 	urlRe    = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
 	anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
 
-	// TODO: improve this regexp to catch all possible entities:
-	htmlEntityRe = regexp.MustCompile(`&[a-z]{2,5};`)
+	// https://www.w3.org/TR/html5/syntax.html#character-references
+	// highest unicode code point in 17 planes (2^20): 1,114,112d =
+	// 7 dec digits or 6 hex digits
+	// named entity references can be 2-31 characters with stuff like &lt;
+	// at one end and &CounterClockwiseContourIntegral; at the other. There
+	// are also sometimes numbers at the end, although this isn't inherent
+	// in the specification; there are never numbers anywhere else in
+	// current character references, though; see &frac34; and &blk12;, etc.
+	// https://www.w3.org/TR/html5/syntax.html#named-character-references
+	//
+	// entity := "&" (named group | number ref) ";"
+	// named group := [a-zA-Z]{2,31}[0-9]{0,2}
+	// number ref := "#" (dec ref | hex ref)
+	// dec ref := [0-9]{1,7}
+	// hex ref := ("x" | "X") [0-9a-fA-F]{1,6}
+	htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`)
 )
 
 // Functions to parse text within a block
diff --git a/vendor/gopkg.in/russross/blackfriday.v2/markdown.go b/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
index ff61cb0..58d2e45 100644
--- a/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
+++ b/vendor/gopkg.in/russross/blackfriday.v2/markdown.go
@@ -93,46 +93,46 @@ const (
 // blockTags is a set of tags that are recognized as HTML block tags.
 // Any of these can be included in markdown text without special escaping.
 var blockTags = map[string]struct{}{
-	"blockquote": struct{}{},
-	"del":        struct{}{},
-	"div":        struct{}{},
-	"dl":         struct{}{},
-	"fieldset":   struct{}{},
-	"form":       struct{}{},
-	"h1":         struct{}{},
-	"h2":         struct{}{},
-	"h3":         struct{}{},
-	"h4":         struct{}{},
-	"h5":         struct{}{},
-	"h6":         struct{}{},
-	"iframe":     struct{}{},
-	"ins":        struct{}{},
-	"math":       struct{}{},
-	"noscript":   struct{}{},
-	"ol":         struct{}{},
-	"pre":        struct{}{},
-	"p":          struct{}{},
-	"script":     struct{}{},
-	"style":      struct{}{},
-	"table":      struct{}{},
-	"ul":         struct{}{},
+	"blockquote": {},
+	"del":        {},
+	"div":        {},
+	"dl":         {},
+	"fieldset":   {},
+	"form":       {},
+	"h1":         {},
+	"h2":         {},
+	"h3":         {},
+	"h4":         {},
+	"h5":         {},
+	"h6":         {},
+	"iframe":     {},
+	"ins":        {},
+	"math":       {},
+	"noscript":   {},
+	"ol":         {},
+	"pre":        {},
+	"p":          {},
+	"script":     {},
+	"style":      {},
+	"table":      {},
+	"ul":         {},
 
 	// HTML5
-	"address":    struct{}{},
-	"article":    struct{}{},
-	"aside":      struct{}{},
-	"canvas":     struct{}{},
-	"figcaption": struct{}{},
-	"figure":     struct{}{},
-	"footer":     struct{}{},
-	"header":     struct{}{},
-	"hgroup":     struct{}{},
-	"main":       struct{}{},
-	"nav":        struct{}{},
-	"output":     struct{}{},
-	"progress":   struct{}{},
-	"section":    struct{}{},
-	"video":      struct{}{},
+	"address":    {},
+	"article":    {},
+	"aside":      {},
+	"canvas":     {},
+	"figcaption": {},
+	"figure":     {},
+	"footer":     {},
+	"header":     {},
+	"hgroup":     {},
+	"main":       {},
+	"nav":        {},
+	"output":     {},
+	"progress":   {},
+	"section":    {},
+	"video":      {},
 }
 
 // Renderer is the rendering interface. This is mostly of interest if you are
@@ -480,11 +480,11 @@ func (p *Markdown) parseRefsToAST() {
 //    [^note]: This is the explanation.
 //
 // Footnotes should be placed at the end of the document in an ordered list.
-// Inline footnotes such as:
+// Finally, there are inline footnotes such as:
 //
-//    Inline footnotes^[Not supported.] also exist.
+//    Inline footnotes^[Also supported.] provide a quick inline explanation,
+//    but are rendered at the bottom of the document.
 //
-// are not yet supported.
 
 // reference holds all information necessary for a reference-style links or
 // footnotes.
@@ -813,7 +813,17 @@ func ispunct(c byte) bool {
 
 // Test if a character is a whitespace character.
 func isspace(c byte) bool {
-	return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
+	return ishorizontalspace(c) || isverticalspace(c)
+}
+
+// Test if a character is a horizontal whitespace character.
+func ishorizontalspace(c byte) bool {
+	return c == ' ' || c == '\t'
+}
+
+// Test if a character is a vertical character.
+func isverticalspace(c byte) bool {
+	return c == '\n' || c == '\r' || c == '\f' || c == '\v'
 }
 
 // Test if a character is letter.
diff --git a/vendor/maunium.net/go/gomatrix/.gitignore b/vendor/maunium.net/go/gomatrix/.gitignore
deleted file mode 100644
index daf913b..0000000
--- a/vendor/maunium.net/go/gomatrix/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe
-*.test
-*.prof
diff --git a/vendor/maunium.net/go/gomatrix/.travis.yml b/vendor/maunium.net/go/gomatrix/.travis.yml
deleted file mode 100644
index fadc326..0000000
--- a/vendor/maunium.net/go/gomatrix/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: go
-go:
- - 1.8
-install:
- - go get github.com/golang/lint/golint
- - go get github.com/fzipp/gocyclo
- - go get github.com/client9/misspell/...
- - go get github.com/gordonklaus/ineffassign
-script: ./hooks/pre-commit
diff --git a/vendor/maunium.net/go/gomatrix/LICENSE b/vendor/maunium.net/go/gomatrix/LICENSE
deleted file mode 100644
index 8dada3e..0000000
--- a/vendor/maunium.net/go/gomatrix/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   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.
diff --git a/vendor/maunium.net/go/gomatrix/README.md b/vendor/maunium.net/go/gomatrix/README.md
deleted file mode 100644
index ea9109a..0000000
--- a/vendor/maunium.net/go/gomatrix/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# gomatrix
-[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix)
-
-A Golang Matrix client.
-
-**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.**
diff --git a/vendor/maunium.net/go/gomatrix/client.go b/vendor/maunium.net/go/gomatrix/client.go
deleted file mode 100644
index 0806138..0000000
--- a/vendor/maunium.net/go/gomatrix/client.go
+++ /dev/null
@@ -1,794 +0,0 @@
-// Package gomatrix implements the Matrix Client-Server API.
-//
-// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html
-package gomatrix
-
-import (
-	"bytes"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"maunium.net/go/maulogger"
-	"net/http"
-	"net/url"
-	"path"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
-)
-
-// Client represents a Matrix client.
-type Client struct {
-	HomeserverURL *url.URL     // The base homeserver URL
-	Prefix        string       // The API prefix eg '/_matrix/client/r0'
-	UserID        string       // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
-	AccessToken   string       // The access_token for the client.
-	Client        *http.Client // The underlying HTTP client which will be used to make HTTP requests.
-	Syncer        Syncer       // The thing which can process /sync responses
-	Store         Storer       // The thing which can store rooms/tokens/ids
-	Logger        maulogger.Logger
-
-	// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
-	// no user_id parameter will be sent.
-	// See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion
-	AppServiceUserID string
-
-	syncingMutex sync.Mutex // protects syncingID
-	syncingID    uint32     // Identifies the current Sync. Only one Sync can be active at any given time.
-}
-
-// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
-type HTTPError struct {
-	WrappedError error
-	RespError    *RespError
-	Message      string
-	Code         int
-}
-
-func (e HTTPError) Error() string {
-	var wrappedErrMsg string
-	if e.WrappedError != nil {
-		wrappedErrMsg = e.WrappedError.Error()
-	}
-	return fmt.Sprintf("msg=%s code=%d wrapped=%s", e.Message, e.Code, wrappedErrMsg)
-}
-
-// BuildURL builds a URL with the Client's homserver/prefix/access_token set already.
-func (cli *Client) BuildURL(urlPath ...string) string {
-	ps := []string{cli.Prefix}
-	for _, p := range urlPath {
-		ps = append(ps, p)
-	}
-	return cli.BuildBaseURL(ps...)
-}
-
-// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must
-// supply the prefix in the path.
-func (cli *Client) BuildBaseURL(urlPath ...string) string {
-	// copy the URL. Purposefully ignore error as the input is from a valid URL already
-	hsURL, _ := url.Parse(cli.HomeserverURL.String())
-	parts := []string{hsURL.Path}
-	parts = append(parts, urlPath...)
-	hsURL.Path = path.Join(parts...)
-	query := hsURL.Query()
-	if cli.AccessToken != "" {
-		query.Set("access_token", cli.AccessToken)
-	}
-	if cli.AppServiceUserID != "" {
-		query.Set("user_id", cli.AppServiceUserID)
-	}
-	hsURL.RawQuery = query.Encode()
-	return hsURL.String()
-}
-
-// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already.
-func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string {
-	u, _ := url.Parse(cli.BuildURL(urlPath...))
-	q := u.Query()
-	for k, v := range urlQuery {
-		q.Set(k, v)
-	}
-	u.RawQuery = q.Encode()
-	return u.String()
-}
-
-// SetCredentials sets the user ID and access token on this client instance.
-func (cli *Client) SetCredentials(userID, accessToken string) {
-	cli.AccessToken = accessToken
-	cli.UserID = userID
-}
-
-// ClearCredentials removes the user ID and access token on this client instance.
-func (cli *Client) ClearCredentials() {
-	cli.AccessToken = ""
-	cli.UserID = ""
-}
-
-// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the
-// error will be nil.
-//
-// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine.
-// Fatal sync errors can be caused by:
-//   - The failure to create a filter.
-//   - Client.Syncer.OnFailedSync returning an error in response to a failed sync.
-//   - Client.Syncer.ProcessResponse returning an error.
-// If you wish to continue retrying in spite of these fatal errors, call Sync() again.
-func (cli *Client) Sync() error {
-	// Mark the client as syncing.
-	// We will keep syncing until the syncing state changes. Either because
-	// Sync is called or StopSync is called.
-	syncingID := cli.incrementSyncingID()
-	nextBatch := cli.Store.LoadNextBatch(cli.UserID)
-	filterID := cli.Store.LoadFilterID(cli.UserID)
-	if filterID == "" {
-		filterJSON := cli.Syncer.GetFilterJSON(cli.UserID)
-		resFilter, err := cli.CreateFilter(filterJSON)
-		if err != nil {
-			return err
-		}
-		filterID = resFilter.FilterID
-		cli.Store.SaveFilterID(cli.UserID, filterID)
-	}
-
-	for {
-		resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
-		if err != nil {
-			duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
-			if err2 != nil {
-				return err2
-			}
-			time.Sleep(duration)
-			continue
-		}
-
-		// Check that the syncing state hasn't changed
-		// Either because we've stopped syncing or another sync has been started.
-		// We discard the response from our sync.
-		if cli.getSyncingID() != syncingID {
-			return nil
-		}
-
-		// Save the token now *before* processing it. This means it's possible
-		// to not process some events, but it means that we won't get constantly stuck processing
-		// a malformed/buggy event which keeps making us panic.
-		cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch)
-		if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil {
-			return err
-		}
-
-		nextBatch = resSync.NextBatch
-	}
-}
-
-func (cli *Client) incrementSyncingID() uint32 {
-	cli.syncingMutex.Lock()
-	defer cli.syncingMutex.Unlock()
-	cli.syncingID++
-	return cli.syncingID
-}
-
-func (cli *Client) getSyncingID() uint32 {
-	cli.syncingMutex.Lock()
-	defer cli.syncingMutex.Unlock()
-	return cli.syncingID
-}
-
-// StopSync stops the ongoing sync started by Sync.
-func (cli *Client) StopSync() {
-	// Advance the syncing state so that any running Syncs will terminate.
-	cli.incrementSyncingID()
-}
-
-func (cli *Client) LogRequest(req *http.Request, body string) {
-	if cli.Logger == nil {
-		return
-	}
-
-	cli.Logger.Debugfln("%s %s %s", req.Method, req.URL.Path, body)
-}
-
-// MakeRequest makes a JSON HTTP request to the given URL.
-// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
-//
-// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along
-// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned
-// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError.
-func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
-	var req *http.Request
-	var err error
-	logBody := "{}"
-	if reqBody != nil {
-		var jsonStr []byte
-		jsonStr, err = json.Marshal(reqBody)
-		if err != nil {
-			return nil, err
-		}
-		logBody = string(jsonStr)
-		req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
-	} else {
-		req, err = http.NewRequest(method, httpURL, nil)
-	}
-
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("Content-Type", "application/json")
-	cli.LogRequest(req, logBody)
-	res, err := cli.Client.Do(req)
-	if res != nil {
-		defer res.Body.Close()
-	}
-	if err != nil {
-		return nil, err
-	}
-	contents, err := ioutil.ReadAll(res.Body)
-	if res.StatusCode/100 != 2 { // not 2xx
-		var wrap error
-		respErr := &RespError{}
-		if _ = json.Unmarshal(contents, respErr); respErr.ErrCode != "" {
-			wrap = respErr
-		} else {
-			respErr = nil
-		}
-
-		// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
-		// HTTP error instead (e.g proxy errors which return HTML).
-		msg := "Failed to " + method + " JSON to " + req.URL.Path
-		if wrap == nil {
-			msg = msg + ": " + string(contents)
-		}
-
-		return contents, HTTPError{
-			Code:         res.StatusCode,
-			Message:      msg,
-			WrappedError: wrap,
-			RespError:    respErr,
-		}
-	}
-	if err != nil {
-		return nil, err
-	}
-
-	if resBody != nil {
-		if err = json.Unmarshal(contents, &resBody); err != nil {
-			return nil, err
-		}
-	}
-
-	return contents, nil
-}
-
-// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
-func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) {
-	urlPath := cli.BuildURL("user", cli.UserID, "filter")
-	_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
-	return
-}
-
-// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
-func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) {
-	query := map[string]string{
-		"timeout": strconv.Itoa(timeout),
-	}
-	if since != "" {
-		query["since"] = since
-	}
-	if filterID != "" {
-		query["filter"] = filterID
-	}
-	if setPresence != "" {
-		query["set_presence"] = setPresence
-	}
-	if fullState {
-		query["full_state"] = "true"
-	}
-	urlPath := cli.BuildURLWithQuery([]string{"sync"}, query)
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
-	var bodyBytes []byte
-	bodyBytes, err = cli.MakeRequest("POST", u, req, nil)
-	if err != nil {
-		httpErr, ok := err.(HTTPError)
-		if !ok { // network error
-			return
-		}
-		if httpErr.Code == 401 {
-			// body should be RespUserInteractive, if it isn't, fail with the error
-			err = json.Unmarshal(bodyBytes, &uiaResp)
-			return
-		}
-		return
-	}
-	// body should be RespRegister
-	err = json.Unmarshal(bodyBytes, &resp)
-	return
-}
-
-// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
-//
-// Registers with kind=user. For kind=guest, see RegisterGuest.
-func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
-	u := cli.BuildURL("register")
-	return cli.register(u, req)
-}
-
-// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
-// with kind=guest.
-//
-// For kind=user, see Register.
-func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
-	query := map[string]string{
-		"kind": "guest",
-	}
-	u := cli.BuildURLWithQuery([]string{"register"}, query)
-	return cli.register(u, req)
-}
-
-// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth
-//
-// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration
-// this way. If the homeserver does not, an error is returned.
-//
-// This does not set credentials on the client instance. See SetCredentials() instead.
-//
-// 	res, err := cli.RegisterDummy(&gomatrix.ReqRegister{
-//		Username: "alice",
-//		Password: "wonderland",
-//	})
-//  if err != nil {
-// 		panic(err)
-// 	}
-// 	token := res.AccessToken
-func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
-	res, uia, err := cli.Register(req)
-	if err != nil && uia == nil {
-		return nil, err
-	}
-	if uia != nil && uia.HasSingleStageFlow("m.login.dummy") {
-		req.Auth = struct {
-			Type    string `json:"type"`
-			Session string `json:"session,omitempty"`
-		}{"m.login.dummy", uia.Session}
-		res, _, err = cli.Register(req)
-		if err != nil {
-			return nil, err
-		}
-	}
-	if res == nil {
-		return nil, fmt.Errorf("registration failed: does this server support m.login.dummy? ")
-	}
-	return res, nil
-}
-
-// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
-// This does not set credentials on this client instance. See SetCredentials() instead.
-func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) {
-	urlPath := cli.BuildURL("login")
-	_, err = cli.MakeRequest("POST", urlPath, req, &resp)
-	return
-}
-
-// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
-// This does not clear the credentials from the client instance. See ClearCredentials() instead.
-func (cli *Client) Logout() (resp *RespLogout, err error) {
-	urlPath := cli.BuildURL("logout")
-	_, err = cli.MakeRequest("POST", urlPath, nil, &resp)
-	return
-}
-
-// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
-func (cli *Client) Versions() (resp *RespVersions, err error) {
-	urlPath := cli.BuildBaseURL("_matrix", "client", "versions")
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias
-//
-// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will
-// be JSON encoded and used as the request body.
-func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) {
-	var urlPath string
-	if serverName != "" {
-		urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{
-			"server_name": serverName,
-		})
-	} else {
-		urlPath = cli.BuildURL("join", roomIDorAlias)
-	}
-	_, err = cli.MakeRequest("POST", urlPath, content, &resp)
-	return
-}
-
-// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
-func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) {
-	urlPath := cli.BuildURL("profile", mxid, "displayname")
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
-func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) {
-	urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname
-func (cli *Client) SetDisplayName(displayName string) (err error) {
-	urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
-	s := struct {
-		DisplayName string `json:"displayname"`
-	}{displayName}
-	_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
-	return
-}
-
-// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url
-func (cli *Client) GetAvatarURL() (url string, err error) {
-	urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
-	s := struct {
-		AvatarURL string `json:"avatar_url"`
-	}{}
-
-	_, err = cli.MakeRequest("GET", urlPath, nil, &s)
-	if err != nil {
-		return "", err
-	}
-
-	return s.AvatarURL, nil
-}
-
-// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url
-func (cli *Client) SetAvatarURL(url string) (err error) {
-	urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
-	s := struct {
-		AvatarURL string `json:"avatar_url"`
-	}{url}
-	_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
-// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJSON interface{}) (resp *RespSendEvent, err error) {
-	txnID := txnID()
-	urlPath := cli.BuildURL("rooms", roomID, "send", eventType.String(), txnID)
-	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
-	return
-}
-
-// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
-// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendMassagedMessageEvent(roomID string, eventType EventType, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
-	txnID := txnID()
-	urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "send", eventType.String(), txnID}, map[string]string{
-		"ts": strconv.FormatInt(ts, 10),
-	})
-	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
-	return
-}
-
-// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
-// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
-	urlPath := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
-	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
-	return
-}
-
-// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
-// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
-func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
-	urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{
-		"ts": strconv.FormatInt(ts, 10),
-	})
-	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
-	return
-}
-
-// SendText sends an m.room.message event into the given room with a msgtype of m.text
-// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
-func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
-	return cli.SendMessageEvent(roomID, EventMessage, Content{
-		MsgType: MsgText,
-		Body:    text,
-	})
-}
-
-// SendImage sends an m.room.message event into the given room with a msgtype of m.image
-// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
-func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
-	return cli.SendMessageEvent(roomID, EventMessage, Content{
-		MsgType: MsgImage,
-		Body:    body,
-		URL:     url,
-	})
-}
-
-// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
-// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
-func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) {
-	return cli.SendMessageEvent(roomID, EventMessage, Content{
-		MsgType: MsgVideo,
-		Body:    body,
-		URL:     url,
-	})
-}
-
-// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
-// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
-func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) {
-	return cli.SendMessageEvent(roomID, EventMessage, Content{
-		MsgType: MsgNotice,
-		Body:    text,
-	})
-}
-
-// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
-func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) {
-	txnID := txnID()
-	urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID)
-	_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
-	return
-}
-
-// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
-//  resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{
-//  	Preset: "public_chat",
-//  })
-//  fmt.Println("Room:", resp.RoomID)
-func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) {
-	urlPath := cli.BuildURL("createRoom")
-	_, err = cli.MakeRequest("POST", urlPath, req, &resp)
-	return
-}
-
-// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
-func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) {
-	u := cli.BuildURL("rooms", roomID, "leave")
-	_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
-	return
-}
-
-// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
-func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) {
-	u := cli.BuildURL("rooms", roomID, "forget")
-	_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
-	return
-}
-
-// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
-func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) {
-	u := cli.BuildURL("rooms", roomID, "invite")
-	_, err = cli.MakeRequest("POST", u, req, &resp)
-	return
-}
-
-// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint
-func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
-	u := cli.BuildURL("rooms", roomID, "invite")
-	_, err = cli.MakeRequest("POST", u, req, &resp)
-	return
-}
-
-// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
-func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) {
-	u := cli.BuildURL("rooms", roomID, "kick")
-	_, err = cli.MakeRequest("POST", u, req, &resp)
-	return
-}
-
-// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
-func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) {
-	u := cli.BuildURL("rooms", roomID, "ban")
-	_, err = cli.MakeRequest("POST", u, req, &resp)
-	return
-}
-
-// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
-func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
-	u := cli.BuildURL("rooms", roomID, "unban")
-	_, err = cli.MakeRequest("POST", u, req, &resp)
-	return
-}
-
-// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
-func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) {
-	req := ReqTyping{Typing: typing, Timeout: timeout}
-	u := cli.BuildURL("rooms", roomID, "typing", cli.UserID)
-	_, err = cli.MakeRequest("PUT", u, req, &resp)
-	return
-}
-
-func (cli *Client) SetPresence(status string) (err error) {
-	req := ReqPresence{Presence: status}
-	u := cli.BuildURL("presence", cli.UserID, "status")
-	_, err = cli.MakeRequest("PUT", u, req, nil)
-	return
-}
-
-// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
-// the HTTP response body, or return an error.
-// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
-func (cli *Client) StateEvent(roomID string, eventType EventType, stateKey string, outContent interface{}) (err error) {
-	u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
-	_, err = cli.MakeRequest("GET", u, nil, outContent)
-	return
-}
-
-// UploadLink uploads an HTTP URL and then returns an MXC URI.
-func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
-	res, err := cli.Client.Get(link)
-	if res != nil {
-		defer res.Body.Close()
-	}
-	if err != nil {
-		return nil, err
-	}
-	return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
-}
-
-func (cli *Client) Download(mxcURL string) (io.ReadCloser, error) {
-	if !strings.HasPrefix(mxcURL, "mxc://") {
-		return nil, errors.New("invalid Matrix content URL")
-	}
-	parts := strings.Split(mxcURL[len("mxc://"):], "/")
-	if len(parts) != 2 {
-		return nil, errors.New("invalid Matrix content URL")
-	}
-	u := cli.BuildBaseURL("_matrix/media/r0/download", parts[0], parts[1])
-	resp, err := cli.Client.Get(u)
-	if err != nil {
-		return nil, err
-	}
-	return resp.Body, nil
-}
-
-func (cli *Client) DownloadBytes(mxcURL string) ([]byte, error) {
-	resp, err := cli.Download(mxcURL)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Close()
-	return ioutil.ReadAll(resp)
-}
-
-func (cli *Client) UploadBytes(data []byte, contentType string) (*RespMediaUpload, error) {
-	return cli.Upload(bytes.NewReader(data), contentType, int64(len(data)))
-}
-
-// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
-// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
-func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
-	req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("Content-Type", contentType)
-	req.ContentLength = contentLength
-	cli.LogRequest(req, fmt.Sprintf("%d bytes", contentLength))
-	res, err := cli.Client.Do(req)
-	if res != nil {
-		defer res.Body.Close()
-	}
-	if err != nil {
-		return nil, err
-	}
-	if res.StatusCode != 200 {
-		contents, err := ioutil.ReadAll(res.Body)
-		if err != nil {
-			return nil, HTTPError{
-				Message: "Upload request failed - Failed to read response body: " + err.Error(),
-				Code:    res.StatusCode,
-			}
-		}
-		return nil, HTTPError{
-			Message: "Upload request failed: " + string(contents),
-			Code:    res.StatusCode,
-		}
-	}
-	var m RespMediaUpload
-	if err := json.NewDecoder(res.Body).Decode(&m); err != nil {
-		return nil, err
-	}
-	return &m, nil
-}
-
-// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
-//
-// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
-// This API is primarily designed for application services which may want to efficiently look up joined members in a room.
-func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) {
-	u := cli.BuildURL("rooms", roomID, "joined_members")
-	_, err = cli.MakeRequest("GET", u, nil, &resp)
-	return
-}
-
-// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
-//
-// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
-// This API is primarily designed for application services which may want to efficiently look up joined rooms.
-func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
-	u := cli.BuildURL("joined_rooms")
-	_, err = cli.MakeRequest("GET", u, nil, &resp)
-	return
-}
-
-// Messages returns a list of message and state events for a room. It uses
-// pagination query parameters to paginate history in the room.
-// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
-func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
-	query := map[string]string{
-		"from": from,
-		"dir":  string(dir),
-	}
-	if to != "" {
-		query["to"] = to
-	}
-	if limit != 0 {
-		query["limit"] = strconv.Itoa(limit)
-	}
-
-	urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-func (cli *Client) GetEvent(roomID, eventID string) (resp *Event, err error) {
-	urlPath := cli.BuildURL("rooms", roomID, "event", eventID)
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-func (cli *Client) MarkRead(roomID, eventID string) (err error) {
-	urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
-	_, err = cli.MakeRequest("POST", urlPath, struct{}{}, nil)
-	return
-}
-
-// TurnServer returns turn server details and credentials for the client to use when initiating calls.
-// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
-func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
-	urlPath := cli.BuildURL("voip", "turnServer")
-	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
-	return
-}
-
-func txnID() string {
-	return "go" + strconv.FormatInt(time.Now().UnixNano(), 10)
-}
-
-// NewClient creates a new Matrix Client ready for syncing
-func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
-	hsURL, err := url.Parse(homeserverURL)
-	if err != nil {
-		return nil, err
-	}
-	// By default, use an in-memory store which will never save filter ids / next batch tokens to disk.
-	// The client will work with this storer: it just won't remember across restarts.
-	// In practice, a database backend should be used.
-	store := NewInMemoryStore()
-	cli := Client{
-		AccessToken:   accessToken,
-		HomeserverURL: hsURL,
-		UserID:        userID,
-		Prefix:        "/_matrix/client/r0",
-		Syncer:        NewDefaultSyncer(userID, store),
-		Store:         store,
-	}
-	// By default, use the default HTTP client.
-	cli.Client = http.DefaultClient
-
-	return &cli, nil
-}
diff --git a/vendor/maunium.net/go/gomatrix/events.go b/vendor/maunium.net/go/gomatrix/events.go
deleted file mode 100644
index 30166cd..0000000
--- a/vendor/maunium.net/go/gomatrix/events.go
+++ /dev/null
@@ -1,413 +0,0 @@
-package gomatrix
-
-import (
-	"encoding/json"
-	"strings"
-	"sync"
-)
-
-type EventTypeClass int
-
-const (
-	// Normal message events
-	MessageEventType EventTypeClass = iota
-	// State events
-	StateEventType
-	// Ephemeral events
-	EphemeralEventType
-	// Account data events
-	AccountDataEventType
-	// Unknown events
-	UnknownEventType
-)
-
-type EventType struct {
-	Type  string
-	Class EventTypeClass
-}
-
-func NewEventType(name string) EventType {
-	evtType := EventType{Type: name}
-	evtType.Class = evtType.GuessClass()
-	return evtType
-}
-
-func (et *EventType) IsState() bool {
-	return et.Class == StateEventType
-}
-
-func (et *EventType) IsEphemeral() bool {
-	return et.Class == EphemeralEventType
-}
-
-func (et *EventType) IsCustom() bool {
-	return !strings.HasPrefix(et.Type, "m.")
-}
-
-func (et *EventType) GuessClass() EventTypeClass {
-	switch et.Type {
-	case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
-		StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type:
-		return StateEventType
-	case EphemeralEventReceipt.Type, EphemeralEventTyping.Type:
-		return EphemeralEventType
-	case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type:
-		return AccountDataEventType
-	case EventRedaction.Type, EventMessage.Type, EventSticker.Type:
-		return MessageEventType
-	default:
-		return UnknownEventType
-	}
-}
-
-func (et *EventType) UnmarshalJSON(data []byte) error {
-	err := json.Unmarshal(data, &et.Type)
-	if err != nil {
-		return err
-	}
-	et.Class = et.GuessClass()
-	return nil
-}
-
-func (et *EventType) MarshalJSON() ([]byte, error) {
-	return json.Marshal(&et.Type)
-}
-
-func (et *EventType) String() string {
-	return et.Type
-}
-
-// State events
-var (
-	StateAliases        = EventType{"m.room.aliases", StateEventType}
-	StateCanonicalAlias = EventType{"m.room.canonical_alias", StateEventType}
-	StateCreate         = EventType{"m.room.create", StateEventType}
-	StateJoinRules      = EventType{"m.room.join_rules", StateEventType}
-	StateMember         = EventType{"m.room.member", StateEventType}
-	StatePowerLevels    = EventType{"m.room.power_levels", StateEventType}
-	StateRoomName       = EventType{"m.room.name", StateEventType}
-	StateTopic          = EventType{"m.room.topic", StateEventType}
-	StateRoomAvatar     = EventType{"m.room.avatar", StateEventType}
-	StatePinnedEvents   = EventType{"m.room.pinned_events", StateEventType}
-)
-
-// Message events
-var (
-	EventRedaction = EventType{"m.room.redaction", MessageEventType}
-	EventMessage   = EventType{"m.room.message", MessageEventType}
-	EventSticker   = EventType{"m.sticker", MessageEventType}
-)
-
-// Ephemeral events
-var (
-	EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
-	EphemeralEventTyping  = EventType{"m.receipt", EphemeralEventType}
-)
-
-// Account data events
-var (
-	AccountDataDirectChats = EventType{"m.direct", AccountDataEventType}
-	AccountDataPushRules   = EventType{"m.push_rules", AccountDataEventType}
-	AccountDataRoomTags    = EventType{"m.tag", AccountDataEventType}
-)
-
-type MessageType string
-
-// Msgtypes
-const (
-	MsgText     MessageType = "m.text"
-	MsgEmote                = "m.emote"
-	MsgNotice               = "m.notice"
-	MsgImage                = "m.image"
-	MsgLocation             = "m.location"
-	MsgVideo                = "m.video"
-	MsgAudio                = "m.audio"
-	MsgFile                 = "m.file"
-)
-
-type Format string
-
-// Message formats
-const (
-	FormatHTML Format = "org.matrix.custom.html"
-)
-
-// Event represents a single Matrix event.
-type Event struct {
-	StateKey  *string   `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
-	Sender    string    `json:"sender"`              // The user ID of the sender of the event
-	Type      EventType `json:"type"`                // The event type
-	Timestamp int64     `json:"origin_server_ts"`    // The unix timestamp when this message was sent by the origin server
-	ID        string    `json:"event_id"`            // The unique ID of this event
-	RoomID    string    `json:"room_id"`             // The room the event was sent to. May be nil (e.g. for presence)
-	Content   Content   `json:"content"`             // The JSON content of the event.
-	Redacts   string    `json:"redacts,omitempty"`   // The event ID that was redacted if a m.room.redaction event
-	Unsigned  Unsigned  `json:"unsigned,omitempty"`  // Unsigned content set by own homeserver.
-
-	InviteRoomState []StrippedState `json:"invite_room_state"`
-}
-
-func (evt *Event) GetStateKey() string {
-	if evt.StateKey != nil {
-		return *evt.StateKey
-	}
-	return ""
-}
-
-type StrippedState struct {
-	Content  Content   `json:"content"`
-	Type     EventType `json:"type"`
-	StateKey string    `json:"state_key"`
-}
-
-type Unsigned struct {
-	PrevContent   *Content `json:"prev_content,omitempty"`
-	PrevSender    string   `json:"prev_sender,omitempty"`
-	ReplacesState string   `json:"replaces_state,omitempty"`
-	Age           int64    `json:"age,omitempty"`
-}
-
-type Content struct {
-	VeryRaw json.RawMessage        `json:"-"`
-	Raw     map[string]interface{} `json:"-"`
-
-	MsgType       MessageType `json:"msgtype,omitempty"`
-	Body          string      `json:"body,omitempty"`
-	Format        Format      `json:"format,omitempty"`
-	FormattedBody string      `json:"formatted_body,omitempty"`
-
-	Info *FileInfo `json:"info,omitempty"`
-	URL  string    `json:"url,omitempty"`
-
-	// Membership key for easy access in m.room.member events
-	Membership Membership `json:"membership,omitempty"`
-
-	RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
-
-	PowerLevels
-	Member
-	Aliases []string `json:"aliases,omitempty"`
-	CanonicalAlias
-	RoomName
-	RoomTopic
-
-	RoomTags      Tags     `json:"tags,omitempty"`
-	TypingUserIDs []string `json:"user_ids,omitempty"`
-}
-
-type serializableContent Content
-
-func (content *Content) UnmarshalJSON(data []byte) error {
-	content.VeryRaw = data
-	if err := json.Unmarshal(data, &content.Raw); err != nil {
-		return err
-	}
-	return json.Unmarshal(data, (*serializableContent)(content))
-}
-
-func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) {
-	err = json.Unmarshal(content.VeryRaw, &pl)
-	return
-}
-
-func (content *Content) UnmarshalMember() (m Member, err error) {
-	err = json.Unmarshal(content.VeryRaw, &m)
-	return
-}
-
-func (content *Content) UnmarshalCanonicalAlias() (ca CanonicalAlias, err error) {
-	err = json.Unmarshal(content.VeryRaw, &ca)
-	return
-}
-
-func (content *Content) GetInfo() *FileInfo {
-	if content.Info == nil {
-		content.Info = &FileInfo{}
-	}
-	return content.Info
-}
-
-type Tags map[string]struct {
-	Order string `json:"order"`
-}
-
-type RoomName struct {
-	Name string `json:"name,omitempty"`
-}
-
-type RoomTopic struct {
-	Topic string `json:"topic,omitempty"`
-}
-
-// Membership is an enum specifying the membership state of a room member.
-type Membership string
-
-// The allowed membership states as specified in spec section 10.5.5.
-const (
-	MembershipJoin   Membership = "join"
-	MembershipLeave  Membership = "leave"
-	MembershipInvite Membership = "invite"
-	MembershipBan    Membership = "ban"
-	MembershipKnock  Membership = "knock"
-)
-
-type Member struct {
-	Membership       Membership        `json:"membership,omitempty"`
-	AvatarURL        string            `json:"avatar_url,omitempty"`
-	Displayname      string            `json:"displayname,omitempty"`
-	ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
-	Reason           string            `json:"reason,omitempty"`
-}
-
-type ThirdPartyInvite struct {
-	DisplayName string `json:"display_name"`
-	Signed      struct {
-		Token      string          `json:"token"`
-		Signatures json.RawMessage `json:"signatures"`
-		MXID       string          `json:"mxid"`
-	}
-}
-
-type CanonicalAlias struct {
-	Alias string `json:"alias,omitempty"`
-}
-
-type PowerLevels struct {
-	usersLock    sync.RWMutex   `json:"-"`
-	Users        map[string]int `json:"users,omitempty"`
-	UsersDefault int            `json:"users_default,omitempty"`
-
-	eventsLock    sync.RWMutex   `json:"-"`
-	Events        map[string]int `json:"events,omitempty"`
-	EventsDefault int            `json:"events_default,omitempty"`
-
-	StateDefaultPtr *int `json:"state_default,omitempty"`
-
-	InvitePtr *int `json:"invite,omitempty"`
-	KickPtr   *int `json:"kick,omitempty"`
-	BanPtr    *int `json:"ban,omitempty"`
-	RedactPtr *int `json:"redact,omitempty"`
-}
-
-func (pl *PowerLevels) Invite() int {
-	if pl.InvitePtr != nil {
-		return *pl.InvitePtr
-	}
-	return 50
-}
-
-func (pl *PowerLevels) Kick() int {
-	if pl.KickPtr != nil {
-		return *pl.KickPtr
-	}
-	return 50
-}
-
-func (pl *PowerLevels) Ban() int {
-	if pl.BanPtr != nil {
-		return *pl.BanPtr
-	}
-	return 50
-}
-
-func (pl *PowerLevels) Redact() int {
-	if pl.RedactPtr != nil {
-		return *pl.RedactPtr
-	}
-	return 50
-}
-
-func (pl *PowerLevels) StateDefault() int {
-	if pl.StateDefaultPtr != nil {
-		return *pl.StateDefaultPtr
-	}
-	return 50
-}
-
-func (pl *PowerLevels) GetUserLevel(userID string) int {
-	pl.usersLock.RLock()
-	defer pl.usersLock.RUnlock()
-	level, ok := pl.Users[userID]
-	if !ok {
-		return pl.UsersDefault
-	}
-	return level
-}
-
-func (pl *PowerLevels) SetUserLevel(userID string, level int) {
-	pl.usersLock.Lock()
-	defer pl.usersLock.Unlock()
-	if level == pl.UsersDefault {
-		delete(pl.Users, userID)
-	} else {
-		pl.Users[userID] = level
-	}
-}
-
-func (pl *PowerLevels) EnsureUserLevel(userID string, level int) bool {
-	existingLevel := pl.GetUserLevel(userID)
-	if existingLevel != level {
-		pl.SetUserLevel(userID, level)
-		return true
-	}
-	return false
-}
-
-func (pl *PowerLevels) GetEventLevel(eventType EventType) int {
-	pl.eventsLock.RLock()
-	defer pl.eventsLock.RUnlock()
-	level, ok := pl.Events[eventType.String()]
-	if !ok {
-		if eventType.IsState() {
-			return pl.StateDefault()
-		}
-		return pl.EventsDefault
-	}
-	return level
-}
-
-func (pl *PowerLevels) SetEventLevel(eventType EventType, level int) {
-	pl.eventsLock.Lock()
-	defer pl.eventsLock.Unlock()
-	if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
-		delete(pl.Events, eventType.String())
-	} else {
-		pl.Events[eventType.String()] = level
-	}
-}
-
-func (pl *PowerLevels) EnsureEventLevel(eventType EventType, level int) bool {
-	existingLevel := pl.GetEventLevel(eventType)
-	if existingLevel != level {
-		pl.SetEventLevel(eventType, level)
-		return true
-	}
-	return false
-}
-
-type FileInfo struct {
-	MimeType      string    `json:"mimetype,omitempty"`
-	ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
-	ThumbnailURL  string    `json:"thumbnail_url,omitempty"`
-	Height        int       `json:"h,omitempty"`
-	Width         int       `json:"w,omitempty"`
-	Duration      uint      `json:"duration,omitempty"`
-	Size          int       `json:"size,omitempty"`
-}
-
-func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
-	if fileInfo.ThumbnailInfo == nil {
-		fileInfo.ThumbnailInfo = &FileInfo{}
-	}
-	return fileInfo.ThumbnailInfo
-}
-
-type RelatesTo struct {
-	InReplyTo InReplyTo `json:"m.in_reply_to,omitempty"`
-}
-
-type InReplyTo struct {
-	EventID string `json:"event_id,omitempty"`
-	// Not required, just for future-proofing
-	RoomID string `json:"room_id,omitempty"`
-}
diff --git a/vendor/maunium.net/go/gomatrix/filter.go b/vendor/maunium.net/go/gomatrix/filter.go
deleted file mode 100644
index 2a0c37f..0000000
--- a/vendor/maunium.net/go/gomatrix/filter.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2017 Jan Christian Grünhage
-//
-// 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.
-
-package gomatrix
-
-import "errors"
-
-//Filter is used by clients to specify how the server should filter responses to e.g. sync requests
-//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering
-type Filter struct {
-	AccountData FilterPart `json:"account_data,omitempty"`
-	EventFields []string   `json:"event_fields,omitempty"`
-	EventFormat string     `json:"event_format,omitempty"`
-	Presence    FilterPart `json:"presence,omitempty"`
-	Room        RoomFilter `json:"room,omitempty"`
-}
-
-// RoomFilter is used to define filtering rules for room events
-type RoomFilter struct {
-	AccountData  FilterPart `json:"account_data,omitempty"`
-	Ephemeral    FilterPart `json:"ephemeral,omitempty"`
-	IncludeLeave bool       `json:"include_leave,omitempty"`
-	NotRooms     []string   `json:"not_rooms,omitempty"`
-	Rooms        []string   `json:"rooms,omitempty"`
-	State        FilterPart `json:"state,omitempty"`
-	Timeline     FilterPart `json:"timeline,omitempty"`
-}
-
-// FilterPart is used to define filtering rules for specific categories of events
-type FilterPart struct {
-	NotRooms    []string `json:"not_rooms,omitempty"`
-	Rooms       []string `json:"rooms,omitempty"`
-	Limit       int      `json:"limit,omitempty"`
-	NotSenders  []string `json:"not_senders,omitempty"`
-	NotTypes    []string `json:"not_types,omitempty"`
-	Senders     []string `json:"senders,omitempty"`
-	Types       []string `json:"types,omitempty"`
-	ContainsURL *bool    `json:"contains_url,omitempty"`
-}
-
-// Validate checks if the filter contains valid property values
-func (filter *Filter) Validate() error {
-	if filter.EventFormat != "client" && filter.EventFormat != "federation" {
-		return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]")
-	}
-	return nil
-}
-
-// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request
-func DefaultFilter() Filter {
-	return Filter{
-		AccountData: DefaultFilterPart(),
-		EventFields: nil,
-		EventFormat: "client",
-		Presence:    DefaultFilterPart(),
-		Room: RoomFilter{
-			AccountData:  DefaultFilterPart(),
-			Ephemeral:    DefaultFilterPart(),
-			IncludeLeave: false,
-			NotRooms:     nil,
-			Rooms:        nil,
-			State:        DefaultFilterPart(),
-			Timeline:     DefaultFilterPart(),
-		},
-	}
-}
-
-// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request
-func DefaultFilterPart() FilterPart {
-	return FilterPart{
-		NotRooms:   nil,
-		Rooms:      nil,
-		Limit:      20,
-		NotSenders: nil,
-		NotTypes:   nil,
-		Senders:    nil,
-		Types:      nil,
-	}
-}
diff --git a/vendor/maunium.net/go/gomatrix/reply.go b/vendor/maunium.net/go/gomatrix/reply.go
deleted file mode 100644
index 6985421..0000000
--- a/vendor/maunium.net/go/gomatrix/reply.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package gomatrix
-
-import (
-	"fmt"
-	"regexp"
-	"strings"
-
-	"golang.org/x/net/html"
-)
-
-var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
-
-func TrimReplyFallbackHTML(html string) string {
-	return HTMLReplyFallbackRegex.ReplaceAllString(html, "")
-}
-
-func TrimReplyFallbackText(text string) string {
-	if !strings.HasPrefix(text, "> ") || !strings.Contains(text, "\n") {
-		return text
-	}
-
-	lines := strings.Split(text, "\n")
-	for len(lines) > 0 && strings.HasPrefix(lines[0], "> ") {
-		lines = lines[1:]
-	}
-	return strings.TrimSpace(strings.Join(lines, "\n"))
-}
-
-func (content *Content) RemoveReplyFallback() {
-	if len(content.GetReplyTo()) > 0 {
-		if content.Format == FormatHTML {
-			content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody)
-		}
-		content.Body = TrimReplyFallbackText(content.Body)
-	}
-}
-
-func (content *Content) GetReplyTo() string {
-	if content.RelatesTo != nil {
-		return content.RelatesTo.InReplyTo.EventID
-	}
-	return ""
-}
-
-const ReplyFormat = `<mx-reply><blockquote>
-<a href="https://matrix.to/#/%s/%s">In reply to</a>
-<a href="https://matrix.to/#/%s">%s</a>
-%s
-</blockquote></mx-reply>
-`
-
-func (evt *Event) GenerateReplyFallbackHTML() string {
-	body := evt.Content.FormattedBody
-	if len(body) == 0 {
-		body = html.EscapeString(evt.Content.Body)
-	}
-
-	senderDisplayName := evt.Sender
-
-	return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body)
-}
-
-func (evt *Event) GenerateReplyFallbackText() string {
-	body := evt.Content.Body
-	lines := strings.Split(strings.TrimSpace(body), "\n")
-	firstLine, lines := lines[0], lines[1:]
-
-	senderDisplayName := evt.Sender
-
-	var fallbackText strings.Builder
-	fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
-	for _, line := range lines {
-		fmt.Fprintf(&fallbackText, "\n> %s", line)
-	}
-	fallbackText.WriteString("\n\n")
-	return fallbackText.String()
-}
-
-func (content *Content) SetReply(inReplyTo *Event) {
-	if content.RelatesTo == nil {
-		content.RelatesTo = &RelatesTo{}
-	}
-	content.RelatesTo.InReplyTo = InReplyTo{
-		EventID: inReplyTo.ID,
-		RoomID:  inReplyTo.RoomID,
-	}
-
-	if content.MsgType == MsgText || content.MsgType == MsgNotice {
-		if len(content.FormattedBody) == 0 || content.Format != FormatHTML {
-			content.FormattedBody = html.EscapeString(content.Body)
-			content.Format = FormatHTML
-		}
-		content.FormattedBody = inReplyTo.GenerateReplyFallbackHTML() + content.FormattedBody
-		content.Body = inReplyTo.GenerateReplyFallbackText() + content.Body
-	}
-}
diff --git a/vendor/maunium.net/go/gomatrix/requests.go b/vendor/maunium.net/go/gomatrix/requests.go
deleted file mode 100644
index d8e10a6..0000000
--- a/vendor/maunium.net/go/gomatrix/requests.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package gomatrix
-
-// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
-type ReqRegister struct {
-	Username                 string      `json:"username,omitempty"`
-	BindEmail                bool        `json:"bind_email,omitempty"`
-	Password                 string      `json:"password,omitempty"`
-	DeviceID                 string      `json:"device_id,omitempty"`
-	InitialDeviceDisplayName string      `json:"initial_device_display_name"`
-	Auth                     interface{} `json:"auth,omitempty"`
-}
-
-// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
-type ReqLogin struct {
-	Type                     string `json:"type"`
-	Password                 string `json:"password,omitempty"`
-	Medium                   string `json:"medium,omitempty"`
-	User                     string `json:"user,omitempty"`
-	Address                  string `json:"address,omitempty"`
-	Token                    string `json:"token,omitempty"`
-	DeviceID                 string `json:"device_id,omitempty"`
-	InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
-}
-
-// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
-type ReqCreateRoom struct {
-	Visibility      string                 `json:"visibility,omitempty"`
-	RoomAliasName   string                 `json:"room_alias_name,omitempty"`
-	Name            string                 `json:"name,omitempty"`
-	Topic           string                 `json:"topic,omitempty"`
-	Invite          []string               `json:"invite,omitempty"`
-	Invite3PID      []ReqInvite3PID        `json:"invite_3pid,omitempty"`
-	CreationContent map[string]interface{} `json:"creation_content,omitempty"`
-	InitialState    []*Event                `json:"initial_state,omitempty"`
-	Preset          string                 `json:"preset,omitempty"`
-	IsDirect        bool                   `json:"is_direct,omitempty"`
-}
-
-// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
-type ReqRedact struct {
-	Reason string `json:"reason,omitempty"`
-}
-
-// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57
-// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
-type ReqInvite3PID struct {
-	IDServer string `json:"id_server"`
-	Medium   string `json:"medium"`
-	Address  string `json:"address"`
-}
-
-// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
-type ReqInviteUser struct {
-	UserID string `json:"user_id"`
-}
-
-// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
-type ReqKickUser struct {
-	Reason string `json:"reason,omitempty"`
-	UserID string `json:"user_id"`
-}
-
-// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
-type ReqBanUser struct {
-	Reason string `json:"reason,omitempty"`
-	UserID string `json:"user_id"`
-}
-
-// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
-type ReqUnbanUser struct {
-	UserID string `json:"user_id"`
-}
-
-// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
-type ReqTyping struct {
-	Typing  bool  `json:"typing"`
-	Timeout int64 `json:"timeout,omitempty"`
-}
-
-type ReqPresence struct {
-	Presence string `json:"presence"`
-}
\ No newline at end of file
diff --git a/vendor/maunium.net/go/gomatrix/responses.go b/vendor/maunium.net/go/gomatrix/responses.go
deleted file mode 100644
index 9524d62..0000000
--- a/vendor/maunium.net/go/gomatrix/responses.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package gomatrix
-
-// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
-// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
-type RespError struct {
-	ErrCode string `json:"errcode"`
-	Err     string `json:"error"`
-}
-
-// Error returns the errcode and error message.
-func (e RespError) Error() string {
-	return e.ErrCode + ": " + e.Err
-}
-
-// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
-type RespCreateFilter struct {
-	FilterID string `json:"filter_id"`
-}
-
-// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
-type RespVersions struct {
-	Versions []string `json:"versions"`
-}
-
-// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join
-type RespJoinRoom struct {
-	RoomID string `json:"room_id"`
-}
-
-// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
-type RespLeaveRoom struct{}
-
-// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
-type RespForgetRoom struct{}
-
-// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
-type RespInviteUser struct{}
-
-// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
-type RespKickUser struct{}
-
-// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
-type RespBanUser struct{}
-
-// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
-type RespUnbanUser struct{}
-
-// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
-type RespTyping struct{}
-
-// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
-type RespJoinedRooms struct {
-	JoinedRooms []string `json:"joined_rooms"`
-}
-
-// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
-type RespJoinedMembers struct {
-	Joined map[string]struct {
-		DisplayName *string `json:"display_name"`
-		AvatarURL   *string `json:"avatar_url"`
-	} `json:"joined"`
-}
-
-// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
-type RespMessages struct {
-	Start string  `json:"start"`
-	Chunk []*Event `json:"chunk"`
-	End   string  `json:"end"`
-}
-
-// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
-type RespSendEvent struct {
-	EventID string `json:"event_id"`
-}
-
-// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
-type RespMediaUpload struct {
-	ContentURI string `json:"content_uri"`
-}
-
-// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api
-type RespUserInteractive struct {
-	Flows []struct {
-		Stages []string `json:"stages"`
-	} `json:"flows"`
-	Params    map[string]interface{} `json:"params"`
-	Session   string                 `json:"string"`
-	Completed []string               `json:"completed"`
-	ErrCode   string                 `json:"errcode"`
-	Error     string                 `json:"error"`
-}
-
-// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName.
-func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool {
-	for _, f := range r.Flows {
-		if len(f.Stages) == 1 && f.Stages[0] == stageName {
-			return true
-		}
-	}
-	return false
-}
-
-// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
-type RespUserDisplayName struct {
-	DisplayName string `json:"displayname"`
-}
-
-// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
-type RespRegister struct {
-	AccessToken  string `json:"access_token"`
-	DeviceID     string `json:"device_id"`
-	HomeServer   string `json:"home_server"`
-	RefreshToken string `json:"refresh_token"`
-	UserID       string `json:"user_id"`
-}
-
-// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
-type RespLogin struct {
-	AccessToken string `json:"access_token"`
-	DeviceID    string `json:"device_id"`
-	HomeServer  string `json:"home_server"`
-	UserID      string `json:"user_id"`
-}
-
-// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
-type RespLogout struct{}
-
-// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
-type RespCreateRoom struct {
-	RoomID string `json:"room_id"`
-}
-
-// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
-type RespSync struct {
-	NextBatch   string `json:"next_batch"`
-	AccountData struct {
-		Events []*Event `json:"events"`
-	} `json:"account_data"`
-	Presence struct {
-		Events []*Event `json:"events"`
-	} `json:"presence"`
-	Rooms struct {
-		Leave map[string]struct {
-			State struct {
-				Events []*Event `json:"events"`
-			} `json:"state"`
-			Timeline struct {
-				Events    []*Event `json:"events"`
-				Limited   bool    `json:"limited"`
-				PrevBatch string  `json:"prev_batch"`
-			} `json:"timeline"`
-		} `json:"leave"`
-		Join map[string]struct {
-			State struct {
-				Events []*Event `json:"events"`
-			} `json:"state"`
-			Timeline struct {
-				Events    []*Event `json:"events"`
-				Limited   bool    `json:"limited"`
-				PrevBatch string  `json:"prev_batch"`
-			} `json:"timeline"`
-			Ephemeral struct {
-				Events    []*Event `json:"events"`
-			} `json:"ephemeral"`
-			AccountData struct {
-				Events    []*Event `json:"events"`
-			} `json:"account_data"`
-		} `json:"join"`
-		Invite map[string]struct {
-			State struct {
-				Events []*Event `json:"events"`
-			} `json:"invite_state"`
-		} `json:"invite"`
-	} `json:"rooms"`
-}
-
-type RespTurnServer struct {
-	Username string   `json:"username"`
-	Password string   `json:"password"`
-	TTL      int      `json:"ttl"`
-	URIs     []string `json:"uris"`
-}
diff --git a/vendor/maunium.net/go/gomatrix/room.go b/vendor/maunium.net/go/gomatrix/room.go
deleted file mode 100644
index 80a91d8..0000000
--- a/vendor/maunium.net/go/gomatrix/room.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package gomatrix
-
-// Room represents a single Matrix room.
-type Room struct {
-	ID    string
-	State map[EventType]map[string]*Event
-}
-
-// UpdateState updates the room's current state with the given Event. This will clobber events based
-// on the type/state_key combination.
-func (room Room) UpdateState(event *Event) {
-	_, exists := room.State[event.Type]
-	if !exists {
-		room.State[event.Type] = make(map[string]*Event)
-	}
-	room.State[event.Type][*event.StateKey] = event
-}
-
-// GetStateEvent returns the state event for the given type/state_key combo, or nil.
-func (room Room) GetStateEvent(eventType EventType, stateKey string) *Event {
-	stateEventMap, _ := room.State[eventType]
-	event, _ := stateEventMap[stateKey]
-	return event
-}
-
-// GetMembershipState returns the membership state of the given user ID in this room. If there is
-// no entry for this member, 'leave' is returned for consistency with left users.
-func (room Room) GetMembershipState(userID string) Membership {
-	state := MembershipLeave
-	event := room.GetStateEvent(StateMember, userID)
-	if event != nil {
-		state = event.Content.Membership
-	}
-	return state
-}
-
-// NewRoom creates a new Room with the given ID
-func NewRoom(roomID string) *Room {
-	// Init the State map and return a pointer to the Room
-	return &Room{
-		ID:    roomID,
-		State: make(map[EventType]map[string]*Event),
-	}
-}
diff --git a/vendor/maunium.net/go/gomatrix/store.go b/vendor/maunium.net/go/gomatrix/store.go
deleted file mode 100644
index 6dc687e..0000000
--- a/vendor/maunium.net/go/gomatrix/store.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package gomatrix
-
-// Storer is an interface which must be satisfied to store client data.
-//
-// You can either write a struct which persists this data to disk, or you can use the
-// provided "InMemoryStore" which just keeps data around in-memory which is lost on
-// restarts.
-type Storer interface {
-	SaveFilterID(userID, filterID string)
-	LoadFilterID(userID string) string
-	SaveNextBatch(userID, nextBatchToken string)
-	LoadNextBatch(userID string) string
-	SaveRoom(room *Room)
-	LoadRoom(roomID string) *Room
-}
-
-// InMemoryStore implements the Storer interface.
-//
-// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
-// or next batch tokens on any goroutine other than the syncing goroutine: the one
-// which called Client.Sync().
-type InMemoryStore struct {
-	Filters   map[string]string
-	NextBatch map[string]string
-	Rooms     map[string]*Room
-}
-
-// SaveFilterID to memory.
-func (s *InMemoryStore) SaveFilterID(userID, filterID string) {
-	s.Filters[userID] = filterID
-}
-
-// LoadFilterID from memory.
-func (s *InMemoryStore) LoadFilterID(userID string) string {
-	return s.Filters[userID]
-}
-
-// SaveNextBatch to memory.
-func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) {
-	s.NextBatch[userID] = nextBatchToken
-}
-
-// LoadNextBatch from memory.
-func (s *InMemoryStore) LoadNextBatch(userID string) string {
-	return s.NextBatch[userID]
-}
-
-// SaveRoom to memory.
-func (s *InMemoryStore) SaveRoom(room *Room) {
-	s.Rooms[room.ID] = room
-}
-
-// LoadRoom from memory.
-func (s *InMemoryStore) LoadRoom(roomID string) *Room {
-	return s.Rooms[roomID]
-}
-
-// NewInMemoryStore constructs a new InMemoryStore.
-func NewInMemoryStore() *InMemoryStore {
-	return &InMemoryStore{
-		Filters:   make(map[string]string),
-		NextBatch: make(map[string]string),
-		Rooms:     make(map[string]*Room),
-	}
-}
diff --git a/vendor/maunium.net/go/gomatrix/sync.go b/vendor/maunium.net/go/gomatrix/sync.go
deleted file mode 100644
index 09170d7..0000000
--- a/vendor/maunium.net/go/gomatrix/sync.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package gomatrix
-
-import (
-	"encoding/json"
-	"fmt"
-	"runtime/debug"
-	"time"
-)
-
-// Syncer represents an interface that must be satisfied in order to do /sync requests on a client.
-type Syncer interface {
-	// Process the /sync response. The since parameter is the since= value that was used to produce the response.
-	// This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped
-	// permanently.
-	ProcessResponse(resp *RespSync, since string) error
-	// OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently.
-	OnFailedSync(res *RespSync, err error) (time.Duration, error)
-	// GetFilterJSON for the given user ID. NOT the filter ID.
-	GetFilterJSON(userID string) json.RawMessage
-}
-
-// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively
-// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
-// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information.
-type DefaultSyncer struct {
-	UserID    string
-	Store     Storer
-	listeners map[EventType][]OnEventListener // event type to listeners array
-}
-
-// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
-type OnEventListener func(*Event)
-
-// NewDefaultSyncer returns an instantiated DefaultSyncer
-func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
-	return &DefaultSyncer{
-		UserID:    userID,
-		Store:     store,
-		listeners: make(map[EventType][]OnEventListener),
-	}
-}
-
-// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
-// unrepeating events. Returns a fatal error if a listener panics.
-func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) {
-	if !s.shouldProcessResponse(res, since) {
-		return
-	}
-
-	defer func() {
-		if r := recover(); r != nil {
-			err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack())
-		}
-	}()
-
-	for roomID, roomData := range res.Rooms.Join {
-		room := s.getOrCreateRoom(roomID)
-		for _, event := range roomData.State.Events {
-			event.RoomID = roomID
-			room.UpdateState(event)
-			s.notifyListeners(event)
-		}
-		for _, event := range roomData.Timeline.Events {
-			event.RoomID = roomID
-			s.notifyListeners(event)
-		}
-	}
-	for roomID, roomData := range res.Rooms.Invite {
-		room := s.getOrCreateRoom(roomID)
-		for _, event := range roomData.State.Events {
-			event.RoomID = roomID
-			room.UpdateState(event)
-			s.notifyListeners(event)
-		}
-	}
-	for roomID, roomData := range res.Rooms.Leave {
-		room := s.getOrCreateRoom(roomID)
-		for _, event := range roomData.Timeline.Events {
-			if event.StateKey != nil {
-				event.RoomID = roomID
-				room.UpdateState(event)
-				s.notifyListeners(event)
-			}
-		}
-	}
-	return
-}
-
-// OnEventType allows callers to be notified when there are new events for the given event type.
-// There are no duplicate checks.
-func (s *DefaultSyncer) OnEventType(eventType EventType, callback OnEventListener) {
-	_, exists := s.listeners[eventType]
-	if !exists {
-		s.listeners[eventType] = []OnEventListener{}
-	}
-	s.listeners[eventType] = append(s.listeners[eventType], callback)
-}
-
-// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
-// stuff that shouldn't be processed.
-func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool {
-	if since == "" {
-		return false
-	}
-	// This is a horrible hack because /sync will return the most recent messages for a room
-	// as soon as you /join it. We do NOT want to process those events in that particular room
-	// because they may have already been processed (if you toggle the bot in/out of the room).
-	//
-	// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
-	// exists and is "join" and then discard processing that room entirely if so.
-	// TODO: We probably want to process messages from after the last join event in the timeline.
-	for roomID, roomData := range resp.Rooms.Join {
-		for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
-			e := roomData.Timeline.Events[i]
-			if e.Type == StateMember && e.GetStateKey() == s.UserID {
-				if e.Content.Membership == "join" {
-					_, ok := resp.Rooms.Join[roomID]
-					if !ok {
-						continue
-					}
-					delete(resp.Rooms.Join, roomID)   // don't re-process messages
-					delete(resp.Rooms.Invite, roomID) // don't re-process invites
-					break
-				}
-			}
-		}
-	}
-	return true
-}
-
-// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
-func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
-	room := s.Store.LoadRoom(roomID)
-	if room == nil { // create a new Room
-		room = NewRoom(roomID)
-		s.Store.SaveRoom(room)
-	}
-	return room
-}
-
-func (s *DefaultSyncer) notifyListeners(event *Event) {
-	listeners, exists := s.listeners[event.Type]
-	if !exists {
-		return
-	}
-	for _, fn := range listeners {
-		fn(event)
-	}
-}
-
-// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
-func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) {
-	return 10 * time.Second, nil
-}
-
-// GetFilterJSON returns a filter with a timeline limit of 50.
-func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage {
-	return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
-}
diff --git a/vendor/maunium.net/go/gomatrix/userids.go b/vendor/maunium.net/go/gomatrix/userids.go
deleted file mode 100644
index 23e7807..0000000
--- a/vendor/maunium.net/go/gomatrix/userids.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package gomatrix
-
-import (
-	"bytes"
-	"encoding/hex"
-	"fmt"
-	"strings"
-)
-
-const lowerhex = "0123456789abcdef"
-
-// encode the given byte using quoted-printable encoding (e.g "=2f")
-// and writes it to the buffer
-// See https://golang.org/src/mime/quotedprintable/writer.go
-func encode(buf *bytes.Buffer, b byte) {
-	buf.WriteByte('=')
-	buf.WriteByte(lowerhex[b>>4])
-	buf.WriteByte(lowerhex[b&0x0f])
-}
-
-// escape the given alpha character and writes it to the buffer
-func escape(buf *bytes.Buffer, b byte) {
-	buf.WriteByte('_')
-	if b == '_' {
-		buf.WriteByte('_') // another _
-	} else {
-		buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z
-	}
-}
-
-func shouldEncode(b byte) bool {
-	return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z')
-}
-
-func shouldEscape(b byte) bool {
-	return (b >= 'A' && b <= 'Z') || b == '_'
-}
-
-func isValidByte(b byte) bool {
-	return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-'
-}
-
-func isValidEscapedChar(b byte) bool {
-	return b == '_' || (b >= 'a' && b <= 'z')
-}
-
-// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form.
-// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
-//
-// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z
-// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges
-// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs)
-// and converted to lower-case hex with a leading "=". For example:
-//   Alph@Bet_50up  => _alph=40_bet=5f50up
-func EncodeUserLocalpart(str string) string {
-	strBytes := []byte(str)
-	var outputBuffer bytes.Buffer
-	for _, b := range strBytes {
-		if shouldEncode(b) {
-			encode(&outputBuffer, b)
-		} else if shouldEscape(b) {
-			escape(&outputBuffer, b)
-		} else {
-			outputBuffer.WriteByte(b)
-		}
-	}
-	return outputBuffer.String()
-}
-
-// DecodeUserLocalpart decodes the given string back into the original input string.
-// Returns an error if the given string is not a valid user ID localpart encoding.
-// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
-//
-// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For
-// example:
-//  _alph=40_bet=5f50up  =>  Alph@Bet_50up
-// Returns an error if the input string contains characters outside the
-// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has
-// an invalid _ escaped byte (e.g. "_5").
-func DecodeUserLocalpart(str string) (string, error) {
-	strBytes := []byte(str)
-	var outputBuffer bytes.Buffer
-	for i := 0; i < len(strBytes); i++ {
-		b := strBytes[i]
-		if !isValidByte(b) {
-			return "", fmt.Errorf("Byte pos %d: Invalid byte", i)
-		}
-
-		if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
-			if i+1 >= len(strBytes) {
-				return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i)
-			}
-			if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
-				return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i)
-			}
-			if strBytes[i+1] == '_' {
-				outputBuffer.WriteByte('_')
-			} else {
-				outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z
-			}
-			i++ // skip next byte since we just handled it
-		} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
-			if i+2 >= len(strBytes) {
-				return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i)
-			}
-			dst := make([]byte, 1)
-			_, err := hex.Decode(dst, strBytes[i+1:i+3])
-			if err != nil {
-				return "", err
-			}
-			outputBuffer.WriteByte(dst[0])
-			i += 2 // skip next 2 bytes since we just handled it
-		} else { // pass through
-			outputBuffer.WriteByte(b)
-		}
-	}
-	return outputBuffer.String(), nil
-}
-
-// ExtractUserLocalpart extracts the localpart portion of a user ID.
-// See http://matrix.org/docs/spec/intro.html#user-identifiers
-func ExtractUserLocalpart(userID string) (string, error) {
-	if len(userID) == 0 || userID[0] != '@' {
-		return "", fmt.Errorf("%s is not a valid user id", userID)
-	}
-	return strings.TrimPrefix(
-		strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
-		"@", // remove "@" prefix
-	), nil
-}
diff --git a/vendor/maunium.net/go/maulogger/LICENSE b/vendor/maunium.net/go/maulogger/LICENSE
deleted file mode 100644
index c9739fb..0000000
--- a/vendor/maunium.net/go/maulogger/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2016 Tulir Asokan
-
-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/vendor/maunium.net/go/maulogger/README.md b/vendor/maunium.net/go/maulogger/README.md
deleted file mode 100644
index 68fe253..0000000
--- a/vendor/maunium.net/go/maulogger/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# maulogger
-A logger in Go.
-
-Docs: [godoc.org/maunium.net/go/maulogger](https://godoc.org/maunium.net/go/maulogger)
-
-Go get: `go get maunium.net/go/maulogger`
diff --git a/vendor/maunium.net/go/maulogger/logger.go b/vendor/maunium.net/go/maulogger/logger.go
deleted file mode 100644
index e887237..0000000
--- a/vendor/maunium.net/go/maulogger/logger.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package maulog
-
-import (
-	"bufio"
-	"fmt"
-	"os"
-	"time"
-)
-
-// Level is the severity level of a log entry.
-type Level struct {
-	Name            string
-	Severity, Color int
-}
-
-// LogWriter writes to the log with an optional prefix
-type LogWriter struct {
-	Level  Level
-	Prefix string
-}
-
-func (lw LogWriter) Write(p []byte) (n int, err error) {
-	log(lw.Level, fmt.Sprint(lw.Prefix, string(p)))
-	return len(p), nil
-}
-
-// GetColor gets the ANSI escape color code for the log level.
-func (lvl Level) GetColor() []byte {
-	if lvl.Color < 0 {
-		return []byte("")
-	}
-	return []byte(fmt.Sprintf("\x1b[%dm", lvl.Color))
-}
-
-// GetReset gets the ANSI escape reset code.
-func (lvl Level) GetReset() []byte {
-	if lvl.Color < 0 {
-		return []byte("")
-	}
-	return []byte("\x1b[0m")
-}
-
-var (
-	// Debug is the level for debug messages.
-	Debug = Level{Name: "DEBUG", Color: 36, Severity: 0}
-	// Info is the level for basic log messages.
-	Info = Level{Name: "INFO", Color: -1, Severity: 10}
-	// Warn is the level saying that something went wrong, but the program will continue operating mostly normally.
-	Warn = Level{Name: "WARN", Color: 33, Severity: 50}
-	// Error is the level saying that something went wrong and the program may not operate as expected, but will still continue.
-	Error = Level{Name: "ERROR", Color: 31, Severity: 100}
-	// Fatal is the level saying that something went wrong and the program will not operate normally.
-	Fatal = Level{Name: "FATAL", Color: 35, Severity: 9001}
-)
-
-// PrintLevel tells the first severity level at which messages should be printed to stdout
-var PrintLevel = 10
-
-// PrintDebug means PrintLevel = 0, kept for backwards compatibility
-var PrintDebug = false
-
-// FileTimeformat is the time format used in log file names.
-var FileTimeformat = "2006-01-02"
-
-// FileformatArgs is an undocumented integer.
-var FileformatArgs = 3
-
-// Fileformat is the format used for log file names.
-var Fileformat = func(now string, i int) string { return fmt.Sprintf("%[1]s-%02[2]d.log", now, i) }
-
-// Timeformat is the time format used in logging.
-var Timeformat = "15:04:05 02.01.2006"
-
-var writer *bufio.Writer
-var lines int
-
-// InitWithWriter initializes MauLogger with the given writer.
-func InitWithWriter(w *bufio.Writer) {
-	writer = w
-}
-
-// Init initializes MauLogger.
-func Init() {
-	// Find the next file name.
-	now := time.Now().Format(FileTimeformat)
-	i := 1
-	for ; ; i++ {
-		if _, err := os.Stat(Fileformat(now, i)); os.IsNotExist(err) {
-			break
-		}
-		if i == 99 {
-			i = 1
-			break
-		}
-	}
-	// Open the file
-	file, err := os.OpenFile(Fileformat(now, i), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0700)
-	if err != nil {
-		panic(err)
-	}
-	if file == nil {
-		panic(os.ErrInvalid)
-	}
-	// Create a writer
-	writer = bufio.NewWriter(file)
-}
-
-// Debugf formats and logs a debug message.
-func Debugf(message string, args ...interface{}) {
-	logln(Debug, fmt.Sprintf(message, args...))
-}
-
-// Printf formats and logs a string in the Info log level.
-func Printf(message string, args ...interface{}) {
-	Infof(message, args...)
-}
-
-// Infof formats and logs a string in the Info log level.
-func Infof(message string, args ...interface{}) {
-	logln(Info, fmt.Sprintf(message, args...))
-}
-
-// Warnf formats and logs a string in the Warn log level.
-func Warnf(message string, args ...interface{}) {
-	logln(Warn, fmt.Sprintf(message, args...))
-}
-
-// Errorf formats and logs a string in the Error log level.
-func Errorf(message string, args ...interface{}) {
-	logln(Error, fmt.Sprintf(message, args...))
-}
-
-// Fatalf formats and logs a string in the Fatal log level.
-func Fatalf(message string, args ...interface{}) {
-	logln(Fatal, fmt.Sprintf(message, args...))
-}
-
-// Logf formats and logs a message in the given log level.
-func Logf(level Level, message string, args ...interface{}) {
-	logln(level, fmt.Sprintf(message, args...))
-}
-
-// Debugln logs a debug message.
-func Debugln(args ...interface{}) {
-	log(Debug, fmt.Sprintln(args...))
-}
-
-// Println logs a string in the Info log level.
-func Println(args ...interface{}) {
-	Infoln(args...)
-}
-
-// Infoln logs a string in the Info log level.
-func Infoln(args ...interface{}) {
-	log(Info, fmt.Sprintln(args...))
-}
-
-// Warnln logs a string in the Warn log level.
-func Warnln(args ...interface{}) {
-	log(Warn, fmt.Sprintln(args...))
-}
-
-// Errorln logs a string in the Error log level.
-func Errorln(args ...interface{}) {
-	log(Error, fmt.Sprintln(args...))
-}
-
-// Fatalln logs a string in the Fatal log level.
-func Fatalln(args ...interface{}) {
-	log(Fatal, fmt.Sprintln(args...))
-}
-
-// Logln logs a message in the given log level.
-func Logln(level Level, args ...interface{}) {
-	log(level, fmt.Sprintln(args...))
-}
-
-func logln(level Level, message string) {
-	log(level, fmt.Sprintln(message))
-}
-
-func log(level Level, message string) {
-	// Prefix the message with the timestamp and log level.
-	msg := []byte(fmt.Sprintf("[%[1]s] [%[2]s] %[3]s", time.Now().Format(Timeformat), level.Name, message))
-
-	if writer != nil {
-		// Write it to the log file.
-		_, err := writer.Write(msg)
-		if err != nil {
-			panic(err)
-		}
-		lines++
-		// Flush the file if needed
-		if lines == 5 {
-			lines = 0
-			writer.Flush()
-		}
-	}
-
-	// Print to stdout using correct color
-	if level.Severity >= PrintLevel || PrintDebug {
-		if level.Severity >= Error.Severity {
-			os.Stderr.Write(level.GetColor())
-			os.Stderr.Write(msg)
-			os.Stderr.Write(level.GetReset())
-		} else {
-			os.Stdout.Write(level.GetColor())
-			os.Stdout.Write(msg)
-			os.Stdout.Write(level.GetReset())
-		}
-	}
-}
-
-// Shutdown cleans up the logger.
-func Shutdown() {
-	if writer != nil {
-		writer.Flush()
-	}
-}
diff --git a/vendor/maunium.net/go/mautrix/.gitignore b/vendor/maunium.net/go/mautrix/.gitignore
new file mode 100644
index 0000000..66f8fb5
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/.gitignore
@@ -0,0 +1,2 @@
+.idea/
+.vscode/
diff --git a/vendor/maunium.net/go/mautrix/LICENSE b/vendor/maunium.net/go/mautrix/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
diff --git a/vendor/maunium.net/go/mautrix/README.md b/vendor/maunium.net/go/mautrix/README.md
new file mode 100644
index 0000000..ca135a6
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/README.md
@@ -0,0 +1,4 @@
+# mautrix-go
+[![GoDoc](https://godoc.org/maunium.net/go/mautrix?status.svg)](https://godoc.org/maunium.net/go/mautrix)
+
+A Golang Matrix framework.
diff --git a/vendor/maunium.net/go/mautrix/client.go b/vendor/maunium.net/go/mautrix/client.go
new file mode 100644
index 0000000..d908b62
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/client.go
@@ -0,0 +1,796 @@
+// Package mautrix implements the Matrix Client-Server API.
+//
+// Specification can be found at http://matrix.org/docs/spec/client_server/r0.4.0.html
+package mautrix
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"path"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+type Logger interface {
+	Debugfln(message string, args ...interface{})
+}
+
+// Client represents a Matrix client.
+type Client struct {
+	HomeserverURL *url.URL     // The base homeserver URL
+	Prefix        string       // The API prefix eg '/_matrix/client/r0'
+	UserID        string       // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
+	AccessToken   string       // The access_token for the client.
+	Client        *http.Client // The underlying HTTP client which will be used to make HTTP requests.
+	Syncer        Syncer       // The thing which can process /sync responses
+	Store         Storer       // The thing which can store rooms/tokens/ids
+	Logger        Logger
+
+	// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
+	// no user_id parameter will be sent.
+	// See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion
+	AppServiceUserID string
+
+	syncingMutex sync.Mutex // protects syncingID
+	syncingID    uint32     // Identifies the current Sync. Only one Sync can be active at any given time.
+}
+
+// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
+type HTTPError struct {
+	WrappedError error
+	RespError    *RespError
+	Message      string
+	Code         int
+}
+
+func (e HTTPError) Error() string {
+	var wrappedErrMsg string
+	if e.WrappedError != nil {
+		wrappedErrMsg = e.WrappedError.Error()
+	}
+	return fmt.Sprintf("msg=%s code=%d wrapped=%s", e.Message, e.Code, wrappedErrMsg)
+}
+
+// BuildURL builds a URL with the Client's homserver/prefix/access_token set already.
+func (cli *Client) BuildURL(urlPath ...string) string {
+	ps := []string{cli.Prefix}
+	for _, p := range urlPath {
+		ps = append(ps, p)
+	}
+	return cli.BuildBaseURL(ps...)
+}
+
+// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must
+// supply the prefix in the path.
+func (cli *Client) BuildBaseURL(urlPath ...string) string {
+	// copy the URL. Purposefully ignore error as the input is from a valid URL already
+	hsURL, _ := url.Parse(cli.HomeserverURL.String())
+	parts := []string{hsURL.Path}
+	parts = append(parts, urlPath...)
+	hsURL.Path = path.Join(parts...)
+	query := hsURL.Query()
+	if cli.AccessToken != "" {
+		query.Set("access_token", cli.AccessToken)
+	}
+	if cli.AppServiceUserID != "" {
+		query.Set("user_id", cli.AppServiceUserID)
+	}
+	hsURL.RawQuery = query.Encode()
+	return hsURL.String()
+}
+
+// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already.
+func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string {
+	u, _ := url.Parse(cli.BuildURL(urlPath...))
+	q := u.Query()
+	for k, v := range urlQuery {
+		q.Set(k, v)
+	}
+	u.RawQuery = q.Encode()
+	return u.String()
+}
+
+// SetCredentials sets the user ID and access token on this client instance.
+func (cli *Client) SetCredentials(userID, accessToken string) {
+	cli.AccessToken = accessToken
+	cli.UserID = userID
+}
+
+// ClearCredentials removes the user ID and access token on this client instance.
+func (cli *Client) ClearCredentials() {
+	cli.AccessToken = ""
+	cli.UserID = ""
+}
+
+// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the
+// error will be nil.
+//
+// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine.
+// Fatal sync errors can be caused by:
+//   - The failure to create a filter.
+//   - Client.Syncer.OnFailedSync returning an error in response to a failed sync.
+//   - Client.Syncer.ProcessResponse returning an error.
+// If you wish to continue retrying in spite of these fatal errors, call Sync() again.
+func (cli *Client) Sync() error {
+	// Mark the client as syncing.
+	// We will keep syncing until the syncing state changes. Either because
+	// Sync is called or StopSync is called.
+	syncingID := cli.incrementSyncingID()
+	nextBatch := cli.Store.LoadNextBatch(cli.UserID)
+	filterID := cli.Store.LoadFilterID(cli.UserID)
+	if filterID == "" {
+		filterJSON := cli.Syncer.GetFilterJSON(cli.UserID)
+		resFilter, err := cli.CreateFilter(filterJSON)
+		if err != nil {
+			return err
+		}
+		filterID = resFilter.FilterID
+		cli.Store.SaveFilterID(cli.UserID, filterID)
+	}
+	for {
+		resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
+		if err != nil {
+			duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
+			if err2 != nil {
+				return err2
+			}
+			time.Sleep(duration)
+			continue
+		}
+
+		// Check that the syncing state hasn't changed
+		// Either because we've stopped syncing or another sync has been started.
+		// We discard the response from our sync.
+		if cli.getSyncingID() != syncingID {
+			return nil
+		}
+
+		// Save the token now *before* processing it. This means it's possible
+		// to not process some events, but it means that we won't get constantly stuck processing
+		// a malformed/buggy event which keeps making us panic.
+		cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch)
+		if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil {
+			return err
+		}
+
+		nextBatch = resSync.NextBatch
+	}
+}
+
+func (cli *Client) incrementSyncingID() uint32 {
+	cli.syncingMutex.Lock()
+	defer cli.syncingMutex.Unlock()
+	cli.syncingID++
+	return cli.syncingID
+}
+
+func (cli *Client) getSyncingID() uint32 {
+	cli.syncingMutex.Lock()
+	defer cli.syncingMutex.Unlock()
+	return cli.syncingID
+}
+
+// StopSync stops the ongoing sync started by Sync.
+func (cli *Client) StopSync() {
+	// Advance the syncing state so that any running Syncs will terminate.
+	cli.incrementSyncingID()
+}
+
+func (cli *Client) LogRequest(req *http.Request, body string) {
+	if cli.Logger == nil {
+		return
+	}
+
+	cli.Logger.Debugfln("%s %s %s", req.Method, req.URL.Path, body)
+}
+
+// MakeRequest makes a JSON HTTP request to the given URL.
+// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
+//
+// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along
+// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned
+// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError.
+func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
+	var req *http.Request
+	var err error
+	logBody := "{}"
+	if reqBody != nil {
+		var jsonStr []byte
+		jsonStr, err = json.Marshal(reqBody)
+		if err != nil {
+			return nil, err
+		}
+		logBody = string(jsonStr)
+		req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
+	} else {
+		req, err = http.NewRequest(method, httpURL, nil)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/json")
+	cli.LogRequest(req, logBody)
+	res, err := cli.Client.Do(req)
+	if res != nil {
+		defer res.Body.Close()
+	}
+	if err != nil {
+		return nil, err
+	}
+	contents, err := ioutil.ReadAll(res.Body)
+	if res.StatusCode/100 != 2 { // not 2xx
+		var wrap error
+		respErr := &RespError{}
+		if _ = json.Unmarshal(contents, respErr); respErr.ErrCode != "" {
+			wrap = respErr
+		} else {
+			respErr = nil
+		}
+
+		// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
+		// HTTP error instead (e.g proxy errors which return HTML).
+		msg := "Failed to " + method + " JSON to " + req.URL.Path
+		if wrap == nil {
+			msg = msg + ": " + string(contents)
+		}
+
+		return contents, HTTPError{
+			Code:         res.StatusCode,
+			Message:      msg,
+			WrappedError: wrap,
+			RespError:    respErr,
+		}
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	if resBody != nil {
+		if err = json.Unmarshal(contents, &resBody); err != nil {
+			return nil, err
+		}
+	}
+
+	return contents, nil
+}
+
+// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
+func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) {
+	urlPath := cli.BuildURL("user", cli.UserID, "filter")
+	_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
+	return
+}
+
+// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
+func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) {
+	query := map[string]string{
+		"timeout": strconv.Itoa(timeout),
+	}
+	if since != "" {
+		query["since"] = since
+	}
+	if filterID != "" {
+		query["filter"] = filterID
+	}
+	if setPresence != "" {
+		query["set_presence"] = setPresence
+	}
+	if fullState {
+		query["full_state"] = "true"
+	}
+	urlPath := cli.BuildURLWithQuery([]string{"sync"}, query)
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
+	var bodyBytes []byte
+	bodyBytes, err = cli.MakeRequest("POST", u, req, nil)
+	if err != nil {
+		httpErr, ok := err.(HTTPError)
+		if !ok { // network error
+			return
+		}
+		if httpErr.Code == 401 {
+			// body should be RespUserInteractive, if it isn't, fail with the error
+			err = json.Unmarshal(bodyBytes, &uiaResp)
+			return
+		}
+		return
+	}
+	// body should be RespRegister
+	err = json.Unmarshal(bodyBytes, &resp)
+	return
+}
+
+// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
+//
+// Registers with kind=user. For kind=guest, see RegisterGuest.
+func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
+	u := cli.BuildURL("register")
+	return cli.register(u, req)
+}
+
+// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
+// with kind=guest.
+//
+// For kind=user, see Register.
+func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
+	query := map[string]string{
+		"kind": "guest",
+	}
+	u := cli.BuildURLWithQuery([]string{"register"}, query)
+	return cli.register(u, req)
+}
+
+// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth
+//
+// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration
+// this way. If the homeserver does not, an error is returned.
+//
+// This does not set credentials on the client instance. See SetCredentials() instead.
+//
+// 	res, err := cli.RegisterDummy(&mautrix.ReqRegister{
+//		Username: "alice",
+//		Password: "wonderland",
+//	})
+//  if err != nil {
+// 		panic(err)
+// 	}
+// 	token := res.AccessToken
+func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
+	res, uia, err := cli.Register(req)
+	if err != nil && uia == nil {
+		return nil, err
+	}
+	if uia != nil && uia.HasSingleStageFlow("m.login.dummy") {
+		req.Auth = struct {
+			Type    string `json:"type"`
+			Session string `json:"session,omitempty"`
+		}{"m.login.dummy", uia.Session}
+		res, _, err = cli.Register(req)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if res == nil {
+		return nil, fmt.Errorf("registration failed: does this server support m.login.dummy? ")
+	}
+	return res, nil
+}
+
+// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
+// This does not set credentials on this client instance. See SetCredentials() instead.
+func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) {
+	urlPath := cli.BuildURL("login")
+	_, err = cli.MakeRequest("POST", urlPath, req, &resp)
+	return
+}
+
+// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
+// This does not clear the credentials from the client instance. See ClearCredentials() instead.
+func (cli *Client) Logout() (resp *RespLogout, err error) {
+	urlPath := cli.BuildURL("logout")
+	_, err = cli.MakeRequest("POST", urlPath, nil, &resp)
+	return
+}
+
+// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
+func (cli *Client) Versions() (resp *RespVersions, err error) {
+	urlPath := cli.BuildBaseURL("_matrix", "client", "versions")
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias
+//
+// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will
+// be JSON encoded and used as the request body.
+func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) {
+	var urlPath string
+	if serverName != "" {
+		urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{
+			"server_name": serverName,
+		})
+	} else {
+		urlPath = cli.BuildURL("join", roomIDorAlias)
+	}
+	_, err = cli.MakeRequest("POST", urlPath, content, &resp)
+	return
+}
+
+// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
+func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) {
+	urlPath := cli.BuildURL("profile", mxid, "displayname")
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
+func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) {
+	urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname
+func (cli *Client) SetDisplayName(displayName string) (err error) {
+	urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
+	s := struct {
+		DisplayName string `json:"displayname"`
+	}{displayName}
+	_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
+	return
+}
+
+// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url
+func (cli *Client) GetAvatarURL() (url string, err error) {
+	urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
+	s := struct {
+		AvatarURL string `json:"avatar_url"`
+	}{}
+
+	_, err = cli.MakeRequest("GET", urlPath, nil, &s)
+	if err != nil {
+		return "", err
+	}
+
+	return s.AvatarURL, nil
+}
+
+// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url
+func (cli *Client) SetAvatarURL(url string) (err error) {
+	urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
+	s := struct {
+		AvatarURL string `json:"avatar_url"`
+	}{url}
+	_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMessageEvent(roomID string, eventType EventType, contentJSON interface{}) (resp *RespSendEvent, err error) {
+	txnID := txnID()
+	urlPath := cli.BuildURL("rooms", roomID, "send", eventType.String(), txnID)
+	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+	return
+}
+
+// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMassagedMessageEvent(roomID string, eventType EventType, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
+	txnID := txnID()
+	urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "send", eventType.String(), txnID}, map[string]string{
+		"ts": strconv.FormatInt(ts, 10),
+	})
+	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+	return
+}
+
+// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
+	urlPath := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
+	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+	return
+}
+
+// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMassagedStateEvent(roomID string, eventType EventType, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
+	urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{
+		"ts": strconv.FormatInt(ts, 10),
+	})
+	_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+	return
+}
+
+// SendText sends an m.room.message event into the given room with a msgtype of m.text
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
+func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
+	return cli.SendMessageEvent(roomID, EventMessage, Content{
+		MsgType: MsgText,
+		Body:    text,
+	})
+}
+
+// SendImage sends an m.room.message event into the given room with a msgtype of m.image
+// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
+func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
+	return cli.SendMessageEvent(roomID, EventMessage, Content{
+		MsgType: MsgImage,
+		Body:    body,
+		URL:     url,
+	})
+}
+
+// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
+// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
+func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) {
+	return cli.SendMessageEvent(roomID, EventMessage, Content{
+		MsgType: MsgVideo,
+		Body:    body,
+		URL:     url,
+	})
+}
+
+// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
+func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) {
+	return cli.SendMessageEvent(roomID, EventMessage, Content{
+		MsgType: MsgNotice,
+		Body:    text,
+	})
+}
+
+// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
+func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) {
+	txnID := txnID()
+	urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID)
+	_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
+	return
+}
+
+// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
+//  resp, err := cli.CreateRoom(&mautrix.ReqCreateRoom{
+//  	Preset: "public_chat",
+//  })
+//  fmt.Println("Room:", resp.RoomID)
+func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) {
+	urlPath := cli.BuildURL("createRoom")
+	_, err = cli.MakeRequest("POST", urlPath, req, &resp)
+	return
+}
+
+// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
+func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) {
+	u := cli.BuildURL("rooms", roomID, "leave")
+	_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
+	return
+}
+
+// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
+func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) {
+	u := cli.BuildURL("rooms", roomID, "forget")
+	_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
+	return
+}
+
+// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
+func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) {
+	u := cli.BuildURL("rooms", roomID, "invite")
+	_, err = cli.MakeRequest("POST", u, req, &resp)
+	return
+}
+
+// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint
+func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
+	u := cli.BuildURL("rooms", roomID, "invite")
+	_, err = cli.MakeRequest("POST", u, req, &resp)
+	return
+}
+
+// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
+func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) {
+	u := cli.BuildURL("rooms", roomID, "kick")
+	_, err = cli.MakeRequest("POST", u, req, &resp)
+	return
+}
+
+// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
+func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) {
+	u := cli.BuildURL("rooms", roomID, "ban")
+	_, err = cli.MakeRequest("POST", u, req, &resp)
+	return
+}
+
+// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
+func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
+	u := cli.BuildURL("rooms", roomID, "unban")
+	_, err = cli.MakeRequest("POST", u, req, &resp)
+	return
+}
+
+// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
+func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) {
+	req := ReqTyping{Typing: typing, Timeout: timeout}
+	u := cli.BuildURL("rooms", roomID, "typing", cli.UserID)
+	_, err = cli.MakeRequest("PUT", u, req, &resp)
+	return
+}
+
+func (cli *Client) SetPresence(status string) (err error) {
+	req := ReqPresence{Presence: status}
+	u := cli.BuildURL("presence", cli.UserID, "status")
+	_, err = cli.MakeRequest("PUT", u, req, nil)
+	return
+}
+
+// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
+// the HTTP response body, or return an error.
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
+func (cli *Client) StateEvent(roomID string, eventType EventType, stateKey string, outContent interface{}) (err error) {
+	u := cli.BuildURL("rooms", roomID, "state", eventType.String(), stateKey)
+	_, err = cli.MakeRequest("GET", u, nil, outContent)
+	return
+}
+
+// UploadLink uploads an HTTP URL and then returns an MXC URI.
+func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
+	res, err := cli.Client.Get(link)
+	if res != nil {
+		defer res.Body.Close()
+	}
+	if err != nil {
+		return nil, err
+	}
+	return cli.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
+}
+
+func (cli *Client) Download(mxcURL string) (io.ReadCloser, error) {
+	if !strings.HasPrefix(mxcURL, "mxc://") {
+		return nil, errors.New("invalid Matrix content URL")
+	}
+	parts := strings.Split(mxcURL[len("mxc://"):], "/")
+	if len(parts) != 2 {
+		return nil, errors.New("invalid Matrix content URL")
+	}
+	u := cli.BuildBaseURL("_matrix/media/r0/download", parts[0], parts[1])
+	resp, err := cli.Client.Get(u)
+	if err != nil {
+		return nil, err
+	}
+	return resp.Body, nil
+}
+
+func (cli *Client) DownloadBytes(mxcURL string) ([]byte, error) {
+	resp, err := cli.Download(mxcURL)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Close()
+	return ioutil.ReadAll(resp)
+}
+
+func (cli *Client) UploadBytes(data []byte, contentType string) (*RespMediaUpload, error) {
+	return cli.Upload(bytes.NewReader(data), contentType, int64(len(data)))
+}
+
+// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
+func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
+	req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", contentType)
+	req.ContentLength = contentLength
+	cli.LogRequest(req, fmt.Sprintf("%d bytes", contentLength))
+	res, err := cli.Client.Do(req)
+	if res != nil {
+		defer res.Body.Close()
+	}
+	if err != nil {
+		return nil, err
+	}
+	if res.StatusCode != 200 {
+		contents, err := ioutil.ReadAll(res.Body)
+		if err != nil {
+			return nil, HTTPError{
+				Message: "Upload request failed - Failed to read response body: " + err.Error(),
+				Code:    res.StatusCode,
+			}
+		}
+		return nil, HTTPError{
+			Message: "Upload request failed: " + string(contents),
+			Code:    res.StatusCode,
+		}
+	}
+	var m RespMediaUpload
+	if err := json.NewDecoder(res.Body).Decode(&m); err != nil {
+		return nil, err
+	}
+	return &m, nil
+}
+
+// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
+//
+// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
+// This API is primarily designed for application services which may want to efficiently look up joined members in a room.
+func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) {
+	u := cli.BuildURL("rooms", roomID, "joined_members")
+	_, err = cli.MakeRequest("GET", u, nil, &resp)
+	return
+}
+
+// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
+//
+// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
+// This API is primarily designed for application services which may want to efficiently look up joined rooms.
+func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
+	u := cli.BuildURL("joined_rooms")
+	_, err = cli.MakeRequest("GET", u, nil, &resp)
+	return
+}
+
+// Messages returns a list of message and state events for a room. It uses
+// pagination query parameters to paginate history in the room.
+// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
+func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
+	query := map[string]string{
+		"from": from,
+		"dir":  string(dir),
+	}
+	if to != "" {
+		query["to"] = to
+	}
+	if limit != 0 {
+		query["limit"] = strconv.Itoa(limit)
+	}
+
+	urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+func (cli *Client) GetEvent(roomID, eventID string) (resp *Event, err error) {
+	urlPath := cli.BuildURL("rooms", roomID, "event", eventID)
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+func (cli *Client) MarkRead(roomID, eventID string) (err error) {
+	urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
+	_, err = cli.MakeRequest("POST", urlPath, struct{}{}, nil)
+	return
+}
+
+// TurnServer returns turn server details and credentials for the client to use when initiating calls.
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
+func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
+	urlPath := cli.BuildURL("voip", "turnServer")
+	_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+	return
+}
+
+func txnID() string {
+	return "go" + strconv.FormatInt(time.Now().UnixNano(), 10)
+}
+
+// NewClient creates a new Matrix Client ready for syncing
+func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
+	hsURL, err := url.Parse(homeserverURL)
+	if err != nil {
+		return nil, err
+	}
+	// By default, use an in-memory store which will never save filter ids / next batch tokens to disk.
+	// The client will work with this storer: it just won't remember across restarts.
+	// In practice, a database backend should be used.
+	store := NewInMemoryStore()
+	cli := Client{
+		AccessToken:   accessToken,
+		HomeserverURL: hsURL,
+		UserID:        userID,
+		Prefix:        "/_matrix/client/r0",
+		Syncer:        NewDefaultSyncer(userID, store),
+		Store:         store,
+	}
+	// By default, use the default HTTP client.
+	cli.Client = http.DefaultClient
+
+	return &cli, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/events.go b/vendor/maunium.net/go/mautrix/events.go
new file mode 100644
index 0000000..c974bf9
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/events.go
@@ -0,0 +1,448 @@
+// Copyright 2018 Tulir Asokan
+package mautrix
+
+import (
+	"encoding/json"
+	"strings"
+	"sync"
+)
+
+type EventTypeClass int
+
+const (
+	// Normal message events
+	MessageEventType EventTypeClass = iota
+	// State events
+	StateEventType
+	// Ephemeral events
+	EphemeralEventType
+	// Account data events
+	AccountDataEventType
+	// Unknown events
+	UnknownEventType
+)
+
+type EventType struct {
+	Type  string
+	Class EventTypeClass
+}
+
+func NewEventType(name string) EventType {
+	evtType := EventType{Type: name}
+	evtType.Class = evtType.GuessClass()
+	return evtType
+}
+
+func (et *EventType) IsState() bool {
+	return et.Class == StateEventType
+}
+
+func (et *EventType) IsEphemeral() bool {
+	return et.Class == EphemeralEventType
+}
+
+func (et *EventType) IsCustom() bool {
+	return !strings.HasPrefix(et.Type, "m.")
+}
+
+func (et *EventType) GuessClass() EventTypeClass {
+	switch et.Type {
+	case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
+		StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateTopic.Type, StatePinnedEvents.Type:
+		return StateEventType
+	case EphemeralEventReceipt.Type, EphemeralEventTyping.Type:
+		return EphemeralEventType
+	case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type:
+		return AccountDataEventType
+	case EventRedaction.Type, EventMessage.Type, EventSticker.Type:
+		return MessageEventType
+	default:
+		return UnknownEventType
+	}
+}
+
+func (et *EventType) UnmarshalJSON(data []byte) error {
+	err := json.Unmarshal(data, &et.Type)
+	if err != nil {
+		return err
+	}
+	et.Class = et.GuessClass()
+	return nil
+}
+
+func (et *EventType) MarshalJSON() ([]byte, error) {
+	return json.Marshal(&et.Type)
+}
+
+func (et *EventType) String() string {
+	return et.Type
+}
+
+// State events
+var (
+	StateAliases        = EventType{"m.room.aliases", StateEventType}
+	StateCanonicalAlias = EventType{"m.room.canonical_alias", StateEventType}
+	StateCreate         = EventType{"m.room.create", StateEventType}
+	StateJoinRules      = EventType{"m.room.join_rules", StateEventType}
+	StateMember         = EventType{"m.room.member", StateEventType}
+	StatePowerLevels    = EventType{"m.room.power_levels", StateEventType}
+	StateRoomName       = EventType{"m.room.name", StateEventType}
+	StateTopic          = EventType{"m.room.topic", StateEventType}
+	StateRoomAvatar     = EventType{"m.room.avatar", StateEventType}
+	StatePinnedEvents   = EventType{"m.room.pinned_events", StateEventType}
+)
+
+// Message events
+var (
+	EventRedaction = EventType{"m.room.redaction", MessageEventType}
+	EventMessage   = EventType{"m.room.message", MessageEventType}
+	EventSticker   = EventType{"m.sticker", MessageEventType}
+)
+
+// Ephemeral events
+var (
+	EphemeralEventReceipt = EventType{"m.receipt", EphemeralEventType}
+	EphemeralEventTyping  = EventType{"m.typing", EphemeralEventType}
+)
+
+// Account data events
+var (
+	AccountDataDirectChats = EventType{"m.direct", AccountDataEventType}
+	AccountDataPushRules   = EventType{"m.push_rules", AccountDataEventType}
+	AccountDataRoomTags    = EventType{"m.tag", AccountDataEventType}
+)
+
+type MessageType string
+
+// Msgtypes
+const (
+	MsgText     MessageType = "m.text"
+	MsgEmote                = "m.emote"
+	MsgNotice               = "m.notice"
+	MsgImage                = "m.image"
+	MsgLocation             = "m.location"
+	MsgVideo                = "m.video"
+	MsgAudio                = "m.audio"
+	MsgFile                 = "m.file"
+)
+
+type Format string
+
+// Message formats
+const (
+	FormatHTML Format = "org.matrix.custom.html"
+)
+
+// Event represents a single Matrix event.
+type Event struct {
+	StateKey  *string   `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
+	Sender    string    `json:"sender"`              // The user ID of the sender of the event
+	Type      EventType `json:"type"`                // The event type
+	Timestamp int64     `json:"origin_server_ts"`    // The unix timestamp when this message was sent by the origin server
+	ID        string    `json:"event_id"`            // The unique ID of this event
+	RoomID    string    `json:"room_id"`             // The room the event was sent to. May be nil (e.g. for presence)
+	Content   Content   `json:"content"`             // The JSON content of the event.
+	Redacts   string    `json:"redacts,omitempty"`   // The event ID that was redacted if a m.room.redaction event
+	Unsigned  Unsigned  `json:"unsigned,omitempty"`  // Unsigned content set by own homeserver.
+
+	InviteRoomState []StrippedState `json:"invite_room_state"`
+}
+
+func (evt *Event) GetStateKey() string {
+	if evt.StateKey != nil {
+		return *evt.StateKey
+	}
+	return ""
+}
+
+type StrippedState struct {
+	Content  Content   `json:"content"`
+	Type     EventType `json:"type"`
+	StateKey string    `json:"state_key"`
+}
+
+type Unsigned struct {
+	PrevContent   *Content `json:"prev_content,omitempty"`
+	PrevSender    string   `json:"prev_sender,omitempty"`
+	ReplacesState string   `json:"replaces_state,omitempty"`
+	Age           int64    `json:"age,omitempty"`
+
+	PassiveCommand map[string]*MatchedPassiveCommand `json:"m.passive_command,omitempty"`
+}
+
+type MatchedPassiveCommand struct {
+	// Matched  string     `json:"matched"`
+	// Value    string     `json:"value"`
+	Captured [][]string `json:"captured"`
+
+	BackCompatCommand   string            `json:"command"`
+	BackCompatArguments map[string]string `json:"arguments"`
+}
+
+type Content struct {
+	VeryRaw json.RawMessage        `json:"-"`
+	Raw     map[string]interface{} `json:"-"`
+
+	MsgType       MessageType `json:"msgtype,omitempty"`
+	Body          string      `json:"body,omitempty"`
+	Format        Format      `json:"format,omitempty"`
+	FormattedBody string      `json:"formatted_body,omitempty"`
+
+	Info *FileInfo `json:"info,omitempty"`
+	URL  string    `json:"url,omitempty"`
+
+	// Membership key for easy access in m.room.member events
+	Membership Membership `json:"membership,omitempty"`
+
+	RelatesTo *RelatesTo      `json:"m.relates_to,omitempty"`
+	Command   *MatchedCommand `json:"m.command,omitempty"`
+
+	PowerLevels
+	Member
+	Aliases []string `json:"aliases,omitempty"`
+	CanonicalAlias
+	RoomName
+	RoomTopic
+
+	RoomTags      Tags     `json:"tags,omitempty"`
+	TypingUserIDs []string `json:"user_ids,omitempty"`
+}
+
+type serializableContent Content
+
+var DisableFancyEventParsing = false
+
+func (content *Content) UnmarshalJSON(data []byte) error {
+	content.VeryRaw = data
+	if err := json.Unmarshal(data, &content.Raw); err != nil || DisableFancyEventParsing {
+		return err
+	}
+	return json.Unmarshal(data, (*serializableContent)(content))
+}
+
+func (content *Content) GetCommand() *MatchedCommand {
+	if content.Command == nil {
+		content.Command = &MatchedCommand{}
+	}
+	return content.Command
+}
+
+func (content *Content) GetRelatesTo() *RelatesTo {
+	if content.RelatesTo == nil {
+		content.RelatesTo = &RelatesTo{}
+	}
+	return content.RelatesTo
+}
+
+func (content *Content) UnmarshalPowerLevels() (pl PowerLevels, err error) {
+	err = json.Unmarshal(content.VeryRaw, &pl)
+	return
+}
+
+func (content *Content) UnmarshalMember() (m Member, err error) {
+	err = json.Unmarshal(content.VeryRaw, &m)
+	return
+}
+
+func (content *Content) UnmarshalCanonicalAlias() (ca CanonicalAlias, err error) {
+	err = json.Unmarshal(content.VeryRaw, &ca)
+	return
+}
+
+func (content *Content) GetInfo() *FileInfo {
+	if content.Info == nil {
+		content.Info = &FileInfo{}
+	}
+	return content.Info
+}
+
+type Tags map[string]struct {
+	Order json.Number `json:"order"`
+}
+
+type RoomName struct {
+	Name string `json:"name,omitempty"`
+}
+
+type RoomTopic struct {
+	Topic string `json:"topic,omitempty"`
+}
+
+// Membership is an enum specifying the membership state of a room member.
+type Membership string
+
+// The allowed membership states as specified in spec section 10.5.5.
+const (
+	MembershipJoin   Membership = "join"
+	MembershipLeave  Membership = "leave"
+	MembershipInvite Membership = "invite"
+	MembershipBan    Membership = "ban"
+	MembershipKnock  Membership = "knock"
+)
+
+type Member struct {
+	Membership       Membership        `json:"membership,omitempty"`
+	AvatarURL        string            `json:"avatar_url,omitempty"`
+	Displayname      string            `json:"displayname,omitempty"`
+	ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
+	Reason           string            `json:"reason,omitempty"`
+}
+
+type ThirdPartyInvite struct {
+	DisplayName string `json:"display_name"`
+	Signed      struct {
+		Token      string          `json:"token"`
+		Signatures json.RawMessage `json:"signatures"`
+		MXID       string          `json:"mxid"`
+	}
+}
+
+type CanonicalAlias struct {
+	Alias string `json:"alias,omitempty"`
+}
+
+type PowerLevels struct {
+	usersLock    sync.RWMutex   `json:"-"`
+	Users        map[string]int `json:"users,omitempty"`
+	UsersDefault int            `json:"users_default,omitempty"`
+
+	eventsLock    sync.RWMutex   `json:"-"`
+	Events        map[string]int `json:"events,omitempty"`
+	EventsDefault int            `json:"events_default,omitempty"`
+
+	StateDefaultPtr *int `json:"state_default,omitempty"`
+
+	InvitePtr *int `json:"invite,omitempty"`
+	KickPtr   *int `json:"kick,omitempty"`
+	BanPtr    *int `json:"ban,omitempty"`
+	RedactPtr *int `json:"redact,omitempty"`
+}
+
+func (pl *PowerLevels) Invite() int {
+	if pl.InvitePtr != nil {
+		return *pl.InvitePtr
+	}
+	return 50
+}
+
+func (pl *PowerLevels) Kick() int {
+	if pl.KickPtr != nil {
+		return *pl.KickPtr
+	}
+	return 50
+}
+
+func (pl *PowerLevels) Ban() int {
+	if pl.BanPtr != nil {
+		return *pl.BanPtr
+	}
+	return 50
+}
+
+func (pl *PowerLevels) Redact() int {
+	if pl.RedactPtr != nil {
+		return *pl.RedactPtr
+	}
+	return 50
+}
+
+func (pl *PowerLevels) StateDefault() int {
+	if pl.StateDefaultPtr != nil {
+		return *pl.StateDefaultPtr
+	}
+	return 50
+}
+
+func (pl *PowerLevels) GetUserLevel(userID string) int {
+	pl.usersLock.RLock()
+	defer pl.usersLock.RUnlock()
+	level, ok := pl.Users[userID]
+	if !ok {
+		return pl.UsersDefault
+	}
+	return level
+}
+
+func (pl *PowerLevels) SetUserLevel(userID string, level int) {
+	pl.usersLock.Lock()
+	defer pl.usersLock.Unlock()
+	if level == pl.UsersDefault {
+		delete(pl.Users, userID)
+	} else {
+		pl.Users[userID] = level
+	}
+}
+
+func (pl *PowerLevels) EnsureUserLevel(userID string, level int) bool {
+	existingLevel := pl.GetUserLevel(userID)
+	if existingLevel != level {
+		pl.SetUserLevel(userID, level)
+		return true
+	}
+	return false
+}
+
+func (pl *PowerLevels) GetEventLevel(eventType EventType) int {
+	pl.eventsLock.RLock()
+	defer pl.eventsLock.RUnlock()
+	level, ok := pl.Events[eventType.String()]
+	if !ok {
+		if eventType.IsState() {
+			return pl.StateDefault()
+		}
+		return pl.EventsDefault
+	}
+	return level
+}
+
+func (pl *PowerLevels) SetEventLevel(eventType EventType, level int) {
+	pl.eventsLock.Lock()
+	defer pl.eventsLock.Unlock()
+	if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
+		delete(pl.Events, eventType.String())
+	} else {
+		pl.Events[eventType.String()] = level
+	}
+}
+
+func (pl *PowerLevels) EnsureEventLevel(eventType EventType, level int) bool {
+	existingLevel := pl.GetEventLevel(eventType)
+	if existingLevel != level {
+		pl.SetEventLevel(eventType, level)
+		return true
+	}
+	return false
+}
+
+type FileInfo struct {
+	MimeType      string    `json:"mimetype,omitempty"`
+	ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
+	ThumbnailURL  string    `json:"thumbnail_url,omitempty"`
+	Height        int       `json:"h,omitempty"`
+	Width         int       `json:"w,omitempty"`
+	Duration      uint      `json:"duration,omitempty"`
+	Size          int       `json:"size,omitempty"`
+}
+
+func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
+	if fileInfo.ThumbnailInfo == nil {
+		fileInfo.ThumbnailInfo = &FileInfo{}
+	}
+	return fileInfo.ThumbnailInfo
+}
+
+type RelatesTo struct {
+	InReplyTo InReplyTo `json:"m.in_reply_to,omitempty"`
+}
+
+type InReplyTo struct {
+	EventID string `json:"event_id,omitempty"`
+	// Not required, just for future-proofing
+	RoomID string `json:"room_id,omitempty"`
+}
+
+type MatchedCommand struct {
+	Target    string            `json:"target"`
+	Matched   string            `json:"matched"`
+	Arguments map[string]string `json:"arguments"`
+}
diff --git a/vendor/maunium.net/go/mautrix/filter.go b/vendor/maunium.net/go/mautrix/filter.go
new file mode 100644
index 0000000..41cab2d
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/filter.go
@@ -0,0 +1,90 @@
+// Copyright 2017 Jan Christian Grünhage
+//
+// 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.
+
+package mautrix
+
+import "errors"
+
+//Filter is used by clients to specify how the server should filter responses to e.g. sync requests
+//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering
+type Filter struct {
+	AccountData FilterPart `json:"account_data,omitempty"`
+	EventFields []string   `json:"event_fields,omitempty"`
+	EventFormat string     `json:"event_format,omitempty"`
+	Presence    FilterPart `json:"presence,omitempty"`
+	Room        RoomFilter `json:"room,omitempty"`
+}
+
+// RoomFilter is used to define filtering rules for room events
+type RoomFilter struct {
+	AccountData  FilterPart `json:"account_data,omitempty"`
+	Ephemeral    FilterPart `json:"ephemeral,omitempty"`
+	IncludeLeave bool       `json:"include_leave,omitempty"`
+	NotRooms     []string   `json:"not_rooms,omitempty"`
+	Rooms        []string   `json:"rooms,omitempty"`
+	State        FilterPart `json:"state,omitempty"`
+	Timeline     FilterPart `json:"timeline,omitempty"`
+}
+
+// FilterPart is used to define filtering rules for specific categories of events
+type FilterPart struct {
+	NotRooms    []string `json:"not_rooms,omitempty"`
+	Rooms       []string `json:"rooms,omitempty"`
+	Limit       int      `json:"limit,omitempty"`
+	NotSenders  []string `json:"not_senders,omitempty"`
+	NotTypes    []string `json:"not_types,omitempty"`
+	Senders     []string `json:"senders,omitempty"`
+	Types       []string `json:"types,omitempty"`
+	ContainsURL *bool    `json:"contains_url,omitempty"`
+}
+
+// Validate checks if the filter contains valid property values
+func (filter *Filter) Validate() error {
+	if filter.EventFormat != "client" && filter.EventFormat != "federation" {
+		return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]")
+	}
+	return nil
+}
+
+// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request
+func DefaultFilter() Filter {
+	return Filter{
+		AccountData: DefaultFilterPart(),
+		EventFields: nil,
+		EventFormat: "client",
+		Presence:    DefaultFilterPart(),
+		Room: RoomFilter{
+			AccountData:  DefaultFilterPart(),
+			Ephemeral:    DefaultFilterPart(),
+			IncludeLeave: false,
+			NotRooms:     nil,
+			Rooms:        nil,
+			State:        DefaultFilterPart(),
+			Timeline:     DefaultFilterPart(),
+		},
+	}
+}
+
+// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request
+func DefaultFilterPart() FilterPart {
+	return FilterPart{
+		NotRooms:   nil,
+		Rooms:      nil,
+		Limit:      20,
+		NotSenders: nil,
+		NotTypes:   nil,
+		Senders:    nil,
+		Types:      nil,
+	}
+}
diff --git a/vendor/maunium.net/go/mautrix/reply.go b/vendor/maunium.net/go/mautrix/reply.go
new file mode 100644
index 0000000..5e3af92
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/reply.go
@@ -0,0 +1,97 @@
+// Copyright 2018 Tulir Asokan
+package mautrix
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+
+	"golang.org/x/net/html"
+)
+
+var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
+
+func TrimReplyFallbackHTML(html string) string {
+	return HTMLReplyFallbackRegex.ReplaceAllString(html, "")
+}
+
+func TrimReplyFallbackText(text string) string {
+	if !strings.HasPrefix(text, "> ") || !strings.Contains(text, "\n") {
+		return text
+	}
+
+	lines := strings.Split(text, "\n")
+	for len(lines) > 0 && strings.HasPrefix(lines[0], "> ") {
+		lines = lines[1:]
+	}
+	return strings.TrimSpace(strings.Join(lines, "\n"))
+}
+
+func (content *Content) RemoveReplyFallback() {
+	if len(content.GetReplyTo()) > 0 {
+		if content.Format == FormatHTML {
+			content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody)
+		}
+		content.Body = TrimReplyFallbackText(content.Body)
+	}
+}
+
+func (content *Content) GetReplyTo() string {
+	if content.RelatesTo != nil {
+		return content.RelatesTo.InReplyTo.EventID
+	}
+	return ""
+}
+
+const ReplyFormat = `<mx-reply><blockquote>
+<a href="https://matrix.to/#/%s/%s">In reply to</a>
+<a href="https://matrix.to/#/%s">%s</a>
+%s
+</blockquote></mx-reply>
+`
+
+func (evt *Event) GenerateReplyFallbackHTML() string {
+	body := evt.Content.FormattedBody
+	if len(body) == 0 {
+		body = html.EscapeString(evt.Content.Body)
+	}
+
+	senderDisplayName := evt.Sender
+
+	return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body)
+}
+
+func (evt *Event) GenerateReplyFallbackText() string {
+	body := evt.Content.Body
+	lines := strings.Split(strings.TrimSpace(body), "\n")
+	firstLine, lines := lines[0], lines[1:]
+
+	senderDisplayName := evt.Sender
+
+	var fallbackText strings.Builder
+	fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
+	for _, line := range lines {
+		fmt.Fprintf(&fallbackText, "\n> %s", line)
+	}
+	fallbackText.WriteString("\n\n")
+	return fallbackText.String()
+}
+
+func (content *Content) SetReply(inReplyTo *Event) {
+	if content.RelatesTo == nil {
+		content.RelatesTo = &RelatesTo{}
+	}
+	content.RelatesTo.InReplyTo = InReplyTo{
+		EventID: inReplyTo.ID,
+		RoomID:  inReplyTo.RoomID,
+	}
+
+	if content.MsgType == MsgText || content.MsgType == MsgNotice {
+		if len(content.FormattedBody) == 0 || content.Format != FormatHTML {
+			content.FormattedBody = html.EscapeString(content.Body)
+			content.Format = FormatHTML
+		}
+		content.FormattedBody = inReplyTo.GenerateReplyFallbackHTML() + content.FormattedBody
+		content.Body = inReplyTo.GenerateReplyFallbackText() + content.Body
+	}
+}
diff --git a/vendor/maunium.net/go/mautrix/requests.go b/vendor/maunium.net/go/mautrix/requests.go
new file mode 100644
index 0000000..b90e6fb
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/requests.go
@@ -0,0 +1,82 @@
+package mautrix
+
+// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
+type ReqRegister struct {
+	Username                 string      `json:"username,omitempty"`
+	BindEmail                bool        `json:"bind_email,omitempty"`
+	Password                 string      `json:"password,omitempty"`
+	DeviceID                 string      `json:"device_id,omitempty"`
+	InitialDeviceDisplayName string      `json:"initial_device_display_name"`
+	Auth                     interface{} `json:"auth,omitempty"`
+}
+
+// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
+type ReqLogin struct {
+	Type                     string `json:"type"`
+	Password                 string `json:"password,omitempty"`
+	Medium                   string `json:"medium,omitempty"`
+	User                     string `json:"user,omitempty"`
+	Address                  string `json:"address,omitempty"`
+	Token                    string `json:"token,omitempty"`
+	DeviceID                 string `json:"device_id,omitempty"`
+	InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
+}
+
+// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
+type ReqCreateRoom struct {
+	Visibility      string                 `json:"visibility,omitempty"`
+	RoomAliasName   string                 `json:"room_alias_name,omitempty"`
+	Name            string                 `json:"name,omitempty"`
+	Topic           string                 `json:"topic,omitempty"`
+	Invite          []string               `json:"invite,omitempty"`
+	Invite3PID      []ReqInvite3PID        `json:"invite_3pid,omitempty"`
+	CreationContent map[string]interface{} `json:"creation_content,omitempty"`
+	InitialState    []*Event               `json:"initial_state,omitempty"`
+	Preset          string                 `json:"preset,omitempty"`
+	IsDirect        bool                   `json:"is_direct,omitempty"`
+}
+
+// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
+type ReqRedact struct {
+	Reason string `json:"reason,omitempty"`
+}
+
+// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57
+// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
+type ReqInvite3PID struct {
+	IDServer string `json:"id_server"`
+	Medium   string `json:"medium"`
+	Address  string `json:"address"`
+}
+
+// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
+type ReqInviteUser struct {
+	UserID string `json:"user_id"`
+}
+
+// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
+type ReqKickUser struct {
+	Reason string `json:"reason,omitempty"`
+	UserID string `json:"user_id"`
+}
+
+// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
+type ReqBanUser struct {
+	Reason string `json:"reason,omitempty"`
+	UserID string `json:"user_id"`
+}
+
+// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
+type ReqUnbanUser struct {
+	UserID string `json:"user_id"`
+}
+
+// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
+type ReqTyping struct {
+	Typing  bool  `json:"typing"`
+	Timeout int64 `json:"timeout,omitempty"`
+}
+
+type ReqPresence struct {
+	Presence string `json:"presence"`
+}
diff --git a/vendor/maunium.net/go/mautrix/responses.go b/vendor/maunium.net/go/mautrix/responses.go
new file mode 100644
index 0000000..2adf90a
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/responses.go
@@ -0,0 +1,182 @@
+package mautrix
+
+// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
+type RespError struct {
+	ErrCode string `json:"errcode"`
+	Err     string `json:"error"`
+}
+
+// Error returns the errcode and error message.
+func (e RespError) Error() string {
+	return e.ErrCode + ": " + e.Err
+}
+
+// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
+type RespCreateFilter struct {
+	FilterID string `json:"filter_id"`
+}
+
+// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
+type RespVersions struct {
+	Versions []string `json:"versions"`
+}
+
+// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join
+type RespJoinRoom struct {
+	RoomID string `json:"room_id"`
+}
+
+// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
+type RespLeaveRoom struct{}
+
+// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
+type RespForgetRoom struct{}
+
+// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
+type RespInviteUser struct{}
+
+// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
+type RespKickUser struct{}
+
+// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
+type RespBanUser struct{}
+
+// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
+type RespUnbanUser struct{}
+
+// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
+type RespTyping struct{}
+
+// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
+type RespJoinedRooms struct {
+	JoinedRooms []string `json:"joined_rooms"`
+}
+
+// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
+type RespJoinedMembers struct {
+	Joined map[string]struct {
+		DisplayName *string `json:"display_name"`
+		AvatarURL   *string `json:"avatar_url"`
+	} `json:"joined"`
+}
+
+// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
+type RespMessages struct {
+	Start string   `json:"start"`
+	Chunk []*Event `json:"chunk"`
+	End   string   `json:"end"`
+}
+
+// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
+type RespSendEvent struct {
+	EventID string `json:"event_id"`
+}
+
+// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
+type RespMediaUpload struct {
+	ContentURI string `json:"content_uri"`
+}
+
+// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api
+type RespUserInteractive struct {
+	Flows []struct {
+		Stages []string `json:"stages"`
+	} `json:"flows"`
+	Params    map[string]interface{} `json:"params"`
+	Session   string                 `json:"string"`
+	Completed []string               `json:"completed"`
+	ErrCode   string                 `json:"errcode"`
+	Error     string                 `json:"error"`
+}
+
+// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName.
+func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool {
+	for _, f := range r.Flows {
+		if len(f.Stages) == 1 && f.Stages[0] == stageName {
+			return true
+		}
+	}
+	return false
+}
+
+// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
+type RespUserDisplayName struct {
+	DisplayName string `json:"displayname"`
+}
+
+// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
+type RespRegister struct {
+	AccessToken  string `json:"access_token"`
+	DeviceID     string `json:"device_id"`
+	HomeServer   string `json:"home_server"`
+	RefreshToken string `json:"refresh_token"`
+	UserID       string `json:"user_id"`
+}
+
+// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
+type RespLogin struct {
+	AccessToken string `json:"access_token"`
+	DeviceID    string `json:"device_id"`
+	HomeServer  string `json:"home_server"`
+	UserID      string `json:"user_id"`
+}
+
+// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
+type RespLogout struct{}
+
+// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
+type RespCreateRoom struct {
+	RoomID string `json:"room_id"`
+}
+
+// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
+type RespSync struct {
+	NextBatch   string `json:"next_batch"`
+	AccountData struct {
+		Events []*Event `json:"events"`
+	} `json:"account_data"`
+	Presence struct {
+		Events []*Event `json:"events"`
+	} `json:"presence"`
+	Rooms struct {
+		Leave map[string]struct {
+			State struct {
+				Events []*Event `json:"events"`
+			} `json:"state"`
+			Timeline struct {
+				Events    []*Event `json:"events"`
+				Limited   bool     `json:"limited"`
+				PrevBatch string   `json:"prev_batch"`
+			} `json:"timeline"`
+		} `json:"leave"`
+		Join map[string]struct {
+			State struct {
+				Events []*Event `json:"events"`
+			} `json:"state"`
+			Timeline struct {
+				Events    []*Event `json:"events"`
+				Limited   bool     `json:"limited"`
+				PrevBatch string   `json:"prev_batch"`
+			} `json:"timeline"`
+			Ephemeral struct {
+				Events []*Event `json:"events"`
+			} `json:"ephemeral"`
+			AccountData struct {
+				Events []*Event `json:"events"`
+			} `json:"account_data"`
+		} `json:"join"`
+		Invite map[string]struct {
+			State struct {
+				Events []*Event `json:"events"`
+			} `json:"invite_state"`
+		} `json:"invite"`
+	} `json:"rooms"`
+}
+
+type RespTurnServer struct {
+	Username string   `json:"username"`
+	Password string   `json:"password"`
+	TTL      int      `json:"ttl"`
+	URIs     []string `json:"uris"`
+}
diff --git a/vendor/maunium.net/go/mautrix/room.go b/vendor/maunium.net/go/mautrix/room.go
new file mode 100644
index 0000000..086e259
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/room.go
@@ -0,0 +1,44 @@
+package mautrix
+
+// Room represents a single Matrix room.
+type Room struct {
+	ID    string
+	State map[EventType]map[string]*Event
+}
+
+// UpdateState updates the room's current state with the given Event. This will clobber events based
+// on the type/state_key combination.
+func (room Room) UpdateState(event *Event) {
+	_, exists := room.State[event.Type]
+	if !exists {
+		room.State[event.Type] = make(map[string]*Event)
+	}
+	room.State[event.Type][*event.StateKey] = event
+}
+
+// GetStateEvent returns the state event for the given type/state_key combo, or nil.
+func (room Room) GetStateEvent(eventType EventType, stateKey string) *Event {
+	stateEventMap, _ := room.State[eventType]
+	event, _ := stateEventMap[stateKey]
+	return event
+}
+
+// GetMembershipState returns the membership state of the given user ID in this room. If there is
+// no entry for this member, 'leave' is returned for consistency with left users.
+func (room Room) GetMembershipState(userID string) Membership {
+	state := MembershipLeave
+	event := room.GetStateEvent(StateMember, userID)
+	if event != nil {
+		state = event.Content.Membership
+	}
+	return state
+}
+
+// NewRoom creates a new Room with the given ID
+func NewRoom(roomID string) *Room {
+	// Init the State map and return a pointer to the Room
+	return &Room{
+		ID:    roomID,
+		State: make(map[EventType]map[string]*Event),
+	}
+}
diff --git a/vendor/maunium.net/go/mautrix/store.go b/vendor/maunium.net/go/mautrix/store.go
new file mode 100644
index 0000000..774398e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/store.go
@@ -0,0 +1,65 @@
+package mautrix
+
+// Storer is an interface which must be satisfied to store client data.
+//
+// You can either write a struct which persists this data to disk, or you can use the
+// provided "InMemoryStore" which just keeps data around in-memory which is lost on
+// restarts.
+type Storer interface {
+	SaveFilterID(userID, filterID string)
+	LoadFilterID(userID string) string
+	SaveNextBatch(userID, nextBatchToken string)
+	LoadNextBatch(userID string) string
+	SaveRoom(room *Room)
+	LoadRoom(roomID string) *Room
+}
+
+// InMemoryStore implements the Storer interface.
+//
+// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
+// or next batch tokens on any goroutine other than the syncing goroutine: the one
+// which called Client.Sync().
+type InMemoryStore struct {
+	Filters   map[string]string
+	NextBatch map[string]string
+	Rooms     map[string]*Room
+}
+
+// SaveFilterID to memory.
+func (s *InMemoryStore) SaveFilterID(userID, filterID string) {
+	s.Filters[userID] = filterID
+}
+
+// LoadFilterID from memory.
+func (s *InMemoryStore) LoadFilterID(userID string) string {
+	return s.Filters[userID]
+}
+
+// SaveNextBatch to memory.
+func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) {
+	s.NextBatch[userID] = nextBatchToken
+}
+
+// LoadNextBatch from memory.
+func (s *InMemoryStore) LoadNextBatch(userID string) string {
+	return s.NextBatch[userID]
+}
+
+// SaveRoom to memory.
+func (s *InMemoryStore) SaveRoom(room *Room) {
+	s.Rooms[room.ID] = room
+}
+
+// LoadRoom from memory.
+func (s *InMemoryStore) LoadRoom(roomID string) *Room {
+	return s.Rooms[roomID]
+}
+
+// NewInMemoryStore constructs a new InMemoryStore.
+func NewInMemoryStore() *InMemoryStore {
+	return &InMemoryStore{
+		Filters:   make(map[string]string),
+		NextBatch: make(map[string]string),
+		Rooms:     make(map[string]*Room),
+	}
+}
diff --git a/vendor/maunium.net/go/mautrix/sync.go b/vendor/maunium.net/go/mautrix/sync.go
new file mode 100644
index 0000000..9589edc
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/sync.go
@@ -0,0 +1,159 @@
+package mautrix
+
+import (
+	"encoding/json"
+	"fmt"
+	"runtime/debug"
+	"time"
+)
+
+// Syncer represents an interface that must be satisfied in order to do /sync requests on a client.
+type Syncer interface {
+	// Process the /sync response. The since parameter is the since= value that was used to produce the response.
+	// This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped
+	// permanently.
+	ProcessResponse(resp *RespSync, since string) error
+	// OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently.
+	OnFailedSync(res *RespSync, err error) (time.Duration, error)
+	// GetFilterJSON for the given user ID. NOT the filter ID.
+	GetFilterJSON(userID string) json.RawMessage
+}
+
+// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively
+// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
+// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information.
+type DefaultSyncer struct {
+	UserID    string
+	Store     Storer
+	listeners map[EventType][]OnEventListener // event type to listeners array
+}
+
+// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
+type OnEventListener func(*Event)
+
+// NewDefaultSyncer returns an instantiated DefaultSyncer
+func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
+	return &DefaultSyncer{
+		UserID:    userID,
+		Store:     store,
+		listeners: make(map[EventType][]OnEventListener),
+	}
+}
+
+// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
+// unrepeating events. Returns a fatal error if a listener panics.
+func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) {
+	if !s.shouldProcessResponse(res, since) {
+		return
+	}
+
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack())
+		}
+	}()
+
+	for roomID, roomData := range res.Rooms.Join {
+		room := s.getOrCreateRoom(roomID)
+		for _, event := range roomData.State.Events {
+			event.RoomID = roomID
+			room.UpdateState(event)
+			s.notifyListeners(event)
+		}
+		for _, event := range roomData.Timeline.Events {
+			event.RoomID = roomID
+			s.notifyListeners(event)
+		}
+	}
+	for roomID, roomData := range res.Rooms.Invite {
+		room := s.getOrCreateRoom(roomID)
+		for _, event := range roomData.State.Events {
+			event.RoomID = roomID
+			room.UpdateState(event)
+			s.notifyListeners(event)
+		}
+	}
+	for roomID, roomData := range res.Rooms.Leave {
+		room := s.getOrCreateRoom(roomID)
+		for _, event := range roomData.Timeline.Events {
+			if event.StateKey != nil {
+				event.RoomID = roomID
+				room.UpdateState(event)
+				s.notifyListeners(event)
+			}
+		}
+	}
+	return
+}
+
+// OnEventType allows callers to be notified when there are new events for the given event type.
+// There are no duplicate checks.
+func (s *DefaultSyncer) OnEventType(eventType EventType, callback OnEventListener) {
+	_, exists := s.listeners[eventType]
+	if !exists {
+		s.listeners[eventType] = []OnEventListener{}
+	}
+	s.listeners[eventType] = append(s.listeners[eventType], callback)
+}
+
+// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
+// stuff that shouldn't be processed.
+func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool {
+	if since == "" {
+		return false
+	}
+	// This is a horrible hack because /sync will return the most recent messages for a room
+	// as soon as you /join it. We do NOT want to process those events in that particular room
+	// because they may have already been processed (if you toggle the bot in/out of the room).
+	//
+	// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
+	// exists and is "join" and then discard processing that room entirely if so.
+	// TODO: We probably want to process messages from after the last join event in the timeline.
+	for roomID, roomData := range resp.Rooms.Join {
+		for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
+			e := roomData.Timeline.Events[i]
+			if e.Type == StateMember && e.GetStateKey() == s.UserID {
+				if e.Content.Membership == "join" {
+					_, ok := resp.Rooms.Join[roomID]
+					if !ok {
+						continue
+					}
+					delete(resp.Rooms.Join, roomID)   // don't re-process messages
+					delete(resp.Rooms.Invite, roomID) // don't re-process invites
+					break
+				}
+			}
+		}
+	}
+	return true
+}
+
+// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
+func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
+	room := s.Store.LoadRoom(roomID)
+	if room == nil { // create a new Room
+		room = NewRoom(roomID)
+		s.Store.SaveRoom(room)
+	}
+	return room
+}
+
+func (s *DefaultSyncer) notifyListeners(event *Event) {
+	listeners, exists := s.listeners[event.Type]
+	if !exists {
+		return
+	}
+	for _, fn := range listeners {
+		fn(event)
+	}
+}
+
+// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
+func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) {
+	return 10 * time.Second, nil
+}
+
+// GetFilterJSON returns a filter with a timeline limit of 50.
+func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage {
+	return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
+}
diff --git a/vendor/maunium.net/go/mautrix/userids.go b/vendor/maunium.net/go/mautrix/userids.go
new file mode 100644
index 0000000..ce6e02d
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/userids.go
@@ -0,0 +1,130 @@
+package mautrix
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"strings"
+)
+
+const lowerhex = "0123456789abcdef"
+
+// encode the given byte using quoted-printable encoding (e.g "=2f")
+// and writes it to the buffer
+// See https://golang.org/src/mime/quotedprintable/writer.go
+func encode(buf *bytes.Buffer, b byte) {
+	buf.WriteByte('=')
+	buf.WriteByte(lowerhex[b>>4])
+	buf.WriteByte(lowerhex[b&0x0f])
+}
+
+// escape the given alpha character and writes it to the buffer
+func escape(buf *bytes.Buffer, b byte) {
+	buf.WriteByte('_')
+	if b == '_' {
+		buf.WriteByte('_') // another _
+	} else {
+		buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z
+	}
+}
+
+func shouldEncode(b byte) bool {
+	return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z')
+}
+
+func shouldEscape(b byte) bool {
+	return (b >= 'A' && b <= 'Z') || b == '_'
+}
+
+func isValidByte(b byte) bool {
+	return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-'
+}
+
+func isValidEscapedChar(b byte) bool {
+	return b == '_' || (b >= 'a' && b <= 'z')
+}
+
+// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form.
+// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
+//
+// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z
+// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges
+// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs)
+// and converted to lower-case hex with a leading "=". For example:
+//   Alph@Bet_50up  => _alph=40_bet=5f50up
+func EncodeUserLocalpart(str string) string {
+	strBytes := []byte(str)
+	var outputBuffer bytes.Buffer
+	for _, b := range strBytes {
+		if shouldEncode(b) {
+			encode(&outputBuffer, b)
+		} else if shouldEscape(b) {
+			escape(&outputBuffer, b)
+		} else {
+			outputBuffer.WriteByte(b)
+		}
+	}
+	return outputBuffer.String()
+}
+
+// DecodeUserLocalpart decodes the given string back into the original input string.
+// Returns an error if the given string is not a valid user ID localpart encoding.
+// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
+//
+// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For
+// example:
+//  _alph=40_bet=5f50up  =>  Alph@Bet_50up
+// Returns an error if the input string contains characters outside the
+// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has
+// an invalid _ escaped byte (e.g. "_5").
+func DecodeUserLocalpart(str string) (string, error) {
+	strBytes := []byte(str)
+	var outputBuffer bytes.Buffer
+	for i := 0; i < len(strBytes); i++ {
+		b := strBytes[i]
+		if !isValidByte(b) {
+			return "", fmt.Errorf("Byte pos %d: Invalid byte", i)
+		}
+
+		if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
+			if i+1 >= len(strBytes) {
+				return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i)
+			}
+			if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
+				return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i)
+			}
+			if strBytes[i+1] == '_' {
+				outputBuffer.WriteByte('_')
+			} else {
+				outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z
+			}
+			i++ // skip next byte since we just handled it
+		} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
+			if i+2 >= len(strBytes) {
+				return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i)
+			}
+			dst := make([]byte, 1)
+			_, err := hex.Decode(dst, strBytes[i+1:i+3])
+			if err != nil {
+				return "", err
+			}
+			outputBuffer.WriteByte(dst[0])
+			i += 2 // skip next 2 bytes since we just handled it
+		} else { // pass through
+			outputBuffer.WriteByte(b)
+		}
+	}
+	return outputBuffer.String(), nil
+}
+
+// ExtractUserLocalpart extracts the localpart portion of a user ID.
+// See http://matrix.org/docs/spec/intro.html#user-identifiers
+func ExtractUserLocalpart(userID string) (string, error) {
+	if len(userID) == 0 || userID[0] != '@' {
+		return "", fmt.Errorf("%s is not a valid user id", userID)
+	}
+	return strings.TrimPrefix(
+		strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
+		"@",                               // remove "@" prefix
+	), nil
+}
diff --git a/vendor/maunium.net/go/tcell/README.adoc b/vendor/maunium.net/go/tcell/README.adoc
new file mode 100644
index 0000000..3b60057
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/README.adoc
@@ -0,0 +1,270 @@
+= tcell
+
+
+image:https://img.shields.io/travis/gdamore/tcell.svg?label=linux[Linux Status,link="https://travis-ci.org/gdamore/tcell"]
+image:https://img.shields.io/appveyor/ci/gdamore/tcell.svg?label=windows[Windows Status,link="https://ci.appveyor.com/project/gdamore/tcell"]
+image:https://img.shields.io/badge/license-APACHE2-blue.svg[Apache License,link="https://github.com/gdamore/tcell/blob/master/LICENSE"]
+image:https://img.shields.io/badge/gitter-join-brightgreen.svg[Gitter,link="https://gitter.im/gdamore/tcell"]
+image:https://img.shields.io/badge/godoc-reference-blue.svg[GoDoc,link="https://godoc.org/github.com/gdamore/tcell"]
+image:http://goreportcard.com/badge/gdamore/tcell[Go Report Card,link="http://goreportcard.com/report/gdamore/tcell"]
+image:https://codecov.io/gh/gdamore/tcell/branch/master/graph/badge.svg[codecov,link="https://codecov.io/gh/gdamore/tcell"]
+image:https://tidelift.com/badges/github/gdamore/tcell?style=flat[Dependencies]
+
+[cols="2",grid="none"]
+|===
+|_Tcell_ is a _Go_ package that provides a cell based view for text terminals, like _xterm_.
+It was inspired by _termbox_, but includes many additional improvements.
+a|[.right]
+image::logos/tcell.png[float="right"]
+|===
+
+## Examples
+
+* https://github.com/gdamore/proxima5[proxima5] - space shooter (https://youtu.be/jNxKTCmY_bQ[video])
+* https://github.com/gdamore/govisor[govisor] - service management UI (http://2.bp.blogspot.com/--OsvnfzSNow/Vf7aqMw3zXI/AAAAAAAAARo/uOMtOvw4Sbg/s1600/Screen%2BShot%2B2015-09-20%2Bat%2B9.08.41%2BAM.png[screenshot])
+* mouse demo - included mouse test (http://2.bp.blogspot.com/-fWvW5opT0es/VhIdItdKqJI/AAAAAAAAATE/7Ojc0L1SpB0/s1600/Screen%2BShot%2B2015-10-04%2Bat%2B11.47.13%2BPM.png[screenshot])
+* https://github.com/gdamore/gomatrix[gomatrix] - converted from Termbox
+* https://github.com/zyedidia/micro/[micro] - lightweight text editor with syntax-highlighting and themes
+* https://github.com/viktomas/godu[godu] - simple golang utility helping to discover large files/folders.
+* https://github.com/rivo/tview[tview] - rich interactive widgets for terminal UIs
+* https://github.com/marcusolsson/tui-go[tui-go] - UI library for terminal apps
+* https://github.com/rgm3/gomandelbrot[gomandelbrot] - Mandelbrot!
+* https://github.com/senorprogrammer/wtf[WTF]- Personal information dashboard for your terminal
+* https://github.com/browsh-org/browsh[browsh] - A fully-modern text-based browser, rendering to TTY and browsers (https://www.youtube.com/watch?v=HZq86XfBoRo[video])
+* https://github.com/sachaos/go-life[go-life] - Conway's Game of Life.
+
+## Pure Go Terminfo Database
+
+_Tcell_ includes a full parser and expander for terminfo capability strings,
+so that it can avoid hard coding escape strings for formatting.  It also favors
+portability, and includes support for all POSIX systems.
+
+The database is also flexible & extensible, and can modified by either running
+a program to build the entire database, or an entry for just a single terminal.
+
+## More Portable
+
+_Tcell_ is portable to a wide variety of systems.
+_Tcell_ is believed
+to work with all of the systems officially supported by golang with
+the exception of nacl (which lacks any kind of a terminal interface).
+(Plan9 is not supported by _Tcell_, but it is experimental status only
+in golang.)  For all of these systems *except Solaris/illumos*, _Tcell_
+is pure Go, with no need for CGO.
+
+## No Async IO
+
+_Tcell_ is able to operate without requiring `SIGIO` signals (unlike _termbox_),
+or asynchronous I/O, and can instead use standard Go file
+objects and Go routines.
+This means it should be safe, especially for
+use with programs that use exec, or otherwise need to manipulate the
+tty streams.
+This model is also much closer to idiomatic Go, leading
+to fewer surprises.
+
+## Rich Unicode & non-Unicode support
+
+_Tcell_ includes enhanced support for Unicode, including wide characters and
+combining characters, provided your terminal can support them.
+Note that
+Windows terminals generally don't support the full Unicode repertoire.
+
+It will also convert to and from Unicode locales, so that the program
+can work with UTF-8 internally, and get reasonable output in other locales.
+_Tcell_ tries hard to convert to native characters on both input and output, and
+on output _Tcell_ even makes use of the alternate character set to facilitate
+drawing certain characters.
+
+## More Function Keys
+
+_Tcell_ also has richer support for a larger number of special keys that some terminals can send.
+
+## Better Color Handling
+
+_Tcell_ will respect your terminal's color space as specified within your terminfo
+entries, so that for example attempts to emit color sequences on VT100 terminals
+won't result in unintended consequences.
+
+In Windows mode, _Tcell_ supports 16 colors, bold, dim, and reverse,
+instead of just termbox's 8 colors with reverse.  (Note that there is some
+conflation with bold/dim and colors.)
+
+_Tcell_ maps 16 colors down to 8, for terminals that need it.
+(The upper 8 colors are just brighter versions of the lower 8.)
+
+## Better Mouse Support
+
+_Tcell_ supports enhanced mouse tracking mode, so your application can receive
+regular mouse motion events, and wheel events, if your terminal supports it.
+
+## _Termbox_ Compatibility
+
+A compatibility layer for _termbox_ is provided in the `compat` directory.
+To use it, try importing `github.com/gdamore/tcell/termbox`
+instead.  Most _termbox-go_ programs will probably work without further
+modification.
+
+## Working With Unicode
+
+Internally Tcell uses UTF-8, just like Go.
+However, Tcell understands how to
+convert to and from other character sets, using the capabilities of
+the `golang.org/x/text/encoding packages`.
+Your application must supply
+them, as the full set of the most common ones bloats the program by about 2MB.
+If you're lazy, and want them all anyway, see the `encoding` sub-directory.
+
+## Wide & Combining Characters
+
+The `SetContent()` API takes a primary rune, and an optional list of combining runes.
+If any of the runes is a wide (East Asian) rune occupying two cells,
+then the library will skip output from the following cell, but care must be
+taken in the application to avoid explicitly attempting to set content in the
+next cell, otherwise the results are undefined.  (Normally wide character
+is displayed, and the other character is not; do not depend on that behavior.)
+
+Experience has shown that the vanilla Windows 8 console application does not
+support any of these characters properly, but at least some options like
+_ConEmu_ do support Wide characters.
+
+## Colors
+
+_Tcell_ assumes the ANSI/XTerm color model, including the 256 color map that
+XTerm uses when it supports 256 colors.  The terminfo guidance will be
+honored, with respect to the number of colors supported.  Also, only
+terminals which expose ANSI style `setaf` and `setab` will support color;
+if you have a color terminal that only has `setf` and `setb`, please let me
+know; it wouldn't be hard to add that if there is need.
+
+## 24-bit Color
+
+_Tcell_ _supports true color_!  (That is, if your terminal can support it,
+_Tcell_ can accurately display 24-bit color.)
+
+To use 24-bit color, you need to use a terminal that supports it.  Modern
+xterm and similar teminal emulators can support this.  As terminfo lacks any
+way to describe this capability, we fabricate the capability for
+terminals with names ending in `*-truecolor`.  The stock distribution ships
+with a database that defines `xterm-truecolor`.
+To try it out, set your
+`TERM` variable to `xterm-truecolor`.
+
+When using TrueColor, programs will display the colors that the programmer
+intended, overriding any "`themes`" you may have set in your terminal
+emulator.  (For some cases, accurate color fidelity is more important
+than respecting themes.  For other cases, such as typical text apps that
+only use a few colors, its more desirable to respect the themes that
+the user has established.)
+
+If you find this undesirable, you can either use a `TERM` variable
+that lacks the `TRUECOLOR` setting, or set `TCELL_TRUECOLOR=disable` in your
+environment.
+
+## Performance
+
+Reasonable attempts have been made to minimize sending data to terminals,
+avoiding repeated sequences or drawing the same cell on refresh updates.
+
+## Terminfo
+
+(Not relevent for Windows users.)
+
+The Terminfo implementation operates with two forms of database.  The first
+is the built-in go database, which contains a number of real database entries
+that are compiled into the program directly.  This should minimize calling
+out to database file searches.
+
+The second is in the form of JSON files, that contain the same information,
+which can be located either by the `$TCELLDB` environment file, `$HOME/.tcelldb`,
+or is located in the Go source directory as `database.json`.
+
+These files (both the Go and the JSON files) can be generated using the
+mkinfo.go program.  If you need to regnerate the entire set for some reason,
+run the mkdatabase.sh file.  The generation uses the infocmp(1) program on
+the system to collect the necessary information.
+
+The `mkinfo.go` program can also be used to generate specific database entries
+for named terminals, in case your favorite terminal is missing.  (If you
+find that this is the case, please let me know and I'll try to add it!)
+
+_Tcell_ requires that the terminal support the `cup` mode of cursor addressing.
+Terminals without absolute cursor addressability are not supported.
+This is unlikely to be a problem; such terminals have not been mass produced
+since the early 1970s.
+
+## Mouse Support
+
+Mouse support is detected via the `kmous` terminfo variable, however,
+enablement/disablement and decoding mouse events is done using hard coded
+sequences based on the XTerm X11 model.  As of this writing all popular
+terminals with mouse tracking support this model.  (Full terminfo support
+is not possible as terminfo sequences are not defined.)
+
+On Windows, the mouse works normally.
+
+Mouse wheel buttons on various terminals are known to work, but the support
+in terminal emulators, as well as support for various buttons and
+live mouse tracking, varies widely.  Modern _xterm_, macOS _Terminal_, and _iTerm_ all work well.
+
+## Testablity
+
+There is a `SimulationScreen`, that can be used to simulate a real screen
+for automated testing.  The supplied tests do this.  The simulation contains
+event delivery, screen resizing support, and capabilities to inject events
+and examine "`physical`" screen contents.
+
+## Platforms
+
+### POSIX (Linux, FreeBSD, macOS, Solaris, etc.)
+
+For mainstream systems with a suitably well defined system call interface
+to tty settings, everything works using pure Go.
+
+For the remainder (right now means only Solaris/illumos) we use POSIX function
+calls to manage termios, which implies that CGO is required on those platforms.
+
+### Windows
+
+Windows console mode applications are supported.  Unfortunately _mintty_
+and other _cygwin_ style applications are not supported.
+
+Modern console applications like ConEmu, as well as the Windows 10
+console itself, support all the good features (resize, mouse tracking, etc.)
+
+I haven't figured out how to cleanly resolve the dichotomy between cygwin
+style termios and the Windows Console API; it seems that perhaps nobody else
+has either.  If anyone has suggestions, let me know!  Really, if you're
+using a Windows application, you should use the native Windows console or a
+fully compatible console implementation.
+
+### Plan9 and Native Client (Nacl)
+
+The nacl and plan9 platforms won't work, but compilation stubs are supplied
+for folks that want to include parts of this in software targetting those
+platforms.  The Simulation screen works, but as Tcell doesn't know how to
+allocate a real screen object on those platforms, `NewScreen()` will fail.
+
+If anyone has wisdom about how to improve support for either of these,
+please let me know.  PRs are especially welcome.
+
+### Commercial Support
+
+_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options.
+
+[cols="2",align="center",frame="none", grid="none"]
+|===
+^.^|
+image:logos/tidelift.png[100,100]
+a|
+https://tidelift.com/[Tidelift] subscriptions include support for _Tcell_, as well as many other open source packages.
+
+^.^|
+image:logos/staysail.png[100,100]
+a|
+mailto:info@staysail.tech[Staysail Systems, Inc.] offers direct support, and custom development around _Tcell_ on an hourly basis.
+
+^.^|
+image:logos/patreon.png[100,100]
+a|I also welcome donations at https://www.patron.com/gedamore/[Patreon], if you just want to make a contribution.
+|===
diff --git a/vendor/maunium.net/go/tcell/cell.go b/vendor/maunium.net/go/tcell/cell.go
index 496f10f..957b62f 100644
--- a/vendor/maunium.net/go/tcell/cell.go
+++ b/vendor/maunium.net/go/tcell/cell.go
@@ -52,6 +52,10 @@ func (cb *CellBuffer) SetContent(x int, y int,
 		i := 0
 		for i < len(c.currComb) {
 			r := c.currComb[i]
+			if r == '\u200d' {
+				i += 2
+				continue
+			}
 			if runewidth.RuneWidth(r) != 0 {
 				// not a combining character, yank it
 				c.currComb = append(c.currComb[:i-1], c.currComb[i+1:]...)
@@ -175,12 +179,13 @@ func (cb *CellBuffer) Resize(w, h int) {
 
 // Fill fills the entire cell buffer array with the specified character
 // and style.  Normally choose ' ' to clear the screen.  This API doesn't
-// support combining characters.
+// support combining characters, or characters with a width larger than one.
 func (cb *CellBuffer) Fill(r rune, style Style) {
 	for i := range cb.cells {
 		c := &cb.cells[i]
 		c.currMain = r
 		c.currComb = nil
 		c.currStyle = style
+		c.width = 1
 	}
 }
diff --git a/vendor/maunium.net/go/tcell/console_win.go b/vendor/maunium.net/go/tcell/console_win.go
index 5957a17..a7507c2 100644
--- a/vendor/maunium.net/go/tcell/console_win.go
+++ b/vendor/maunium.net/go/tcell/console_win.go
@@ -28,7 +28,6 @@ type cScreen struct {
 	in         syscall.Handle
 	out        syscall.Handle
 	cancelflag syscall.Handle
-	title      syscall.Handle
 	scandone   chan struct{}
 	evch       chan Event
 	quit       chan struct{}
diff --git a/vendor/maunium.net/go/tcell/tcell.png b/vendor/maunium.net/go/tcell/tcell.png
deleted file mode 100644
index 24333c4..0000000
Binary files a/vendor/maunium.net/go/tcell/tcell.png and /dev/null differ
diff --git a/vendor/maunium.net/go/tcell/tcell.svg b/vendor/maunium.net/go/tcell/tcell.svg
deleted file mode 100644
index d8695d5..0000000
--- a/vendor/maunium.net/go/tcell/tcell.svg
+++ /dev/null
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:xlink="http://www.w3.org/1999/xlink"
-   id="svg8"
-   version="1.1"
-   viewBox="0 0 210 297"
-   height="297mm"
-   width="210mm">
-  <defs
-     id="defs2">
-    <linearGradient
-       id="linearGradient2680">
-      <stop
-         id="stop2676"
-         offset="0"
-         style="stop-color:#ababab;stop-opacity:1;" />
-      <stop
-         id="stop2678"
-         offset="1"
-         style="stop-color:#ababab;stop-opacity:0;" />
-    </linearGradient>
-    <marker
-       style="overflow:visible"
-       id="Arrow1Lstart"
-       refX="0.0"
-       refY="0.0"
-       orient="auto">
-      <path
-         transform="scale(0.8) translate(12.5,0)"
-         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
-         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
-         id="path848" />
-    </marker>
-    <radialGradient
-       gradientUnits="userSpaceOnUse"
-       gradientTransform="matrix(1,0,0,0.28804762,0,94.764912)"
-       r="19.622099"
-       fy="133.10568"
-       fx="111.58373"
-       cy="133.10568"
-       cx="111.58373"
-       id="radialGradient2684"
-       xlink:href="#linearGradient2680" />
-  </defs>
-  <metadata
-     id="metadata5">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     id="layer1">
-    <rect
-       y="99.44445"
-       x="31.750006"
-       height="86.430557"
-       width="129.64584"
-       id="rect1130"
-       style="opacity:1;fill:#5d6c53;fill-opacity:1;stroke:#244f24;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
-    <text
-       transform="scale(0.99941234,1.000588)"
-       id="text1128"
-       y="160.47581"
-       x="44.689861"
-       style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:46.0962944px;line-height:1.25;font-family:'Glass TTY VT220';-inkscape-font-specification:'Glass TTY VT220, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#5ef86d;fill-opacity:1;stroke:#59ff32;stroke-width:0.80994618;stroke-opacity:0.94520545;"
-       xml:space="preserve"><tspan
-         style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:46.0962944px;font-family:'Glass TTY VT220';-inkscape-font-specification:'Glass TTY VT220, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#5ef86d;fill-opacity:1;stroke:#59ff32;stroke-width:0.80994618;stroke-opacity:0.94520545;"
-         y="160.47581"
-         x="44.689861"
-         id="tspan1126">tcell</tspan></text>
-    <flowRoot
-       style="fill:black;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px"
-       id="flowRoot1132"
-       xml:space="preserve"><flowRegion
-         id="flowRegion1134"><rect
-           y="432.51968"
-           x="290"
-           height="160"
-           width="330"
-           id="rect1136" /></flowRegion><flowPara
-         id="flowPara1138"></flowPara></flowRoot>  </g>
-</svg>
diff --git a/vendor/maunium.net/go/tcell/terminfo/term_termite.go b/vendor/maunium.net/go/tcell/terminfo/term_termite.go
new file mode 100644
index 0000000..8e7f683
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/terminfo/term_termite.go
@@ -0,0 +1,152 @@
+// Generated automatically.  DO NOT HAND-EDIT.
+
+package terminfo
+
+func init() {
+	// VTE-based terminal
+	AddTerminfo(&Terminfo{
+		Name:            "xterm-termite",
+		Columns:         80,
+		Lines:           24,
+		Colors:          256,
+		Bell:            "\a",
+		Clear:           "\x1b[H\x1b[2J",
+		EnterCA:         "\x1b[?1049h",
+		ExitCA:          "\x1b[?1049l",
+		ShowCursor:      "\x1b[?12l\x1b[?25h",
+		HideCursor:      "\x1b[?25l",
+		AttrOff:         "\x1b(B\x1b[m",
+		Underline:       "\x1b[4m",
+		Bold:            "\x1b[1m",
+		Dim:             "\x1b[2m",
+		Reverse:         "\x1b[7m",
+		EnterKeypad:     "\x1b[?1h\x1b=",
+		ExitKeypad:      "\x1b[?1l\x1b>",
+		SetFg:           "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
+		SetBg:           "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
+		SetFgBg:         "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m",
+		AltChars:        "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
+		EnterAcs:        "\x1b(0",
+		ExitAcs:         "\x1b(B",
+		Mouse:           "\x1b[M",
+		MouseMode:       "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c",
+		SetCursor:       "\x1b[%i%p1%d;%p2%dH",
+		CursorBack1:     "\b",
+		CursorUp1:       "\x1b[A",
+		KeyUp:           "\x1bOA",
+		KeyDown:         "\x1bOB",
+		KeyRight:        "\x1bOC",
+		KeyLeft:         "\x1bOD",
+		KeyInsert:       "\x1b[2~",
+		KeyDelete:       "\x1b[3~",
+		KeyBackspace:    "\xff",
+		KeyHome:         "\x1bOH",
+		KeyEnd:          "\x1bOF",
+		KeyPgUp:         "\x1b[5~",
+		KeyPgDn:         "\x1b[6~",
+		KeyF1:           "\x1bOP",
+		KeyF2:           "\x1bOQ",
+		KeyF3:           "\x1bOR",
+		KeyF4:           "\x1bOS",
+		KeyF5:           "\x1b[15~",
+		KeyF6:           "\x1b[17~",
+		KeyF7:           "\x1b[18~",
+		KeyF8:           "\x1b[19~",
+		KeyF9:           "\x1b[20~",
+		KeyF10:          "\x1b[21~",
+		KeyF11:          "\x1b[23~",
+		KeyF12:          "\x1b[24~",
+		KeyF13:          "\x1b[1;2P",
+		KeyF14:          "\x1b[1;2Q",
+		KeyF15:          "\x1b[1;2R",
+		KeyF16:          "\x1b[1;2S",
+		KeyF17:          "\x1b[15;2~",
+		KeyF18:          "\x1b[17;2~",
+		KeyF19:          "\x1b[18;2~",
+		KeyF20:          "\x1b[19;2~",
+		KeyF21:          "\x1b[20;2~",
+		KeyF22:          "\x1b[21;2~",
+		KeyF23:          "\x1b[23;2~",
+		KeyF24:          "\x1b[24;2~",
+		KeyF25:          "\x1b[1;5P",
+		KeyF26:          "\x1b[1;5Q",
+		KeyF27:          "\x1b[1;5R",
+		KeyF28:          "\x1b[1;5S",
+		KeyF29:          "\x1b[15;5~",
+		KeyF30:          "\x1b[17;5~",
+		KeyF31:          "\x1b[18;5~",
+		KeyF32:          "\x1b[19;5~",
+		KeyF33:          "\x1b[20;5~",
+		KeyF34:          "\x1b[21;5~",
+		KeyF35:          "\x1b[23;5~",
+		KeyF36:          "\x1b[24;5~",
+		KeyF37:          "\x1b[1;6P",
+		KeyF38:          "\x1b[1;6Q",
+		KeyF39:          "\x1b[1;6R",
+		KeyF40:          "\x1b[1;6S",
+		KeyF41:          "\x1b[15;6~",
+		KeyF42:          "\x1b[17;6~",
+		KeyF43:          "\x1b[18;6~",
+		KeyF44:          "\x1b[19;6~",
+		KeyF45:          "\x1b[20;6~",
+		KeyF46:          "\x1b[21;6~",
+		KeyF47:          "\x1b[23;6~",
+		KeyF48:          "\x1b[24;6~",
+		KeyF49:          "\x1b[1;3P",
+		KeyF50:          "\x1b[1;3Q",
+		KeyF51:          "\x1b[1;3R",
+		KeyF52:          "\x1b[1;3S",
+		KeyF53:          "\x1b[15;3~",
+		KeyF54:          "\x1b[17;3~",
+		KeyF55:          "\x1b[18;3~",
+		KeyF56:          "\x1b[19;3~",
+		KeyF57:          "\x1b[20;3~",
+		KeyF58:          "\x1b[21;3~",
+		KeyF59:          "\x1b[23;3~",
+		KeyF60:          "\x1b[24;3~",
+		KeyF61:          "\x1b[1;4P",
+		KeyF62:          "\x1b[1;4Q",
+		KeyF63:          "\x1b[1;4R",
+		KeyBacktab:      "\x1b[Z",
+		KeyShfLeft:      "\x1b[1;2D",
+		KeyShfRight:     "\x1b[1;2C",
+		KeyShfUp:        "\x1b[1;2A",
+		KeyShfDown:      "\x1b[1;2B",
+		KeyCtrlLeft:     "\x1b[1;5D",
+		KeyCtrlRight:    "\x1b[1;5C",
+		KeyCtrlUp:       "\x1b[1;5A",
+		KeyCtrlDown:     "\x1b[1;5B",
+		KeyMetaLeft:     "\x1b[1;9D",
+		KeyMetaRight:    "\x1b[1;9C",
+		KeyMetaUp:       "\x1b[1;9A",
+		KeyMetaDown:     "\x1b[1;9B",
+		KeyAltLeft:      "\x1b[1;3D",
+		KeyAltRight:     "\x1b[1;3C",
+		KeyAltUp:        "\x1b[1;3A",
+		KeyAltDown:      "\x1b[1;3B",
+		KeyAltShfLeft:   "\x1b[1;4D",
+		KeyAltShfRight:  "\x1b[1;4C",
+		KeyAltShfUp:     "\x1b[1;4A",
+		KeyAltShfDown:   "\x1b[1;4B",
+		KeyMetaShfLeft:  "\x1b[1;10D",
+		KeyMetaShfRight: "\x1b[1;10C",
+		KeyMetaShfUp:    "\x1b[1;10A",
+		KeyMetaShfDown:  "\x1b[1;10B",
+		KeyCtrlShfLeft:  "\x1b[1;6D",
+		KeyCtrlShfRight: "\x1b[1;6C",
+		KeyCtrlShfUp:    "\x1b[1;6A",
+		KeyCtrlShfDown:  "\x1b[1;6B",
+		KeyShfHome:      "\x1b[1;2H",
+		KeyShfEnd:       "\x1b[1;2F",
+		KeyCtrlHome:     "\x1b[1;5H",
+		KeyCtrlEnd:      "\x1b[1;5F",
+		KeyAltHome:      "\x1b[1;9H",
+		KeyAltEnd:       "\x1b[1;9F",
+		KeyCtrlShfHome:  "\x1b[1;6H",
+		KeyCtrlShfEnd:   "\x1b[1;6F",
+		KeyMetaShfHome:  "\x1b[1;10H",
+		KeyMetaShfEnd:   "\x1b[1;10F",
+		KeyAltShfHome:   "\x1b[1;4H",
+		KeyAltShfEnd:    "\x1b[1;4F",
+	})
+}
diff --git a/vendor/maunium.net/go/tcell/tscreen.go b/vendor/maunium.net/go/tcell/tscreen.go
index d8e62b2..dd49814 100644
--- a/vendor/maunium.net/go/tcell/tscreen.go
+++ b/vendor/maunium.net/go/tcell/tscreen.go
@@ -444,8 +444,8 @@ func (t *tScreen) ResetTitle() {
 func (t *tScreen) Fini() {
 	t.Lock()
 	defer t.Unlock()
-	
-	ti := t.ti	
+
+	ti := t.ti
 	t.cells.Resize(0, 0)
 	t.TPuts(ti.ShowCursor)
 	t.TPuts(ti.AttrOff)
@@ -467,7 +467,7 @@ func (t *tScreen) Fini() {
 	default:
 		close(t.quit)
 	}
-	
+
 	t.termioFini()
 }
 
diff --git a/vendor/maunium.net/go/tview/LICENSE.txt b/vendor/maunium.net/go/tview/LICENSE.txt
index 8aa2645..9d69430 100644
--- a/vendor/maunium.net/go/tview/LICENSE.txt
+++ b/vendor/maunium.net/go/tview/LICENSE.txt
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) [year] [fullname]
+Copyright (c) 2018 Oliver Kuederle
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/maunium.net/go/tview/README.md b/vendor/maunium.net/go/tview/README.md
index 3e5734e..7ce06a2 100644
--- a/vendor/maunium.net/go/tview/README.md
+++ b/vendor/maunium.net/go/tview/README.md
@@ -12,6 +12,7 @@ Among these components are:
 - __Input forms__ (include __input/password fields__, __drop-down selections__, __checkboxes__, and __buttons__)
 - Navigable multi-color __text views__
 - Sophisticated navigable __table views__
+- Flexible __tree views__
 - Selectable __lists__
 - __Grid__, __Flexbox__ and __page layouts__
 - Modal __message windows__
@@ -64,9 +65,17 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
 
 (There are no corresponding tags in the project. I only keep such a history in this README.)
 
+- v0.19 (2018-10-28)
+  - Added `QueueUpdate()` and `QueueEvent()` to `Application` to help with modifications to primitives from goroutines.
+- v0.18 (2018-10-18)
+  - `InputField` elements can now be navigated freely.
+- v0.17 (2018-06-20)
+  - Added `TreeView`.
+- v0.15 (2018-05-02)
+  - `Flex` and `Grid` don't clear their background per default, thus allowing for custom modals. See the [Wiki](https://github.com/rivo/tview/wiki/Modal) for an example.
 - v0.14 (2018-04-13)
   - Added an `Escape()` function which keep strings like color or region tags from being recognized as such.
-  - Added `ANSIIWriter()` and `TranslateANSII()` which convert ANSII escape sequences to `tview` color tags.
+  - Added `ANSIWriter()` and `TranslateANSI()` which convert ANSI escape sequences to `tview` color tags.
 - v0.13 (2018-04-01)
   - Added background colors and text attributes to color tags.
 - v0.12 (2018-03-13)
diff --git a/vendor/maunium.net/go/tview/ansi.go b/vendor/maunium.net/go/tview/ansi.go
new file mode 100644
index 0000000..4d14c28
--- /dev/null
+++ b/vendor/maunium.net/go/tview/ansi.go
@@ -0,0 +1,237 @@
+package tview
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+)
+
+// The states of the ANSI escape code parser.
+const (
+	ansiText = iota
+	ansiEscape
+	ansiSubstring
+	ansiControlSequence
+)
+
+// ansi is a io.Writer which translates ANSI escape codes into tview color
+// tags.
+type ansi struct {
+	io.Writer
+
+	// Reusable buffers.
+	buffer                        *bytes.Buffer // The entire output text of one Write().
+	csiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.
+
+	// The current state of the parser. One of the ansi constants.
+	state int
+}
+
+// ANSIWriter returns an io.Writer which translates any ANSI escape codes
+// written to it into tview color tags. Other escape codes don't have an effect
+// and are simply removed. The translated text is written to the provided
+// writer.
+func ANSIWriter(writer io.Writer) io.Writer {
+	return &ansi{
+		Writer:          writer,
+		buffer:          new(bytes.Buffer),
+		csiParameter:    new(bytes.Buffer),
+		csiIntermediate: new(bytes.Buffer),
+		state:           ansiText,
+	}
+}
+
+// Write parses the given text as a string of runes, translates ANSI escape
+// codes to color tags and writes them to the output writer.
+func (a *ansi) Write(text []byte) (int, error) {
+	defer func() {
+		a.buffer.Reset()
+	}()
+
+	for _, r := range string(text) {
+		switch a.state {
+
+		// We just entered an escape sequence.
+		case ansiEscape:
+			switch r {
+			case '[': // Control Sequence Introducer.
+				a.csiParameter.Reset()
+				a.csiIntermediate.Reset()
+				a.state = ansiControlSequence
+			case 'c': // Reset.
+				fmt.Fprint(a.buffer, "[-:-:-]")
+				a.state = ansiText
+			case 'P', ']', 'X', '^', '_': // Substrings and commands.
+				a.state = ansiSubstring
+			default: // Ignore.
+				a.state = ansiText
+			}
+
+		// CSI Sequences.
+		case ansiControlSequence:
+			switch {
+			case r >= 0x30 && r <= 0x3f: // Parameter bytes.
+				if _, err := a.csiParameter.WriteRune(r); err != nil {
+					return 0, err
+				}
+			case r >= 0x20 && r <= 0x2f: // Intermediate bytes.
+				if _, err := a.csiIntermediate.WriteRune(r); err != nil {
+					return 0, err
+				}
+			case r >= 0x40 && r <= 0x7e: // Final byte.
+				switch r {
+				case 'E': // Next line.
+					count, _ := strconv.Atoi(a.csiParameter.String())
+					if count == 0 {
+						count = 1
+					}
+					fmt.Fprint(a.buffer, strings.Repeat("\n", count))
+				case 'm': // Select Graphic Rendition.
+					var (
+						background, foreground, attributes string
+						clearAttributes                    bool
+					)
+					fields := strings.Split(a.csiParameter.String(), ";")
+					if len(fields) == 0 || len(fields) == 1 && fields[0] == "0" {
+						// Reset.
+						if _, err := a.buffer.WriteString("[-:-:-]"); err != nil {
+							return 0, err
+						}
+						break
+					}
+					lookupColor := func(colorNumber int, bright bool) string {
+						if colorNumber < 0 || colorNumber > 7 {
+							return "black"
+						}
+						if bright {
+							colorNumber += 8
+						}
+						return [...]string{
+							"black",
+							"red",
+							"green",
+							"yellow",
+							"blue",
+							"darkmagenta",
+							"darkcyan",
+							"white",
+							"#7f7f7f",
+							"#ff0000",
+							"#00ff00",
+							"#ffff00",
+							"#5c5cff",
+							"#ff00ff",
+							"#00ffff",
+							"#ffffff",
+						}[colorNumber]
+					}
+					for index, field := range fields {
+						switch field {
+						case "1", "01":
+							attributes += "b"
+						case "2", "02":
+							attributes += "d"
+						case "4", "04":
+							attributes += "u"
+						case "5", "05":
+							attributes += "l"
+						case "7", "07":
+							attributes += "7"
+						case "22", "24", "25", "27":
+							clearAttributes = true
+						case "30", "31", "32", "33", "34", "35", "36", "37":
+							colorNumber, _ := strconv.Atoi(field)
+							foreground = lookupColor(colorNumber-30, false)
+						case "40", "41", "42", "43", "44", "45", "46", "47":
+							colorNumber, _ := strconv.Atoi(field)
+							background = lookupColor(colorNumber-40, false)
+						case "90", "91", "92", "93", "94", "95", "96", "97":
+							colorNumber, _ := strconv.Atoi(field)
+							foreground = lookupColor(colorNumber-90, true)
+						case "100", "101", "102", "103", "104", "105", "106", "107":
+							colorNumber, _ := strconv.Atoi(field)
+							background = lookupColor(colorNumber-100, true)
+						case "38", "48":
+							var color string
+							if len(fields) > index+1 {
+								if fields[index+1] == "5" && len(fields) > index+2 { // 8-bit colors.
+									colorNumber, _ := strconv.Atoi(fields[index+2])
+									if colorNumber <= 7 {
+										color = lookupColor(colorNumber, false)
+									} else if colorNumber <= 15 {
+										color = lookupColor(colorNumber, true)
+									} else if colorNumber <= 231 {
+										red := (colorNumber - 16) / 36
+										green := ((colorNumber - 16) / 6) % 6
+										blue := (colorNumber - 16) % 6
+										color = fmt.Sprintf("#%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
+									} else if colorNumber <= 255 {
+										grey := 255 * (colorNumber - 232) / 23
+										color = fmt.Sprintf("#%02x%02x%02x", grey, grey, grey)
+									}
+								} else if fields[index+1] == "2" && len(fields) > index+4 { // 24-bit colors.
+									red, _ := strconv.Atoi(fields[index+2])
+									green, _ := strconv.Atoi(fields[index+3])
+									blue, _ := strconv.Atoi(fields[index+4])
+									color = fmt.Sprintf("#%02x%02x%02x", red, green, blue)
+								}
+							}
+							if len(color) > 0 {
+								if field == "38" {
+									foreground = color
+								} else {
+									background = color
+								}
+							}
+						}
+					}
+					if len(attributes) > 0 || clearAttributes {
+						attributes = ":" + attributes
+					}
+					if len(foreground) > 0 || len(background) > 0 || len(attributes) > 0 {
+						fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
+					}
+				}
+				a.state = ansiText
+			default: // Undefined byte.
+				a.state = ansiText // Abort CSI.
+			}
+
+			// We just entered a substring/command sequence.
+		case ansiSubstring:
+			if r == 27 { // Most likely the end of the substring.
+				a.state = ansiEscape
+			} // Ignore all other characters.
+
+			// "ansiText" and all others.
+		default:
+			if r == 27 {
+				// This is the start of an escape sequence.
+				a.state = ansiEscape
+			} else {
+				// Just a regular rune. Send to buffer.
+				if _, err := a.buffer.WriteRune(r); err != nil {
+					return 0, err
+				}
+			}
+		}
+	}
+
+	// Write buffer to target writer.
+	n, err := a.buffer.WriteTo(a.Writer)
+	if err != nil {
+		return int(n), err
+	}
+	return len(text), nil
+}
+
+// TranslateANSI replaces ANSI escape sequences found in the provided string
+// with tview's color tags and returns the resulting string.
+func TranslateANSI(text string) string {
+	var buffer bytes.Buffer
+	writer := ANSIWriter(&buffer)
+	writer.Write([]byte(text))
+	return buffer.String()
+}
diff --git a/vendor/maunium.net/go/tview/ansii.go b/vendor/maunium.net/go/tview/ansii.go
deleted file mode 100644
index 0ce3d4a..0000000
--- a/vendor/maunium.net/go/tview/ansii.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package tview
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"strconv"
-	"strings"
-)
-
-// The states of the ANSII escape code parser.
-const (
-	ansiiText = iota
-	ansiiEscape
-	ansiiSubstring
-	ansiiControlSequence
-)
-
-// ansii is a io.Writer which translates ANSII escape codes into tview color
-// tags.
-type ansii struct {
-	io.Writer
-
-	// Reusable buffers.
-	buffer                        *bytes.Buffer // The entire output text of one Write().
-	csiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.
-
-	// The current state of the parser. One of the ansii constants.
-	state int
-}
-
-// ANSIIWriter returns an io.Writer which translates any ANSII escape codes
-// written to it into tview color tags. Other escape codes don't have an effect
-// and are simply removed. The translated text is written to the provided
-// writer.
-func ANSIIWriter(writer io.Writer) io.Writer {
-	return &ansii{
-		Writer:          writer,
-		buffer:          new(bytes.Buffer),
-		csiParameter:    new(bytes.Buffer),
-		csiIntermediate: new(bytes.Buffer),
-		state:           ansiiText,
-	}
-}
-
-// Write parses the given text as a string of runes, translates ANSII escape
-// codes to color tags and writes them to the output writer.
-func (a *ansii) Write(text []byte) (int, error) {
-	defer func() {
-		a.buffer.Reset()
-	}()
-
-	for _, r := range string(text) {
-		switch a.state {
-
-		// We just entered an escape sequence.
-		case ansiiEscape:
-			switch r {
-			case '[': // Control Sequence Introducer.
-				a.csiParameter.Reset()
-				a.csiIntermediate.Reset()
-				a.state = ansiiControlSequence
-			case 'c': // Reset.
-				fmt.Fprint(a.buffer, "[-:-:-]")
-				a.state = ansiiText
-			case 'P', ']', 'X', '^', '_': // Substrings and commands.
-				a.state = ansiiSubstring
-			default: // Ignore.
-				a.state = ansiiText
-			}
-
-		// CSI Sequences.
-		case ansiiControlSequence:
-			switch {
-			case r >= 0x30 && r <= 0x3f: // Parameter bytes.
-				if _, err := a.csiParameter.WriteRune(r); err != nil {
-					return 0, err
-				}
-			case r >= 0x20 && r <= 0x2f: // Intermediate bytes.
-				if _, err := a.csiIntermediate.WriteRune(r); err != nil {
-					return 0, err
-				}
-			case r >= 0x40 && r <= 0x7e: // Final byte.
-				switch r {
-				case 'E': // Next line.
-					count, _ := strconv.Atoi(a.csiParameter.String())
-					if count == 0 {
-						count = 1
-					}
-					fmt.Fprint(a.buffer, strings.Repeat("\n", count))
-				case 'm': // Select Graphic Rendition.
-					var (
-						background, foreground, attributes string
-						clearAttributes                    bool
-					)
-					fields := strings.Split(a.csiParameter.String(), ";")
-					if len(fields) == 0 || len(fields) == 1 && fields[0] == "0" {
-						// Reset.
-						if _, err := a.buffer.WriteString("[-:-:-]"); err != nil {
-							return 0, err
-						}
-						break
-					}
-					lookupColor := func(colorNumber int, bright bool) string {
-						if colorNumber < 0 || colorNumber > 7 {
-							return "black"
-						}
-						if bright {
-							colorNumber += 8
-						}
-						return [...]string{
-							"black",
-							"red",
-							"green",
-							"yellow",
-							"blue",
-							"darkmagenta",
-							"darkcyan",
-							"white",
-							"#7f7f7f",
-							"#ff0000",
-							"#00ff00",
-							"#ffff00",
-							"#5c5cff",
-							"#ff00ff",
-							"#00ffff",
-							"#ffffff",
-						}[colorNumber]
-					}
-					for index, field := range fields {
-						switch field {
-						case "1", "01":
-							attributes += "b"
-						case "2", "02":
-							attributes += "d"
-						case "4", "04":
-							attributes += "u"
-						case "5", "05":
-							attributes += "l"
-						case "7", "07":
-							attributes += "7"
-						case "22", "24", "25", "27":
-							clearAttributes = true
-						case "30", "31", "32", "33", "34", "35", "36", "37":
-							colorNumber, _ := strconv.Atoi(field)
-							foreground = lookupColor(colorNumber-30, false)
-						case "40", "41", "42", "43", "44", "45", "46", "47":
-							colorNumber, _ := strconv.Atoi(field)
-							background = lookupColor(colorNumber-40, false)
-						case "90", "91", "92", "93", "94", "95", "96", "97":
-							colorNumber, _ := strconv.Atoi(field)
-							foreground = lookupColor(colorNumber-90, true)
-						case "100", "101", "102", "103", "104", "105", "106", "107":
-							colorNumber, _ := strconv.Atoi(field)
-							background = lookupColor(colorNumber-100, true)
-						case "38", "48":
-							var color string
-							if len(fields) > index+1 {
-								if fields[index+1] == "5" && len(fields) > index+2 { // 8-bit colors.
-									colorNumber, _ := strconv.Atoi(fields[index+2])
-									if colorNumber <= 7 {
-										color = lookupColor(colorNumber, false)
-									} else if colorNumber <= 15 {
-										color = lookupColor(colorNumber, true)
-									} else if colorNumber <= 231 {
-										red := (colorNumber - 16) / 36
-										green := ((colorNumber - 16) / 6) % 6
-										blue := (colorNumber - 16) % 6
-										color = fmt.Sprintf("%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
-									} else if colorNumber <= 255 {
-										grey := 255 * (colorNumber - 232) / 23
-										color = fmt.Sprintf("%02x%02x%02x", grey, grey, grey)
-									}
-								} else if fields[index+1] == "2" && len(fields) > index+4 { // 24-bit colors.
-									red, _ := strconv.Atoi(fields[index+2])
-									green, _ := strconv.Atoi(fields[index+3])
-									blue, _ := strconv.Atoi(fields[index+4])
-									color = fmt.Sprintf("%02x%02x%02x", red, green, blue)
-								}
-							}
-							if len(color) > 0 {
-								if field == "38" {
-									foreground = color
-								} else {
-									background = color
-								}
-							}
-						}
-					}
-					if len(attributes) > 0 || clearAttributes {
-						attributes = ":" + attributes
-					}
-					if len(foreground) > 0 || len(background) > 0 || len(attributes) > 0 {
-						fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
-					}
-				}
-				a.state = ansiiText
-			default: // Undefined byte.
-				a.state = ansiiText // Abort CSI.
-			}
-
-			// We just entered a substring/command sequence.
-		case ansiiSubstring:
-			if r == 27 { // Most likely the end of the substring.
-				a.state = ansiiEscape
-			} // Ignore all other characters.
-
-			// "ansiiText" and all others.
-		default:
-			if r == 27 {
-				// This is the start of an escape sequence.
-				a.state = ansiiEscape
-			} else {
-				// Just a regular rune. Send to buffer.
-				if _, err := a.buffer.WriteRune(r); err != nil {
-					return 0, err
-				}
-			}
-		}
-	}
-
-	// Write buffer to target writer.
-	n, err := a.buffer.WriteTo(a.Writer)
-	if err != nil {
-		return int(n), err
-	}
-	return len(text), nil
-}
-
-// TranslateANSII replaces ANSII escape sequences found in the provided string
-// with tview's color tags and returns the resulting string.
-func TranslateANSII(text string) string {
-	var buffer bytes.Buffer
-	writer := ANSIIWriter(&buffer)
-	writer.Write([]byte(text))
-	return buffer.String()
-}
diff --git a/vendor/maunium.net/go/tview/application.go b/vendor/maunium.net/go/tview/application.go
index 7e6da3a..ae4d7f8 100644
--- a/vendor/maunium.net/go/tview/application.go
+++ b/vendor/maunium.net/go/tview/application.go
@@ -1,24 +1,36 @@
 package tview
 
 import (
-	"fmt"
-	"os"
 	"sync"
 
 	"maunium.net/go/tcell"
 )
 
+// The size of the event/update/redraw channels.
+const queueSize = 100
+
 // Application represents the top node of an application.
 //
 // It is not strictly required to use this class as none of the other classes
 // depend on it. However, it provides useful tools to set up an application and
 // plays nicely with all widgets.
+//
+// The following command displays a primitive p on the screen until Ctrl-C is
+// pressed:
+//
+//   if err := tview.NewApplication().SetRoot(p, true).Run(); err != nil {
+//       panic(err)
+//   }
 type Application struct {
 	sync.RWMutex
 
 	// The application's screen.
 	screen tcell.Screen
 
+	// Indicates whether the application's screen is currently active. This is
+	// false during suspended mode.
+	running bool
+
 	// The primitive which currently has the keyboard focus.
 	focus Primitive
 
@@ -48,13 +60,23 @@ type Application struct {
 	// was drawn.
 	afterDraw func(screen tcell.Screen)
 
-	// If this value is true, the application has entered suspended mode.
-	suspended bool
+	// Used to send screen events from separate goroutine to main event loop
+	events chan tcell.Event
+
+	// Functions queued from goroutines, used to serialize updates to primitives.
+	updates chan func()
+
+	// A channel which signals the end of the suspended mode.
+	suspendToken chan struct{}
 }
 
 // NewApplication creates and returns a new application.
 func NewApplication() *Application {
-	return &Application{}
+	return &Application{
+		events:       make(chan tcell.Event, queueSize),
+		updates:      make(chan func(), queueSize),
+		suspendToken: make(chan struct{}, 1),
+	}
 }
 
 // SetInputCapture sets a function which captures all key events before they are
@@ -97,140 +119,222 @@ func (a *Application) GetScreen() tcell.Screen {
 	return a.screen
 }
 
+// SetScreen allows you to provide your own tcell.Screen object. For most
+// applications, this is not needed and you should be familiar with
+// tcell.Screen when using this function. Run() will call Init() and Fini() on
+// the provided screen object.
+//
+// This function is typically called before calling Run(). Calling it while an
+// application is running will switch the application to the new screen. Fini()
+// will be called on the old screen and Init() on the new screen (errors
+// returned by Init() will lead to a panic).
+//
+// Note that calling Suspend() will invoke Fini() on your screen object and it
+// will not be restored when suspended mode ends. Instead, a new default screen
+// object will be created.
+func (a *Application) SetScreen(screen tcell.Screen) *Application {
+	a.Lock()
+	defer a.Unlock()
+	if a.running {
+		a.screen.Fini()
+	}
+	a.screen = screen
+	if a.running {
+		if err := a.screen.Init(); err != nil {
+			panic(err)
+		}
+	}
+	return a
+}
+
 // Run starts the application and thus the event loop. This function returns
 // when Stop() was called.
 func (a *Application) Run() error {
 	var err error
 	a.Lock()
 
-	// Make a screen.
-	a.screen, err = tcell.NewScreen()
-	if err != nil {
-		a.Unlock()
-		return err
+	// Make a screen if there is none yet.
+	if a.screen == nil {
+		a.screen, err = tcell.NewScreen()
+		if err != nil {
+			a.Unlock()
+			return err
+		}
 	}
 	if err = a.screen.Init(); err != nil {
 		a.Unlock()
 		return err
 	}
 	a.screen.EnableMouse()
+	a.running = true
+
+	// We catch panics to clean up because they mess up the terminal.
+	defer func() {
+		if p := recover(); p != nil {
+			if a.screen != nil {
+				a.screen.Fini()
+			}
+			a.running = false
+			panic(p)
+		}
+	}()
 
 	// Draw the screen for the first time.
 	a.Unlock()
-	a.Draw()
+	a.draw()
+
+	// Separate loop to wait for screen events.
+	var wg sync.WaitGroup
+	wg.Add(1)
+	a.suspendToken <- struct{}{} // We need this to get started.
+	go func() {
+		defer wg.Done()
+		for range a.suspendToken {
+			for {
+				a.RLock()
+				screen := a.screen
+				a.RUnlock()
+				if screen == nil {
+					// We have no screen. We might need to stop.
+					break
+				}
 
-	// Start event loop.
-	for {
-		a.Lock()
-		screen := a.screen
-		if a.suspended {
-			a.suspended = false // Clear previous suspended flag.
-		}
-		a.Unlock()
-		if screen == nil {
-			break
+				// Wait for next event and queue it.
+				event := screen.PollEvent()
+				if event != nil {
+					// Regular event. Queue.
+					a.QueueEvent(event)
+					continue
+				}
+
+				// A screen was finalized (event is nil).
+				a.RLock()
+				running := a.running
+				a.RUnlock()
+				if running {
+					// The application was stopped. End the event loop.
+					a.QueueEvent(nil)
+					return
+				}
+
+				// We're in suspended mode (running is false). Pause and wait for new
+				// token.
+				break
+			}
 		}
+	}()
 
-		// Wait for next event.
-		event := a.screen.PollEvent()
-		if event == nil {
-			a.Lock()
-			if a.suspended {
-				// This screen was renewed due to suspended mode.
-				a.suspended = false
-				a.Unlock()
-				continue // Resume.
+	// Start event loop.
+EventLoop:
+	for {
+		select {
+		case event := <-a.events:
+			if event == nil {
+				break EventLoop
 			}
-			a.Unlock()
 
-			// The screen was finalized. Exit the loop.
-			break
-		}
+			switch event := event.(type) {
+			case *tcell.EventKey:
+				a.RLock()
+				p := a.focus
+				inputCapture := a.inputCapture
+				a.RUnlock()
+
+				// Intercept keys.
+				if inputCapture != nil {
+					event = inputCapture(event)
+					if event == nil {
+						continue // Don't forward event.
+					}
+				}
 
-		switch event := event.(type) {
-		case *tcell.EventKey:
-			a.RLock()
-			p := a.focus
-			a.RUnlock()
-
-			// Intercept keys.
-			if a.inputCapture != nil {
-				event = a.inputCapture(event)
-				if event == nil {
-					break // Don't forward event.
+				// Ctrl-C closes the application.
+				if event.Key() == tcell.KeyCtrlC {
+					a.Stop()
 				}
-			}
 
-			// Pass other key events to the currently focused primitive.
-			if p != nil {
-				if handler := p.InputHandler(); handler != nil {
-					handler(event, func(p Primitive) {
-						a.SetFocus(p)
-					})
-					a.Draw()
+				// Pass other key events to the currently focused primitive.
+				if p != nil {
+					if handler := p.InputHandler(); handler != nil {
+						handler(event, func(p Primitive) {
+							a.SetFocus(p)
+						})
+						a.draw()
+					}
 				}
-			}
-		case *tcell.EventMouse:
-			a.RLock()
-			p := a.focus
-			a.RUnlock()
-
-			// Intercept keys.
-			if a.mouseCapture != nil {
-				event = a.mouseCapture(event)
-				if event == nil {
-					break // Don't forward event.
+
+			case *tcell.EventMouse:
+				a.RLock()
+				p := a.focus
+				a.RUnlock()
+
+				// Intercept keys.
+				if a.mouseCapture != nil {
+					event = a.mouseCapture(event)
+					if event == nil {
+						break // Don't forward event.
+					}
 				}
-			}
 
-			// Pass other key events to the currently focused primitive.
-			if p != nil {
-				if handler := p.MouseHandler(); handler != nil {
-					handler(event, func(p Primitive) {
-						a.SetFocus(p)
-					})
-					//a.Draw()
+				// Pass other key events to the currently focused primitive.
+				if p != nil {
+					if handler := p.MouseHandler(); handler != nil {
+						handler(event, func(p Primitive) {
+							a.SetFocus(p)
+						})
+						//a.Draw()
+					}
 				}
-			}
-		case *tcell.EventPaste:
-			a.RLock()
-			p := a.focus
-			a.RUnlock()
-
-			if a.pasteCapture != nil {
-				event = a.pasteCapture(event)
-				if event == nil {
-					break
+			case *tcell.EventPaste:
+				a.RLock()
+				p := a.focus
+				a.RUnlock()
+
+				if a.pasteCapture != nil {
+					event = a.pasteCapture(event)
+					if event == nil {
+						break
+					}
 				}
-			}
 
-			if p != nil {
-				if handler := p.PasteHandler(); handler != nil {
-					handler(event)
-					a.Draw()
+				if p != nil {
+					if handler := p.PasteHandler(); handler != nil {
+						handler(event)
+						a.Draw()
+					}
 				}
+			case *tcell.EventResize:
+				a.RLock()
+				screen := a.screen
+				a.RUnlock()
+				screen.Clear()
+				a.draw()
 			}
-		case *tcell.EventResize:
-			a.Lock()
-			screen := a.screen
-			a.Unlock()
-			screen.Clear()
-			a.Draw()
+
+		// If we have updates, now is the time to execute them.
+		case updater := <-a.updates:
+			updater()
 		}
 	}
 
+	a.running = false
+	close(a.suspendToken)
+	wg.Wait()
+
 	return nil
 }
 
 // Stop stops the application, causing Run() to return.
 func (a *Application) Stop() {
-	a.RLock()
-	defer a.RUnlock()
-	if a.screen == nil {
+	a.Lock()
+	defer a.Unlock()
+	screen := a.screen
+	if screen == nil {
 		return
 	}
-	a.screen.Fini()
 	a.screen = nil
+	screen.Fini()
+	// a.running is still true, the main loop will clean up.
 }
 
 // Suspend temporarily suspends the application by exiting terminal UI mode and
@@ -243,29 +347,24 @@ func (a *Application) Stop() {
 func (a *Application) Suspend(f func()) bool {
 	a.Lock()
 
-	if a.suspended || a.screen == nil {
-		// Application is already suspended.
+	screen := a.screen
+	if screen == nil {
+		// Screen has not yet been initialized.
 		a.Unlock()
 		return false
 	}
 
-	// Enter suspended mode.
-	a.suspended = true
+	// Enter suspended mode. Make a new screen here already so our event loop can
+	// continue.
+	a.screen = nil
+	a.running = false
+	screen.Fini()
 	a.Unlock()
-	a.Stop()
-
-	// Deal with panics during suspended mode. Exit the program.
-	defer func() {
-		if p := recover(); p != nil {
-			fmt.Println(p)
-			os.Exit(1)
-		}
-	}()
 
 	// Wait for "f" to return.
 	f()
 
-	// Make a new screen and redraw.
+	// Initialize our new screen and draw the contents.
 	a.Lock()
 	var err error
 	a.screen, err = tcell.NewScreen()
@@ -278,23 +377,36 @@ func (a *Application) Suspend(f func()) bool {
 		panic(err)
 	}
 	a.screen.EnableMouse()
+	a.running = true
 	a.Unlock()
-	a.Draw()
+	a.draw()
+	a.suspendToken <- struct{}{}
+	// One key event will get lost, see https://github.com/gdamore/tcell/issues/194
 
 	// Continue application loop.
 	return true
 }
 
-// Draw refreshes the screen. It calls the Draw() function of the application's
-// root primitive and then syncs the screen buffer.
+// Draw refreshes the screen (during the next update cycle). It calls the Draw()
+// function of the application's root primitive and then syncs the screen
+// buffer.
 func (a *Application) Draw() *Application {
-	a.RLock()
+	a.QueueUpdate(func() {
+		a.draw()
+	})
+	return a
+}
+
+// draw actually does what Draw() promises to do.
+func (a *Application) draw() *Application {
+	a.Lock()
+	defer a.Unlock()
+
 	screen := a.screen
 	root := a.root
 	fullscreen := a.rootFullscreen
 	before := a.beforeDraw
 	after := a.afterDraw
-	a.RUnlock()
 
 	// Maybe we're not ready yet or not anymore.
 	if screen == nil || root == nil {
@@ -427,3 +539,35 @@ func (a *Application) GetFocus() Primitive {
 	defer a.RUnlock()
 	return a.focus
 }
+
+// QueueUpdate is used to synchronize access to primitives from non-main
+// goroutines. The provided function will be executed as part of the event loop
+// and thus will not cause race conditions with other such update functions or
+// the Draw() function.
+//
+// Note that Draw() is not implicitly called after the execution of f as that
+// may not be desirable. You can call Draw() from f if the screen should be
+// refreshed after each update. Alternatively, use QueueUpdateDraw() to follow
+// up with an immediate refresh of the screen.
+func (a *Application) QueueUpdate(f func()) *Application {
+	a.updates <- f
+	return a
+}
+
+// QueueUpdateDraw works like QueueUpdate() except it refreshes the screen
+// immediately after executing f.
+func (a *Application) QueueUpdateDraw(f func()) *Application {
+	a.QueueUpdate(func() {
+		f()
+		a.draw()
+	})
+	return a
+}
+
+// QueueEvent sends an event to the Application event loop.
+//
+// It is not recommended for event to be nil.
+func (a *Application) QueueEvent(event tcell.Event) *Application {
+	a.events <- event
+	return a
+}
diff --git a/vendor/maunium.net/go/tview/borders.go b/vendor/maunium.net/go/tview/borders.go
new file mode 100644
index 0000000..946c878
--- /dev/null
+++ b/vendor/maunium.net/go/tview/borders.go
@@ -0,0 +1,45 @@
+package tview
+
+// Borders defines various borders used when primitives are drawn.
+// These may be changed to accommodate a different look and feel.
+var Borders = struct {
+	Horizontal  rune
+	Vertical    rune
+	TopLeft     rune
+	TopRight    rune
+	BottomLeft  rune
+	BottomRight rune
+
+	LeftT   rune
+	RightT  rune
+	TopT    rune
+	BottomT rune
+	Cross   rune
+
+	HorizontalFocus  rune
+	VerticalFocus    rune
+	TopLeftFocus     rune
+	TopRightFocus    rune
+	BottomLeftFocus  rune
+	BottomRightFocus rune
+}{
+	Horizontal:  BoxDrawingsLightHorizontal,
+	Vertical:    BoxDrawingsLightVertical,
+	TopLeft:     BoxDrawingsLightDownAndRight,
+	TopRight:    BoxDrawingsLightDownAndLeft,
+	BottomLeft:  BoxDrawingsLightUpAndRight,
+	BottomRight: BoxDrawingsLightUpAndLeft,
+
+	LeftT:   BoxDrawingsLightVerticalAndRight,
+	RightT:  BoxDrawingsLightVerticalAndLeft,
+	TopT:    BoxDrawingsLightDownAndHorizontal,
+	BottomT: BoxDrawingsLightUpAndHorizontal,
+	Cross:   BoxDrawingsLightVerticalAndHorizontal,
+
+	HorizontalFocus:  BoxDrawingsDoubleHorizontal,
+	VerticalFocus:    BoxDrawingsDoubleVertical,
+	TopLeftFocus:     BoxDrawingsDoubleDownAndRight,
+	TopRightFocus:    BoxDrawingsDoubleDownAndLeft,
+	BottomLeftFocus:  BoxDrawingsDoubleUpAndRight,
+	BottomRightFocus: BoxDrawingsDoubleUpAndLeft,
+}
diff --git a/vendor/maunium.net/go/tview/box.go b/vendor/maunium.net/go/tview/box.go
index ff3cc1e..85bf1b2 100644
--- a/vendor/maunium.net/go/tview/box.go
+++ b/vendor/maunium.net/go/tview/box.go
@@ -32,6 +32,9 @@ type Box struct {
 	// The color of the border.
 	borderColor tcell.Color
 
+	// The style attributes of the border.
+	borderAttributes tcell.AttrMask
+
 	// The title. Only visible if there is a border, too.
 	title string
 
@@ -48,10 +51,6 @@ type Box struct {
 	// Whether or not this box has focus.
 	hasFocus bool
 
-	// If set to true, the inner rect of this box will be within the screen at the
-	// last time the box was drawn.
-	clampToScreen bool
-
 	// An optional capture function which receives a key event and returns the
 	// event to be forwarded to the primitive's default input handler (nil if
 	// nothing should be forwarded).
@@ -78,7 +77,6 @@ func NewBox() *Box {
 		borderColor:     Styles.BorderColor,
 		titleColor:      Styles.TitleColor,
 		titleAlign:      AlignCenter,
-		clampToScreen:   true,
 	}
 	b.focus = b
 	return b
@@ -121,6 +119,7 @@ func (b *Box) SetRect(x, y, width, height int) {
 	b.y = y
 	b.width = width
 	b.height = height
+	b.innerX = -1 // Mark inner rect as uninitialized.
 }
 
 // SetDrawFunc sets a callback function which is invoked after the box primitive
@@ -273,6 +272,15 @@ func (b *Box) SetBorderColor(color tcell.Color) *Box {
 	return b
 }
 
+// SetBorderAttributes sets the border's style attributes. You can combine
+// different attributes using bitmask operations:
+//
+//   box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
+func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
+	b.borderAttributes = attr
+	return b
+}
+
 // SetTitle sets the box's title.
 func (b *Box) SetTitle(title string) *Box {
 	b.title = title
@@ -319,30 +327,30 @@ func (b *Box) Draw(screen tcell.Screen) {
 
 	// Draw border.
 	if b.border && b.width >= 2 && b.height >= 2 {
-		border := background.Foreground(b.borderColor)
+		border := background.Foreground(b.borderColor) | tcell.Style(b.borderAttributes)
 		var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
 		if b.focus.HasFocus() {
-			vertical = GraphicsDbVertBar
-			horizontal = GraphicsDbHorBar
-			topLeft = GraphicsDbTopLeftCorner
-			topRight = GraphicsDbTopRightCorner
-			bottomLeft = GraphicsDbBottomLeftCorner
-			bottomRight = GraphicsDbBottomRightCorner
+			horizontal = Borders.HorizontalFocus
+			vertical = Borders.VerticalFocus
+			topLeft = Borders.TopLeftFocus
+			topRight = Borders.TopRightFocus
+			bottomLeft = Borders.BottomLeftFocus
+			bottomRight = Borders.BottomRightFocus
 		} else {
-			vertical = GraphicsHoriBar
-			horizontal = GraphicsVertBar
-			topLeft = GraphicsTopLeftCorner
-			topRight = GraphicsTopRightCorner
-			bottomLeft = GraphicsBottomLeftCorner
-			bottomRight = GraphicsBottomRightCorner
+			horizontal = Borders.Horizontal
+			vertical = Borders.Vertical
+			topLeft = Borders.TopLeft
+			topRight = Borders.TopRight
+			bottomLeft = Borders.BottomLeft
+			bottomRight = Borders.BottomRight
 		}
 		for x := b.x + 1; x < b.x+b.width-1; x++ {
-			screen.SetContent(x, b.y, vertical, nil, border)
-			screen.SetContent(x, b.y+b.height-1, vertical, nil, border)
+			screen.SetContent(x, b.y, horizontal, nil, border)
+			screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
 		}
 		for y := b.y + 1; y < b.y+b.height-1; y++ {
-			screen.SetContent(b.x, y, horizontal, nil, border)
-			screen.SetContent(b.x+b.width-1, y, horizontal, nil, border)
+			screen.SetContent(b.x, y, vertical, nil, border)
+			screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
 		}
 		screen.SetContent(b.x, b.y, topLeft, nil, border)
 		screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
@@ -351,11 +359,11 @@ func (b *Box) Draw(screen tcell.Screen) {
 
 		// Draw title.
 		if b.title != "" && b.width >= 4 {
-			_, printed := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
-			if StringWidth(b.title)-printed > 0 && printed > 0 {
+			printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
+			if len(b.title)-printed > 0 && printed > 0 {
 				_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
 				fg, _, _ := style.Decompose()
-				Print(screen, string(GraphicsEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
+				Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
 			}
 		}
 	}
@@ -370,22 +378,20 @@ func (b *Box) Draw(screen tcell.Screen) {
 	}
 
 	// Clamp inner rect to screen.
-	if b.clampToScreen {
-		width, height := screen.Size()
-		if b.innerX < 0 {
-			b.innerWidth += b.innerX
-			b.innerX = 0
-		}
-		if b.innerX+b.innerWidth >= width {
-			b.innerWidth = width - b.innerX
-		}
-		if b.innerY+b.innerHeight >= height {
-			b.innerHeight = height - b.innerY
-		}
-		if b.innerY < 0 {
-			b.innerHeight += b.innerY
-			b.innerY = 0
-		}
+	width, height := screen.Size()
+	if b.innerX < 0 {
+		b.innerWidth += b.innerX
+		b.innerX = 0
+	}
+	if b.innerX+b.innerWidth >= width {
+		b.innerWidth = width - b.innerX
+	}
+	if b.innerY+b.innerHeight >= height {
+		b.innerHeight = height - b.innerY
+	}
+	if b.innerY < 0 {
+		b.innerHeight += b.innerY
+		b.innerY = 0
 	}
 }
 
diff --git a/vendor/maunium.net/go/tview/checkbox.go b/vendor/maunium.net/go/tview/checkbox.go
index ae58720..48d4592 100644
--- a/vendor/maunium.net/go/tview/checkbox.go
+++ b/vendor/maunium.net/go/tview/checkbox.go
@@ -124,9 +124,9 @@ func (c *Checkbox) SetChangedFunc(handler func(checked bool)) *Checkbox {
 	return c
 }
 
-// SetDoneFunc sets a handler which is called when the user is done entering
-// text. The callback function is provided with the key that was pressed, which
-// is one of the following:
+// SetDoneFunc sets a handler which is called when the user is done using the
+// checkbox. The callback function is provided with the key that was pressed,
+// which is one of the following:
 //
 //   - KeyEscape: Abort text input.
 //   - KeyTab: Move to the next field.
diff --git a/vendor/maunium.net/go/tview/doc.go b/vendor/maunium.net/go/tview/doc.go
index ccaaaf1..ddc410f 100644
--- a/vendor/maunium.net/go/tview/doc.go
+++ b/vendor/maunium.net/go/tview/doc.go
@@ -7,10 +7,12 @@ Widgets
 
 The package implements the following widgets:
 
-  - TextView: Scrollable windows that display multi-colored text. Text may also
+  - TextView: A scrollable window that display multi-colored text. Text may also
     be highlighted.
-  - Table: Scrollable display of tabular data. Table cells, rows, or columns may
-    also be highlighted.
+  - Table: A scrollable display of tabular data. Table cells, rows, or columns
+    may also be highlighted.
+  - TreeView: A scrollable display for hierarchical data. Tree nodes can be
+    highlighted, collapsed, expanded, and more.
   - List: A navigable text list with optional keyboard shortcuts.
   - InputField: One-line input fields to enter text.
   - DropDown: Drop-down selection fields.
@@ -83,7 +85,7 @@ tag is as follows:
 
   [<foreground>:<background>:<flags>]
 
-Each of the three fields can be left blank and trailing fields can be ommitted.
+Each of the three fields can be left blank and trailing fields can be omitted.
 (Empty square brackets "[]", however, are not considered color tags.) Colors
 that are not specified will be left unchanged. A field with just a dash ("-")
 means "reset to default".
@@ -135,6 +137,27 @@ Unicode Support
 
 This package supports unicode characters including wide characters.
 
+Concurrency
+
+Many functions in this package are not thread-safe. For many applications, this
+may not be an issue: If your code makes changes in response to key events, it
+will execute in the main goroutine and thus will not cause any race conditions.
+
+If you access your primitives from other goroutines, however, you will need to
+synchronize execution. The easiest way to do this is to call
+Application.QueueUpdate() or Application.QueueUpdateDraw() (see the function
+documentation for details):
+
+  go func() {
+    app.QueueUpdateDraw(func() {
+      table.SetCellSimple(0, 0, "Foo bar")
+    })
+  }()
+
+One exception to this is the io.Writer interface implemented by TextView. You
+can safely write to a TextView from any goroutine. See the TextView
+documentation for details.
+
 Type Hierarchy
 
 All widgets listed above contain the Box type. All of Box's functions are
diff --git a/vendor/maunium.net/go/tview/dropdown.go b/vendor/maunium.net/go/tview/dropdown.go
index 4e4d888..02c93bd 100644
--- a/vendor/maunium.net/go/tview/dropdown.go
+++ b/vendor/maunium.net/go/tview/dropdown.go
@@ -354,6 +354,7 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
 
 			// Hand control over to the list.
 			d.open = true
+			optionBefore := d.currentOption
 			d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
 				// An option was selected. Close the list again.
 				d.open = false
@@ -374,6 +375,10 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
 						d.prefix = string(r[:len(r)-1])
 					}
 					evalPrefix()
+				} else if event.Key() == tcell.KeyEscape {
+					d.open = false
+					d.currentOption = optionBefore
+					setFocus(d)
 				} else {
 					d.prefix = ""
 				}
diff --git a/vendor/maunium.net/go/tview/flex.go b/vendor/maunium.net/go/tview/flex.go
index ad42d3a..bc1cfe1 100644
--- a/vendor/maunium.net/go/tview/flex.go
+++ b/vendor/maunium.net/go/tview/flex.go
@@ -28,7 +28,7 @@ type Flex struct {
 	*Box
 
 	// The items to be positioned.
-	items []flexItem
+	items []*flexItem
 
 	// FlexRow or FlexColumn.
 	direction int
@@ -79,7 +79,7 @@ func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
 // You can provide a nil value for the primitive. This will still consume screen
 // space but nothing will be drawn.
 func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
-	f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
+	f.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
 	return f
 }
 
@@ -94,6 +94,19 @@ func (f *Flex) RemoveItem(p Primitive) *Flex {
 	return f
 }
 
+// ResizeItem sets a new size for the item(s) with the given primitive. If there
+// are multiple Flex items with the same primitive, they will all receive the
+// same size. For details regarding the size parameters, see AddItem().
+func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {
+	for _, item := range f.items {
+		if item.Item == p {
+			item.FixedSize = fixedSize
+			item.Proportion = proportion
+		}
+	}
+	return f
+}
+
 // Draw draws this primitive onto the screen.
 func (f *Flex) Draw(screen tcell.Screen) {
 	f.Box.Draw(screen)
diff --git a/vendor/maunium.net/go/tview/form.go b/vendor/maunium.net/go/tview/form.go
index fe0e980..e960a52 100644
--- a/vendor/maunium.net/go/tview/form.go
+++ b/vendor/maunium.net/go/tview/form.go
@@ -26,7 +26,7 @@ type FormItem interface {
 	// required.
 	GetFieldWidth() int
 
-	// SetEnteredFunc sets the handler function for when the user finished
+	// SetFinishedFunc sets the handler function for when the user finished
 	// entering data into the item. The handler may receive events for the
 	// Enter key (we're done), the Escape key (cancel input), the Tab key (move to
 	// next field), and the Backtab key (move to previous field).
@@ -218,6 +218,37 @@ func (f *Form) AddButton(label string, selected func()) *Form {
 	return f
 }
 
+// GetButton returns the button at the specified 0-based index. Note that
+// buttons have been specially prepared for this form and modifying some of
+// their attributes may have unintended side effects.
+func (f *Form) GetButton(index int) *Button {
+	return f.buttons[index]
+}
+
+// RemoveButton removes the button at the specified position, starting with 0
+// for the button that was added first.
+func (f *Form) RemoveButton(index int) *Form {
+	f.buttons = append(f.buttons[:index], f.buttons[index+1:]...)
+	return f
+}
+
+// GetButtonCount returns the number of buttons in this form.
+func (f *Form) GetButtonCount() int {
+	return len(f.buttons)
+}
+
+// GetButtonIndex returns the index of the button with the given label, starting
+// with 0 for the button that was added first. If no such label was found, -1
+// is returned.
+func (f *Form) GetButtonIndex(label string) int {
+	for index, button := range f.buttons {
+		if button.GetLabel() == label {
+			return index
+		}
+	}
+	return -1
+}
+
 // Clear removes all input elements from the form, including the buttons if
 // specified.
 func (f *Form) Clear(includeButtons bool) *Form {
@@ -251,6 +282,14 @@ func (f *Form) GetFormItem(index int) FormItem {
 	return f.items[index]
 }
 
+// RemoveFormItem removes the form element at the given position, starting with
+// index 0. Elements are referenced in the order they were added. Buttons are
+// not included.
+func (f *Form) RemoveFormItem(index int) *Form {
+	f.items = append(f.items[:index], f.items[index+1:]...)
+	return f
+}
+
 // GetFormItemByLabel returns the first form element with the given label. If
 // no such element is found, nil is returned. Buttons are not searched and will
 // therefore not be returned.
@@ -263,6 +302,18 @@ func (f *Form) GetFormItemByLabel(label string) FormItem {
 	return nil
 }
 
+// GetFormItemIndex returns the index of the first form element with the given
+// label. If no such element is found, -1 is returned. Buttons are not searched
+// and will therefore not be returned.
+func (f *Form) GetFormItemIndex(label string) int {
+	for index, item := range f.items {
+		if item.GetLabel() == label {
+			return index
+		}
+	}
+	return -1
+}
+
 // SetCancelFunc sets a handler which is called when the user hits the Escape
 // key.
 func (f *Form) SetCancelFunc(callback func()) *Form {
diff --git a/vendor/maunium.net/go/tview/grid.go b/vendor/maunium.net/go/tview/grid.go
index 77797ba..d8f6c97 100644
--- a/vendor/maunium.net/go/tview/grid.go
+++ b/vendor/maunium.net/go/tview/grid.go
@@ -583,11 +583,11 @@ func (g *Grid) Draw(screen tcell.Screen) {
 				}
 				by := item.y - 1
 				if by >= 0 && by < height {
-					PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
+					PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
 				}
 				by = item.y + item.h
 				if by >= 0 && by < height {
-					PrintJoinedBorder(screen, x+bx, y+by, GraphicsHoriBar, g.bordersColor)
+					PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Horizontal, g.bordersColor)
 				}
 			}
 			for by := item.y; by < item.y+item.h; by++ { // Left/right lines.
@@ -596,28 +596,28 @@ func (g *Grid) Draw(screen tcell.Screen) {
 				}
 				bx := item.x - 1
 				if bx >= 0 && bx < width {
-					PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
+					PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
 				}
 				bx = item.x + item.w
 				if bx >= 0 && bx < width {
-					PrintJoinedBorder(screen, x+bx, y+by, GraphicsVertBar, g.bordersColor)
+					PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.Vertical, g.bordersColor)
 				}
 			}
 			bx, by := item.x-1, item.y-1 // Top-left corner.
 			if bx >= 0 && bx < width && by >= 0 && by < height {
-				PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopLeftCorner, g.bordersColor)
+				PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopLeft, g.bordersColor)
 			}
 			bx, by = item.x+item.w, item.y-1 // Top-right corner.
 			if bx >= 0 && bx < width && by >= 0 && by < height {
-				PrintJoinedBorder(screen, x+bx, y+by, GraphicsTopRightCorner, g.bordersColor)
+				PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.TopRight, g.bordersColor)
 			}
 			bx, by = item.x-1, item.y+item.h // Bottom-left corner.
 			if bx >= 0 && bx < width && by >= 0 && by < height {
-				PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomLeftCorner, g.bordersColor)
+				PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomLeft, g.bordersColor)
 			}
 			bx, by = item.x+item.w, item.y+item.h // Bottom-right corner.
 			if bx >= 0 && bx < width && by >= 0 && by < height {
-				PrintJoinedBorder(screen, x+bx, y+by, GraphicsBottomRightCorner, g.bordersColor)
+				PrintJoinedSemigraphics(screen, x+bx, y+by, Borders.BottomRight, g.bordersColor)
 			}
 		}
 	}
diff --git a/vendor/maunium.net/go/tview/inputfield.go b/vendor/maunium.net/go/tview/inputfield.go
index 73224f2..ccc66e3 100644
--- a/vendor/maunium.net/go/tview/inputfield.go
+++ b/vendor/maunium.net/go/tview/inputfield.go
@@ -11,10 +11,23 @@ import (
 )
 
 // InputField is a one-line box (three lines if there is a title) where the
-// user can enter text.
+// user can enter text. Use SetAcceptanceFunc() to accept or reject input,
+// SetChangedFunc() to listen for changes, and SetMaskCharacter() to hide input
+// from onlookers (e.g. for password input).
 //
-// Use SetMaskCharacter() to hide input from onlookers (e.g. for password
-// input).
+// The following keys can be used for navigation and editing:
+//
+//   - Left arrow: Move left by one character.
+//   - Right arrow: Move right by one character.
+//   - Home, Ctrl-A, Alt-a: Move to the beginning of the line.
+//   - End, Ctrl-E, Alt-e: Move to the end of the line.
+//   - Alt-left, Alt-b: Move left by one word.
+//   - Alt-right, Alt-f: Move right by one word.
+//   - Backspace: Delete the character before the cursor.
+//   - Delete: Delete the character after the cursor.
+//   - Ctrl-K: Delete from the cursor to the end of the line.
+//   - Ctrl-W: Delete the last word before the cursor.
+//   - Ctrl-U: Delete the entire line.
 //
 // See https://github.com/rivo/tview/wiki/InputField for an example.
 type InputField struct {
@@ -53,6 +66,12 @@ type InputField struct {
 	// disables masking.
 	maskCharacter rune
 
+	// The cursor position as a byte index into the text string.
+	cursorPos int
+
+	// The number of bytes of the text string skipped ahead while drawing.
+	offset int
+
 	// An optional function which may reject the last character that was entered.
 	accept func(text string, ch rune) bool
 
@@ -83,6 +102,7 @@ func NewInputField() *InputField {
 // SetText sets the current text of the input field.
 func (i *InputField) SetText(text string) *InputField {
 	i.text = text
+	i.cursorPos = len(text)
 	if i.changed != nil {
 		i.changed(text)
 	}
@@ -174,7 +194,7 @@ func (i *InputField) SetMaskCharacter(mask rune) *InputField {
 // SetAcceptanceFunc sets a handler which may reject the last character that was
 // entered (by returning false).
 //
-// This package defines a number of variables Prefixed with InputField which may
+// This package defines a number of variables prefixed with InputField which may
 // be used for common input (e.g. numbers, maximum text length).
 func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
 	i.accept = handler
@@ -244,54 +264,67 @@ func (i *InputField) Draw(screen tcell.Screen) {
 		screen.SetContent(x+index, y, ' ', nil, fieldStyle)
 	}
 
-	// Draw placeholder text.
+	// Text.
+	var cursorScreenPos int
 	text := i.text
 	if text == "" && i.placeholder != "" {
-		Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
+		// Draw placeholder text.
+		Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
+		i.offset = 0
 	} else {
 		// Draw entered text.
 		if i.maskCharacter > 0 {
 			text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
-		} else {
-			text = Escape(text)
 		}
-		fieldWidth-- // We need one cell for the cursor.
-		if fieldWidth < runewidth.StringWidth(text) {
-			Print(screen, text, x, y, fieldWidth, AlignRight, i.fieldTextColor)
+		stringWidth := runewidth.StringWidth(text)
+		if fieldWidth >= stringWidth {
+			// We have enough space for the full text.
+			Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
+			i.offset = 0
+			iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+				if textPos >= i.cursorPos {
+					return true
+				}
+				cursorScreenPos += screenWidth
+				return false
+			})
 		} else {
-			Print(screen, text, x, y, fieldWidth, AlignLeft, i.fieldTextColor)
+			// The text doesn't fit. Where is the cursor?
+			if i.cursorPos < 0 {
+				i.cursorPos = 0
+			} else if i.cursorPos > len(text) {
+				i.cursorPos = len(text)
+			}
+			// Shift the text so the cursor is inside the field.
+			var shiftLeft int
+			if i.offset > i.cursorPos {
+				i.offset = i.cursorPos
+			} else if subWidth := runewidth.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
+				shiftLeft = subWidth - fieldWidth + 1
+			}
+			currentOffset := i.offset
+			iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+				if textPos >= currentOffset {
+					if shiftLeft > 0 {
+						i.offset = textPos + textWidth
+						shiftLeft -= screenWidth
+					} else {
+						if textPos+textWidth > i.cursorPos {
+							return true
+						}
+						cursorScreenPos += screenWidth
+					}
+				}
+				return false
+			})
+			Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
 		}
 	}
 
 	// Set cursor.
 	if i.focus.HasFocus() {
-		i.setCursor(screen)
-	}
-}
-
-// setCursor sets the cursor position.
-func (i *InputField) setCursor(screen tcell.Screen) {
-	x := i.x
-	y := i.y
-	rightLimit := x + i.width
-	if i.border {
-		x++
-		y++
-		rightLimit -= 2
-	}
-	fieldWidth := runewidth.StringWidth(i.text)
-	if i.fieldWidth > 0 && fieldWidth > i.fieldWidth-1 {
-		fieldWidth = i.fieldWidth - 1
-	}
-	if i.labelWidth > 0 {
-		x += i.labelWidth + fieldWidth
-	} else {
-		x += StringWidth(i.label) + fieldWidth
-	}
-	if x >= rightLimit {
-		x = rightLimit - 1
+		screen.ShowCursor(x+cursorScreenPos, y)
 	}
-	screen.ShowCursor(x, y)
 }
 
 // InputHandler returns the handler for this primitive.
@@ -305,27 +338,101 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
 			}
 		}()
 
+		// Movement functions.
+		home := func() { i.cursorPos = 0 }
+		end := func() { i.cursorPos = len(i.text) }
+		moveLeft := func() {
+			iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+				i.cursorPos -= textWidth
+				return true
+			})
+		}
+		moveRight := func() {
+			iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+				i.cursorPos += textWidth
+				return true
+			})
+		}
+		moveWordLeft := func() {
+			i.cursorPos = len(regexp.MustCompile(`\S+\s*$`).ReplaceAllString(i.text[:i.cursorPos], ""))
+		}
+		moveWordRight := func() {
+			i.cursorPos = len(i.text) - len(regexp.MustCompile(`^\s*\S+\s*`).ReplaceAllString(i.text[i.cursorPos:], ""))
+		}
+
+		// Add character function. Returns whether or not the rune character is
+		// accepted.
+		add := func(r rune) bool {
+			newText := i.text[:i.cursorPos] + string(r) + i.text[i.cursorPos:]
+			if i.accept != nil {
+				return i.accept(newText, r)
+			}
+			i.text = newText
+			i.cursorPos += len(string(r))
+			return true
+		}
+
 		// Process key event.
 		switch key := event.Key(); key {
 		case tcell.KeyRune: // Regular character.
-			newText := i.text + string(event.Rune())
-			if i.accept != nil {
-				if !i.accept(newText, event.Rune()) {
+			if event.Modifiers()&tcell.ModAlt > 0 {
+				// We accept some Alt- key combinations.
+				switch event.Rune() {
+				case 'a': // Home.
+					home()
+				case 'e': // End.
+					end()
+				case 'b': // Move word left.
+					moveWordLeft()
+				case 'f': // Move word right.
+					moveWordRight()
+				}
+			} else {
+				// Other keys are simply accepted as regular characters.
+				if !add(event.Rune()) {
 					break
 				}
 			}
-			i.text = newText
 		case tcell.KeyCtrlU: // Delete all.
 			i.text = ""
+			i.cursorPos = 0
+		case tcell.KeyCtrlK: // Delete until the end of the line.
+			i.text = i.text[:i.cursorPos]
 		case tcell.KeyCtrlW: // Delete last word.
-			lastWord := regexp.MustCompile(`\s*\S+\s*$`)
-			i.text = lastWord.ReplaceAllString(i.text, "")
-		case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete last character.
-			if len(i.text) == 0 {
-				break
+			lastWord := regexp.MustCompile(`\S+\s*$`)
+			newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
+			i.cursorPos -= len(i.text) - len(newText)
+			i.text = newText
+		case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
+			iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+				i.text = i.text[:textPos] + i.text[textPos+textWidth:]
+				i.cursorPos -= textWidth
+				return true
+			})
+			if i.offset >= i.cursorPos {
+				i.offset = 0
+			}
+		case tcell.KeyDelete: // Delete character after the cursor.
+			iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+				i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
+				return true
+			})
+		case tcell.KeyLeft:
+			if event.Modifiers()&tcell.ModAlt > 0 {
+				moveWordLeft()
+			} else {
+				moveLeft()
+			}
+		case tcell.KeyRight:
+			if event.Modifiers()&tcell.ModAlt > 0 {
+				moveWordRight()
+			} else {
+				moveRight()
 			}
-			runes := []rune(i.text)
-			i.text = string(runes[:len(runes)-1])
+		case tcell.KeyHome, tcell.KeyCtrlA:
+			home()
+		case tcell.KeyEnd, tcell.KeyCtrlE:
+			end()
 		case tcell.KeyEnter, tcell.KeyTab, tcell.KeyBacktab, tcell.KeyEscape: // We're done.
 			if i.done != nil {
 				i.done(key)
diff --git a/vendor/maunium.net/go/tview/list.go b/vendor/maunium.net/go/tview/list.go
index bc5be85..e8dc5dd 100644
--- a/vendor/maunium.net/go/tview/list.go
+++ b/vendor/maunium.net/go/tview/list.go
@@ -85,6 +85,19 @@ func (l *List) GetCurrentItem() int {
 	return l.currentItem
 }
 
+// RemoveItem removes the item with the given index (starting at 0) from the
+// list. Does nothing if the index is out of range.
+func (l *List) RemoveItem(index int) *List {
+	if index < 0 || index >= len(l.items) {
+		return l
+	}
+	l.items = append(l.items[:index], l.items[index+1:]...)
+	if l.currentItem >= len(l.items) {
+		l.currentItem = len(l.items) - 1
+	}
+	return l
+}
+
 // SetMainTextColor sets the color of the items' main text.
 func (l *List) SetMainTextColor(color tcell.Color) *List {
 	l.mainTextColor = color
@@ -127,7 +140,7 @@ func (l *List) ShowSecondaryText(show bool) *List {
 //
 // This function is also called when the first item is added or when
 // SetCurrentItem() is called.
-func (l *List) SetChangedFunc(handler func(int, string, string, rune)) *List {
+func (l *List) SetChangedFunc(handler func(index int, mainText string, secondaryText string, shortcut rune)) *List {
 	l.changed = handler
 	return l
 }
diff --git a/vendor/maunium.net/go/tview/modal.go b/vendor/maunium.net/go/tview/modal.go
index f53a265..f5e92f1 100644
--- a/vendor/maunium.net/go/tview/modal.go
+++ b/vendor/maunium.net/go/tview/modal.go
@@ -40,6 +40,11 @@ func NewModal() *Modal {
 		SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
 		SetButtonTextColor(Styles.PrimaryTextColor)
 	m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
+	m.form.SetCancelFunc(func() {
+		if m.done != nil {
+			m.done(-1, "")
+		}
+	})
 	m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
 	m.frame.SetBorder(true).
 		SetBackgroundColor(Styles.ContrastBackgroundColor).
@@ -81,6 +86,16 @@ func (m *Modal) AddButtons(labels []string) *Modal {
 					m.done(i, l)
 				}
 			})
+			button := m.form.GetButton(m.form.GetButtonCount() - 1)
+			button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+				switch event.Key() {
+				case tcell.KeyDown, tcell.KeyRight:
+					return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
+				case tcell.KeyUp, tcell.KeyLeft:
+					return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
+				}
+				return event
+			})
 		}(index, label)
 	}
 	return m
diff --git a/vendor/maunium.net/go/tview/semigraphics.go b/vendor/maunium.net/go/tview/semigraphics.go
new file mode 100644
index 0000000..2455c87
--- /dev/null
+++ b/vendor/maunium.net/go/tview/semigraphics.go
@@ -0,0 +1,296 @@
+package tview
+
+import "maunium.net/go/tcell"
+
+// Semigraphics provides an easy way to access unicode characters for drawing.
+//
+// Named like the unicode characters, 'Semigraphics'-prefix used if unicode block
+// isn't prefixed itself.
+const (
+	// Block: General Punctation U+2000-U+206F (http://unicode.org/charts/PDF/U2000.pdf)
+	SemigraphicsHorizontalEllipsis rune = '\u2026' // …
+
+	// Block: Box Drawing U+2500-U+257F (http://unicode.org/charts/PDF/U2500.pdf)
+	BoxDrawingsLightHorizontal                    rune = '\u2500' // ─
+	BoxDrawingsHeavyHorizontal                    rune = '\u2501' // ━
+	BoxDrawingsLightVertical                      rune = '\u2502' // │
+	BoxDrawingsHeavyVertical                      rune = '\u2503' // ┃
+	BoxDrawingsLightTripleDashHorizontal          rune = '\u2504' // ┄
+	BoxDrawingsHeavyTripleDashHorizontal          rune = '\u2505' // ┅
+	BoxDrawingsLightTripleDashVertical            rune = '\u2506' // ┆
+	BoxDrawingsHeavyTripleDashVertical            rune = '\u2507' // ┇
+	BoxDrawingsLightQuadrupleDashHorizontal       rune = '\u2508' // ┈
+	BoxDrawingsHeavyQuadrupleDashHorizontal       rune = '\u2509' // ┉
+	BoxDrawingsLightQuadrupleDashVertical         rune = '\u250a' // ┊
+	BoxDrawingsHeavyQuadrupleDashVertical         rune = '\u250b' // ┋
+	BoxDrawingsLightDownAndRight                  rune = '\u250c' // ┌
+	BoxDrawingsDownLighAndRightHeavy              rune = '\u250d' // ┍
+	BoxDrawingsDownHeavyAndRightLight             rune = '\u250e' // ┎
+	BoxDrawingsHeavyDownAndRight                  rune = '\u250f' // ┏
+	BoxDrawingsLightDownAndLeft                   rune = '\u2510' // ┐
+	BoxDrawingsDownLighAndLeftHeavy               rune = '\u2511' // ┑
+	BoxDrawingsDownHeavyAndLeftLight              rune = '\u2512' // ┒
+	BoxDrawingsHeavyDownAndLeft                   rune = '\u2513' // ┓
+	BoxDrawingsLightUpAndRight                    rune = '\u2514' // └
+	BoxDrawingsUpLightAndRightHeavy               rune = '\u2515' // ┕
+	BoxDrawingsUpHeavyAndRightLight               rune = '\u2516' // ┖
+	BoxDrawingsHeavyUpAndRight                    rune = '\u2517' // ┗
+	BoxDrawingsLightUpAndLeft                     rune = '\u2518' // ┘
+	BoxDrawingsUpLightAndLeftHeavy                rune = '\u2519' // ┙
+	BoxDrawingsUpHeavyAndLeftLight                rune = '\u251a' // ┚
+	BoxDrawingsHeavyUpAndLeft                     rune = '\u251b' // ┛
+	BoxDrawingsLightVerticalAndRight              rune = '\u251c' // ├
+	BoxDrawingsVerticalLightAndRightHeavy         rune = '\u251d' // ┝
+	BoxDrawingsUpHeavyAndRightDownLight           rune = '\u251e' // ┞
+	BoxDrawingsDownHeacyAndRightUpLight           rune = '\u251f' // ┟
+	BoxDrawingsVerticalHeavyAndRightLight         rune = '\u2520' // ┠
+	BoxDrawingsDownLightAnbdRightUpHeavy          rune = '\u2521' // ┡
+	BoxDrawingsUpLightAndRightDownHeavy           rune = '\u2522' // ┢
+	BoxDrawingsHeavyVerticalAndRight              rune = '\u2523' // ┣
+	BoxDrawingsLightVerticalAndLeft               rune = '\u2524' // ┤
+	BoxDrawingsVerticalLightAndLeftHeavy          rune = '\u2525' // ┥
+	BoxDrawingsUpHeavyAndLeftDownLight            rune = '\u2526' // ┦
+	BoxDrawingsDownHeavyAndLeftUpLight            rune = '\u2527' // ┧
+	BoxDrawingsVerticalheavyAndLeftLight          rune = '\u2528' // ┨
+	BoxDrawingsDownLightAndLeftUpHeavy            rune = '\u2529' // ┨
+	BoxDrawingsUpLightAndLeftDownHeavy            rune = '\u252a' // ┪
+	BoxDrawingsHeavyVerticalAndLeft               rune = '\u252b' // ┫
+	BoxDrawingsLightDownAndHorizontal             rune = '\u252c' // ┬
+	BoxDrawingsLeftHeavyAndRightDownLight         rune = '\u252d' // ┭
+	BoxDrawingsRightHeavyAndLeftDownLight         rune = '\u252e' // ┮
+	BoxDrawingsDownLightAndHorizontalHeavy        rune = '\u252f' // ┯
+	BoxDrawingsDownHeavyAndHorizontalLight        rune = '\u2530' // ┰
+	BoxDrawingsRightLightAndLeftDownHeavy         rune = '\u2531' // ┱
+	BoxDrawingsLeftLightAndRightDownHeavy         rune = '\u2532' // ┲
+	BoxDrawingsHeavyDownAndHorizontal             rune = '\u2533' // ┳
+	BoxDrawingsLightUpAndHorizontal               rune = '\u2534' // ┴
+	BoxDrawingsLeftHeavyAndRightUpLight           rune = '\u2535' // ┵
+	BoxDrawingsRightHeavyAndLeftUpLight           rune = '\u2536' // ┶
+	BoxDrawingsUpLightAndHorizontalHeavy          rune = '\u2537' // ┷
+	BoxDrawingsUpHeavyAndHorizontalLight          rune = '\u2538' // ┸
+	BoxDrawingsRightLightAndLeftUpHeavy           rune = '\u2539' // ┹
+	BoxDrawingsLeftLightAndRightUpHeavy           rune = '\u253a' // ┺
+	BoxDrawingsHeavyUpAndHorizontal               rune = '\u253b' // ┻
+	BoxDrawingsLightVerticalAndHorizontal         rune = '\u253c' // ┼
+	BoxDrawingsLeftHeavyAndRightVerticalLight     rune = '\u253d' // ┽
+	BoxDrawingsRightHeavyAndLeftVerticalLight     rune = '\u253e' // ┾
+	BoxDrawingsVerticalLightAndHorizontalHeavy    rune = '\u253f' // ┿
+	BoxDrawingsUpHeavyAndDownHorizontalLight      rune = '\u2540' // ╀
+	BoxDrawingsDownHeavyAndUpHorizontalLight      rune = '\u2541' // ╁
+	BoxDrawingsVerticalHeavyAndHorizontalLight    rune = '\u2542' // ╂
+	BoxDrawingsLeftUpHeavyAndRightDownLight       rune = '\u2543' // ╃
+	BoxDrawingsRightUpHeavyAndLeftDownLight       rune = '\u2544' // ╄
+	BoxDrawingsLeftDownHeavyAndRightUpLight       rune = '\u2545' // ╅
+	BoxDrawingsRightDownHeavyAndLeftUpLight       rune = '\u2546' // ╆
+	BoxDrawingsDownLightAndUpHorizontalHeavy      rune = '\u2547' // ╇
+	BoxDrawingsUpLightAndDownHorizontalHeavy      rune = '\u2548' // ╈
+	BoxDrawingsRightLightAndLeftVerticalHeavy     rune = '\u2549' // ╉
+	BoxDrawingsLeftLightAndRightVerticalHeavy     rune = '\u254a' // ╊
+	BoxDrawingsHeavyVerticalAndHorizontal         rune = '\u254b' // ╋
+	BoxDrawingsLightDoubleDashHorizontal          rune = '\u254c' // ╌
+	BoxDrawingsHeavyDoubleDashHorizontal          rune = '\u254d' // ╍
+	BoxDrawingsLightDoubleDashVertical            rune = '\u254e' // ╎
+	BoxDrawingsHeavyDoubleDashVertical            rune = '\u254f' // ╏
+	BoxDrawingsDoubleHorizontal                   rune = '\u2550' // ═
+	BoxDrawingsDoubleVertical                     rune = '\u2551' // ║
+	BoxDrawingsDownSingleAndRightDouble           rune = '\u2552' // ╒
+	BoxDrawingsDownDoubleAndRightSingle           rune = '\u2553' // ╓
+	BoxDrawingsDoubleDownAndRight                 rune = '\u2554' // ╔
+	BoxDrawingsDownSingleAndLeftDouble            rune = '\u2555' // ╕
+	BoxDrawingsDownDoubleAndLeftSingle            rune = '\u2556' // ╖
+	BoxDrawingsDoubleDownAndLeft                  rune = '\u2557' // ╗
+	BoxDrawingsUpSingleAndRightDouble             rune = '\u2558' // ╘
+	BoxDrawingsUpDoubleAndRightSingle             rune = '\u2559' // ╙
+	BoxDrawingsDoubleUpAndRight                   rune = '\u255a' // ╚
+	BoxDrawingsUpSingleAndLeftDouble              rune = '\u255b' // ╛
+	BoxDrawingsUpDobuleAndLeftSingle              rune = '\u255c' // ╜
+	BoxDrawingsDoubleUpAndLeft                    rune = '\u255d' // ╝
+	BoxDrawingsVerticalSingleAndRightDouble       rune = '\u255e' // ╞
+	BoxDrawingsVerticalDoubleAndRightSingle       rune = '\u255f' // ╟
+	BoxDrawingsDoubleVerticalAndRight             rune = '\u2560' // ╠
+	BoxDrawingsVerticalSingleAndLeftDouble        rune = '\u2561' // ╡
+	BoxDrawingsVerticalDoubleAndLeftSingle        rune = '\u2562' // ╢
+	BoxDrawingsDoubleVerticalAndLeft              rune = '\u2563' // ╣
+	BoxDrawingsDownSingleAndHorizontalDouble      rune = '\u2564' // ╤
+	BoxDrawingsDownDoubleAndHorizontalSingle      rune = '\u2565' // ╥
+	BoxDrawingsDoubleDownAndHorizontal            rune = '\u2566' // ╦
+	BoxDrawingsUpSingleAndHorizontalDouble        rune = '\u2567' // ╧
+	BoxDrawingsUpDoubleAndHorizontalSingle        rune = '\u2568' // ╨
+	BoxDrawingsDoubleUpAndHorizontal              rune = '\u2569' // ╩
+	BoxDrawingsVerticalSingleAndHorizontalDouble  rune = '\u256a' // ╪
+	BoxDrawingsVerticalDoubleAndHorizontalSingle  rune = '\u256b' // ╫
+	BoxDrawingsDoubleVerticalAndHorizontal        rune = '\u256c' // ╬
+	BoxDrawingsLightArcDownAndRight               rune = '\u256d' // ╭
+	BoxDrawingsLightArcDownAndLeft                rune = '\u256e' // ╮
+	BoxDrawingsLightArcUpAndLeft                  rune = '\u256f' // ╯
+	BoxDrawingsLightArcUpAndRight                 rune = '\u2570' // ╰
+	BoxDrawingsLightDiagonalUpperRightToLowerLeft rune = '\u2571' // ╱
+	BoxDrawingsLightDiagonalUpperLeftToLowerRight rune = '\u2572' // ╲
+	BoxDrawingsLightDiagonalCross                 rune = '\u2573' // ╳
+	BoxDrawingsLightLeft                          rune = '\u2574' // ╴
+	BoxDrawingsLightUp                            rune = '\u2575' // ╵
+	BoxDrawingsLightRight                         rune = '\u2576' // ╶
+	BoxDrawingsLightDown                          rune = '\u2577' // ╷
+	BoxDrawingsHeavyLeft                          rune = '\u2578' // ╸
+	BoxDrawingsHeavyUp                            rune = '\u2579' // ╹
+	BoxDrawingsHeavyRight                         rune = '\u257a' // ╺
+	BoxDrawingsHeavyDown                          rune = '\u257b' // ╻
+	BoxDrawingsLightLeftAndHeavyRight             rune = '\u257c' // ╼
+	BoxDrawingsLightUpAndHeavyDown                rune = '\u257d' // ╽
+	BoxDrawingsHeavyLeftAndLightRight             rune = '\u257e' // ╾
+	BoxDrawingsHeavyUpAndLightDown                rune = '\u257f' // ╿
+)
+
+// SemigraphicJoints is a map for joining semigraphic (or otherwise) runes.
+// So far only light lines are supported but if you want to change the border
+// styling you need to provide the joints, too.
+// The matching will be sorted ascending by rune value, so you don't need to
+// provide all rune combinations,
+// e.g. (─) + (│) = (┼) will also match (│) + (─) = (┼)
+var SemigraphicJoints = map[string]rune{
+	// (─) + (│) = (┼)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVertical}): BoxDrawingsLightVerticalAndHorizontal,
+	// (─) + (┌) = (┬)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndRight}): BoxDrawingsLightDownAndHorizontal,
+	// (─) + (┐) = (┬)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,
+	// (─) + (└) = (┴)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndRight}): BoxDrawingsLightUpAndHorizontal,
+	// (─) + (┘) = (┴)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,
+	// (─) + (├) = (┼)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+	// (─) + (┤) = (┼)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+	// (─) + (┬) = (┬)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+	// (─) + (┴) = (┴)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+	// (─) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (│) + (┌) = (├)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndRight}): BoxDrawingsLightVerticalAndRight,
+	// (│) + (┐) = (┤)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightVerticalAndLeft,
+	// (│) + (└) = (├)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,
+	// (│) + (┘) = (┤)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,
+	// (│) + (├) = (├)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+	// (│) + (┤) = (┤)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+	// (│) + (┬) = (┼)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (│) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (│) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightVertical, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (┌) + (┐) = (┬)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndLeft}): BoxDrawingsLightDownAndHorizontal,
+	// (┌) + (└) = (├)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndRight,
+	// (┌) + (┘) = (┼)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┌) + (├) = (├)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+	// (┌) + (┤) = (┼)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┌) + (┬) = (┬)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+	// (┌) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┌) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightDownAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (┐) + (└) = (┼)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┐) + (┘) = (┤)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightVerticalAndLeft,
+	// (┐) + (├) = (┼)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┐) + (┤) = (┤)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+	// (┐) + (┬) = (┬)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightDownAndHorizontal,
+	// (┐) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┐) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightDownAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (└) + (┘) = (┴)
+	string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndLeft}): BoxDrawingsLightUpAndHorizontal,
+	// (└) + (├) = (├)
+	string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndRight,
+	// (└) + (┤) = (┼)
+	string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+	// (└) + (┬) = (┼)
+	string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (└) + (┴) = (┴)
+	string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+	// (└) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightUpAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (┘) + (├) = (┼)
+	string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndRight}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┘) + (┤) = (┤)
+	string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndLeft,
+	// (┘) + (┬) = (┼)
+	string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┘) + (┴) = (┴)
+	string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightUpAndHorizontal,
+	// (┘) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightUpAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (├) + (┤) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndLeft}): BoxDrawingsLightVerticalAndHorizontal,
+	// (├) + (┬) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (├) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (├) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndRight, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (┤) + (┬) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightDownAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┤) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┤) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightVerticalAndLeft, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (┬) + (┴) = (┼)
+	string([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightUpAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+	// (┬) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightDownAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+
+	// (┴) + (┼) = (┼)
+	string([]rune{BoxDrawingsLightUpAndHorizontal, BoxDrawingsLightVerticalAndHorizontal}): BoxDrawingsLightVerticalAndHorizontal,
+}
+
+// PrintJoinedSemigraphics prints a semigraphics rune into the screen at the given
+// position with the given color, joining it with any existing semigraphics
+// rune. Background colors are preserved. At this point, only regular single
+// line borders are supported.
+func PrintJoinedSemigraphics(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
+	previous, _, style, _ := screen.GetContent(x, y)
+	style = style.Foreground(color)
+
+	// What's the resulting rune?
+	var result rune
+	if ch == previous {
+		result = ch
+	} else {
+		if ch < previous {
+			previous, ch = ch, previous
+		}
+		result = SemigraphicJoints[string([]rune{previous, ch})]
+	}
+	if result == 0 {
+		result = ch
+	}
+
+	// We only print something if we have something.
+	screen.SetContent(x, y, result, nil, style)
+}
diff --git a/vendor/maunium.net/go/tview/table.go b/vendor/maunium.net/go/tview/table.go
index 2491ec7..6c636e7 100644
--- a/vendor/maunium.net/go/tview/table.go
+++ b/vendor/maunium.net/go/tview/table.go
@@ -231,6 +231,10 @@ type Table struct {
 	// The number of visible rows the last time the table was drawn.
 	visibleRows int
 
+	// The style of the selected rows. If this value is 0, selected rows are
+	// simply inverted.
+	selectedStyle tcell.Style
+
 	// An optional function which gets called when the user presses Enter on a
 	// selected cell. If entire rows selected, the column value is undefined.
 	// Likewise for entire columns.
@@ -276,9 +280,21 @@ func (t *Table) SetBordersColor(color tcell.Color) *Table {
 	return t
 }
 
+// SetSelectedStyle sets a specific style for selected cells. If no such style
+// is set, per default, selected cells are inverted (i.e. their foreground and
+// background colors are swapped).
+//
+// To reset a previous setting to its default, make the following call:
+//
+//   table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
+func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) *Table {
+	t.selectedStyle = tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor) | tcell.Style(attributes)
+	return t
+}
+
 // SetSeparator sets the character used to fill the space between two
 // neighboring cells. This is a space character ' ' per default but you may
-// want to set it to GraphicsVertBar (or any other rune) if the column
+// want to set it to Borders.Vertical (or any other rune) if the column
 // separation should be more visible. If cell borders are activated, this is
 // ignored.
 //
@@ -373,7 +389,7 @@ func (t *Table) SetDoneFunc(handler func(key tcell.Key)) *Table {
 }
 
 // SetCell sets the content of a cell the specified position. It is ok to
-// directly instantiate a TableCell object. If the cell has contain, at least
+// directly instantiate a TableCell object. If the cell has content, at least
 // the Text and Color fields should be set.
 //
 // Note that setting cells in previously unknown rows and columns will
@@ -406,7 +422,7 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table {
 }
 
 // GetCell returns the contents of the cell at the specified position. A valid
-// TableCell object is always returns but it will be uninitialized if the cell
+// TableCell object is always returned but it will be uninitialized if the cell
 // was not previously set.
 func (t *Table) GetCell(row, column int) *TableCell {
 	if row >= len(t.cells) || column >= len(t.cells[row]) {
@@ -415,6 +431,31 @@ func (t *Table) GetCell(row, column int) *TableCell {
 	return t.cells[row][column]
 }
 
+// RemoveRow removes the row at the given position from the table. If there is
+// no such row, this has no effect.
+func (t *Table) RemoveRow(row int) *Table {
+	if row < 0 || row >= len(t.cells) {
+		return t
+	}
+
+	t.cells = append(t.cells[:row], t.cells[row+1:]...)
+
+	return t
+}
+
+// RemoveColumn removes the column at the given position from the table. If
+// there is no such column, this has no effect.
+func (t *Table) RemoveColumn(column int) *Table {
+	for row := range t.cells {
+		if column < 0 || column >= len(t.cells[row]) {
+			continue
+		}
+		t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
+	}
+
+	return t
+}
+
 // GetRowCount returns the number of rows in the table.
 func (t *Table) GetRowCount() int {
 	return len(t.cells)
@@ -644,7 +685,6 @@ ColumnLoop:
 			}
 			expWidth := toDistribute * expansion / expansionTotal
 			widths[index] += expWidth
-			tableWidth += expWidth
 			toDistribute -= expWidth
 			expansionTotal -= expansion
 		}
@@ -668,24 +708,24 @@ ColumnLoop:
 				// Draw borders.
 				rowY *= 2
 				for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
-					drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
+					drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
 				}
-				ch := GraphicsCross
+				ch := Borders.Cross
 				if columnIndex == 0 {
 					if rowY == 0 {
-						ch = GraphicsTopLeftCorner
+						ch = Borders.TopLeft
 					} else {
-						ch = GraphicsLeftT
+						ch = Borders.LeftT
 					}
 				} else if rowY == 0 {
-					ch = GraphicsTopT
+					ch = Borders.TopT
 				}
 				drawBorder(columnX, rowY, ch)
 				rowY++
 				if rowY >= height {
 					break // No space for the text anymore.
 				}
-				drawBorder(columnX, rowY, GraphicsVertBar)
+				drawBorder(columnX, rowY, Borders.Vertical)
 			} else if columnIndex > 0 {
 				// Draw separator.
 				drawBorder(columnX, rowY, t.separator)
@@ -706,18 +746,18 @@ ColumnLoop:
 			_, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color)|tcell.Style(cell.Attributes))
 			if StringWidth(cell.Text)-printed > 0 && printed > 0 {
 				_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
-				printWithStyle(screen, string(GraphicsEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
+				printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
 			}
 		}
 
 		// Draw bottom border.
 		if rowY := 2 * len(rows); t.borders && rowY < height {
 			for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
-				drawBorder(columnX+pos+1, rowY, GraphicsHoriBar)
+				drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
 			}
-			ch := GraphicsBottomT
+			ch := Borders.BottomT
 			if columnIndex == 0 {
-				ch = GraphicsBottomLeftCorner
+				ch = Borders.BottomLeft
 			}
 			drawBorder(columnX, rowY, ch)
 		}
@@ -730,26 +770,31 @@ ColumnLoop:
 		for rowY := range rows {
 			rowY *= 2
 			if rowY+1 < height {
-				drawBorder(columnX, rowY+1, GraphicsVertBar)
+				drawBorder(columnX, rowY+1, Borders.Vertical)
 			}
-			ch := GraphicsRightT
+			ch := Borders.RightT
 			if rowY == 0 {
-				ch = GraphicsTopRightCorner
+				ch = Borders.TopRight
 			}
 			drawBorder(columnX, rowY, ch)
 		}
 		if rowY := 2 * len(rows); rowY < height {
-			drawBorder(columnX, rowY, GraphicsBottomRightCorner)
+			drawBorder(columnX, rowY, Borders.BottomRight)
 		}
 	}
 
 	// Helper function which colors the background of a box.
-	colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, selected bool) {
+	// backgroundColor == tcell.ColorDefault => Don't color the background.
+	// textColor == tcell.ColorDefault => Don't change the text color.
+	// attr == 0 => Don't change attributes.
+	// invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
+	//                   set background to textColor.
+	colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
 		for by := 0; by < h && fromY+by < y+height; by++ {
 			for bx := 0; bx < w && fromX+bx < x+width; bx++ {
 				m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
-				if selected {
-					fg, _, _ := style.Decompose()
+				fg, bg, a := style.Decompose()
+				if invert {
 					if fg == textColor || fg == t.bordersColor {
 						fg = backgroundColor
 					}
@@ -758,10 +803,16 @@ ColumnLoop:
 					}
 					style = style.Background(textColor).Foreground(fg)
 				} else {
-					if backgroundColor == tcell.ColorDefault {
-						continue
+					if backgroundColor != tcell.ColorDefault {
+						bg = backgroundColor
 					}
-					style = style.Background(backgroundColor)
+					if textColor != tcell.ColorDefault {
+						fg = textColor
+					}
+					if attr != 0 {
+						a = attr
+					}
+					style = style.Background(bg).Foreground(fg) | tcell.Style(a)
 				}
 				screen.SetContent(fromX+bx, fromY+by, m, c, style)
 			}
@@ -770,11 +821,12 @@ ColumnLoop:
 
 	// Color the cell backgrounds. To avoid undesirable artefacts, we combine
 	// the drawing of a cell by background color, selected cells last.
-	cellsByBackgroundColor := make(map[tcell.Color][]*struct {
+	type cellInfo struct {
 		x, y, w, h int
 		text       tcell.Color
 		selected   bool
-	})
+	}
+	cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
 	var backgroundColors []tcell.Color
 	for rowY, row := range rows {
 		columnX := 0
@@ -794,11 +846,7 @@ ColumnLoop:
 			columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
 			cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
 			entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
-			cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &struct {
-				x, y, w, h int
-				text       tcell.Color
-				selected   bool
-			}{
+			cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
 				x:        bx,
 				y:        by,
 				w:        bw,
@@ -822,13 +870,18 @@ ColumnLoop:
 		_, _, lj := c.Hcl()
 		return li < lj
 	})
+	selFg, selBg, selAttr := t.selectedStyle.Decompose()
 	for _, bgColor := range backgroundColors {
 		entries := cellsByBackgroundColor[bgColor]
 		for _, cell := range entries {
 			if cell.selected {
-				defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, true)
+				if t.selectedStyle != 0 {
+					defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
+				} else {
+					defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, 0, true)
+				}
 			} else {
-				colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.text, false)
+				colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
 			}
 		}
 	}
diff --git a/vendor/maunium.net/go/tview/textview.go b/vendor/maunium.net/go/tview/textview.go
index 44aeb1e..63d9796 100644
--- a/vendor/maunium.net/go/tview/textview.go
+++ b/vendor/maunium.net/go/tview/textview.go
@@ -31,7 +31,7 @@ type textViewIndex struct {
 // TextView is a box which displays text. It implements the io.Writer interface
 // so you can stream text to it. This does not trigger a redraw automatically
 // but if a handler is installed via SetChangedFunc(), you can cause it to be
-// redrawn.
+// redrawn. (See SetChangedFunc() for more details.)
 //
 // Navigation
 //
@@ -103,6 +103,10 @@ type TextView struct {
 	// during re-indexing. Set to -1 if there is no current highlight.
 	fromHighlight, toHighlight int
 
+	// The screen space column of the highlight in its first line. Set to -1 if
+	// there is no current highlight.
+	posHighlight int
+
 	// A set of region IDs that are currently highlighted.
 	highlights map[string]struct{}
 
@@ -170,6 +174,7 @@ func NewTextView() *TextView {
 		align:         AlignLeft,
 		wrap:          true,
 		textColor:     Styles.PrimaryTextColor,
+		regions:       false,
 		dynamicColors: false,
 	}
 }
@@ -255,8 +260,20 @@ func (t *TextView) SetRegions(regions bool) *TextView {
 }
 
 // SetChangedFunc sets a handler function which is called when the text of the
-// text view has changed. This is typically used to cause the application to
-// redraw the screen.
+// text view has changed. This is useful when text is written to this io.Writer
+// in a separate goroutine. This does not automatically cause the screen to be
+// refreshed so you may want to use the "changed" handler to redraw the screen.
+//
+// Note that to avoid race conditions or deadlocks, there are a few rules you
+// should follow:
+//
+//   - You can call Application.Draw() from this handler.
+//   - You can call TextView.HasFocus() from this handler.
+//   - During the execution of this handler, access to any other variables from
+//     this primitive or any other primitive should be queued using
+//     Application.QueueUpdate().
+//
+// See package description for details on dealing with concurrency.
 func (t *TextView) SetChangedFunc(handler func()) *TextView {
 	t.changed = handler
 	return t
@@ -270,6 +287,16 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
 	return t
 }
 
+// ScrollTo scrolls to the specified row and column (both starting with 0).
+func (t *TextView) ScrollTo(row, column int) *TextView {
+	if !t.scrollable {
+		return t
+	}
+	t.lineOffset = row
+	t.columnOffset = column
+	return t
+}
+
 // ScrollToBeginning scrolls to the top left corner of the text if the text view
 // is scrollable.
 func (t *TextView) ScrollToBeginning() *TextView {
@@ -294,6 +321,12 @@ func (t *TextView) ScrollToEnd() *TextView {
 	return t
 }
 
+// GetScrollOffset returns the number of rows and columns that are skipped at
+// the top left corner when the text view has been scrolled.
+func (t *TextView) GetScrollOffset() (row, column int) {
+	return t.lineOffset, t.columnOffset
+}
+
 // Clear removes all text from the buffer.
 func (t *TextView) Clear() *TextView {
 	t.buffer = nil
@@ -420,13 +453,33 @@ func (t *TextView) GetRegionText(regionID string) string {
 	return escapePattern.ReplaceAllString(buffer.String(), `[$1$2]`)
 }
 
+// Focus is called when this primitive receives focus.
+func (t *TextView) Focus(delegate func(p Primitive)) {
+	// Implemented here with locking because this is used by layout primitives.
+	t.Lock()
+	defer t.Unlock()
+	t.hasFocus = true
+}
+
+// HasFocus returns whether or not this primitive has focus.
+func (t *TextView) HasFocus() bool {
+	// Implemented here with locking because this may be used in the "changed"
+	// callback.
+	t.Lock()
+	defer t.Unlock()
+	return t.hasFocus
+}
+
 // Write lets us implement the io.Writer interface. Tab characters will be
 // replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
 // as a new line.
 func (t *TextView) Write(p []byte) (n int, err error) {
 	// Notify at the end.
-	if t.changed != nil {
-		defer t.changed()
+	t.Lock()
+	changed := t.changed
+	t.Unlock()
+	if changed != nil {
+		defer changed() // Deadlocks may occur if we lock here.
 	}
 
 	t.Lock()
@@ -492,7 +545,7 @@ func (t *TextView) reindexBuffer(width int) {
 		return // Nothing has changed. We can still use the current index.
 	}
 	t.index = nil
-	t.fromHighlight, t.toHighlight = -1, -1
+	t.fromHighlight, t.toHighlight, t.posHighlight = -1, -1, -1
 
 	// If there's no space, there's no index.
 	if width < 1 {
@@ -511,8 +564,9 @@ func (t *TextView) reindexBuffer(width int) {
 			colorTags       [][]string
 			escapeIndices   [][]int
 		)
+		strippedStr := str
 		if t.dynamicColors {
-			colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str)
+			colorTagIndices, colorTags, escapeIndices, strippedStr, _ = decomposeString(str)
 		}
 
 		// Find all regions in this line. Then remove them.
@@ -523,14 +577,12 @@ func (t *TextView) reindexBuffer(width int) {
 		if t.regions {
 			regionIndices = regionPattern.FindAllStringIndex(str, -1)
 			regions = regionPattern.FindAllStringSubmatch(str, -1)
-			str = regionPattern.ReplaceAllString(str, "")
-			if !t.dynamicColors {
-				// We haven't detected escape tags yet. Do it now.
-				escapeIndices = escapePattern.FindAllStringIndex(str, -1)
-				str = escapePattern.ReplaceAllString(str, "[$1$2]")
-			}
+			strippedStr = regionPattern.ReplaceAllString(strippedStr, "")
 		}
 
+		// We don't need the original string anymore for now.
+		str = strippedStr
+
 		// Split the line if required.
 		var splitLines []string
 		if t.wrap && len(str) > 0 {
@@ -574,15 +626,53 @@ func (t *TextView) reindexBuffer(width int) {
 
 			// Shift original position with tags.
 			lineLength := len(splitLine)
+			remainingLength := lineLength
+			tagEnd := originalPos
+			totalTagLength := 0
 			for {
-				if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength {
+				// Which tag comes next?
+				nextTag := make([][3]int, 0, 3)
+				if colorPos < len(colorTagIndices) {
+					nextTag = append(nextTag, [3]int{colorTagIndices[colorPos][0], colorTagIndices[colorPos][1], 0}) // 0 = color tag.
+				}
+				if regionPos < len(regionIndices) {
+					nextTag = append(nextTag, [3]int{regionIndices[regionPos][0], regionIndices[regionPos][1], 1}) // 1 = region tag.
+				}
+				if escapePos < len(escapeIndices) {
+					nextTag = append(nextTag, [3]int{escapeIndices[escapePos][0], escapeIndices[escapePos][1], 2}) // 2 = escape tag.
+				}
+				minPos := -1
+				tagIndex := -1
+				for index, pair := range nextTag {
+					if minPos < 0 || pair[0] < minPos {
+						minPos = pair[0]
+						tagIndex = index
+					}
+				}
+
+				// Is the next tag in range?
+				if tagIndex < 0 || minPos >= tagEnd+remainingLength {
+					break // No. We're done with this line.
+				}
+
+				// Advance.
+				strippedTagStart := nextTag[tagIndex][0] - originalPos - totalTagLength
+				tagEnd = nextTag[tagIndex][1]
+				tagLength := tagEnd - nextTag[tagIndex][0]
+				if nextTag[tagIndex][2] == 2 {
+					tagLength = 1
+				}
+				totalTagLength += tagLength
+				remainingLength = lineLength - (tagEnd - originalPos - totalTagLength)
+
+				// Process the tag.
+				switch nextTag[tagIndex][2] {
+				case 0:
 					// Process color tags.
-					originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
 					foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
 					colorPos++
-				} else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength {
+				case 1:
 					// Process region tags.
-					originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
 					regionID = regions[regionPos][1]
 					_, highlighted = t.highlights[regionID]
 
@@ -591,23 +681,21 @@ func (t *TextView) reindexBuffer(width int) {
 						line := len(t.index)
 						if t.fromHighlight < 0 {
 							t.fromHighlight, t.toHighlight = line, line
+							t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart])
 						} else if line > t.toHighlight {
 							t.toHighlight = line
 						}
 					}
 
 					regionPos++
-				} else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength {
+				case 2:
 					// Process escape tags.
-					originalPos++
 					escapePos++
-				} else {
-					break
 				}
 			}
 
 			// Advance to next line.
-			originalPos += lineLength
+			originalPos += lineLength + totalTagLength
 
 			// Append this line.
 			line.NextPos = originalPos
@@ -649,7 +737,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
 	t.pageSize = height
 
 	// If the width has changed, we need to reindex.
-	if width != t.lastWidth {
+	if width != t.lastWidth && t.wrap {
 		t.index = nil
 	}
 	t.lastWidth = width
@@ -672,6 +760,16 @@ func (t *TextView) Draw(screen tcell.Screen) {
 			// No, let's move to the start of the highlights.
 			t.lineOffset = t.fromHighlight
 		}
+
+		// If the highlight is too far to the right, move it to the middle.
+		if t.posHighlight-t.columnOffset > 3*width/4 {
+			t.columnOffset = t.posHighlight - width/2
+		}
+
+		// If the highlight is off-screen on the left, move it on-screen.
+		if t.posHighlight-t.columnOffset < 0 {
+			t.columnOffset = t.posHighlight - width/4
+		}
 	}
 	t.scrollToHighlights = false
 
@@ -737,8 +835,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
 			colorTags       [][]string
 			escapeIndices   [][]int
 		)
+		strippedText := text
 		if t.dynamicColors {
-			colorTagIndices, colorTags, escapeIndices, _, _ = decomposeString(text)
+			colorTagIndices, colorTags, escapeIndices, strippedText, _ = decomposeString(text)
 		}
 
 		// Get regions.
@@ -749,8 +848,10 @@ func (t *TextView) Draw(screen tcell.Screen) {
 		if t.regions {
 			regionIndices = regionPattern.FindAllStringIndex(text, -1)
 			regions = regionPattern.FindAllStringSubmatch(text, -1)
+			strippedText = regionPattern.ReplaceAllString(strippedText, "")
 			if !t.dynamicColors {
 				escapeIndices = escapePattern.FindAllStringIndex(text, -1)
+				strippedText = string(escapePattern.ReplaceAllString(strippedText, "[$1$2]"))
 			}
 		}
 
@@ -769,11 +870,29 @@ func (t *TextView) Draw(screen tcell.Screen) {
 		}
 
 		// Print the line.
-		var currentTag, currentRegion, currentEscapeTag, skipped, runeSeqWidth int
-		runeSequence := make([]rune, 0, 10)
-		flush := func() {
-			if len(runeSequence) == 0 {
-				return
+		var colorPos, regionPos, escapePos, tagOffset, skipped int
+		iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+			// Process tags.
+			for {
+				if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
+					// Get the color.
+					foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
+					tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
+					colorPos++
+				} else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
+					// Get the region.
+					regionID = regions[regionPos][1]
+					tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
+					regionPos++
+				} else {
+					break
+				}
+			}
+
+			// Skip the second-to-last character of an escape tag.
+			if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+				tagOffset++
+				escapePos++
 			}
 
 			// Mix the existing style with the new style.
@@ -803,87 +922,30 @@ func (t *TextView) Draw(screen tcell.Screen) {
 				style = style.Background(fg).Foreground(bg)
 			}
 
-			// Draw the character.
-			var comb []rune
-			if len(runeSequence) > 1 {
-				// Allocate space for the combining characters only when necessary.
-				comb = make([]rune, len(runeSequence)-1)
-				copy(comb, runeSequence[1:])
-			}
-			for offset := 0; offset < runeSeqWidth; offset++ {
-				screen.SetContent(x+posX+offset, y+line-t.lineOffset, runeSequence[0], comb, style)
-			}
-
-			// Advance.
-			posX += runeSeqWidth
-			runeSequence = runeSequence[:0]
-			runeSeqWidth = 0
-		}
-		for pos, ch := range text {
-			// Get the color.
-			if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
-				flush()
-				if pos == colorTagIndices[currentTag][1]-1 {
-					foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
-					currentTag++
-				}
-				continue
-			}
-
-			// Get the region.
-			if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
-				flush()
-				if pos == regionIndices[currentRegion][1]-1 {
-					regionID = regions[currentRegion][1]
-					currentRegion++
-				}
-				continue
-			}
-
-			// Skip the second-to-last character of an escape tag.
-			if currentEscapeTag < len(escapeIndices) && pos >= escapeIndices[currentEscapeTag][0] && pos < escapeIndices[currentEscapeTag][1] {
-				flush()
-				if pos == escapeIndices[currentEscapeTag][1]-1 {
-					currentEscapeTag++
-				} else if pos == escapeIndices[currentEscapeTag][1]-2 {
-					continue
-				}
-			}
-
-			// Determine the width of this rune.
-			chWidth := runewidth.RuneWidth(ch)
-			if chWidth == 0 {
-				// If this is not a modifier, we treat it as a space character.
-				if len(runeSequence) == 0 {
-					ch = ' '
-					chWidth = 1
-				} else {
-					runeSequence = append(runeSequence, ch)
-					continue
-				}
-			}
-
 			// Skip to the right.
 			if !t.wrap && skipped < skip {
-				skipped += chWidth
-				continue
+				skipped += screenWidth
+				return false
 			}
 
 			// Stop at the right border.
-			if posX+runeSeqWidth+chWidth > width {
-				break
+			if posX+screenWidth > width {
+				return true
 			}
 
-			// Flush the rune sequence.
-			flush()
+			// Draw the character.
+			for offset := screenWidth - 1; offset >= 0; offset-- {
+				if offset == 0 {
+					screen.SetContent(x+posX+offset, y+line-t.lineOffset, main, comb, style)
+				} else {
+					screen.SetContent(x+posX+offset, y+line-t.lineOffset, ' ', nil, style)
+				}
+			}
 
-			// Queue this rune.
-			runeSequence = append(runeSequence, ch)
-			runeSeqWidth += chWidth
-		}
-		if posX+runeSeqWidth <= width {
-			flush()
-		}
+			// Advance.
+			posX += screenWidth
+			return false
+		})
 	}
 
 	// If this view is not scrollable, we'll purge the buffer of lines that have
diff --git a/vendor/maunium.net/go/tview/treeview.go b/vendor/maunium.net/go/tview/treeview.go
new file mode 100644
index 0000000..1b0af21
--- /dev/null
+++ b/vendor/maunium.net/go/tview/treeview.go
@@ -0,0 +1,684 @@
+package tview
+
+import (
+	"maunium.net/go/tcell"
+)
+
+// Tree navigation events.
+const (
+	treeNone int = iota
+	treeHome
+	treeEnd
+	treeUp
+	treeDown
+	treePageUp
+	treePageDown
+)
+
+// TreeNode represents one node in a tree view.
+type TreeNode struct {
+	// The reference object.
+	reference interface{}
+
+	// This node's child nodes.
+	children []*TreeNode
+
+	// The item's text.
+	text string
+
+	// The text color.
+	color tcell.Color
+
+	// Whether or not this node can be selected.
+	selectable bool
+
+	// Whether or not this node's children should be displayed.
+	expanded bool
+
+	// The additional horizontal indent of this node's text.
+	indent int
+
+	// An optional function which is called when the user selects this node.
+	selected func()
+
+	// Temporary member variables.
+	parent    *TreeNode // The parent node (nil for the root).
+	level     int       // The hierarchy level (0 for the root, 1 for its children, and so on).
+	graphicsX int       // The x-coordinate of the left-most graphics rune.
+	textX     int       // The x-coordinate of the first rune of the text.
+}
+
+// NewTreeNode returns a new tree node.
+func NewTreeNode(text string) *TreeNode {
+	return &TreeNode{
+		text:       text,
+		color:      Styles.PrimaryTextColor,
+		indent:     2,
+		expanded:   true,
+		selectable: true,
+	}
+}
+
+// Walk traverses this node's subtree in depth-first, pre-order (NLR) order and
+// calls the provided callback function on each traversed node (which includes
+// this node) with the traversed node and its parent node (nil for this node).
+// The callback returns whether traversal should continue with the traversed
+// node's child nodes (true) or not recurse any deeper (false).
+func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
+	n.parent = nil
+	nodes := []*TreeNode{n}
+	for len(nodes) > 0 {
+		// Pop the top node and process it.
+		node := nodes[len(nodes)-1]
+		nodes = nodes[:len(nodes)-1]
+		if !callback(node, node.parent) {
+			// Don't add any children.
+			continue
+		}
+
+		// Add children in reverse order.
+		for index := len(node.children) - 1; index >= 0; index-- {
+			node.children[index].parent = node
+			nodes = append(nodes, node.children[index])
+		}
+	}
+
+	return n
+}
+
+// SetReference allows you to store a reference of any type in this node. This
+// will allow you to establish a mapping between the TreeView hierarchy and your
+// internal tree structure.
+func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
+	n.reference = reference
+	return n
+}
+
+// GetReference returns this node's reference object.
+func (n *TreeNode) GetReference() interface{} {
+	return n.reference
+}
+
+// SetChildren sets this node's child nodes.
+func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
+	n.children = childNodes
+	return n
+}
+
+// GetChildren returns this node's children.
+func (n *TreeNode) GetChildren() []*TreeNode {
+	return n.children
+}
+
+// ClearChildren removes all child nodes from this node.
+func (n *TreeNode) ClearChildren() *TreeNode {
+	n.children = nil
+	return n
+}
+
+// AddChild adds a new child node to this node.
+func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
+	n.children = append(n.children, node)
+	return n
+}
+
+// SetSelectable sets a flag indicating whether this node can be selected by
+// the user.
+func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
+	n.selectable = selectable
+	return n
+}
+
+// SetSelectedFunc sets a function which is called when the user selects this
+// node by hitting Enter when it is selected.
+func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
+	n.selected = handler
+	return n
+}
+
+// SetExpanded sets whether or not this node's child nodes should be displayed.
+func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
+	n.expanded = expanded
+	return n
+}
+
+// Expand makes the child nodes of this node appear.
+func (n *TreeNode) Expand() *TreeNode {
+	n.expanded = true
+	return n
+}
+
+// Collapse makes the child nodes of this node disappear.
+func (n *TreeNode) Collapse() *TreeNode {
+	n.expanded = false
+	return n
+}
+
+// ExpandAll expands this node and all descendent nodes.
+func (n *TreeNode) ExpandAll() *TreeNode {
+	n.Walk(func(node, parent *TreeNode) bool {
+		node.expanded = true
+		return true
+	})
+	return n
+}
+
+// CollapseAll collapses this node and all descendent nodes.
+func (n *TreeNode) CollapseAll() *TreeNode {
+	n.Walk(func(node, parent *TreeNode) bool {
+		n.expanded = false
+		return true
+	})
+	return n
+}
+
+// IsExpanded returns whether the child nodes of this node are visible.
+func (n *TreeNode) IsExpanded() bool {
+	return n.expanded
+}
+
+// SetText sets the node's text which is displayed.
+func (n *TreeNode) SetText(text string) *TreeNode {
+	n.text = text
+	return n
+}
+
+// SetColor sets the node's text color.
+func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
+	n.color = color
+	return n
+}
+
+// SetIndent sets an additional indentation for this node's text. A value of 0
+// keeps the text as far left as possible with a minimum of line graphics. Any
+// value greater than that moves the text to the right.
+func (n *TreeNode) SetIndent(indent int) *TreeNode {
+	n.indent = indent
+	return n
+}
+
+// TreeView displays tree structures. A tree consists of nodes (TreeNode
+// objects) where each node has zero or more child nodes and exactly one parent
+// node (except for the root node which has no parent node).
+//
+// The SetRoot() function is used to specify the root of the tree. Other nodes
+// are added locally to the root node or any of its descendents. See the
+// TreeNode documentation for details on node attributes. (You can use
+// SetReference() to store a reference to nodes of your own tree structure.)
+//
+// Nodes can be selected by calling SetCurrentNode(). The user can navigate the
+// selection or the tree by using the following keys:
+//
+//   - j, down arrow, right arrow: Move (the selection) down by one node.
+//   - k, up arrow, left arrow: Move (the selection) up by one node.
+//   - g, home: Move (the selection) to the top.
+//   - G, end: Move (the selection) to the bottom.
+//   - Ctrl-F, page down: Move (the selection) down by one page.
+//   - Ctrl-B, page up: Move (the selection) up by one page.
+//
+// Selected nodes can trigger the "selected" callback when the user hits Enter.
+//
+// The root node corresponds to level 0, its children correspond to level 1,
+// their children to level 2, and so on. Per default, the first level that is
+// displayed is 0, i.e. the root node. You can call SetTopLevel() to hide
+// levels.
+//
+// If graphics are turned on (see SetGraphics()), lines indicate the tree's
+// hierarchy. Alternative (or additionally), you can set different prefixes
+// using SetPrefixes() for different levels, for example to display hierarchical
+// bullet point lists.
+//
+// See https://github.com/rivo/tview/wiki/TreeView for an example.
+type TreeView struct {
+	*Box
+
+	// The root node.
+	root *TreeNode
+
+	// The currently selected node or nil if no node is selected.
+	currentNode *TreeNode
+
+	// The movement to be performed during the call to Draw(), one of the
+	// constants defined above.
+	movement int
+
+	// The top hierarchical level shown. (0 corresponds to the root level.)
+	topLevel int
+
+	// Strings drawn before the nodes, based on their level.
+	prefixes []string
+
+	// Vertical scroll offset.
+	offsetY int
+
+	// If set to true, all node texts will be aligned horizontally.
+	align bool
+
+	// If set to true, the tree structure is drawn using lines.
+	graphics bool
+
+	// The color of the lines.
+	graphicsColor tcell.Color
+
+	// An optional function which is called when the user has navigated to a new
+	// tree node.
+	changed func(node *TreeNode)
+
+	// An optional function which is called when a tree item was selected.
+	selected func(node *TreeNode)
+
+	// The visible nodes, top-down, as set by process().
+	nodes []*TreeNode
+}
+
+// NewTreeView returns a new tree view.
+func NewTreeView() *TreeView {
+	return &TreeView{
+		Box:           NewBox(),
+		graphics:      true,
+		graphicsColor: Styles.GraphicsColor,
+	}
+}
+
+// SetRoot sets the root node of the tree.
+func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
+	t.root = root
+	return t
+}
+
+// GetRoot returns the root node of the tree. If no such node was previously
+// set, nil is returned.
+func (t *TreeView) GetRoot() *TreeNode {
+	return t.root
+}
+
+// SetCurrentNode sets the currently selected node. Provide nil to clear all
+// selections. Selected nodes must be visible and selectable, or else the
+// selection will be changed to the top-most selectable and visible node.
+//
+// This function does NOT trigger the "changed" callback.
+func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
+	t.currentNode = node
+	return t
+}
+
+// GetCurrentNode returns the currently selected node or nil of no node is
+// currently selected.
+func (t *TreeView) GetCurrentNode() *TreeNode {
+	return t.currentNode
+}
+
+// SetTopLevel sets the first tree level that is visible with 0 referring to the
+// root, 1 to the root's child nodes, and so on. Nodes above the top level are
+// not displayed.
+func (t *TreeView) SetTopLevel(topLevel int) *TreeView {
+	t.topLevel = topLevel
+	return t
+}
+
+// SetPrefixes defines the strings drawn before the nodes' texts. This is a
+// slice of strings where each element corresponds to a node's hierarchy level,
+// i.e. 0 for the root, 1 for the root's children, and so on (levels will
+// cycle).
+//
+// For example, to display a hierarchical list with bullet points:
+//
+//   treeView.SetGraphics(false).
+//     SetPrefixes([]string{"* ", "- ", "x "})
+func (t *TreeView) SetPrefixes(prefixes []string) *TreeView {
+	t.prefixes = prefixes
+	return t
+}
+
+// SetAlign controls the horizontal alignment of the node texts. If set to true,
+// all texts except that of top-level nodes will be placed in the same column.
+// If set to false, they will indent with the hierarchy.
+func (t *TreeView) SetAlign(align bool) *TreeView {
+	t.align = align
+	return t
+}
+
+// SetGraphics sets a flag which determines whether or not line graphics are
+// drawn to illustrate the tree's hierarchy.
+func (t *TreeView) SetGraphics(showGraphics bool) *TreeView {
+	t.graphics = showGraphics
+	return t
+}
+
+// SetGraphicsColor sets the colors of the lines used to draw the tree structure.
+func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView {
+	t.graphicsColor = color
+	return t
+}
+
+// SetChangedFunc sets the function which is called when the user navigates to
+// a new tree node.
+func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView {
+	t.changed = handler
+	return t
+}
+
+// SetSelectedFunc sets the function which is called when the user selects a
+// node by pressing Enter on the current selection.
+func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView {
+	t.selected = handler
+	return t
+}
+
+// process builds the visible tree, populates the "nodes" slice, and processes
+// pending selection actions.
+func (t *TreeView) process() {
+	_, _, _, height := t.GetInnerRect()
+
+	// Determine visible nodes and their placement.
+	var graphicsOffset, maxTextX int
+	t.nodes = nil
+	selectedIndex := -1
+	topLevelGraphicsX := -1
+	if t.graphics {
+		graphicsOffset = 1
+	}
+	t.root.Walk(func(node, parent *TreeNode) bool {
+		// Set node attributes.
+		node.parent = parent
+		if parent == nil {
+			node.level = 0
+			node.graphicsX = 0
+			node.textX = 0
+		} else {
+			node.level = parent.level + 1
+			node.graphicsX = parent.textX
+			node.textX = node.graphicsX + graphicsOffset + node.indent
+		}
+		if !t.graphics && t.align {
+			// Without graphics, we align nodes on the first column.
+			node.textX = 0
+		}
+		if node.level == t.topLevel {
+			// No graphics for top level nodes.
+			node.graphicsX = 0
+			node.textX = 0
+		}
+		if node.textX > maxTextX {
+			maxTextX = node.textX
+		}
+		if node == t.currentNode && node.selectable {
+			selectedIndex = len(t.nodes)
+		}
+
+		// Maybe we want to skip this level.
+		if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) {
+			topLevelGraphicsX = node.graphicsX
+		}
+
+		// Add and recurse (if desired).
+		if node.level >= t.topLevel {
+			t.nodes = append(t.nodes, node)
+		}
+		return node.expanded
+	})
+
+	// Post-process positions.
+	for _, node := range t.nodes {
+		// If text must align, we correct the positions.
+		if t.align && node.level > t.topLevel {
+			node.textX = maxTextX
+		}
+
+		// If we skipped levels, shift to the left.
+		if topLevelGraphicsX > 0 {
+			node.graphicsX -= topLevelGraphicsX
+			node.textX -= topLevelGraphicsX
+		}
+	}
+
+	// Process selection. (Also trigger events if necessary.)
+	if selectedIndex >= 0 {
+		// Move the selection.
+		newSelectedIndex := selectedIndex
+	MovementSwitch:
+		switch t.movement {
+		case treeUp:
+			for newSelectedIndex > 0 {
+				newSelectedIndex--
+				if t.nodes[newSelectedIndex].selectable {
+					break MovementSwitch
+				}
+			}
+			newSelectedIndex = selectedIndex
+		case treeDown:
+			for newSelectedIndex < len(t.nodes)-1 {
+				newSelectedIndex++
+				if t.nodes[newSelectedIndex].selectable {
+					break MovementSwitch
+				}
+			}
+			newSelectedIndex = selectedIndex
+		case treeHome:
+			for newSelectedIndex = 0; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
+				if t.nodes[newSelectedIndex].selectable {
+					break MovementSwitch
+				}
+			}
+			newSelectedIndex = selectedIndex
+		case treeEnd:
+			for newSelectedIndex = len(t.nodes) - 1; newSelectedIndex >= 0; newSelectedIndex-- {
+				if t.nodes[newSelectedIndex].selectable {
+					break MovementSwitch
+				}
+			}
+			newSelectedIndex = selectedIndex
+		case treePageUp:
+			if newSelectedIndex+height < len(t.nodes) {
+				newSelectedIndex += height
+			} else {
+				newSelectedIndex = len(t.nodes) - 1
+			}
+			for ; newSelectedIndex < len(t.nodes); newSelectedIndex++ {
+				if t.nodes[newSelectedIndex].selectable {
+					break MovementSwitch
+				}
+			}
+			newSelectedIndex = selectedIndex
+		case treePageDown:
+			if newSelectedIndex >= height {
+				newSelectedIndex -= height
+			} else {
+				newSelectedIndex = 0
+			}
+			for ; newSelectedIndex >= 0; newSelectedIndex-- {
+				if t.nodes[newSelectedIndex].selectable {
+					break MovementSwitch
+				}
+			}
+			newSelectedIndex = selectedIndex
+		}
+		t.currentNode = t.nodes[newSelectedIndex]
+		if newSelectedIndex != selectedIndex {
+			t.movement = treeNone
+			if t.changed != nil {
+				t.changed(t.currentNode)
+			}
+		}
+		selectedIndex = newSelectedIndex
+
+		// Move selection into viewport.
+		if selectedIndex-t.offsetY >= height {
+			t.offsetY = selectedIndex - height + 1
+		}
+		if selectedIndex < t.offsetY {
+			t.offsetY = selectedIndex
+		}
+	} else {
+		// If selection is not visible or selectable, select the first candidate.
+		if t.currentNode != nil {
+			for index, node := range t.nodes {
+				if node.selectable {
+					selectedIndex = index
+					t.currentNode = node
+					break
+				}
+			}
+		}
+		if selectedIndex < 0 {
+			t.currentNode = nil
+		}
+	}
+}
+
+// Draw draws this primitive onto the screen.
+func (t *TreeView) Draw(screen tcell.Screen) {
+	t.Box.Draw(screen)
+	if t.root == nil {
+		return
+	}
+
+	// Build the tree if necessary.
+	if t.nodes == nil {
+		t.process()
+	}
+	defer func() {
+		t.nodes = nil // Rebuild during next call to Draw()
+	}()
+
+	// Scroll the tree.
+	x, y, width, height := t.GetInnerRect()
+	switch t.movement {
+	case treeUp:
+		t.offsetY--
+	case treeDown:
+		t.offsetY++
+	case treeHome:
+		t.offsetY = 0
+	case treeEnd:
+		t.offsetY = len(t.nodes)
+	case treePageUp:
+		t.offsetY -= height
+	case treePageDown:
+		t.offsetY += height
+	}
+	t.movement = treeNone
+
+	// Fix invalid offsets.
+	if t.offsetY >= len(t.nodes)-height {
+		t.offsetY = len(t.nodes) - height
+	}
+	if t.offsetY < 0 {
+		t.offsetY = 0
+	}
+
+	// Draw the tree.
+	posY := y
+	lineStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.graphicsColor)
+	for index, node := range t.nodes {
+		// Skip invisible parts.
+		if posY >= y+height+1 {
+			break
+		}
+		if index < t.offsetY {
+			continue
+		}
+
+		// Draw the graphics.
+		if t.graphics {
+			// Draw ancestor branches.
+			ancestor := node.parent
+			for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel {
+				if ancestor.graphicsX >= width {
+					continue
+				}
+
+				// Draw a branch if this ancestor is not a last child.
+				if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor {
+					if posY-1 >= y && ancestor.textX > ancestor.graphicsX {
+						PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, t.graphicsColor)
+					}
+					if posY < y+height {
+						screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle)
+					}
+				}
+				ancestor = ancestor.parent
+			}
+
+			if node.textX > node.graphicsX && node.graphicsX < width {
+				// Connect to the node above.
+				if posY-1 >= y && t.nodes[index-1].graphicsX <= node.graphicsX && t.nodes[index-1].textX > node.graphicsX {
+					PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, t.graphicsColor)
+				}
+
+				// Join this node.
+				if posY < y+height {
+					screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle)
+					for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ {
+						screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle)
+					}
+				}
+			}
+		}
+
+		// Draw the prefix and the text.
+		if node.textX < width && posY < y+height {
+			// Prefix.
+			var prefixWidth int
+			if len(t.prefixes) > 0 {
+				_, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color)
+			}
+
+			// Text.
+			if node.textX+prefixWidth < width {
+				style := tcell.StyleDefault.Foreground(node.color)
+				if node == t.currentNode {
+					style = tcell.StyleDefault.Background(node.color).Foreground(t.backgroundColor)
+				}
+				printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style)
+			}
+		}
+
+		// Advance.
+		posY++
+	}
+}
+
+// InputHandler returns the handler for this primitive.
+func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
+	return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
+		// Because the tree is flattened into a list only at drawing time, we also
+		// postpone the (selection) movement to drawing time.
+		switch key := event.Key(); key {
+		case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight:
+			t.movement = treeDown
+		case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft:
+			t.movement = treeUp
+		case tcell.KeyHome:
+			t.movement = treeHome
+		case tcell.KeyEnd:
+			t.movement = treeEnd
+		case tcell.KeyPgDn, tcell.KeyCtrlF:
+			t.movement = treePageDown
+		case tcell.KeyPgUp, tcell.KeyCtrlB:
+			t.movement = treePageUp
+		case tcell.KeyRune:
+			switch event.Rune() {
+			case 'g':
+				t.movement = treeHome
+			case 'G':
+				t.movement = treeEnd
+			case 'j':
+				t.movement = treeDown
+			case 'k':
+				t.movement = treeUp
+			}
+		case tcell.KeyEnter:
+			if t.currentNode != nil {
+				if t.selected != nil {
+					t.selected(t.currentNode)
+				}
+				if t.currentNode.selected != nil {
+					t.currentNode.selected()
+				}
+			}
+		}
+
+		t.process()
+	})
+}
diff --git a/vendor/maunium.net/go/tview/util.go b/vendor/maunium.net/go/tview/util.go
index 41e52dd..e408b18 100644
--- a/vendor/maunium.net/go/tview/util.go
+++ b/vendor/maunium.net/go/tview/util.go
@@ -1,11 +1,9 @@
 package tview
 
 import (
-	"fmt"
 	"math"
 	"regexp"
 	"strconv"
-	"strings"
 	"unicode"
 
 	"maunium.net/go/tcell"
@@ -19,97 +17,13 @@ const (
 	AlignRight
 )
 
-// Semigraphical runes.
-const (
-	GraphicsHoriBar             = '\u2500'
-	GraphicsVertBar             = '\u2502'
-	GraphicsTopLeftCorner       = '\u250c'
-	GraphicsTopRightCorner      = '\u2510'
-	GraphicsBottomLeftCorner    = '\u2514'
-	GraphicsBottomRightCorner   = '\u2518'
-	GraphicsLeftT               = '\u251c'
-	GraphicsRightT              = '\u2524'
-	GraphicsTopT                = '\u252c'
-	GraphicsBottomT             = '\u2534'
-	GraphicsCross               = '\u253c'
-	GraphicsDbVertBar           = '\u2550'
-	GraphicsDbHorBar            = '\u2551'
-	GraphicsDbTopLeftCorner     = '\u2554'
-	GraphicsDbTopRightCorner    = '\u2557'
-	GraphicsDbBottomRightCorner = '\u255d'
-	GraphicsDbBottomLeftCorner  = '\u255a'
-	GraphicsEllipsis            = '\u2026'
-)
-
-// joints maps combinations of two graphical runes to the rune that results
-// when joining the two in the same screen cell. The keys of this map are
-// two-rune strings where the value of the first rune is lower than the value
-// of the second rune. Identical runes are not contained.
-var joints = map[string]rune{
-	"\u2500\u2502": GraphicsCross,
-	"\u2500\u250c": GraphicsTopT,
-	"\u2500\u2510": GraphicsTopT,
-	"\u2500\u2514": GraphicsBottomT,
-	"\u2500\u2518": GraphicsBottomT,
-	"\u2500\u251c": GraphicsCross,
-	"\u2500\u2524": GraphicsCross,
-	"\u2500\u252c": GraphicsTopT,
-	"\u2500\u2534": GraphicsBottomT,
-	"\u2500\u253c": GraphicsCross,
-	"\u2502\u250c": GraphicsLeftT,
-	"\u2502\u2510": GraphicsRightT,
-	"\u2502\u2514": GraphicsLeftT,
-	"\u2502\u2518": GraphicsRightT,
-	"\u2502\u251c": GraphicsLeftT,
-	"\u2502\u2524": GraphicsRightT,
-	"\u2502\u252c": GraphicsCross,
-	"\u2502\u2534": GraphicsCross,
-	"\u2502\u253c": GraphicsCross,
-	"\u250c\u2510": GraphicsTopT,
-	"\u250c\u2514": GraphicsLeftT,
-	"\u250c\u2518": GraphicsCross,
-	"\u250c\u251c": GraphicsLeftT,
-	"\u250c\u2524": GraphicsCross,
-	"\u250c\u252c": GraphicsTopT,
-	"\u250c\u2534": GraphicsCross,
-	"\u250c\u253c": GraphicsCross,
-	"\u2510\u2514": GraphicsCross,
-	"\u2510\u2518": GraphicsRightT,
-	"\u2510\u251c": GraphicsCross,
-	"\u2510\u2524": GraphicsRightT,
-	"\u2510\u252c": GraphicsTopT,
-	"\u2510\u2534": GraphicsCross,
-	"\u2510\u253c": GraphicsCross,
-	"\u2514\u2518": GraphicsBottomT,
-	"\u2514\u251c": GraphicsLeftT,
-	"\u2514\u2524": GraphicsCross,
-	"\u2514\u252c": GraphicsCross,
-	"\u2514\u2534": GraphicsBottomT,
-	"\u2514\u253c": GraphicsCross,
-	"\u2518\u251c": GraphicsCross,
-	"\u2518\u2524": GraphicsRightT,
-	"\u2518\u252c": GraphicsCross,
-	"\u2518\u2534": GraphicsBottomT,
-	"\u2518\u253c": GraphicsCross,
-	"\u251c\u2524": GraphicsCross,
-	"\u251c\u252c": GraphicsCross,
-	"\u251c\u2534": GraphicsCross,
-	"\u251c\u253c": GraphicsCross,
-	"\u2524\u252c": GraphicsCross,
-	"\u2524\u2534": GraphicsCross,
-	"\u2524\u253c": GraphicsCross,
-	"\u252c\u2534": GraphicsCross,
-	"\u252c\u253c": GraphicsCross,
-	"\u2534\u253c": GraphicsCross,
-}
-
 // Common regular expressions.
 var (
 	colorPattern     = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([a-zA-Z]+|#[0-9a-zA-Z]{6}|\-)?(:([lbdru]+|\-)?)?)?\]`)
 	regionPattern    = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
 	escapePattern    = regexp.MustCompile(`\[([a-zA-Z0-9_,;: \-\."#]+)\[(\[*)\]`)
 	nonEscapePattern = regexp.MustCompile(`(\[[a-zA-Z0-9_,;: \-\."#]+\[*)\]`)
-	boundaryPattern  = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
+	boundaryPattern  = regexp.MustCompile(`(([[:punct:]]|\n)[ \t\f\r]*|(\s+))`)
 	spacePattern     = regexp.MustCompile(`\s+`)
 )
 
@@ -204,13 +118,12 @@ func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgC
 	defFg, defBg, defAttr := defaultStyle.Decompose()
 	style := defaultStyle.Background(background)
 
-	if fgColor == "-" {
-		style = style.Foreground(defFg)
-	} else if fgColor != "" {
+	style = style.Foreground(defFg)
+	if fgColor != "" {
 		style = style.Foreground(tcell.GetColor(fgColor))
 	}
 
-	if bgColor == "-" {
+	if bgColor == "-" || bgColor == "" && defBg != tcell.ColorDefault {
 		style = style.Background(defBg)
 	} else if bgColor != "" {
 		style = style.Background(tcell.GetColor(bgColor))
@@ -288,8 +201,8 @@ func decomposeString(text string) (colorIndices [][]int, colors [][]string, esca
 // You can change the colors and text styles mid-text by inserting a color tag.
 // See the package description for details.
 //
-// Returns the number of actual runes printed (not including color tags) and the
-// actual width used for the printed runes.
+// Returns the number of actual bytes of the text printed (including color tags)
+// and the actual width used for the printed runes.
 func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
 	return printWithStyle(screen, text, x, y, maxWidth, align, tcell.StyleDefault.Foreground(color))
 }
@@ -302,186 +215,160 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
 	}
 
 	// Decompose the text.
-	colorIndices, colors, escapeIndices, strippedText, _ := decomposeString(text)
+	colorIndices, colors, escapeIndices, strippedText, strippedWidth := decomposeString(text)
 
-	// We deal with runes, not with bytes.
-	runes := []rune(strippedText)
-
-	// This helper function takes positions for a substring of "runes" and returns
-	// a new string corresponding to this substring, making sure printing that
-	// substring will observe color tags.
-	substring := func(from, to int) string {
+	// We want to reduce all alignments to AlignLeft.
+	if align == AlignRight {
+		if strippedWidth <= maxWidth {
+			// There's enough space for the entire text.
+			return printWithStyle(screen, text, x+maxWidth-strippedWidth, y, maxWidth, AlignLeft, style)
+		}
+		// Trim characters off the beginning.
 		var (
-			colorPos, escapePos, runePos, startPos       int
+			bytes, width, colorPos, escapePos, tagOffset int
 			foregroundColor, backgroundColor, attributes string
 		)
-		if from >= len(runes) {
-			return ""
-		}
-		for pos := range text {
-			// Handle color tags.
-			if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
-				if pos == colorIndices[colorPos][1]-1 {
-					if runePos <= from {
-						foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
-					}
-					colorPos++
-				}
-				continue
-			}
-
-			// Handle escape tags.
-			if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
-				if pos == escapeIndices[escapePos][1]-1 {
-					escapePos++
-				} else if pos == escapeIndices[escapePos][1]-2 {
-					continue
-				}
+		_, originalBackground, _ := style.Decompose()
+		iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+			// Update color/escape tag offset and style.
+			if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
+				foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+				style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
+				tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+				colorPos++
 			}
-
-			// Check boundaries.
-			if runePos == from {
-				startPos = pos
-			} else if runePos >= to {
-				return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:pos])
+			if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
+				tagOffset++
+				escapePos++
 			}
-
-			runePos++
-		}
-
-		return fmt.Sprintf(`[%s:%s:%s]%s`, foregroundColor, backgroundColor, attributes, text[startPos:])
-	}
-
-	// We want to reduce everything to AlignLeft.
-	if align == AlignRight {
-		width := 0
-		start := len(runes)
-		for index := start - 1; index >= 0; index-- {
-			w := runewidth.RuneWidth(runes[index])
-			if width+w > maxWidth {
-				break
+			if strippedWidth-screenPos < maxWidth {
+				// We chopped off enough.
+				if escapePos > 0 && textPos+tagOffset-1 >= escapeIndices[escapePos-1][0] && textPos+tagOffset-1 < escapeIndices[escapePos-1][1] {
+					// Unescape open escape sequences.
+					escapeCharPos := escapeIndices[escapePos-1][1] - 2
+					text = text[:escapeCharPos] + text[escapeCharPos+1:]
+				}
+				// Print and return.
+				bytes, width = printWithStyle(screen, text[textPos+tagOffset:], x, y, maxWidth, AlignLeft, style)
+				return true
 			}
-			width += w
-			start = index
-		}
-		for start < len(runes) && runewidth.RuneWidth(runes[start]) == 0 {
-			start++
-		}
-		return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
+			return false
+		})
+		return bytes, width
 	} else if align == AlignCenter {
-		width := runewidth.StringWidth(strippedText)
-		if width == maxWidth {
+		if strippedWidth == maxWidth {
 			// Use the exact space.
 			return printWithStyle(screen, text, x, y, maxWidth, AlignLeft, style)
-		} else if width < maxWidth {
+		} else if strippedWidth < maxWidth {
 			// We have more space than we need.
-			half := (maxWidth - width) / 2
+			half := (maxWidth - strippedWidth) / 2
 			return printWithStyle(screen, text, x+half, y, maxWidth-half, AlignLeft, style)
 		} else {
 			// Chop off runes until we have a perfect fit.
 			var choppedLeft, choppedRight, leftIndex, rightIndex int
-			rightIndex = len(runes) - 1
-			for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
+			rightIndex = len(strippedText)
+			for rightIndex-1 > leftIndex && strippedWidth-choppedLeft-choppedRight > maxWidth {
 				if choppedLeft < choppedRight {
-					leftWidth := runewidth.RuneWidth(runes[leftIndex])
-					choppedLeft += leftWidth
-					leftIndex++
-					for leftIndex < len(runes) && leftIndex < rightIndex && runewidth.RuneWidth(runes[leftIndex]) == 0 {
-						leftIndex++
-					}
+					// Iterate on the left by one character.
+					iterateString(strippedText[leftIndex:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+						choppedLeft += screenWidth
+						leftIndex += textWidth
+						return true
+					})
 				} else {
-					rightWidth := runewidth.RuneWidth(runes[rightIndex])
-					choppedRight += rightWidth
-					rightIndex--
+					// Iterate on the right by one character.
+					iterateStringReverse(strippedText[leftIndex:rightIndex], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+						choppedRight += screenWidth
+						rightIndex -= textWidth
+						return true
+					})
 				}
 			}
-			return printWithStyle(screen, substring(leftIndex, rightIndex), x, y, maxWidth, AlignLeft, style)
+
+			// Add tag offsets and determine start style.
+			var (
+				colorPos, escapePos, tagOffset               int
+				foregroundColor, backgroundColor, attributes string
+			)
+			_, originalBackground, _ := style.Decompose()
+			for index := range strippedText {
+				// We only need the offset of the left index.
+				if index > leftIndex {
+					// We're done.
+					if escapePos > 0 && leftIndex+tagOffset-1 >= escapeIndices[escapePos-1][0] && leftIndex+tagOffset-1 < escapeIndices[escapePos-1][1] {
+						// Unescape open escape sequences.
+						escapeCharPos := escapeIndices[escapePos-1][1] - 2
+						text = text[:escapeCharPos] + text[escapeCharPos+1:]
+					}
+					break
+				}
+
+				// Update color/escape tag offset.
+				if colorPos < len(colorIndices) && index+tagOffset >= colorIndices[colorPos][0] && index+tagOffset < colorIndices[colorPos][1] {
+					if index <= leftIndex {
+						foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+						style = overlayStyle(originalBackground, style, foregroundColor, backgroundColor, attributes)
+					}
+					tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+					colorPos++
+				}
+				if escapePos < len(escapeIndices) && index+tagOffset >= escapeIndices[escapePos][0] && index+tagOffset < escapeIndices[escapePos][1] {
+					tagOffset++
+					escapePos++
+				}
+			}
+			return printWithStyle(screen, text[leftIndex+tagOffset:], x, y, maxWidth, AlignLeft, style)
 		}
 	}
 
 	// Draw text.
-	drawn := 0
-	drawnWidth := 0
 	var (
-		colorPos, escapePos                          int
-		foregroundColor, backgroundColor, attributes string
+		drawn, drawnWidth, colorPos, escapePos, tagOffset int
+		foregroundColor, backgroundColor, attributes      string
 	)
-	runeSequence := make([]rune, 0, 10)
-	runeSeqWidth := 0
-	flush := func() {
-		if len(runeSequence) == 0 {
-			return // Nothing to flush.
-		}
-
-		// Print the rune sequence.
-		finalX := x + drawnWidth
-		_, _, finalStyle, _ := screen.GetContent(finalX, y)
-		_, background, _ := finalStyle.Decompose()
-		finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
-		var comb []rune
-		if len(runeSequence) > 1 {
-			// Allocate space for the combining characters only when necessary.
-			comb = make([]rune, len(runeSequence)-1)
-			copy(comb, runeSequence[1:])
-		}
-		for offset := 0; offset < runeSeqWidth; offset++ {
-			// To avoid undesired effects, we place the same character in all cells.
-			screen.SetContent(finalX+offset, y, runeSequence[0], comb, finalStyle)
+	iterateString(strippedText, func(main rune, comb []rune, textPos, length, screenPos, screenWidth int) bool {
+		// Only continue if there is still space.
+		if drawnWidth+screenWidth > maxWidth {
+			return true
 		}
 
-		// Advance and reset.
-		drawn += len(runeSequence)
-		drawnWidth += runeSeqWidth
-		runeSequence = runeSequence[:0]
-		runeSeqWidth = 0
-	}
-	for pos, ch := range text {
 		// Handle color tags.
-		if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
-			flush()
-			if pos == colorIndices[colorPos][1]-1 {
-				foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
-				colorPos++
-			}
-			continue
+		if colorPos < len(colorIndices) && textPos+tagOffset >= colorIndices[colorPos][0] && textPos+tagOffset < colorIndices[colorPos][1] {
+			foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
+			tagOffset += colorIndices[colorPos][1] - colorIndices[colorPos][0]
+			colorPos++
 		}
 
-		// Handle escape tags.
-		if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
-			flush()
-			if pos == escapeIndices[escapePos][1]-1 {
+		// Handle scape tags.
+		if escapePos < len(escapeIndices) && textPos+tagOffset >= escapeIndices[escapePos][0] && textPos+tagOffset < escapeIndices[escapePos][1] {
+			if textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+				tagOffset++
 				escapePos++
-			} else if pos == escapeIndices[escapePos][1]-2 {
-				continue
 			}
 		}
 
-		// Check if we have enough space for this rune.
-		chWidth := runewidth.RuneWidth(ch)
-		if drawnWidth+chWidth > maxWidth {
-			break // No. We're done then.
-		}
-
-		// Put this rune in the queue.
-		if chWidth == 0 {
-			// If this is not a modifier, we treat it as a space character.
-			if len(runeSequence) == 0 {
-				ch = ' '
-				chWidth = 1
+		// Print the rune sequence.
+		finalX := x + drawnWidth
+		_, _, finalStyle, _ := screen.GetContent(finalX, y)
+		_, background, _ := finalStyle.Decompose()
+		finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
+		for offset := screenWidth - 1; offset >= 0; offset-- {
+			// To avoid undesired effects, we populate all cells.
+			if offset == 0 {
+				screen.SetContent(finalX+offset, y, main, comb, finalStyle)
+			} else {
+				screen.SetContent(finalX+offset, y, ' ', nil, finalStyle)
 			}
-		} else {
-			// We have a character. Flush all previous runes.
-			flush()
 		}
-		runeSequence = append(runeSequence, ch)
-		runeSeqWidth += chWidth
 
-	}
-	if drawnWidth+runeSeqWidth <= maxWidth {
-		flush()
-	}
+		// Advance.
+		drawn += length
+		drawnWidth += screenWidth
+
+		return false
+	})
 
-	return drawn, drawnWidth
+	return drawn + tagOffset + len(escapeIndices), drawnWidth
 }
 
 // PrintSimple prints white text to the screen at the given position.
@@ -507,131 +394,86 @@ func WordWrap(text string, width int) (lines []string) {
 	colorTagIndices, _, escapeIndices, strippedText, _ := decomposeString(text)
 
 	// Find candidate breakpoints.
-	breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
-
-	// This helper function adds a new line to the result slice. The provided
-	// positions are in stripped index space.
-	addLine := func(from, to int) {
-		// Shift indices back to original index space.
-		var colorTagIndex, escapeIndex int
-		for colorTagIndex < len(colorTagIndices) && to >= colorTagIndices[colorTagIndex][0] ||
-			escapeIndex < len(escapeIndices) && to >= escapeIndices[escapeIndex][0] {
-			past := 0
-			if colorTagIndex < len(colorTagIndices) {
-				tagWidth := colorTagIndices[colorTagIndex][1] - colorTagIndices[colorTagIndex][0]
-				if colorTagIndices[colorTagIndex][0] < from {
-					from += tagWidth
-					to += tagWidth
-					colorTagIndex++
-				} else if colorTagIndices[colorTagIndex][0] < to {
-					to += tagWidth
-					colorTagIndex++
-				} else {
-					past++
-				}
-			} else {
-				past++
-			}
-			if escapeIndex < len(escapeIndices) {
-				tagWidth := escapeIndices[escapeIndex][1] - escapeIndices[escapeIndex][0]
-				if escapeIndices[escapeIndex][0] < from {
-					from += tagWidth
-					to += tagWidth
-					escapeIndex++
-				} else if escapeIndices[escapeIndex][0] < to {
-					to += tagWidth
-					escapeIndex++
-				} else {
-					past++
-				}
-			} else {
-				past++
-			}
-			if past == 2 {
-				break // All other indices are beyond the requested string.
+	breakpoints := boundaryPattern.FindAllStringSubmatchIndex(strippedText, -1)
+	// Results in one entry for each candidate. Each entry is an array a of
+	// indices into strippedText where a[6] < 0 for newline/punctuation matches
+	// and a[4] < 0 for whitespace matches.
+
+	// Process stripped text one character at a time.
+	var (
+		colorPos, escapePos, breakpointPos, tagOffset      int
+		lastBreakpoint, lastContinuation, currentLineStart int
+		lineWidth, continuationWidth                       int
+		newlineBreakpoint                                  bool
+	)
+	unescape := func(substr string, startIndex int) string {
+		// A helper function to unescape escaped tags.
+		for index := escapePos; index >= 0; index-- {
+			if index < len(escapeIndices) && startIndex > escapeIndices[index][0] && startIndex < escapeIndices[index][1]-1 {
+				pos := escapeIndices[index][1] - 2 - startIndex
+				return substr[:pos] + substr[pos+1:]
 			}
 		}
-		lines = append(lines, text[from:to])
+		return substr
 	}
+	iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
+		// Handle colour tags.
+		if colorPos < len(colorTagIndices) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
+			tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
+			colorPos++
+		}
 
-	// Determine final breakpoints.
-	var start, lastEnd, newStart, breakPoint int
-	for {
-		// What's our candidate string?
-		var candidate string
-		if breakPoint < len(breakPoints) {
-			candidate = text[start:breakPoints[breakPoint][1]]
-		} else {
-			candidate = text[start:]
+		// Handle escape tags.
+		if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
+			tagOffset++
+			escapePos++
 		}
-		candidate = strings.TrimRightFunc(candidate, unicode.IsSpace)
-
-		if runewidth.StringWidth(candidate) >= width {
-			// We're past the available width.
-			if lastEnd > start {
-				// Use the previous candidate.
-				addLine(start, lastEnd)
-				start = newStart
-			} else {
-				// We have no previous candidate. Make a hard break.
-				var lineWidth int
-				for index, ch := range text {
-					if index < start {
-						continue
-					}
-					chWidth := runewidth.RuneWidth(ch)
-					if lineWidth > 0 && lineWidth+chWidth >= width {
-						addLine(start, index)
-						start = index
-						break
-					}
-					lineWidth += chWidth
-				}
-			}
-		} else {
-			// We haven't hit the right border yet.
-			if breakPoint >= len(breakPoints) {
-				// It's the last line. We're done.
-				if len(candidate) > 0 {
-					addLine(start, len(strippedText))
-				}
-				break
-			} else {
-				// We have a new candidate.
-				lastEnd = start + len(candidate)
-				newStart = breakPoints[breakPoint][1]
-				breakPoint++
-			}
+
+		// Check if a break is warranted.
+		afterContinuation := lastContinuation > 0 && textPos+tagOffset >= lastContinuation
+		noBreakpoint := lastContinuation == 0
+		beyondWidth := lineWidth > 0 && lineWidth > width
+		if beyondWidth && noBreakpoint {
+			// We need a hard break without a breakpoint.
+			lines = append(lines, unescape(text[currentLineStart:textPos+tagOffset], currentLineStart))
+			currentLineStart = textPos + tagOffset
+			lineWidth = continuationWidth
+		} else if afterContinuation && (beyondWidth || newlineBreakpoint) {
+			// Break at last breakpoint or at newline.
+			lines = append(lines, unescape(text[currentLineStart:lastBreakpoint], currentLineStart))
+			currentLineStart = lastContinuation
+			lineWidth = continuationWidth
+			lastBreakpoint, lastContinuation, newlineBreakpoint = 0, 0, false
 		}
-	}
 
-	return
-}
+		// Is this a breakpoint?
+		if breakpointPos < len(breakpoints) && textPos == breakpoints[breakpointPos][0] {
+			// Yes, it is. Set up breakpoint infos depending on its type.
+			lastBreakpoint = breakpoints[breakpointPos][0] + tagOffset
+			lastContinuation = breakpoints[breakpointPos][1] + tagOffset
+			newlineBreakpoint = main == '\n'
+			if breakpoints[breakpointPos][6] < 0 && !newlineBreakpoint {
+				lastBreakpoint++ // Don't skip punctuation.
+			}
+			breakpointPos++
+		}
 
-// PrintJoinedBorder prints a border graphics rune into the screen at the given
-// position with the given color, joining it with any existing border graphics
-// rune. Background colors are preserved. At this point, only regular single
-// line borders are supported.
-func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
-	previous, _, style, _ := screen.GetContent(x, y)
-	style = style.Foreground(color)
-
-	// What's the resulting rune?
-	var result rune
-	if ch == previous {
-		result = ch
-	} else {
-		if ch < previous {
-			previous, ch = ch, previous
+		// Once we hit the continuation point, we start buffering widths.
+		if textPos+tagOffset < lastContinuation {
+			continuationWidth = 0
 		}
-		result = joints[string(previous)+string(ch)]
-	}
-	if result == 0 {
-		result = ch
+
+		lineWidth += screenWidth
+		continuationWidth += screenWidth
+		return false
+	})
+
+	// Flush the rest.
+	if currentLineStart < len(text) {
+		lines = append(lines, unescape(text[currentLineStart:], currentLineStart))
 	}
 
-	// We only print something if we have something.
-	screen.SetContent(x, y, result, nil, style)
+	return
 }
 
 // Escape escapes the given text such that color and/or region tags are not
@@ -643,3 +485,121 @@ func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color
 func Escape(text string) string {
 	return nonEscapePattern.ReplaceAllString(text, "$1[]")
 }
+
+// iterateString iterates through the given string one printed character at a
+// time. For each such character, the callback function is called with the
+// Unicode code points of the character (the first rune and any combining runes
+// which may be nil if there aren't any), the starting position (in bytes)
+// within the original string, its length in bytes, the screen position of the
+// character, and the screen width of it. The iteration stops if the callback
+// returns true. This function returns true if the iteration was stopped before
+// the last character.
+func iterateString(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
+	var (
+		runes               []rune
+		lastZeroWidthJoiner bool
+		startIndex          int
+		startPos            int
+		pos                 int
+	)
+
+	// Helper function which invokes the callback.
+	flush := func(index int) bool {
+		var comb []rune
+		if len(runes) > 1 {
+			comb = runes[1:]
+		}
+		return callback(runes[0], comb, startIndex, index-startIndex, startPos, pos-startPos)
+	}
+
+	for index, r := range text {
+		if unicode.In(r, unicode.Lm, unicode.M) || r == '\u200d' {
+			lastZeroWidthJoiner = r == '\u200d'
+		} else {
+			// We have a rune that's not a modifier. It could be the beginning of a
+			// new character.
+			if !lastZeroWidthJoiner {
+				if len(runes) > 0 {
+					// It is. Invoke callback.
+					if flush(index) {
+						return true // We're done.
+					}
+					// Reset rune store.
+					runes = runes[:0]
+					startIndex = index
+					startPos = pos
+				}
+				pos += runewidth.RuneWidth(r)
+			} else {
+				lastZeroWidthJoiner = false
+			}
+		}
+		runes = append(runes, r)
+	}
+
+	// Flush any remaining runes.
+	if len(runes) > 0 {
+		flush(len(text))
+	}
+
+	return false
+}
+
+// iterateStringReverse iterates through the given string in reverse, starting
+// from the end of the string, one printed character at a time. For each such
+// character, the callback function is called with the Unicode code points of
+// the character (the first rune and any combining runes which may be nil if
+// there aren't any), the starting position (in bytes) within the original
+// string, its length in bytes, the screen position of the character, and the
+// screen width of it. The iteration stops if the callback returns true. This
+// function returns true if the iteration was stopped before the last character.
+func iterateStringReverse(text string, callback func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool) bool {
+	type runePos struct {
+		r     rune
+		pos   int  // The byte position of the rune in the original string.
+		width int  // The screen width of the rune.
+		mod   bool // Modifier or zero-width-joiner.
+	}
+
+	// We use the following:
+	// len(text) >= number of runes in text.
+
+	// Put all runes into a runePos slice in reverse.
+	runesReverse := make([]runePos, len(text))
+	index := len(text) - 1
+	for pos, ch := range text {
+		runesReverse[index].r = ch
+		runesReverse[index].pos = pos
+		runesReverse[index].width = runewidth.RuneWidth(ch)
+		runesReverse[index].mod = unicode.In(ch, unicode.Lm, unicode.M) || ch == '\u200d'
+		index--
+	}
+	runesReverse = runesReverse[index+1:]
+
+	// Parse reverse runes.
+	var screenWidth int
+	buffer := make([]rune, len(text)) // We fill this up from the back so it's forward again.
+	bufferPos := len(text)
+	stringWidth := runewidth.StringWidth(text)
+	for index, r := range runesReverse {
+		// Put this rune into the buffer.
+		bufferPos--
+		buffer[bufferPos] = r.r
+
+		// Do we need to flush the buffer?
+		if r.pos == 0 || !r.mod && runesReverse[index+1].r != '\u200d' {
+			// Yes, invoke callback.
+			var comb []rune
+			if len(text)-bufferPos > 1 {
+				comb = buffer[bufferPos+1:]
+			}
+			if callback(r.r, comb, r.pos, len(text)-r.pos, stringWidth-screenWidth, r.width) {
+				return true
+			}
+			screenWidth += r.width
+			bufferPos = len(text)
+		}
+	}
+
+	return false
+}
-- 
cgit v1.2.3-70-g09d2