[gopls-release-branch.0.6] all: merge master into gopls-release-branch.0.6
b261fe96 go/tools: add vet check for direct comparion of reflect.Values
e78b40c7 internal/lsp: switch to 'go get -u ./...' for transitive upgrades
3ac76b8b gopls/internal/coverage: better error messages and output
c6024661 Revert "internal/lsp/cache: disable GOPACKAGESDRIVER"
35a91593 go/internal/gcimporter: ensure that imported floats are of float kind
5d334387 go/internal/gcimporter: merge go1.11 tests back into gcimporter_test.go
11c3f835 internal/lsp/source: fix Highlight for std and 3rd-party packages
8c34cc9c internal/lsp/cache: fix race condition in snapshot's initializedErr
0deaffd2 gopls/internal/regtest: re-enable the gc_details regtest for -short
af364066 internal/lsp/source: respond with the underlying type to Type Definition requests for composite types
682c7e60 godoc/static: link to golang.org for content moved out of Go root
ca627f83 internal/lsp/semantic: fix some type definitions
04595890 internal/lsp/protocol/typescript: small cleanups and add tsconfig.json
b0e994dc internal/jsonrpc2_v2: move the idle timeout handling out of the server
95535753 internal/lsp: remove unnecessary call to WorkspacePackages in mod tidy
769264cf internal/lsp/source: fix docs for fields of anonymous structs/interfaces
cb7d5993 internal/lsp/source: skip blank identifiers during the function extraction
94a19427 internal/lsp/completion: move postfix completions behind option
09058ab0 internal/lsp/source/completion: add postfix snippet completions
2c039f7f internal/event/label: prevent unsafe get of non-string
4c8e4a8b go/analysis/passes/inspect: fix typo in comment
63ea654b internal/lsp: hold the gc details lock when storing diagnostics
7f6d50ea internal/lsp/source: fix hover and completion with dot imports
b3556f0f internal/lsp: remove some unused parameters, mostly in the cache package
09a00c1a internal/lsp: fix support for SourceFixAll code actions
877f9c48 internal/jsonrpc2_v2: an updated jsonrpc2 library
52cb7724 gopls: upgrade mvdan.cc/gofumpt to v0.1.1
a1191f37 gopls: upgrade honnef.co/go/tools to v0.1.3
d8aeb16b internal/lsp: add defaultLibrary mod for basic types
aa0c7234 internal/lsp/cache: get control of reloadOrphanedFiles
d7a25ada gopls/integration: remove obsolete code
9bdb4197 gopls/internal: add coverage command to compute test coverage
d2e11a2b present: don't drop commands that immediately follow text
695b1675 cmd/present2md: allow comments to be retained in translated document
a14ff98a present: retain complete caption command when parsing captions
1aac171f Update emacs.md - add .dir-locals.el example
2c4a8865 internal/lsp: remove module diagnostics from code actions
9b614f5d internal/lsp/cache: tolerate analysis panics better
9e9211a9 gopls/internal/regtest: fix race when printing regtest state on falure
6d45e3d9 internal/lsp: add a temp workspace per folder, and a helper command
e409f121 internal/lsp: add snippet completion for t.Fatal errs
8e4f4c86 godoc: delete GoogleCN logic
44abc2a7 internal/lsp: only load by view when there are no go.mod files
Change-Id: Ie5d628f640a8edaeb41c4cdf30aafefbc892ec7c
diff --git a/cmd/godoc/main.go b/cmd/godoc/main.go
index 02f0eb6..8780f8b 100644
--- a/cmd/godoc/main.go
+++ b/cmd/godoc/main.go
@@ -192,8 +192,10 @@
}
if *templateDir != "" {
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
+ fs.Bind("/favicon.ico", vfs.OS(*templateDir), "/favicon.ico", vfs.BindReplace)
} else {
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
+ fs.Bind("/favicon.ico", mapfs.New(static.Files), "/favicon.ico", vfs.BindReplace)
}
// Get the GOMOD value, use it to determine if godoc is being invoked in module mode.
diff --git a/cmd/present2md/main.go b/cmd/present2md/main.go
index 15ee3e6..64be64b 100644
--- a/cmd/present2md/main.go
+++ b/cmd/present2md/main.go
@@ -93,6 +93,11 @@
return fmt.Errorf("%v: already markdown", file)
}
+ // Convert all comments before parsing the document.
+ // The '//' comment is treated as normal text and so
+ // is passed through the translation unaltered.
+ data = bytes.Replace(data, []byte("\n#"), []byte("\n//"), -1)
+
doc, err := present.Parse(bytes.NewReader(data), file, 0)
if err != nil {
return err
diff --git a/go/analysis/passes/inspect/inspect.go b/go/analysis/passes/inspect/inspect.go
index 2856df1..4bb652a 100644
--- a/go/analysis/passes/inspect/inspect.go
+++ b/go/analysis/passes/inspect/inspect.go
@@ -3,8 +3,8 @@
// license that can be found in the LICENSE file.
// Package inspect defines an Analyzer that provides an AST inspector
-// (golang.org/x/tools/go/ast/inspect.Inspect) for the syntax trees of a
-// package. It is only a building block for other analyzers.
+// (golang.org/x/tools/go/ast/inspector.Inspector) for the syntax trees
+// of a package. It is only a building block for other analyzers.
//
// Example of use in another analysis:
//
diff --git a/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go b/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go
new file mode 100644
index 0000000..ef21f0e
--- /dev/null
+++ b/go/analysis/passes/reflectvaluecompare/reflectvaluecompare.go
@@ -0,0 +1,99 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package reflectvaluecompare defines an Analyzer that checks for accidentally
+// using == or reflect.DeepEqual to compare reflect.Value values.
+// See issues 43993 and 18871.
+package reflectvaluecompare
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+const Doc = `check for comparing reflect.Value values with == or reflect.DeepEqual
+
+The reflectvaluecompare checker looks for expressions of the form:
+
+ v1 == v2
+ v1 != v2
+ reflect.DeepEqual(v1, v2)
+
+where v1 or v2 are reflect.Values. Comparing reflect.Values directly
+is almost certainly not correct, as it compares the reflect package's
+internal representation, not the underlying value.
+Likely what is intended is:
+
+ v1.Interface() == v2.Interface()
+ v1.Interface() != v2.Interface()
+ reflect.DeepEqual(v1.Interface(), v2.Interface())
+`
+
+var Analyzer = &analysis.Analyzer{
+ Name: "reflectvaluecompare",
+ Doc: Doc,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+ nodeFilter := []ast.Node{
+ (*ast.BinaryExpr)(nil),
+ (*ast.CallExpr)(nil),
+ }
+ inspect.Preorder(nodeFilter, func(n ast.Node) {
+ switch n := n.(type) {
+ case *ast.BinaryExpr:
+ if n.Op != token.EQL && n.Op != token.NEQ {
+ return
+ }
+ if isReflectValue(pass, n.X) || isReflectValue(pass, n.Y) {
+ if n.Op == token.EQL {
+ pass.ReportRangef(n, "avoid using == with reflect.Value")
+ } else {
+ pass.ReportRangef(n, "avoid using != with reflect.Value")
+ }
+ }
+ case *ast.CallExpr:
+ fn, ok := typeutil.Callee(pass.TypesInfo, n).(*types.Func)
+ if !ok {
+ return
+ }
+ if fn.FullName() == "reflect.DeepEqual" && (isReflectValue(pass, n.Args[0]) || isReflectValue(pass, n.Args[1])) {
+ pass.ReportRangef(n, "avoid using reflect.DeepEqual with reflect.Value")
+ }
+ }
+ })
+ return nil, nil
+}
+
+// isReflectValue reports whether the type of e is reflect.Value.
+func isReflectValue(pass *analysis.Pass, e ast.Expr) bool {
+ tv, ok := pass.TypesInfo.Types[e]
+ if !ok { // no type info, something else is wrong
+ return false
+ }
+ // See if the type is reflect.Value
+ named, ok := tv.Type.(*types.Named)
+ if !ok {
+ return false
+ }
+ if obj := named.Obj(); obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != "reflect" || obj.Name() != "Value" {
+ return false
+ }
+ if _, ok := e.(*ast.CompositeLit); ok {
+ // This is reflect.Value{}. Don't treat that as an error.
+ // Users should probably use x.IsValid() rather than x == reflect.Value{}, but the latter isn't wrong.
+ return false
+ }
+ return true
+}
diff --git a/go/analysis/passes/reflectvaluecompare/reflectvaluecompare_test.go b/go/analysis/passes/reflectvaluecompare/reflectvaluecompare_test.go
new file mode 100644
index 0000000..88fc8f2
--- /dev/null
+++ b/go/analysis/passes/reflectvaluecompare/reflectvaluecompare_test.go
@@ -0,0 +1,17 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package reflectvaluecompare_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "golang.org/x/tools/go/analysis/passes/reflectvaluecompare"
+)
+
+func TestReflectValueCompare(t *testing.T) {
+ testdata := analysistest.TestData()
+ analysistest.Run(t, testdata, reflectvaluecompare.Analyzer, "a")
+}
diff --git a/go/analysis/passes/reflectvaluecompare/testdata/src/a/a.go b/go/analysis/passes/reflectvaluecompare/testdata/src/a/a.go
new file mode 100644
index 0000000..e069c6d
--- /dev/null
+++ b/go/analysis/passes/reflectvaluecompare/testdata/src/a/a.go
@@ -0,0 +1,51 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains tests for the reflectvaluecompare checker.
+
+package a
+
+import (
+ "reflect"
+)
+
+func f() {
+ var x, y reflect.Value
+ var a, b interface{}
+ _ = x == y // want `avoid using == with reflect.Value`
+ _ = x == a // want `avoid using == with reflect.Value`
+ _ = a == x // want `avoid using == with reflect.Value`
+ _ = a == b
+
+ // Comparing to reflect.Value{} is ok.
+ _ = a == reflect.Value{}
+ _ = reflect.Value{} == a
+ _ = reflect.Value{} == reflect.Value{}
+}
+func g() {
+ var x, y reflect.Value
+ var a, b interface{}
+ _ = x != y // want `avoid using != with reflect.Value`
+ _ = x != a // want `avoid using != with reflect.Value`
+ _ = a != x // want `avoid using != with reflect.Value`
+ _ = a != b
+
+ // Comparing to reflect.Value{} is ok.
+ _ = a != reflect.Value{}
+ _ = reflect.Value{} != a
+ _ = reflect.Value{} != reflect.Value{}
+}
+func h() {
+ var x, y reflect.Value
+ var a, b interface{}
+ reflect.DeepEqual(x, y) // want `avoid using reflect.DeepEqual with reflect.Value`
+ reflect.DeepEqual(x, a) // want `avoid using reflect.DeepEqual with reflect.Value`
+ reflect.DeepEqual(a, x) // want `avoid using reflect.DeepEqual with reflect.Value`
+ reflect.DeepEqual(a, b)
+
+ // Comparing to reflect.Value{} is ok.
+ reflect.DeepEqual(reflect.Value{}, a)
+ reflect.DeepEqual(a, reflect.Value{})
+ reflect.DeepEqual(reflect.Value{}, reflect.Value{})
+}
diff --git a/go/internal/gcimporter/gcimporter11_test.go b/go/internal/gcimporter/gcimporter11_test.go
deleted file mode 100644
index 3f2f0a0..0000000
--- a/go/internal/gcimporter/gcimporter11_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2018 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 gcimporter
-
-import (
- "go/types"
- "runtime"
- "strings"
- "testing"
-
- "golang.org/x/tools/internal/testenv"
-)
-
-var importedObjectTests = []struct {
- name string
- want string
-}{
- // non-interfaces
- {"crypto.Hash", "type Hash uint"},
- {"go/ast.ObjKind", "type ObjKind int"},
- {"go/types.Qualifier", "type Qualifier func(*Package) string"},
- {"go/types.Comparable", "func Comparable(T Type) bool"},
- {"math.Pi", "const Pi untyped float"},
- {"math.Sin", "func Sin(x float64) float64"},
- {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"},
- {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"},
-
- // interfaces
- {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"},
- {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"},
- {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"},
- {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
- {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"},
- {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"},
- {"go/types.Type", "type Type interface{String() string; Underlying() Type}"},
-}
-
-func TestImportedTypes(t *testing.T) {
- testenv.NeedsGo1Point(t, 11)
- skipSpecialPlatforms(t)
-
- // This package only handles gc export data.
- if runtime.Compiler != "gc" {
- t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
- }
-
- for _, test := range importedObjectTests {
- s := strings.Split(test.name, ".")
- if len(s) != 2 {
- t.Fatal("inconsistent test data")
- }
- importPath := s[0]
- objName := s[1]
-
- pkg, err := Import(make(map[string]*types.Package), importPath, ".", nil)
- if err != nil {
- t.Error(err)
- continue
- }
-
- obj := pkg.Scope().Lookup(objName)
- if obj == nil {
- t.Errorf("%s: object not found", test.name)
- continue
- }
-
- got := types.ObjectString(obj, types.RelativeTo(pkg))
- if got != test.want {
- t.Errorf("%s: got %q; want %q", test.name, got, test.want)
- }
-
- if named, _ := obj.Type().(*types.Named); named != nil {
- verifyInterfaceMethodRecvs(t, named, 0)
- }
- }
-}
-
-// verifyInterfaceMethodRecvs verifies that method receiver types
-// are named if the methods belong to a named interface type.
-func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) {
- // avoid endless recursion in case of an embedding bug that lead to a cycle
- if level > 10 {
- t.Errorf("%s: embeds itself", named)
- return
- }
-
- iface, _ := named.Underlying().(*types.Interface)
- if iface == nil {
- return // not an interface
- }
-
- // check explicitly declared methods
- for i := 0; i < iface.NumExplicitMethods(); i++ {
- m := iface.ExplicitMethod(i)
- recv := m.Type().(*types.Signature).Recv()
- if recv == nil {
- t.Errorf("%s: missing receiver type", m)
- continue
- }
- if recv.Type() != named {
- t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named)
- }
- }
-
- // check embedded interfaces (if they are named, too)
- for i := 0; i < iface.NumEmbeddeds(); i++ {
- // embedding of interfaces cannot have cycles; recursion will terminate
- if etype, _ := iface.EmbeddedType(i).(*types.Named); etype != nil {
- verifyInterfaceMethodRecvs(t, etype, level+1)
- }
- }
-}
-func TestIssue25301(t *testing.T) {
- testenv.NeedsGo1Point(t, 11)
- skipSpecialPlatforms(t)
-
- // This package only handles gc export data.
- if runtime.Compiler != "gc" {
- t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
- }
-
- // On windows, we have to set the -D option for the compiler to avoid having a drive
- // letter and an illegal ':' in the import path - just skip it (see also issue #3483).
- if runtime.GOOS == "windows" {
- t.Skip("avoid dealing with relative paths/drive letters on windows")
- }
-
- compileAndImportPkg(t, "issue25301")
-}
diff --git a/go/internal/gcimporter/gcimporter_test.go b/go/internal/gcimporter/gcimporter_test.go
index 63daca9..cfafbbf 100644
--- a/go/internal/gcimporter/gcimporter_test.go
+++ b/go/internal/gcimporter/gcimporter_test.go
@@ -10,6 +10,7 @@
import (
"bytes"
"fmt"
+ "go/constant"
"go/types"
"io/ioutil"
"os"
@@ -286,6 +287,139 @@
t.Logf("tested %d imports", nimports)
}
+var importedObjectTests = []struct {
+ name string
+ want string
+}{
+ // non-interfaces
+ {"crypto.Hash", "type Hash uint"},
+ {"go/ast.ObjKind", "type ObjKind int"},
+ {"go/types.Qualifier", "type Qualifier func(*Package) string"},
+ {"go/types.Comparable", "func Comparable(T Type) bool"},
+ {"math.Pi", "const Pi untyped float"},
+ {"math.Sin", "func Sin(x float64) float64"},
+ {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"},
+ {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"},
+
+ // interfaces
+ {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"},
+ {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"},
+ {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"},
+ {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
+ {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"},
+ {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"},
+ {"go/types.Type", "type Type interface{String() string; Underlying() Type}"},
+}
+
+func TestImportedTypes(t *testing.T) {
+ testenv.NeedsGo1Point(t, 11)
+ skipSpecialPlatforms(t)
+
+ // This package only handles gc export data.
+ if runtime.Compiler != "gc" {
+ t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
+ }
+
+ for _, test := range importedObjectTests {
+ obj := importObject(t, test.name)
+ if obj == nil {
+ continue // error reported elsewhere
+ }
+ got := types.ObjectString(obj, types.RelativeTo(obj.Pkg()))
+ if got != test.want {
+ t.Errorf("%s: got %q; want %q", test.name, got, test.want)
+ }
+
+ if named, _ := obj.Type().(*types.Named); named != nil {
+ verifyInterfaceMethodRecvs(t, named, 0)
+ }
+ }
+}
+
+func TestImportedConsts(t *testing.T) {
+ testenv.NeedsGo1Point(t, 11)
+ skipSpecialPlatforms(t)
+
+ tests := []struct {
+ name string
+ want constant.Kind
+ }{
+ {"math.Pi", constant.Float},
+ {"math.MaxFloat64", constant.Float},
+ {"math.MaxInt64", constant.Int},
+ }
+
+ for _, test := range tests {
+ obj := importObject(t, test.name)
+ if got := obj.(*types.Const).Val().Kind(); got != test.want {
+ t.Errorf("%s: imported as constant.Kind(%v), want constant.Kind(%v)", test.name, got, test.want)
+ }
+ }
+}
+
+// importObject imports the object specified by a name of the form
+// <import path>.<object name>, e.g. go/types.Type.
+//
+// If any errors occur they are reported via t and the resulting object will
+// be nil.
+func importObject(t *testing.T, name string) types.Object {
+ s := strings.Split(name, ".")
+ if len(s) != 2 {
+ t.Fatal("inconsistent test data")
+ }
+ importPath := s[0]
+ objName := s[1]
+
+ pkg, err := Import(make(map[string]*types.Package), importPath, ".", nil)
+ if err != nil {
+ t.Error(err)
+ return nil
+ }
+
+ obj := pkg.Scope().Lookup(objName)
+ if obj == nil {
+ t.Errorf("%s: object not found", name)
+ return nil
+ }
+ return obj
+}
+
+// verifyInterfaceMethodRecvs verifies that method receiver types
+// are named if the methods belong to a named interface type.
+func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) {
+ // avoid endless recursion in case of an embedding bug that lead to a cycle
+ if level > 10 {
+ t.Errorf("%s: embeds itself", named)
+ return
+ }
+
+ iface, _ := named.Underlying().(*types.Interface)
+ if iface == nil {
+ return // not an interface
+ }
+
+ // check explicitly declared methods
+ for i := 0; i < iface.NumExplicitMethods(); i++ {
+ m := iface.ExplicitMethod(i)
+ recv := m.Type().(*types.Signature).Recv()
+ if recv == nil {
+ t.Errorf("%s: missing receiver type", m)
+ continue
+ }
+ if recv.Type() != named {
+ t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named)
+ }
+ }
+
+ // check embedded interfaces (if they are named, too)
+ for i := 0; i < iface.NumEmbeddeds(); i++ {
+ // embedding of interfaces cannot have cycles; recursion will terminate
+ if etype, _ := iface.EmbeddedType(i).(*types.Named); etype != nil {
+ verifyInterfaceMethodRecvs(t, etype, level+1)
+ }
+ }
+}
+
func TestIssue5815(t *testing.T) {
skipSpecialPlatforms(t)
@@ -501,6 +635,24 @@
}
}
+func TestIssue25301(t *testing.T) {
+ testenv.NeedsGo1Point(t, 11)
+ skipSpecialPlatforms(t)
+
+ // This package only handles gc export data.
+ if runtime.Compiler != "gc" {
+ t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
+ }
+
+ // On windows, we have to set the -D option for the compiler to avoid having a drive
+ // letter and an illegal ':' in the import path - just skip it (see also issue #3483).
+ if runtime.GOOS == "windows" {
+ t.Skip("avoid dealing with relative paths/drive letters on windows")
+ }
+
+ compileAndImportPkg(t, "issue25301")
+}
+
func importPkg(t *testing.T, path, srcDir string) *types.Package {
pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil)
if err != nil {
diff --git a/go/internal/gcimporter/iimport.go b/go/internal/gcimporter/iimport.go
index b236deb..8ed8bc6 100644
--- a/go/internal/gcimporter/iimport.go
+++ b/go/internal/gcimporter/iimport.go
@@ -473,6 +473,14 @@
switch {
case exp > 0:
x = constant.Shift(x, token.SHL, uint(exp))
+ // Ensure that the imported Kind is Float, else this constant may run into
+ // bitsize limits on overlarge integers. Eventually we can instead adopt
+ // the approach of CL 288632, but that CL relies on go/constant APIs that
+ // were introduced in go1.13.
+ //
+ // TODO(rFindley): sync the logic here with tip Go once we no longer
+ // support go1.12.
+ x = constant.ToFloat(x)
case exp < 0:
d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp))
x = constant.BinaryOp(x, token.QUO, d)
diff --git a/godoc/godoc.go b/godoc/godoc.go
index 6d4d115..a88aa12 100644
--- a/godoc/godoc.go
+++ b/godoc/godoc.go
@@ -398,9 +398,8 @@
}
type PageInfo struct {
- Dirname string // directory containing the package
- Err error // error or nil
- GoogleCN bool // page is being served from golang.google.cn
+ Dirname string // directory containing the package
+ Err error // error or nil
Mode PageInfoMode // display metadata from query string
@@ -614,8 +613,7 @@
err := p.ExampleHTML.Execute(&buf, struct {
Name, Doc, Code, Play, Output string
- GoogleCN bool
- }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
+ }{eg.Name, eg.Doc, code, play, out})
if err != nil {
log.Print(err)
}
diff --git a/godoc/golangorgenv/golangorgenv.go b/godoc/golangorgenv/golangorgenv.go
deleted file mode 100644
index 0b96eec..0000000
--- a/godoc/golangorgenv/golangorgenv.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 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 golangorgenv provides environment information for programs running at
-// golang.org and its subdomains.
-package golangorgenv
-
-import (
- "log"
- "os"
- "strconv"
-)
-
-var (
- checkCountry = boolEnv("GOLANGORG_CHECK_COUNTRY")
- enforceHosts = boolEnv("GOLANGORG_ENFORCE_HOSTS")
-)
-
-// CheckCountry reports whether country restrictions should be enforced.
-func CheckCountry() bool {
- return checkCountry
-}
-
-// EnforceHosts reports whether host filtering should be enforced.
-func EnforceHosts() bool {
- return enforceHosts
-}
-
-func boolEnv(key string) bool {
- v := os.Getenv(key)
- if v == "" {
- // TODO(dmitshur): In the future, consider detecting if running in App Engine,
- // and if so, making the environment variables mandatory rather than optional.
- return false
- }
- b, err := strconv.ParseBool(v)
- if err != nil {
- log.Fatalf("environment variable %s (%q) must be a boolean", key, v)
- }
- return b
-}
diff --git a/godoc/page.go b/godoc/page.go
index daf4dc9..126790f 100644
--- a/godoc/page.go
+++ b/godoc/page.go
@@ -9,9 +9,6 @@
"os"
"path/filepath"
"runtime"
- "strings"
-
- "golang.org/x/tools/godoc/golangorgenv"
)
// Page describes the contents of the top-level godoc webpage.
@@ -22,7 +19,6 @@
SrcPath string
Query string
Body []byte
- GoogleCN bool // page is being served from golang.google.cn
TreeView bool // page needs to contain treeview related js and css
// filled in by ServePage
@@ -57,26 +53,6 @@
Title: "File " + relpath,
Subtitle: relpath,
Body: applyTemplate(p.ErrorHTML, "errorHTML", err),
- GoogleCN: googleCN(r),
GoogleAnalytics: p.GoogleAnalytics,
})
}
-
-// googleCN reports whether request r is considered
-// to be served from golang.google.cn.
-func googleCN(r *http.Request) bool {
- if r.FormValue("googlecn") != "" {
- return true
- }
- if strings.HasSuffix(r.Host, ".cn") {
- return true
- }
- if !golangorgenv.CheckCountry() {
- return false
- }
- switch r.Header.Get("X-Appengine-Country") {
- case "", "ZZ", "CN":
- return true
- }
- return false
-}
diff --git a/godoc/search.go b/godoc/search.go
index 0e7509a..33e4feb 100644
--- a/godoc/search.go
+++ b/godoc/search.go
@@ -122,7 +122,6 @@
Tabtitle: query,
Query: query,
Body: body.Bytes(),
- GoogleCN: googleCN(r),
})
}
diff --git a/godoc/server.go b/godoc/server.go
index 8c9b1b9..48e8d95 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -328,7 +328,6 @@
info.TypeInfoIndex[ti.Name] = i
}
- info.GoogleCN = googleCN(r)
var body []byte
if info.Dirname == "/src" {
body = applyTemplate(h.p.PackageRootHTML, "packageRootHTML", info)
@@ -340,7 +339,6 @@
Tabtitle: tabtitle,
Subtitle: subtitle,
Body: body,
- GoogleCN: info.GoogleCN,
TreeView: hasTreeView,
})
}
@@ -610,7 +608,6 @@
SrcPath: relpath,
Tabtitle: relpath,
Body: buf.Bytes(),
- GoogleCN: googleCN(r),
})
}
@@ -689,7 +686,6 @@
SrcPath: relpath,
Tabtitle: relpath,
Body: applyTemplate(p.DirlistHTML, "dirlistHTML", list),
- GoogleCN: googleCN(r),
})
}
@@ -726,7 +722,6 @@
page := Page{
Title: meta.Title,
Subtitle: meta.Subtitle,
- GoogleCN: googleCN(r),
}
// evaluate as template if indicated
diff --git a/godoc/static/analysis/help.html b/godoc/static/analysis/help.html
index 023c07d..dd1b606 100644
--- a/godoc/static/analysis/help.html
+++ b/godoc/static/analysis/help.html
@@ -62,7 +62,7 @@
Clicking on the identifier that defines a named type causes a panel
to appear, displaying information about the named type, including
its size and alignment in bytes, its
- <a href='http://golang.org/ref/spec#Method_sets'>method set</a>, and its
+ <a href='https://golang.org/ref/spec#Method_sets'>method set</a>, and its
<i>implements</i> relation: the set of types T that are assignable to
or from this type U where at least one of T or U is an interface.
diff --git a/godoc/static/example.html b/godoc/static/example.html
index 1e86b25..a7f6691 100644
--- a/godoc/static/example.html
+++ b/godoc/static/example.html
@@ -13,9 +13,7 @@
<div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a>
- {{if not $.GoogleCN}}
<a class="share" title="Share this code">Share</a>
- {{end}}
</div>
</div>
{{else}}
diff --git a/godoc/static/favicon.ico b/godoc/static/favicon.ico
new file mode 100644
index 0000000..8d22584
--- /dev/null
+++ b/godoc/static/favicon.ico
Binary files differ
diff --git a/godoc/static/gen.go b/godoc/static/gen.go
index 85c6714..bfbc5bd 100644
--- a/godoc/static/gen.go
+++ b/godoc/static/gen.go
@@ -35,6 +35,7 @@
"dirlist.html",
"error.html",
"example.html",
+ "favicon.ico",
"godoc.html",
"godocs.js",
"images/minus.gif",
diff --git a/godoc/static/godoc.html b/godoc/static/godoc.html
index 86cacb7..6204d33 100644
--- a/godoc/static/godoc.html
+++ b/godoc/static/godoc.html
@@ -39,7 +39,7 @@
<form method="GET" action="/search">
<div id="menu">
{{if (and .Playground .Title)}}
-<a id="playgroundButton" href="http://play.golang.org/" title="Show Go Playground">Play</a>
+<a id="playgroundButton" href="https://play.golang.org/" title="Show Go Playground">Play</a>
{{end}}
<span class="search-box"><input type="search" id="search" name="q" placeholder="Search" aria-label="Search" required><button type="submit"><span><!-- magnifying glass: --><svg width="24" height="24" viewBox="0 0 24 24"><title>submit search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg></span></button></span>
</div>
@@ -60,9 +60,7 @@
<div class="buttons">
<a class="run" title="Run this code [shift-enter]">Run</a>
<a class="fmt" title="Format this code">Format</a>
- {{if not $.GoogleCN}}
<a class="share" title="Share this code">Share</a>
- {{end}}
</div>
</div>
{{end}}
@@ -100,8 +98,8 @@
the content of this page is licensed under the
Creative Commons Attribution 3.0 License,
and code is licensed under a <a href="/LICENSE">BSD license</a>.<br>
-<a href="/doc/tos.html">Terms of Service</a> |
-<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
+<a href="https://golang.org/doc/tos.html">Terms of Service</a> |
+<a href="https://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
</div>
</div><!-- .container -->
diff --git a/godoc/static/packageroot.html b/godoc/static/packageroot.html
index c246c79..6af463b 100644
--- a/godoc/static/packageroot.html
+++ b/godoc/static/packageroot.html
@@ -38,7 +38,7 @@
</div>
<div class="expanded">
<h2 class="toggleButton" title="Click to hide Standard library section">Standard library â–¾</h2>
- <img alt="" class="gopher" src="/doc/gopher/pkg.png"/>
+ <img alt="" class="gopher" src="https://golang.org/doc/gopher/pkg.png"/>
<div class="pkg-dir">
<table>
<tr>
@@ -115,7 +115,7 @@
<h3 id="subrepo">Sub-repositories</h3>
<p>
These packages are part of the Go Project but outside the main Go tree.
- They are developed under looser <a href="/doc/go1compat">compatibility requirements</a> than the Go core.
+ They are developed under looser <a href="https://golang.org/doc/go1compat">compatibility requirements</a> than the Go core.
Install them with "<a href="/cmd/go/#hdr-Download_and_install_packages_and_dependencies">go get</a>".
</p>
<ul>
diff --git a/godoc/static/static.go b/godoc/static/static.go
index 3dab9ea..35aece4 100644
--- a/godoc/static/static.go
+++ b/godoc/static/static.go
@@ -23,7 +23,7 @@
"analysis/error1.png": "\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x04\xc1\x00\x00\x00\xbd\x08\x03\x00\x00\x00\x8d\xa5\x9a\x96\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x02\xfdPLTE\x00\x01\x00\x02\x05\x01\x0a\x03\x01\x05\x07\x03\x1e\x01\x00\x0b\x0d\x0a\x10\x12\x0f\x16\x17\x13y\x00\x01\x19\x1b\x18\x1b\x1b\x14\x83\x01\x00\x8c\x00\x00\x8c\x00\x04\x20\x1f\x19!#\x20#$\"%%\x1e$%#&'%'(&+)\x1e()'++$*,)/,!-/,02/63(241\x98\x1b\x1b786;8,\x82%#:;9><0>?=C@4@B@\xa0*,CEB\x9c.-KH<MH7\x00f\x00\x00g\x00\x00h\x00JKI\x00i\x01\x00j\x02MOL\x04l\x05TO>\x09n\x07PRP\xa9:<WRA\x0co\x09\x0fp\x0bTVT\x0cr\x17\x11t\x19^YG\xaaDCY[X,`\xae7]\xad\x15v\x1c\\^[\x19y\x1e;b\xac_a^faO>e\xaf*{!bda)}*Bh\xb2egdLj\xafpiR.\x81.Em\xb1ikh\xb3XX0\x8301\x841/\x858Jq\xb5mol;\x86:Vu\xb4>\x89={t\\tvs@\x8b?B\x8c@@\x8dG\\z\xbay{xJ\x8eI^~\xb8\x84|d\xbbkkL\x90K~\x7f}O\x93Mb\x83\xbdN\x95UW\x95V\x82\x84\x81\x8f\x85hm\x87\xbdZ\x98Y[\x9aZ\x87\x89\x86\xc0{|]\x9c\\\\\x9dcr\x8d\xc2\x96\x8cnd\x9dd\x8c\x8e\x8b{\x8f\xc0f\x9fg\x90\x92\x8f~\x93\xc4j\xa3jy\x96\xc5\x94\x96\x93s\xa4l\xa1\x96xq\xa5s\x81\x99\xc3\x97\x99\x96\x83\x9b\xc5\xa5\x9b}u\xaaw\x9a\x9c\x98\xc7\x91\x92\x9c\x9e\x9b~\xaby\x88\xa0\xca\x9e\xa0\x9d\x81\xad|\xab\xa0\x82\x8e\xa2\xc7\xa1\xa3\xa0\xa2\xa4\xa1\x81\xb1\x85\x92\xa5\xca\x8b\xb2\x88\xb4\xa7\x83\xa6\xa8\xa5\x8d\xb5\x8a\x97\xab\xd0\xa9\xab\xa8\x8e\xb6\x8c\x8d\xb8\x93\xab\xad\xaa\xb9\xad\x88\x9f\xae\xce\xae\xb0\xad\x96\xba\x96\x98\xbb\x98\xa3\xb2\xd2\xb0\xb2\xaf\xd9\xa8\xa6\x9b\xbe\x9a\xc2\xb5\x90\xb4\xb6\xb3\xa3\xbf\x9d\xac\xb7\xd2\xb6\xb8\xb5\xa2\xc0\xa4\xb7\xb9\xb6\xdb\xb0\xb3\xa5\xc4\xa7\xba\xbc\xb9\xb1\xbd\xd7\xa7\xc6\xa9\xbd\xbf\xbc\xa8\xc7\xab\xcc\xbf\x99\xaf\xc6\xab\xb8\xc0\xd5\xbf\xc1\xbe\xb2\xc9\xae\xd0\xc3\x9e\xc2\xc4\xc1\xbc\xc4\xd9\xe2\xbd\xbe\xc4\xc6\xc3\xb2\xcd\xb7\xbb\xcd\xb9\xc0\xc8\xdd\xbb\xca\xde\xc7\xc9\xc5\xd8\xc9\x9d\xbd\xcf\xbb\xc6\xca\xda\xc0\xcc\xda\xca\xcc\xc9\xbf\xd2\xbe\xd7\xcf\xa1\xcd\xcf\xcc\xc4\xd0\xde\xcb\xcf\xdf\xc8\xd3\xc1\xe0\xd1\xa5\xd0\xd2\xce\xc8\xd6\xc9\xea\xcc\xcb\xd1\xd3\xd0\xd2\xd3\xdd\xca\xd8\xcb\xd3\xd5\xd2\xdf\xd6\xa8\xcd\xd6\xde\xcc\xda\xce\xe6\xd6\xaa\xd6\xd8\xd5\xd4\xdb\xd0\xeb\xd4\xd2\xd2\xda\xe3\xd8\xd9\xe3\xea\xdb\xaf\xda\xdc\xd9\xd7\xde\xd3\xe1\xdb\xda\xd5\xdf\xda\xdc\xdd\xe7\xdc\xdf\xdb\xd7\xe1\xdc\xde\xe0\xdd\xdc\xe1\xe3\xf0\xe0\xb3\xe0\xe2\xdf\xe2\xe4\xe1\xee\xe1\xe2\xdf\xe5\xe7\xe4\xe6\xe3\xe8\xe5\xea\xe5\xe7\xe4\xf2\xe4\xe5\xe6\xe8\xe5\xe3\xe9\xeb\xe7\xe9\xe6\xe8\xea\xe7\xeb\xed\xea\xf6\xf0\xef\xf2\xf4\xf1\xfe\xf8\xf7\xf9\xfb\xf8\xfe\xff\xfc!l\x99S\x00\x00\x20\x00IDATx^\xed\x9d\x0fXT\xd7\x9d\xf7\xf7\xd9\xd9\xd8w\xd3$C\x03\x9b\x12-K^\xd6fm\xfb\xdc\xe1\x19\xa9\xbe+\x0c\x8d\x1d\x14\xd7?1+\xbeVQ\x8aV1\x1b1\x18\xe7a\x13\xa3\x82\xc6\x84L\x0a\x19%)\x20B\xe6\x89E\x1eY3*\xbe\">\x19Me\xc1\x96hqm\x12\xe8S\xa6\xd1\xb1\xb6\xda\xe4\xf2(\xac\xe4y\xaf\xe7\xed\xa6\xe5y\xcf9\xf7\xdf\xb9\xc3\xbd3(\x03\x97\xd1\xdf\xe7\xd1;\x87;\xbf\xf3\xbb\xe7\x9c\xb9\xf7;\xe7\xfc\xee\x99{\xfejh\x14\x20\x00\x00\x00\xf3\x18\x1a\xfa\xabH*\x15\x8eH\xee\x01\x00\x00\xc6\x10P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x173\x15,p\xa0\xaa\xa2\xfa\x18O\x92\xc2\x99\xba\x8a\xba\xceH\x19\x00\x00\x004\x98\xa8`A\xb7\xb7\xb3\xbb\xb3\xae\x0aK\x98\xe0\xf5\x9c\xee>Yq2R\x16\x00\x00\x00\x16\x13\x15\xac\xcb\xdd\x8d\xb7\xbc\x1bw\xbd\xceT\\#;*\xf8Hy\x00\x00\x00\x18LT0D\xf5*\xe8\x0e\x20\xe4=@wTB'\x0c\x00\x80;\xc1L\x05\xc3\xf4wW{\x05\x84\xea|\xf4/\xef\xfe\x08\xe6\x00\x00\x00,\xa6*X\xd0\xedv{HO\xecX\xe5\x00\xde\x0ex\xea\"\xe5\x00\x00\x00`0U\xc1P0\xd0^M\"\xf9\xbc\xc7\x1b\xec\x0fx\xdd\xd5\x912\x00\x00\x000\x98\xab`\x98\x81\xea&\xbc\xbd\xe6\xc5\xdd\xb1\x16\xaf7\x925\x00\x00\x00\x83\x89\x0a6\x20\xd0\x973n\xfa\xda\x7fM@\x95\xc7\xc2f\x00\x00\x00\xd0b\x9e\x82\x09\x95b\xf8\xfeL\x05V\xb0A\x92\xea\"\xb7%\x01\x00\x00F\x8cy\x0a\x86\xaa\xe8\x14\x0a:\x8a\xect\x07\x11\xe2\xab|\x91\xb2\x00\x00\x00\xb0\x98\xa8`]\xee\xa6\xce\xeeN\x1a\xc9\xefr\x9f\xec>S\xe9\x85\x09\xad\x00\x00\xdc\x11&*\x18\x0a4UW\xd4\xb5\x90y\x14\xe8t\xb5\xc7{Z\x88\x94\x01\x00\x00@\x83\x99\x0a\x06\x00\x000:@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]bK\xc1\\.\xbd$\x00\x00\xf7+&+X\xbb[z4~\xc0\xeb9\x10\xf1\x09\xad\x1fs;u\x92\x84z\x8e\xe3\xcaQ9\xde\xd6\x0f\xcf\xa6\xd0k\xe3\x16\xe1\x17\x176K\xeb\x09c\xa7\xe5\xb8\xe3p\x98w\xef\xd0\xd9\xe8\xc0\x07\xab\xc1/5\x1c\xc7\xea\xf7\x8b\x1c\xd7\xa8\xfcq\xeeG\x8eY+[\x97|0,\xafH-k\x1b\x81\x17Gd{w\x8d\xfa\xd9\x8b\x0e\xfb\x8f\xa2\xf28%\xed\x81\xc3\x7fX\xe3\xc3D(\xc3\xfd\x83\xb9\x0a\xc6W\x1c\xab\xa4\x89\xee\x0a_\x97\xaf\xa2;\x82y\x91\xfd\x8aN\x92\xc0\xbf\xcb\xed\xbe\x8e\xae\xbf\xc5\xbd\x1b\xf6)\x89\x1d.;\xde^\xf2\xfbk9\xa3K|8\xcdi\xe1\xae\xe3;t\xa6\xd2z6\x92\xc5p.\xa5\xbd\xd5\x87_\xfa\xde\x9aq\x89\xd9{\xc5o+\x97\xd3gm\x05\x87\x1a\x9f3\x96\x9e>\xff\xf2r\xa4\x87NqX\xbfa\xb8\xabF\xcds\xd4\xba\xd2\xfa\"Y\x8d\x04\xed\x81\xc3\x7fXw\xd7\xea#\x81\xf5\x1b\xa9\x0c@41W\xc1\xbcM\xddT\xc1\x84*\xb2\xc6\xc7\xb1\xaa\xf0_\xca=\\\x89NR\xe4\x02w\x1co[\xb9\x0b(,\xe5v\xf1\xb5\xe3\x0e.6\x14\xa9\xabpG\xce\x14~P\x18\xc9B\x87%b\x17\xb3~\x89v\xb7]Q\x9a\xd5yd\xeb\x0a\xd3y*\xd0W%\xdd\xe2\xd8G\xa2`w\xd3\xa8}\xb8/)\xf4G\xb2\x1a)\xec\x81#|Xw\xd5\xea#@\xe37\xd2\x09\x03D\x11S\x15\xac\xdd\xc3\x07\xa8\x82uV\x90\xb3\x99\xaf\xe8\x0ck\xeeR\xfb].m\x17ll\x15,\x12w\xe7l\xd9\xdd\\K\x85e\xf4\xa5,$\xaf\xaa4s\xa9\xc19\xee\x102\xc2@\xc1t\x8b3f\x0a\xd6\xc3Es\xa0u\x07\x07\xbe\xabV\x1f\x01c\xe5\x17\x88\x84\x99\x0a\xc6Wt!Q\xc1\x9a\xf6\xd3\x1d\xfb\xc3.\xf5\xd1k\xdb\xaa\x93\x94`\x14\x8c/\xcc\xb4e\x15\x9c\xc3\x7f\xbe\xc4\xd9\xeaK\x16\xa4\xad\xa6C\xae+E\x99\xe9\x85\xaea\x17[\xe3\xca\xb4\x05%X?\xcf\xd98\xae\xac\xa7h\xae}\x8d\xe6\x1b\xb4/\x8d\x13\x87d\x82\xc3\xf6jVf\xeb\xd6Yy<\xf1[\xe3\xcaJ/\xe81p\xc6\xda2\x87`\x8as\x98\x13YF\xde\xf7\xaf\xce\xb29~\x94\x854\xa8\xb5`\xd9)^(\x85;Y\xbfXi\xb6\xba2\xc5\xe2\x14fR\x0do\xa6\xbbk\x97\xa5-\xa9\x153\xf6\x14:\xf01\x88\xecc\x05\xdb\x8a\x8fl\xff\x8c\xf1\xa0)\x0e\x83\xbdDn\xbeh6\xea\xa0C<\x9a\xd8\x8d\x96\x0bi\xf0\x01\xe8\xb7\x03[!\xf5\xc0\xca\x87E\x88\xd4\xea\x06\xce\xe4\xe20\xd945fZ]\xc9\xc6\xfa\xd5\x94A\xc7\x19\x10e\xccT0o\x13\x92\x14\xacN\\(\xf2X]8s\x97\xed\x92NR\xe2\x02\xd7<00\xd0L\x14\xac\x99s\xb56\x16p\x1d\x08}\xdch\xe3\x1c\xe553\xc8e\xdf3kA\xfd\xe1\xd5\\\xe8\xc5\xe6\xe2\xb66\xd78\x96\x09h\xa0\xb11k\xc1\x8c\xac\x92\"\xaeW\xe3\xf8\xac\xdf/\xf6DZgq%y\x9cc\xb7\xa3\x86\xfa\xcd\xda]\x9e\x95v\xc1\xc0\x19c\xcb\xece\x8a\xc3\x7f\xe0_\x90\xe7\xf7\xfb\x89\xe8\x9c\xe5\x8a\x1a\x8f\xd7;8\xed\xd8C\xad\x05K\xfd\x0fPG\xd6\x07hY=\xeb\x17+\x0d\xb7\xac\xb1q\x09)No\x16\xb7\xa6\xbccP,\x8e\xbd\xac\xb9\xcc^D\x92\xadi\xcb\xde:^No\x03`\x05\xbbR\xc4\xd5t\xb0\x1e\xd8\xe2\xb0\xd8\xb9E\x8d\x87\x1d\xa4\xf9\xa2\xda\xa8\xe7\xfc\x8d\\\xb9\xdfO\x15C)\xa4\x81\xad~;\xb0\x15b\x0e\xac|X\x91[\xdd\xc0\x99R\x1c&\x1b[c\xb6\xd5\x95l\x1a\xbfl\x19t\x9c\x01Q\xc6D\x05\xeb\xf4\xf0\xb2\x82U\x9e\xa4{NV\x861\xef\xb5\xbd\xa8\x93\x94\xb9\x20}\x0d\xe2\x8b\xb8\xbf\x91|C.y\x8e\xec\xb6\xa7c\xa9+r\xe0\xd4\xca\x05x\xaf\xb0$\xe4bk\xe6\xde\xa5\x7f\xd1/\xcde\x1c\xe93\x0d\xbf\x15\x20\x9d\x90\x99E\xd8\xfc\xb08\x8d\xc3>\x17\x9b\xf5e-7r\xc6\xd8\xb2\x87P\x8b\xc3\x8c;j\x1d\xe4r\xa8\x9d\xa5U0\xb6\x16*~\x07\xdai+A\x0e\x7f\x88\xdfE\x03X\x02\x16,\xc7\xc9\xbe\xdd\xcbm\\:\xd9y\x9cvK\x8fs\xcd\xf8\xad\xac5\xd8`\xb0\x91\x84\xce\xb1\x82\xd5\xd8\x9b\x893m\xe5uG\x91\x0e\xdcSs\x91\xf2F\xb9Q\x95Q$SH}[\xfdv`+\xa4\x1dEJ\x1fV\xc4Vg`\x9c\xb1\xc5a\xb2\xa9I\xc6\xaf\xa6\x0c\x1a\xbfR\x19\x0c\x9c\x01Q\xc5<\x05\xe3+:\x05A\xe8\xae\x14\xf0\x95['\x0e\x1f}\xe1\xfa`/\xd9zu\x922\x17p\xaf\xa2\xa3\xa3\x86\xc6\xc1>\xab]3w\x16G\x83\xddv\"7$N\xd3'N\xb3(\x0b\xb9\xd8\x0a\xe7\x0e\xe2B\x08Ytr\xc22\xfb0\xb7\x14Y\xc1\x1aq6\x1e\xed|\x9e\xec\xa2\xc3\xb8Z\x8e\x9e\xbd:\xce\x18[\xf6\x10Jq\x10s\xce_\xca\x9c\xfbR\xfd9a\x10iaj\xa1\xd2\xcb\xf5\xe7\x15\xad\xec'\xdd\x14\x8d\xdfW\xc9\x9bRq\xd0@\xeb\x1ar\xd5\xb8\x16\xd1\xbf\x16Q\x15U\x87a\x05\xe5e\x92|h+\xaf\xab`jy\xa3\xdb\xa8\x8a\x821\x854\xb0\xd5m\x07\xb6B\xba\x0a\x16\xb1\xd5\x19\x18glq\x98lj\x92\xf1\xab)\x83\x9e\x82\x198\x03\xa2\x8ay\x0av\xd1-\x13@M\xe2\xac02\xac4\xe2\x0a=\x07B\x93\x0aL\x1c\xac\xc3\x91\xb5\xf3\x90?O\xbc\xd8\xc8\xb9DN\x9c\xb3\x9c\x1f\xa1\xe1A\xe7%R\xd7\x8d~\xc5/\x1b\x16\x1f\x11\x91\x15\xec8\xea\xb0!I\xc1\xe8.?G\xef\xa1\xeb8cl\xd9C(\xc5A\xec9\xdfW_\xb8\x08\x0f9\x91\x06\xb6\x16*\x82\xfdB\xda\xb9\xb4\x0b6A\xc7/-N\x07\x1d\x9a\x09+\x0bp\x07\xa9\x80f)XN&\x90\x0d(.\x0a\x1c3\x16\x89\x87\xd6V\xde0\x92O\xcb\x1b\xe5FU\x14\x8c)\xa4\xbe\xad~;\xb0\x15\xd2U\xb0\xc8\xad\xae\xc28c\x8b\xc3dS\x93\x8c_M\x19\xf4\x14\xcc\xc0\x19\x10U\xccS0!Hh\xf7\x04\x83\x02\xeat\x93\xa1\x03\xef\x0es/r+\xd7\xa3\x93T`\x14l\xd1r2\xee(\xd4^l\x9fq\xb5\xe4\xef\xd0\xa0s\xd1\xdc\xb3\x14\x12\xd66\xbc\x9f\xc4(\x98]V0z'\xa1\x9e\xa3#\x1e\x1dg\x8c-{\x88a\xd7R}/\xd6\x01\xd2\x9f\xebk\xb4\xd7j\x0e\xca\xd6\x82a\xc1[\x99(\xabf\x01\x0a\xf1K\x83\xe2\xa48\x99/Q\xb32l\xe1\x9aK\x93s]\xa4]\x98>\x98\xe3B\xef,\x1a?\x1a^yR\x1c\x16\xb5\xbcQnT\xb5\x0f\xa6\x16R\xdfV\xbf\x1dZ#\xf5\xc1\"\xb6:\x03\xe3\x8c-\x8e\xae\x821~[\x87\xf5\xc1d\xbfr\x1fL\xdf\x19\x10U\xccS0\x91\x804\x1f\x8c\x0c#}a\xe6\x83E\xe8\x82\xb1\x0a\x96EN&a\x89\xf6bC\xcb\xb3\xf0\x10\xab\xc7\x1er\xb1\x1d\x17\x835e\xb4\x03t'\x0a\xe6\xc0\xd7\x15?\x97N\xbe\xd2s\xc6\xd8\xb2\x87`\xcf\xe3<\x9c\xf9\x0ay\xaf\x9c\x16\x1d\xe5ic{l-\x18\xd6\xac|\x0e\x15\xae\\\x8dB\xfcf\x91\x88\xcc\"\xec\xd1A\xaa\x89\x04R\x95fj\xd0H\x06\x94\xfd\x99y\x838\xbd\x95\xe8.\x99M\xd1l\xeb\x08\xf1\xa0\x16\x87E-o\x94\x1bUQ0\xa6\x90\xfa\xb6\xfa\xed\xc0VHW\xc1\"\xb6\xba\xbe3\xb68\xba\x0a\xc6\xf8\xd5\x94A\xe3W*\x83\x813\x84zw\x8f\xfc\xc7\x0b@\x04\xccU0!\xd0\xee\x09\\CtN\xfe\xc5\xb0s\xf2K\xd4~W\x89N\x17\x8c\x9d\x93_\xce\x15\xd6\xee^\xc69\xde\xea\xb8\xe2\xb7\xb9:\xd0Y\x97\xcd\x7f\x05]H\xcb*/K\xe7l\xef^\x10gq\xd7\xf8\xfd\xbd8\xe3N\xee\xf9\xc6Cd\x02\xa8\xd0A\xef'\xf5\x868\x1e\xfc\xc0\xef\xb7\xbb\xfc~\x1e]H\xaf\xe9o\xb4\x9d\xeb\x7f>\xef\x12\xa27\xff\xea\x17\xa4\xf7\x20}g\x1a[e/[\x1cr:\xd7\x1c\xcaK\xbbD\x14,\xad\xac\x19\x1b\xb4j\x0e\xac\xd6B\xb3{+\xb7\x1b\xbd\xc5\xd1\x8bF\xf1K\x8a\xb3\xa6\xa3u%)\x8e\x83\xcb,;\x8e\xfd\x92&*\xe2v6\xef\xe4\xc4{\x91\xf6E\xb5\xcd[\xb9z2'\xdf\xf5\xc1`\x7fAf\xebu\x8d\x07\xb58*ly\xa3\xda\xa8\xe2\xbd\xc8\x0e\xfa}\xa5\x14\xd2\xc0\xd6\xa0\x1d\x94\x0a\xb1\x07V?\xac\xc8\xad\xae\xefL-\x0e\x93M\xe3\x81i3&\x9b\xea\x97-\x83\x9e3b\\\xc0\xfd\x08\x01Q\xc2\\\x05\x0b\x900X\x15M\x1d\xa8\xf0\x86\xf9]\xe4\xf5\xb4\"\x9d\xa4\x0a\xfb\xbbH\xa1f\x81\xddQX\xbf\xc0\x96\xf7\"\xfe\xdbv\x81L\xd0\xc1\xfd\x9b\x9e\x82\xf4\xb9%\xef\xdap\xd2%\xc52\xe87\xfe\xf1<G\xfa\xcaf2\x09\x94R\x10\xe2\xf8\xacd[/88\xaeq\x06\x97\xd6Hc\x20\xf6\x12\xd7\xac\xcc\"r:\xea9\xd3\xda*\x87\xd0\x14\x07\x0dlM\xb7\xe7\x91\xcb\xb2qey\x96-3O+`L-4\xbb\x9b\xd3\xcea\xdd\x10;0\xb2_<\xd4*/\x9c!\x16gy}\xd9\xa24\x878SM\xa8Y\x92\xb6\xa4V\xec\xd7\xf6\x14f\xcdX~\x88\xfe.\x92\xe3\xce\xe2\x82qe\x1a\x0fjqT\xd8\xf2F\xb3Q\x07f\xd1\xbd6m!\x0d>\x00\x83vP*\xc4\x1eX\xf9\xb0\x88A\x84V\xd7w\xa6\x16\x87\xc9\xa6\xf5\xc0\xb4\x99\x9aM\xf5\xcb\x96A\xcf\x191\xaew\x84\xfb\xf5.pG\x98\xab`#\xe6U\xeec\x9d\xa4yH\xe3\x04\x00\x00\xcc%F\x14l\xa2=V\x07\x14\x0c\x00&\x041\xa2`\x13\x0dP0\x00\x98\x10\x80\x82\xdd\x05b`V\xe6\xcdo=\xc4\xf2\xad7\xc3\xe4\x04\x00\x20\xaa\x80\x82\xdd\x0540\xdb+\xa6\xff\xa8\xd5/\xaaa\x7f\x0c\x9b\x1b\x00\x80\xa8\x01\x0a6J\x86\x0b\x18\x96\xb0H\x99\x00\x00\x88\x0e\xa0`\xa3\xe3M\x1d\x01{\xe8!\x18H\x02\xc0\xf8\x00\x0a6:\xf4\xba`\xd0\x09\x03\x80\xf1\x02\x14lt\xe8\x0a\xd8C\x0fE\xca\x06\x00@T\x00\x05\x1b\x1d\xa0`\x00`&\xa0`\xa3\x03\x14\x0c\x00\xcc\x04\x14lt\x80\x82\x01\x80\x99\x80\x82\x8d\x0eP0\x000\x13P\xb0\xd1qo)\x98wf0\x92\xc9\xb8sm\xa67\x92\x09p\x1fc\xb2\x82\xb5\xbb\x95\xd3\xd3\x17~\xb1\xc8\x11\xe3\xb1Z\xad\xc5\xa8\x18o+\xc2X]\x8c\xb7N\xc3/\xb9\xd8,\xb1+\x8c]$\"+X\xbe5\xae\xda0{\x08\xbb\xacqU\x91ld\xc6\xa0\x9e\xdb\xb0\xc3\xc848\x9dG\xc2\x1a\x1ct:\x9d{\xd1^\xbc=\x18\xc6\xea\xd3l\xe7*\xfc\xf2\x0a6\x9b\xffi\x18;\xb4-n[\xb8\xb7\x81\xfb\x1bs\x15\x8c\xaf8&/O\x14t\x9f\x0ck:bx\x8fu[\x10\x05\xb7Y=\xc3W\x1dbh\xc9M\xc0\xdbn\x9fo\x97\xb5%\x9c]\x04\"+X\xc0\x17?\x12]\xa0\xf0G\xa7\xeb\xdb\xfaN\x0f\xdb\x15\xfdz\xee\x8a\xf3D\xb0\xa0\xdch[\xbfW\xf7\x8d\xb6\xf3\x92\xc1A\xe7\xde\xcf\xd1\xe7\xef8\x0f\xde\xd0\xb5\x13\xb9\xfd\xf3W\xb2\xf1\xcb\xef\xdb\xda\x1a\x9c?\x0fc\x87{\x86\x8f\x96\x86}\x1f\xb8\x9f1W\xc1\xbcM\xdd\x92\x82\x05\xaa\xa3\xa5`\xa8\xddz\x00o}\xd6\xf6\xf0f\xc5\x09\xe2kK\xc4+;\x1c\x91\x15\x0c\xa1\x84\x11+\x18B\xf3\xf4m\xa7/\x1e\xbe/\xda\xf5\xbc\x18\xbf!\xbc\x81\xc2f}\x05[\xbfEJ|\xe4<\x85\xb7m\xce\x8ft\xcd\x14\xf6f\x8b\xaf\xe7#(\x18\xda\x94\x10\xe6\xe9\xbd\xc0\xfd\x8d\xa9\x0a\xd6\xee\xe1\xc5\xe7\xe4#\x9f\xdbW\x11\xc3\x0a\xf6\xbf\x7f\xf7_\xff\xf5\xeb\x7f\xfc_x\xfb\xbb\xa7\xc6D\xc1R\xc7A\xc1r\x93\xc3v\xe6\x18\x0c\x14l\xed\x16)\x11m\x05\xe3\x93s\xc3\x1b\x00\xf7/f*\x18_\xd1%\xad\xf4\x81\xfayy\xd5[]\xaa\x95\x98O5\xe2\x17'\xc7\xa7\xcc;\x83\xf7\x9ey\xd4j\xdd\xd4\xb5tj\xe2\x9cA\xd6\x98\xb9\xb2U\xdb|k\xbc{\xdd\xb4\xc4\xd9\xf4\xcb<\xb04y\xca\xc2\xdcaWv\xe5\xcc\xc4i\xebx#\xbf\xdd\x93\xad\"\x93\xd9\x0e\x81\xa8`o\x92\xb6x\xea_\xc9\xf6_e\x05\x93\xbda\x05\xcb\xcfM\x9e2\x8fF\xa1\x8e\xceN\x89O\x9a\x93B\xb3\xeeJML\xdd%z\xe9Z\x98\x14\x9f<\x87<e\x1b+X>>\xc6cA\xc6\x83W:p*\xd2\x10\xe5z\x0eL\x96\xbb`m\x9bWd?\xb3y\xc5m\x9c\xbc\xdd\xb0~\xfe\xda\x86\xdbt\xf7\xa7[\x9e\xc9\xce\xd9\xfc{D\x15\xecu\xa7\xd3\x99\x8d\x07\x89\xb7\x0f\xae\x9f\xbf\xea\x8d[\x08\x9dp\x8a\xacG\x1a\x05\xbb\xb1%'{\xc5f\"e\xaf8\xb3\x0f\xbe\xb1j>u\x80~\xbf=\xe7\xe9-\xaf\x84*\x98\xe2\xec\xa3l\xa7s\xcf\xa7\xdbWdo\xfe\x82\xbe\xb1)\xb1\x1f\x01\x80\x1ef*\x18Y\x1e2\xa0,\xd3\x1dN\xc1\xf8\xa3\x937\x05Qp\xd3\x94\xa3<\xbe\x9es}\xd5\xf3\xe2\xf0\xe5\xd8_]\x95\xf2\xe4\xe4\x94uK\xad\x9aAF\xbb\xd5\xdb\xdf\xdf\xbf\x9f\\\xd9\xaamg\xd5\xa3\xd6\xa4\xe2\xd2\xc9\x0b\xb1A\xd7\xe4i\x1e\xeflk\xe8\x95\x9d\x1b\x97\xef-MJ\x15\x0c\xfc\x0exJE<\xecR\x85\xa2\x82=\xfc\xe6\xd0\x7f\xfc\xe3C\x0f\x7f\xeb\xd7C\xff\xf6\xb0\xa4`\x8a7\xac`\xd6\xd4\xaa\xaa\xd4D\\\x9e\x93\xd6\xa5\xd5M\xee$+Q\x8c\xdc\x84M\xdeM\x09K\x89\x13_b\xea\xb6\xa6b+\x89\xf6`\x05\x0b,\x8d+ma\xcb\xc3\x1f\xf3=\x99\xe1\xf3\x85\xde\xea\x88r=O[}\xe2\xfb\xbf\xfc\xfe\xf6#\xa7\x0e>\xe3$\xe2\xb1#{\xcf\xa9=\xd9\xdb\xc9\xee\xb6\xf9\xeb\xdf9\xb5\xd7\xd9\x80\xa8\x82\xfd~\xbb\xb3\x81\x84\xbdv8_?\xd5\x90\xb3\xf66\xba\xd1\xd6\xb6jc[[\xdbo\x10U\xb0[\xb7n\x9d\"\x0av\xc2\xb9\xa3\xed\xc8f'6\xfd\xe8H\xb63g\xef;\xf3\xb7`\x83O\xe7\xaf:xb\xb33T\xc1\x14g\xb7\x8e\x1cY\xb1j~\xce\x1b\xdb\xbf\x7f\x95\xbeq,R\xff\x11\xb8o1Q\xc1:I\x08zd\x0a\x86/;2\x8cZL\x06\x13|5\xe9\xd8\xa4.\xa4\xbbS\xad\xb3\xf9\xd0u\xea\xdb\xa5.K\xbb\xd66a\x0a\xbeN\x97&\xe1T\xc6T\xbcWH\x0d\xb9\xb2\xbdV\x0f\xfd\xab\xca\xc8o\xf0\xa2\x88f\xc2\x81\x14\xf7zj\xe8\xd7\x0f}\xeb[\x0f\xfd\xdf\xa1\x7f\x94\xe2`\xac\xb7\x84i\xb8\x07\xd1?u&\xeev%\x11\xed\xda5\x05\xeb\xda\x01\xda\x83:`\xdd\x8f\xdfJ\x99\x83Eq\xa0\x9a,\xda\x84\x15l[\x82wxytG\x91Q\xad\xa7\xd7zQ4l\xc8!\xda\xd5\xf04\xeey\x9d\xa2\x9d)\xba\xbd\xb5b3\xee\x1c\xdd>B\x82\xf3X\xc1\x1a\xb2O\x10\xdb\x13\xf4v\xe3y\xf1\xe6$3\x8a\x14\xc1\x0av\x8bd\xb8-\xbe\x93\xfd4\xee\x7fm\xcf\xc1\xa9\x8d\xab\x88\xaf\xb5!\x0a\xa6u\xe6\xdcx\x03\xdd\x96n\x05t[G|;\x17\xb8\xcf0O\xc1\xf8\x8aNA\x10\xba+\x05i\x91\xc8\xf0\x0a\xe6K\xe4\x11\x9fH{\x09\xc1]s\xa6N\x96\x86T\xa9:1\xdevkiKKK)\x8d\x0f1\xb6\x09D\xfeHP\x88\xb7\xba\xc9\xdf\x9bB\xae\xec\xc5S\x07\x061)\xb9\x06~/Ze\x86\x8f\"\x1f\xfa\xfb\xa1??\xf5\xe7\xa1\xa7\x86\xfe\xfc\xb0\xa4`\xac\xb7\x04:<s[\xaf\xa1\xee\xe4\xa9\xf9\xee3\x02\xe9\xc4\xe5N\xa3\xf9\xa7a\x83\xfd\xd63\x8a\xbfy\xc5\x9b\xac\xe2\x04\x13myt\x15,\xaa\xf5\xac\x93\x15\xecj\xce\x8a\xd7\x0f\xfe\xea6\x19:\xeeXK\xf7\xacz\x85\xc8\xd8\xafdK\xac`?qR\x01C\xdbW\xdc\xfe\x02\x93\xb3\x83\xfc\xc1(\xd8;\xe7\xcf\x9fo\xa0q\xb0\x1b\x0d\x9bW\xccwR?\xd9\xc4\x8a\x04\xbfn\x88\xd3,\xf6\x84(\x98\xd6Y\xf6U\xa4\xd0m\x1d\xf1$\x13\xe0>\xc3<\x05\xbb\xe8\x96\x11WY\x0b\xaf`\x83Iu\xa8.\x89\x88]Kr\xca\x86:_\x86\xa4`\xd3\x87\x9b2\xf1!\xd6\x96\xc6\xd3\xc9\x95}R\x1c.\x85F\xb8S%}Zh\xe4\xd7\xe7\x15\xf1\xb1;%\x05{\xe8\xbf\x87\xfech\xe8\xd7C\xbf\x93\xfe\xd4x\x13#\xf9>\xebi\xb2,\xf9\xe2i\xd6$2\xbfi\xe6<\x9a\x7f\x0e>P\xa9U\x8d\xf2\xccK\x9e<M\xee^\xb2\xe5\x09\x1f\xc9\x8fF=\xd58\xd9\x8d\x83[V9\x9fy\x07K\xd8\xc6\xcdt\xc7\xe6\xf5d\x16\xd8-\xc5ts\xce\xfcU[hpl\xad\xd4\xdb\xa2vz\x91\xfc\xf39+\xf6\x9ch\xdb(*\x18\xb9\x01@\x14\xec\xbc\xb3\x0d\xa1\xe1\x91|\xad\xb3\xf5H\xa5\xc5z\x0c\x01\x80\x1e\xe6)\x98\x10$\xb4{\x82A\xb1\x13\x16^\xc1P\xfeB\xb40\x9f$\xa6\xcd$\x83\x9e\x85\x92\x82-\x1cn\xc9\\\xd9\xac\xadre_\xb3\xd2\xf8yh\x84{\xe9\xd4\xd3\x94\xa0\x91_]d\x05\xfb\x0f\xb1=\xfe]V0\xd6[\xc2:bYa\xe5\xd1I\xd2\x1b\xe3\xab\x12q\x01r\xa7\xd2\xfcSqW\xa8\xc9\xaa\xce\xf6\x9a\x97\xd4~q2\x9d\xfe\xa4-\x0fQ\xb0\x90\x15\x81\xa3\\O^\xbeez~\x0f\x16\xa7\x1bG\xb2\x1bp\x1fL\x0c\xe7\xaf\xd8A\xfa`\xe7\x15\xd3\xcd9\x1f}:\x9f\x04\xc4p\xb7\xe9<\x85\x8e\xf6\xa8\x82\x1d\xbc\xaaQ\xb0U\xeb\x89\xf0m\xd1*\xd8\x1fi4\x0d\xed\x18\xd6\x07\x0bu&S\x1c?\xd2\xdb\xa4\xc0\xfd\x86y\x0a&2\xd28\x18jI\x08$\xd0K0\x85\\tB\xaa\xa4`\xe1\xfb&\xac\xadre\xa3\xe9)\xd7\x10\xeaz,\xe4\xca>\x20\xc6Z6\xd1\x09\xe0z~u\x91\x15\xec\xcd\xa1\xa1?\xffyh\xe8\xdfd\x05c\xbd%\xa4\x900\xd7\xb4\x0c|xZ4\x94\x91O\xc2Nd`TE\x86\x8c|r\x06\x19W\xe6\x13\xa1#\xb3)\xf6\xc7\x93\x12i\xca\x93\x813\x07\xa5`P\xf76q\xb8\x17\xedz.\x9e*~\x95\xec\xa5\xfa\x836\xd2\xa1#\x89I\x1d!;n\xe4l$j\xf6\xfa\x1bH\x9cMq*\x9b(\x9ah\x80\xf6\xd0\xe9\x15\x1b7\"\xf49\xd9\xc1(\xd8\x8a-\x88D\xbc\xb4\x0a\x86\xd6\xaf\xc0*\xf5\x9b\xec\x10\x05\xd38c\x15L\x986\x07\x01\x80.\xe6*\x98\x10h\xf7\x04H\x00\xbb?\x10\xf0\xf8\x02\xe1~\x94'$\xcfN\xa6WX\xb1u\xf1\xaem\xa9x(\xd62\xd8B\xef\xd1]\xd4\x1a\xb2s\xd5U\xdb\x80/>\xb7\x05\x9d\xce\x8d\xf7\x05P{bJ\xf1\xa6)q\x8fz:\xc5\xb9\xea\xa5>\x1f\xe9\xddl\xb0.\xae\xf6\xe6Z+\x91\xbe_}d\x05\xfb\xd7\xa1\xa1\xff\xf3\xefCCO\xc9\x0a\xa6z#\xf7\"\xe7\xb44\xcd\x9cr\x91\x94=\xb1\xd8[\x97K\x87wK\xe36x7\xc4\x89\xf7\"\x1f\x9b\xe6\xde\x9fo\xad\x20s\xf2s[\x06\xf89\xc9\xbe\xa0\xc6\x03V\xa4\xd2\xba\xd9\x89b\x1fl\x9e\x95^\xcfQ\xafgW\x9c8\xf5}\xafs\xfe\xde\x13'v\xd0\x91\xdev\xe7\x9e\x13{\x9c\xe2\xbd\xc8\xecU\x0d\xa7^w\x1e$s\xf2_\xf9\xf9\xed\x1b\x9bs\xda>\xc7r\xf3\xfd\xedG\xb0-\x95\x9e\xbd\xd9\x0d'6\xce\xbf\xaa\x99\x93\xbf\xd7\xb9\xa5a\xefZ<&\xfd\xf9\xd5\xb6l\x9c\xed\x97\xafd\xb7]E\x1f\xcd\xcf\xd9\xbb\xe7ig\xf6\xc1\x8f\xd0U2'\xbf\xa1\xad\x8dD\xbd\x14g_\xfc\x82\xde\xd8\x94\x7flT\xcatR\x01@\x83\xb9\x0a\x16\x20a0\xd2\x179)F\xc4\xc2\x8d\x15J\x13\xc4\x0bL(}2!i\xb1gj|\xc6\x998\x1a\xd0\x09\xf9~V\x7f/\xe8fl\xc9$\xab\xf8\xf6D\xbc\xc5\xfd\x9f\xaeyS\xa6\xae\xf3<\x8a\x93\xb9RP\x88vE\x0ed$M\xc9\xd8\x8f\x90\xbe_}d\x05\xfb\xfb\xff\xfa\xef\xa7\x9e\xfa\xef\xdf=\xac(\x98\xe2\x0d\x8f\xf1\x8a\x17ON^J\xa2}\x95\x19\xc5)\xf1\xc9\x194>%\xd0\xf9`b\xb7\xa7kq\xca\xe4\xe9u\xe4w\x91Vk\xdc\xc9*\xbc\xdd\xa4\xf1\x80\x06\xf2\xa7$fHq*O\x12\x8d\xd0G\xbf\x9e\x9b\x1e\xa3\xd1\xa6\x83\x1b\xf7\xae\xc8\xce\xd9HCU\xb7\x1b\xd6*\xf3\xc1~\xb3%g\xfe\xfa\x13\xf4w\x91N\xe7/\x8f\xe0\xcdO\xf0\xdeS\x1b\x9fyz#\xed\xb4\xa1[\xaf?\x9d\xbd\xf1\xbc\xf6w\x91\xb7\x1bVe\xe7l?\xb8*{#\xf9\x01d\xf6\xaf\xe6\xe3-\xee\xdc}\xba\xf9\xe9\x15{\x0ef\xe3\xe4\x0e)\xf8\xb5\x051\xce>b\x02b\x08\x1dM\\\x87\x00@\x1fs\x15,\xf6yH\x9fH\xd9&&\xeb\xe2'\xe2\xa4\x85\xaa\xf8\\\xcd\x8ce\x00`\x00\x05\x1b\x1d\xf7\x94\x82\xa1\xd2\x94\x09\xf8t\x9ddx4\x05`\x0c(\xd8\xe8\x10\x05\x8bi\x91\x98V0\x00\x885@\xc1F\x07(\x18\x00\x98\x09(\xd8\xe8\x18>\x80\xfc\xea\xc3\xa0`\x000^\x80\x82\x8d\x8ea+\xde>\xfc7\xff\xe3aX\xf1\x16\x00\xc6\x09P\xb0\xd1\xf1o\x0f\x85\xf0\xb0\x05K\xd8\x9b\x91\xb2\x01\x00\x10\x15@\xc1FIh'\xec\xab\x16\xcb\xdf\xfcO\xf8\x0d\x0c\x00\x8c\x0f\xa0`\xa3\xe4\x8f!\x12\xf6UK\xee_O\xe9\x04\x09\x03\x80q\x01\x14l\xd4\xbc\xa9\xd1\xb0\xafZ\xeaNZ\x92;\xe1\x99\xa2\x000\x1e\x80\x82E\x19\xdeR=t\xcc2\xb5\x0b$\x0c\x00\xc6\x01P\xb0(C\x14l\xe8\x80e\xdaE\xf6Y\xd4\x00\x00\x8c\x0d\xa0`Q\x86\xb7T\xfdeh\xa8\xce2\x1d$\x0c\x00\xc6\x1eP\xb0(\xc3[*\xb1\x82\x0dUZ2\xbaA\xc2\x00`\xac1Y\xc1\xda\xdd\xe2C\xe1y_u\x85\xf7\xcc\xbd\xf0\x08\x02\xde\xe2\xf9\x92H\xd8.\xcbl\x900\x00\x18k\xccU0\xbe\xe2\x18}\x80\x1f\xef\xf1\xb6w\x9f\xf4\xd4\xdd\x03\x12&+\xd8\xd06\xcbB\x900\x00\x18c\xccU0oS7U\xb0\xa6j\xf2\xa4\xbfkQ[\xb6\xdbDxK\xc5\x97\xa2\x84m\xb2,\x05\x09\x03\x80\xb1\xc5T\x05k\xf7\xf0\xe2s\xf2\xeb\xc4'\xeb\xf9\xbca\xcdc\x02\xde\xe2\xfe\x93\xa8`C\xeb,\xf9\x01\x900\x00\x18K\xccT0\xbe\xa2KZ\xe9#\x20>\x01\xfe\xd8D|D\xe8\x1d\xc2[v\xfd\xe9\xcb/\xc5\xd6\xc9\xb5\xac\x03\x09\x03\x80\xb1\xc4L\x05\xf36\xb1k\x15!$Ti\x96b\x8cM\xb0\x82\xfd?\xb9\x136\xb4\xd0\xb2!x\x0f\xc4\xf6\x00`\xc2b\xa2\x82uzx\xad\x82\x1d\xf3\\3\xb6\x8e\x15xK\xa9@:a\x7f9v\xf4\xe8Q\xdfc\x96b\x900\x00\x18;\xccS0\xbe\xa2S\x10\x84\xeeJA\x90v\xb4\xb8\xb5+\xba\xc6&X\xc1\x06i',\xc5\"R\xca\x0b\x08\x00\x80\xb1\xc1<\x05\xbb\xe8\x96!\xeb\x90\xa1A\x9f\xbb3R\x96X\x00+X?\xfaS\xc5\x97CM\x969\x9b\xb6m+u7\x05A\xc1\x00`\xac0O\xc1\x84\x20\xa1\xdd\x13\xa4W8\xef\xad\x0cD\xca\x11\x13\xf0\x96m\xfc`\xb1e\xdb_\x86\xe2\xa7\xec\xef\xbcx\xb1;8`\xb9\xe7\x88\xd4\x08\x000^\x98\xa7`\"R\x1c\xecZU\x1d\x8fE\xed\x1ex\xa0\x03Q\xb0u\x96I\x7f\xfb\xe5P\x95eW`\x00\x0f\x94\x91\xe5\xe6=\x06(\x180a0W\xc1\x84@\xbb'p\x0d\xa1\xee\x8a\xaa\xee@\x20po\xcc\xa6\xd8\x96k\x99\xb6\xc1\xb2\xeb/\x7f\xf9\xdb'\xdb\xe9\\\x0aP0\x00\x18+\xccU\xb0\x00\x09\x83U!\xb4_\x8a\x88\xdd\x133Z\x13-\xa9\x9e\x96i\xd6/\x87\xb6Y\xaa\xe9\x08\x19\x14\x0c\x00\xc6\x0as\x15\xec\x1e\x84\xb7X\xa6W\xb6_k\xb2x\xfe\xf2\xa5e\xe1E2\x95\x02\x14\x0c\x00\xc6\x0aP\xb0(\xc3[\xe6Uw\xf2\x02?\xcd\xfa'\xde\x92\xd1I\x86\x91a\x14L\xef\xad\x08\x82\x17E=\x8c\xecJ\xdf\x02\x14\x0c\x980\x80\x82E\x19~\xd3Q,`h\xe0\xc0\x03\x93,_)\xee\xba\xd7\x14\x8c\xee\x00\x05\x03&\x0c\xa0`Qf\xe0Z\xb0_\x9c\x1e2}v\xe9\xb1k\x11\xe2`\xa0`\x000*@\xc1\xa2\x8d\xf4\x1b\x03\x81\xbf\xd8\xdeu\x8d\xfe\xa2\xc8\"+\x01I\xbc\xf6\xb8e\xd2w?\xc1\x7f\xbc\xfc\xc8_?\xf2\x02\xd9\xff\xe3\xaf[\x1e\xf9\xe1\x1fH\xe2q\xcb\x83/S\xab\xf7\xbf3\xe9\x81\xef`#\xcb\xb3\x0fZ\x18\x03%\xcbM\xc6\x91b\xfb\xc2#\x96\xaf\xff\xf4\xe5G,O\xfc\x8cu*\xf1\xb3Ix\xf3]\xfc\x7f\xd2\x87\xea\x9b8\xcb_?\x82\x8fH|X\x1e\x7f\x0d'\xc8\x8e\x17nj\xcar\xf3\xa7_yA\xc9Bg\x83\x81\x82\x01\x13\x07P\xb0\xb1B\x18\x18\x18\x10\xc5L\xa3`_\xdbw\xf9\xc3\xefb)y\xfb\xc1}\x97\xf7\x11}\xfa\xe9\xdf\xfd\xf4\xf2\xfb\xdf\xfc!\xde\xf3\x95\xb7/\xbf\xffO\xd4\xea\xef\xf6]\xfe\xed?\x7f\x0f'\x9f\xf8\x905\x90\xb3P_\xb2#\xd5\xf6\xfd\xcb\xff\xf2\x00\xd9|\x93\xc9\xa3\xf4\xa2\xbe\xf6\xde\xcd\x9fY>\xbc\xf9\xde\xd7\x987-\xd4\xe1\xdb7o>\xfe\xec'\x7f\xf8\xe9\xb7\xb1N=\xb8\xef\xb7\xfb\x1e\xfc\xb1\xa6,x\x1f\x9b\x85\x1e;B\xd5\x01`\xdc\x00\x05\x1b{4\x0a\xf6\x1e~\xfdO\xdc!z\xe2\xc7\xa4\xa7\x83\xf7\xfc\x03\xd9\xf3\xe1#7o~\xe3\xb5\x9b\xb2\x15\xe1\xb7\x0fJ\xd6\x8a\x81\x92\x85Z\xc9\x8e\x14\xdb\xf7o\xde\xfc\x84n\x1e`\xf2(\x0a\xf6\xcf\xcf\xde|\xd6\xf2\xf2\xcd\x1f\xfe3\xf3\xa6\x85:|\xe2\xe6\xcd\x07>\x14\x8d\xbeAw|\x83-\xcb\x0b_\x7f\x9f-\x03(\x180\xb1\x00\x05\x1b{4\x0a&'&\x91\x91\xe4'$!\xfeP\x07\xef\xfcDV\x8d\x9b\x1f~{\x92\xb8\x8b\x8c\x03\x15\x03%\x8bd\x15b\xab\xece\x9d*\xbc\xfd\xcd\x9bO|\xef\xdb7\xbf\xf1v\xe8\x11?\xc1*\xf8/\x93\xbe\xf72\x11\xb1I\xf2\x0e\xa5,?\xfc\xc6oo\xb2e\x00\x05\x03&\x16\xa0`cOx\x05\xb3H\xfd\x1fq\x8f\xf8\xe6\x13\xff\xf2\xe1\x1f\xfe\xa0\xe8\x85\xc6`\x98\x82im\xe5\xc3\xc8y\x14.O\xfa\xf0\x81\x0f'}h\xb9\xcc\xbc\xa9(\xd8\xcd\xf7\x9e\xfd\xce\xa4g\x19\x05S\xca\xb2o\xd2\xdb\xf4U\xc9B7\x91j\x0c\x00\xe3\x05(\xd8\xd8C\xf4\xea?\x89L\xb0\x0a\xa6\x0c\x09\xbf\xf1\x82$\x0eO\xbc\xa6\x88\xc4\x03Xg\xf6\xc9\xd6\xaa\x81f\x14\xa9k+o\x94<*\xff\xf0\xbd\x7f\xa0\xff\x997\x95Q$\xe1g\x0f0\xa3H\xb5,\xef}\xe55M\x16\xba\x89Tc\x00\x18/@\xc1\xc6\x1e|\xd5\x7f\xf7\xbb\x1f^\xde\xf75V\xc1\x94\xb0\xfc\xbeI/\x7fry\xdf7q\xe2A%z\xfe\xf8\xb3\x97\xdfS\xac\x15\x03m$_\xdejl\xe5\x8d\x92G\xb2\xc3<ky\x96\xfeg\xdeT\"\xf9\xdf|\xfb\xf2\xe5\x17\x1e\xa7Q\xfb\xcb4\x92\xcf\x94\xe5\xfd\x07_`\xb2<\xf26(\x180\x91\x00\x05\x1b{\xb0\x0c|\xf2O\x93,_\x7f\x8dU\xb0\x9b/?(M\x8d\xd8\xf7\xc4\x03\x96'\xf6\xe1\xc4k_\x97g0\xbc\xf7\xb8\xe5\xc1\x17\x14k\xc5@\xc9\xc2*\x98\xc6V\xd9(y\x14\x05{\xcf\xf2\xfe\xcd\xf7i\xfc_y\x93L\xc0\xa0\xb3)\xde~\xc22\xe9\xdbd\x12\x06\xd9A\xbb[jYn\xfe\xec\x91g\xd5,\xaf=\x02\xb3)\x80\x89\x04(\xd8\xd8\xa3\x88\xc8\xbd\x02(\x180a\x88-\x05s\xb9\xf4\x92\x13\x1dP0\x00\x18+LV\xb0v\xe9\x81:\x81\x03U\x15\xd5\xc7\xf8\x08\xd6\x1fs;u\x92\x84fN\xc2\x11f]\x8d\xe3\x8e\xc3\xc6oF\x07\xf6\x10\xbd6n\x11M\x80\x82iy\x91\xe3\x1a\x99?\xc7\xe1c\x91\x089\xf0]0z\x0f\x91q\xe1\xd38\xad'\xbc\xcd\xf8\xb5\xd9\x84\xc7\\\x05\xe3+\x8e\xd1g\xb4\x06\xdd\xde\xce\xee\xce\xba\xaa\x08\x12Vd\xbf\xa2\x93$\xf4\x7f`s\xf91;\xb9\xeb\xc3\xb3\xc94\xa7\x8d\xf9\xd9\xa79D\x87\xcbN_A\xc1\xb4\\\xf1\xdb\xca\x99?\xc7\xe1c\x91\x089\xf0\x1d\xd0zV|\xbd{\x0f\x0c\xb23\x03.\xf9\xfd\xb5\xdc\x07aM\xc6\xb1\xcd&<\xe6*\x98\xb7\xa9\x9b*X\x17]\xa6\x88\x8f\xb0\xd6G\x0fW\xa2\x93\x94\xb0\xd33\xab\x91\xfb\x0c\x193\x0eKnh\x0eQ\x0e\x0a\xa6\x8f]#\x03\xe3\xf0\xb1\xc8\xd8\xefR\x7f~P(\xa7\xee\xd6\x03\x83\xea\xcc\x88\x8eH\x0a6\x9em6\xc11U\xc1\xda=\xbc\xf4\x9c|\xda\xf9\x0a\xba\xc3/\xf6\xe1R\xfb].m\x17\x0c\xc9gVO\xa8\xb0\x99\x8a\xac`\xf7\x1c\x11\xea\x1d\x91(\xc8\xc0\xddq\xb7\x07^\x16M\x05[\x16\x05\x05\x03d\xccT0\xbe\xa2\x8bY\xf1\xb6\xbf\xbb\xda\x1b\xf6\x9b\xa5\xd7\xb6U')\xa3\x9cY/q\xb6\xfa\x92\x05i\xab/\x91\x1e\x19\xc7\x95\xa3r\x8e\x04/\xfa\xd28)\x86q\xce\xc6qe=Es\xedk\xc8\xe1j\x97\xa5-\xa9\xd5fc\xc0{k\\Y\xe9\x05=F\xd9\xf4\x0f\x81\x87\x1bE\x99\xe9\x85\xd2(\xb2qe\xda\x82\x12\xba\x88\x89\x7fu\x96\xcd\xf1\xa3,\xcd!\x10_\x98i\xcb*8\x87\x8c\xca\xc0x\x18u\xdd\x04\x87\xed\xd5\xac\xcc\xd6\xad\xb3\xf2x\x03\xbf\x87\xa5\x80\xe22M\x01\xca\xe8\x01\xc81\xcbpS\x97\xa8\x85\xd4\xf1\xc0\xa2\xd6M\x83}\xab+\x936*St\xd6\x83\x9a\x8d\xa9\x10\x89u\xbe*\xd5\xd8\x00\xa5}_\"FJ\x20\xb2\xa7\xd0\x81w\x93\xef<\xb6\xe8\x0al!\xf5>!M\x93\xe8U^\xc3gE\x8e\xac\x92\x12\x07\x1d\xe6\xc9\x1f\x80\x913\xd6\x80\xb5\x0d\xaf`\xecy\xa6{F\xddW\x98\xa9`\xde&u\xcd\xee\xa0\xdb\xed\xf6\x84\x0f\x83\xb9l\x97t\x922\xf6\xb2\x81\x81B\xd2\x03\xfb\xb8\xd1\xc69\xcakf\xe0/\xba~\xff\xac\xb2\xeb\xe8z\xd9,?>\xcd\xce\xfa\xfd\xa2\xca\x0d46f-\x98\x91UR\xc4\xf5\x92\xce\\Ys\x99\xbdH\x93\x8d\x81\xec\xcd\xda]\x9e\x95v\xc1\x20\x9b\xfe!P\xcf\xac\x05\xf5\x87WsT\xc1\\\xdc\xd6\xe6\x1a\xc72,)g\xb9\xa2\xc6\xe3\xf5\x0eN+\xd4\xcd\x9c\xab\xb5\xb1\x80\xeb0*\x03\xe3a\xf4uk\x9d\xc5\x95\xe4q\x8e\xdd\x8e\x1a\x03\xbf\xfc\x07\xfe\x05y~\xbf_\x1bJ\xbe\xfe\xfcJ?\x8f\xdf[\xf9\xfcu\xdc\xd4\xdc\xa2\xc6\xc3\x8eB\xa3\x92\xb1\xa8u\xd3`\xe7\x9656.\xc1\x8d\xca\x14\x9d\xf5\xa0fc*\xd4\xffA\xd6\x8bW\xe4\x1a\xeb\xa2\xb6\xaf\x18\xaf\x92\x02\x91\xadi\xcb\xde:^\xce\xd5\x20M\xd1U\x98B\xea~B\x9a&\xd1\xab<\xcb\xe0\xa2\xcc\xda2{Z\xfd\xea\x1a\xe6\x030r\xc6\x18hl#\xf4\xc1\xd4\xf3L\xff\x8c\xba\xaf0Q\xc1:\x89b)}\xb0`\xa0\xbd:l$\xbf\xd7\xf6\xa2NR\xc1N\xbe\xd8\x0a\xc4d:\xd6\xb7\"\x07I\xba\xc8\xa9\xf6\xbc<\xf1B\xe9\xa7-\xe3H\x1f\x04\xff;\xce\x1dGd\xdb\xac\xcd\xc6\xfa\x9d\x8b\xcd\xfa\xb2\x96\x1bf\xd3=\xc4\xca\x05\xf8*\x13\x96\x90\x0b\xa8\x99{\x17\x91s\x12\x7fi\xd6:h\xcfh\x96\xf6|\xebo$W\xe4\x92]M\xa7\x00\x00\x00\x11\xe6IDAT\xe7hv\xbd20\x1eF_\xb7\xcc\"\xec\xef0\x9d\x8cb\xe0Ww\x94\xf3\xae\xd8\x9bYDm\x1d\x9f\xe1\xa3\x13[#\x0f\x0al\xdd\x18\xec\x8b\x06\xb08-X.\xfd%\x15]\xf5\xa0\xc9\xa6T\x08\x95\x93\x8eK\x91\xf14\x1a\xb6}\xa9S:\x8c\x1f\xc8Z\x83\x8f6\xd8\xd8\x87\xd8\xa230G\xd3\xff\x844\xa3H\xdd\xca\xab4r\xb83W\xcb\x11qf?\x00\x16\xc5\x19c\xa0\xb5\x8d<\x8a\x94\xda\xcc\xa8\xbc\xf7\x11\xe6)\x18_\xd1)\x08Bw\xa5\xa0\xb4\xfe@uS\x18\xfb\x97l\xbd:I\x05\xbb\xab\xa3c\x89\xa4`\xe4\x1c\x17cP\xad\xf6~\xd4\x9f\xe6\x97m\x94\xab\xdc.9pI\x17\xa6K\x9b\x8d\xc1Ngm\xd4r}F\xd9\xf4\x0e\xd1\xc7\xd5\x93\x972\xe2\xacp\xee\x20\xae\xa7\x90\x85m/e\xce}\xa9\xfe\x9c\x10:\xe1\xe3\xb3\xda5sgqKhv\xbd20\x1eF_\xb7\xccF|}\xf0h\xe7\xf3\x86~u\x15\xec\x1c7\xd0\xffn\x9f`?\xa7\xb15\xf2\xa0\xc2\xd4\x8d\xc1\xfe*\xd9\x8a\x8d\xca(\x98\xea\x81\xcd\xa6T\x08]\xc2\xc200\xa3\x15\x19\xc1\xb6\xaf\xaa`\xcd\x9c:\x8a\x8dTH\x83O\x88U0\xdd\xca\xab\xecLG\xe4F\xd3!\xa4\xfd\x00X\x14g\x8c\x81\xd6v\xc4\x0afT\xde\xfb\x08\xf3\x14\xec\xa2[&\x80\xa4G\x01\x9eq\x1b\x7f\x97\\\xb1\xbbt\x92*\xe4#mnU\x92\xd2i*8\x0e\xa1C\x0e\xd9\xabz\x95\xcbQ\x9e\x95\xa2\xe6\x15,\xd7fc\x10\xf3\xf8\xb9\xb3F\xd9\xf4\x0eq\x96\xa3\xc2B\x9d-\x91\x02\x1f\xe4+\xbe\xaf\xbep\x11\x1e\xc2!\x0d\x1d\x8e\xac\x9d\x87\xfcyK\x94\xec\xa1e`=\x8c\xban\x99\xc7Q\x87\x0dQ\x053\xf0\xab\xab`\x03\xb6s5\\Y\x0f7\xa0\xb15\xf2\xa0\xc0\xd6\x8d\x81mTF\xc1\x14\x0f\x9alJ\x85\x10ZS\x82\x8e\xa7\x87\xb9\\\x99\xf6U\x9d\xd5\xd0B\x8bD,\xa4\xee'4,\x92?\xac\xf2*5\xe4n\xf8q\xda\x07c?\x00\x16\xc5\x19c\xa0\xb5\x1d\xb1\x82\x19\x95\xf7>\xc2<\x05\x13\x82\x84vO0(\x08\x95>\xba\xebL\x85\xb1\x82m\xe5zt\x92*\xd2Gz\xee3\xedi\xba\xf59\xf4\x9c\x12\xf5W\xafr\xe5[p.}\x99\xebB\x06g7\xb2\xd3\xdc\xf5\x1co\x94M\xef\x10\x9fq\xb5\xd4\x8c8+\x9a{\x96\x82\x0bv\x96\xf4\xe7\xfa\x1a\xed\xb5\x88e\xd1r2\x88)4V0\xc6\xc3\xe8\xebF\x14\xcc.*\x98\x91_\xea\xa1\xbe\x17iX\xd4\x98\xb7r\xd1!\xdaSPm\x8d<\xa8\xb9\x98\xba1\xd8\xe9-c\xb1Q\xf5\x14L\x93\x8d\x11\xd4\xc3\x8e\xc1p\xbf\xc5`\xdb\x97:\xa3\x9d\xe0V\xb6\x0f\x16\xa1\x90\xfa\x9f\x10\xdb$\xfa\x95W\xe9\xe5\xd6\xf4\x9e[\xb0\x92\x9c\xc8\x9a\xb3\x84Aq\xc6\x18hmG\xac`F\xe5\xbd\x8f0O\xc1D\xc48X\xd5\x01\xb2\x0d7\x8a\x8c\xd4\x05\x93?\xd2\xcc\x9d\xda\xd3\xb4\xc3~\xc5\xae\xc4\x91\x87_\xe5\xcd4\x8a\xd1(\xc6\x8a\xf4\x15\xcc\x81Oo~n\x9ea6\xddC,\xcf\xea\xc3c\x09;qv\\\x0c\x94\x94\xe1\xef\xc9r\xee8I\xe6i\xa3xY\xc4\xab\xb0\xc4X\xc1\x18\x0f\xa3\xaf\x1b\xa3`F~\xf3pe\xaf\x84\xde\xf0{\xfe%\xdbq\xeeE\xea[\xb55\xf2\xa0\xc0\xd6\x8d\xc1\x9eE\"S\x8b\xf2\xa4\xbf\x86)\x98&\x1b\xa3`\x83\x8e\xc3\xe9\xf2\x20\xb2\xb7|\xd8-N\xb6}\xd3\xf0\x89\x20,'\xce\xfa3\xf3\x06\xf1\xcbV\"\xf6\x91\x0a\xa9\xff\x09\xb1M\xa2_y\x95\xb3\x9c\x83\xe3\xf2\xe8\\\x1f\xcdY\xa2\xe7\x8c1\xd0\xda2\x0a\xa6SM\x82\xd4fF\xe5\xbd\x8f0W\xc1\x84@\xbb'p\x8d\xcchm\xea\xec\xee\x0c\x17\xc9/Q\xfb]%:]0yN\xbe\x7fV\xc9\x15\xbf\xcd\xd5\x81\xce\xbal~r\x1a\x09\x99\xab3i\xc7n\xf0\x03\xbf\xdf\x8emx$t\xd0\xbbA\xbd4c\x11\xb7\xb3y'WDg[3\xd9T\xc8m\xb3\xfa\x05\xe9=\x06\xd9\xf4\x0f\x81.\xa4e\x95\x97\xa5s\xb6w\xf1hb'\xf7|\xe3!\x179C\xcb\xb9\xb4\xb2f\x9c\xd4Fr\xca\xb9\xc2\xda\xdd\xcb8\xc7[\x1dFeP<\x8c\xben\x17\xd2k\xfa\x1bm\xe7\xfa\x9f\xcf\xbbd\xe8\xb7\xdc^s(/-\xe4n\xef\xee\xb4Y\xc2\x02\xfb\xee\x90\x862\xf0\xa0\xa0\xd6M\xe3\xcc\xce\xad\xe9h]\x89\x1b\x95):\xebA\xcd\xa6\xa9\x10>\xde\xdct\xb9\x9b^\xc0\xa5\xf5!-l\xfb\xaet\xec\xde\xbd\\\xfc\x00Z\xed\x8bj\x9b\xb7r\xf5#(\xa4\xfe'\xa46\x89~\xe5Y\xce\xd9[\x9b\xfdW\xc4B\xb2g\x89\x8e3\x8d\x81\x9a\xa4s\xf2k\xe4:\xebT\x939\xcf\x8c\xca{\x1fa\xae\x82\x05H\x18\xac\x8a$\x9a\xaa+\xeaZ\xd4xE(\xd7\xd3\x8at\x92*\xca\xef\"\xb9\x9a\x17\xf1\xc6v\x81L\x99\xa1_L5\xf6\x1ajqVz\xbf\x1e\x9d\x13\x13b\xe0A\xa8Y\x92\xb6\xa4V\xa0\xbfxc\xb3)\xd8K\\\xb32\x8b\xae\x20\x83l\xfa\x87\xc0\xfd\xaf\x82\xf4\xb9%\xef\xda\xa8\xb3\xe3y\x8e\xf4\x95\xe4\xcb\xb5qey\x96-3/\xe4t\x13j\x16\xd8\x1d\x85\xf5\x0blyFeP<\x8c\xben\xb8\x83\xd08\x83Kk\x14\xc37\xfa~\x07\xb6\xa6\xdb\xf3B'@\x9c\x9dU\x86U\xac#\xd4\xd6\xa0d2j\xdd4\xce\x16\x95\x17\xce\xa0\x8d\xca\x14\x9d\xf5\xa0f\xd3T\x88\x84\xc8\x95\xfew}\xda\xb0I\x1al\xfb\xf6\xe4\xd9g\xac~U,NOa\xd6\x8c\xe5\x87FRH\xfdOHm\x12\xfd\xca\xb3|`#\xe5\xb5\xad<\x87B\xce\x92\xe1\xce4\x06j\xd2%5\x89\xd8\xf5\xd4\xa9&s\x9e\x19\x95\xf7>\xc2\\\x05\x1b1\xafr\x1f\xeb$\xc7\x03et\x06L\x00\xfa\xed\xea\xc5z\x88\x0b\xed\x9cL\x00\xae\xa7\xbdx]\x10\xfa\xce\x16\xa6G\xabp\x13\xb2\x9a\x13\x88\x18Q0\xf3\x1e\xab\x03\x0a6\x91hT\xee\xbd\x0a\xef\xce\x1a\xdf\x13ad\x1c\x92F\xb9\x82#4\xfauwL\xd0jN\x20bD\xc1\xcc\x03\x14l\xc2P\xde\x8a\xf2\xca\xe4?\xae8J\x8c\x83\x0e\xe6qV\xba\xf1y\x8e\xd3\x0d\xc0\xdf1\x13\xb4\x9a\x13\x08P\xb0\xb0\x88\x91[`\"\xd0\xcf-{)s\xa2\x8f\xa8\x04W\x9a\xab\xb1\xb5\x11o#Y\x02\xd1\x01\x14,,4r\xdb\x1b\xc9\x0a\x18\x17\xca\xd2\xf2\"<\xf8o\"pxu\xa6-sMt\xc6\x90@d@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]@\xc1\x00\x00\x88]bK\xc1\xcc\xfby$\x00\x00\x13\x11\x93\x15\xac\xdd\xed\xd5I\x1a\xf11\xb7S')3\xd2\x95\xd8k\x8d\x97\xebR\xd6\xe7\x02\x00\x20&0W\xc1\xf8\x8ac\x95\xc3\x93\x86\x14\xa9\xeb\xdc\x16\x0d[\xf2v\xc4+\xb1\xf7\xf9\x97\x1b\xfeZ[Z\x9f\x0b\x00\x80\xd8\xc0\\\x05\xf36uW\x0eO\x1a\xd1\xc3\x95\xe8$U\x86=J\xce\x88\x02\xe3\xe7M\x0c{\xca4\x00\x00\x13\x18S\x15\xac\xdd\xc3\xcb\xebE2IC\\j\xbf\xcb5\xbc\x0bv\x07\x80\x82\x01\xc0=\x82\x99\x0a\xc6Wt\xc9+\xde2ICzm[u\x92\x12\xba\xab\xd77\xe2}\xe5\xca:\xf5\xea\xb2\xeeX\xc1\xb6\xe2\x9dv\xb2\xca\x8c\xbar\xfc\x95\xa2\xcc\xf4B\x18E\x02@,a\xa6\x82y\x9b\x945\xbb\x99\xa4!.\xdb%\x9d\xa4\x8c\xde\xea\xf5\xfd\xfeYe\xd7\xe5u\xea\x99e\xdd\xb1\x82])\xe2j\xc8\x83\xbf\xd4\x95\xe3{f-\xa8?\xbc\x9a\x03\x05\x03\x80\x18\xc2D\x05\xeb\xf4\xf0\xb2l1ICzm/\xea$Y\x94U\xbb\x94\xd5\xeb\x91\x8b,\x97\xf0<\x99x\xc1.\xeb\x8e\x15\xac\xc6N\x9f\xe0\xc4\xac\x1c\xbfrA?Ys\x0b\x14\x0c\x00b\x08\xf3\x14\x8c\xaf\xe8\x14\x04\xa1\xbbR\x10\xd8\xa41/\xa9O\x1ad\x92,\x8a\x82\x11\xc5\x12\x03Z\xad\xf6~\xd4\x9fF\x16\xd0f\x97u/(/\xe3\xc4\x99\x17\xea\xca\xf1}t\x89!q\x8dT\x00\x00b\x04\xf3\x14\xec\xa2[&\xc0$\x0d\xcd#.y\xab\xb7r*\x12\x1c\x87\xd0!\xba:\x04\xbb\xac{\x81c\xc6\"q-+u\xe5\xf8\xb3\x1c\xd19\x88\xe4\x03@La\x9e\x82\x09AB\xbb'\x18\x14\x98\xa4\xa1\xf9Vu\x9d\xdb\xad:K\xde\x12t\x14\x0cm}\x0e=G\xa3\xfe\xec\xb2\xee\x05\x8e\x0b\xbd\xb3\xe8R\x8b\xea\xca\xf1\x9fq\xb5\xd4\x0c\x14\x0c\x00b\x08\xf3\x14L\x84\x09~\x85\x8d\x83\x8d\xa0\x0b\xa6\xab`\x1d\xf6+v\xbaT\x07\xbb\xac;\x99M\xd1l#\xbb\x99\x95\xe3\x97g\xf5!\xd4c\x97\x14\xacww\x0c<\x92\x1d\x00\xee{\xccU0!\xd0\xee\x09\\\x0bM\xeaQ\xa2\xf6\xbbJ\xf4\xba`\xfa\xab\xd7c\xb7\x99\xab3C\x96\x80\xef\xf3/w}0\xd8_\x90\xd9z\x9d]9\xfeBZVyY\xba\xb8N=Y\xec\xfdG\xc3\x0f\x02\x00\xc0\x04\xc3\\\x05\x0b\x90\xd8WUhR\x87\xebiE:I\x06\xfd\xd5\xeb15\xf6\x1a\xd1BY\xd6\xbd\x96\xd8\x9d%s\xc5\xc8\xe2\x83\xea\xca\xf1=\x05\xe9sK\xde\xb5\x89\xd9\xea\x1d\xf5:G\x01\x00`ba\xae\x82\x8d\x98W\xb9\x8fu\x92\x00\x00\xdc\xe7\xc4\x88\x82\xc1cu\x00\x00\xd0!F\x14\x0c\x00\x00@\x07P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\x00\x00b\x17P0\xf3\x18\xe8\xef\xef\x1fDh\x10\xbf\x0cD\xb2\x05\x00@\x0fP0\xd38\x16g\xb5Z\x13y>\x11\xbf\xc4\x1d\x8dd\x0d\x00\x80\x0e\xa0`c\x88wf\x10\xa1k3\x0d\xd6\"\xaf\xb6z[Z\xda\x11joi\xf1Z\xab\xf5m\x00\x00\x08\x8b\xc9\x0a\xd6\xee\xa6\x97w\xbf\x87>c\xda\x13\x8d\xc1\xd4~\xabDR\x18oMI\x06\xb2\x12\x9ek\xf9I\x89sF\xbc\xb0\xee6k1}\x89\xdb\xa6\xfbv\xb5\xb5[Nv\xdf\x81\x82\xdde\xd1G\xca\xa9\xef;Ern\x871\xca9a\xfc&\x00\x8c#\xe6*\x18_q\x8c>\x98\x95w\x9f\x09`\x82\x91\xecG\x02\x7f,>\xd7\x87\xd9`\x0d\xe3\xce\x9b8r\xc9`\x98\x9d\xec\xceM\xe4#YI\xec\x8a\xf3\x88\x09\xef\xa3\xa5z\xefGR0\xdf\xe9\xe1\xfb\xd0]\x17}\xa4\xdcj\xcb~\xa5\x0d\xb3\xc7\xf9\xb9\xb1\xd1\x89\xf9G\x8c\xdf\x04\x80q\xc4\\\x05\xf36uK\x0a\xa6\\\xccQ\x20\x81v}*\xc3)\x18\x1aqG\x8a\xe5\x9a\xb5\x14\x09#\x15\xb0\x8b\xf1\x1b\xe4\xe4\xa6\x04\xbd\xeaER\xb0\xe9\x8b\x87\xef#\xdcU\xd1\xef\x80\xec\xbdd{\xd0y#\x8cM\x98\xfe\x19\x00\x8c'\xa6*X\xbb\x87\x0f\x8c\x99\x82u\xad\x8bdw\xc7tY\xef`\x00\x97\x9b\xach\x1d\x9f\x9c\xabc\x10I\xc1R\x0d\x14l\xac\x11\x15\xec7o\x80J\x011\x80\x99\x0a\xc6Wt\xa1\x91)X\xb5\xd5j-F\xc5x[\x8d\xf8\xc5\xc9\xf1)\xf3\xce\xe0\xbdg\x1e\xb5Z7u-\x9d\x9a8gPc-*\x18&\xdf\x1a\xef^7-qvw\x88\x87D\xab5\x8e>\xd0Z\xebaWjb\xea.m6\x86\x81$1\xbc\xb6\x8e\x18\xc4U\xe2>\x96uZ\xa8m\xd7\xc2\xa4\xf8\xe49t\xc9\xb8\x81\xc9J\x17\x0cw\xc2\x12\xfb\xd10\xf4\x14\xec\xe8\xec\x94\xf8\xa49)\xb8w*\x05\xf3R\xb5\x85T\x8b\xae9ppiR\xca\xbauI\x9a\xf1e\xf7d\xc9\xc5dm=n7\xac\x9f\xbf\xb6\x81\xcaS\xdb\xe6\x15\xd9\xcfl^\x11\"U\xa2\x82a^qf\x1f|c\xd5\xfc\xcd\xbfG\xe8\x88\xd3\xe9\xdc\x8b\xf6\xe2\xed\x11tc>}\xc1|\x94\xedt\xee\xf9t\xfb\x8a\xec\xcd_0~\x99l\x000\xd6\x98\xa9`\xde&\xa4(XS\xb5\xbb\xea\xa8\xcee.\xc2\x1f\x9d\xbc)\x88\x82\x9b\xa6\x1c\xe5\xf1\xb5\x9d\xeb\xab\x9e\x17\xd7\x82P\x7fuU\xca\x93\x93S\xd6-\xb5j\xaf\xd1\x84M\xfd\xfd\x8bI\x0f\xac\xb3\xeaQkRq\xe9\xe4\x85Z\x0f\xe8\xa4\xcf'\xaa\x9c\xc6Cn\xc2&\xef\xa6\x84\xa5\x9al,g|U\xd6b\x9f\x0f\xebS\xc0\x17\x8f\xb3\xb7\xe4&\x84\xd8\xfa\x12S\xb75\x15[i\xd4\xeb\xb4\xd5\xa7f=fmA\xc3\xd0Q\xb0\x93\xd6\xa5\xd5M\xee$+\x96\xaac\xbe'3|>_gH!\x95\xa2\xb3\x07\x1e\x98\x96\xbc\xab8!\xb1b6\x1bo\x1b\xf0\x94\x8a\x84\xdc\x1f\xd9\x91\xbd\xe7\xd4\x9e\xec\xed8\xf5\xcb\xefo?r\xea\xe03\xce/4\xef\xa3\xec\x9f\xdc\xba\xb5\xe5\x0d\x9c\xf8\xe8H\xb63g\xef;\xf3\xb7\x20t\xa3m\xfeO>G\x9f\xef}\xba\x0d\x8f-\xcf\xb7\xb5\x89*w\xeb\xc8\x91\x15\xab\xe6\xe7\xbc\xb1\xfd\xfbW\x19\xbfL6\x00\x18kLT\xb0N\x0f\xaf*X\xe5\x99\xee\xce\xea*\xe3\xbb\x87\xb9dH\xb5\x98\x0c\xc6\xf8j2:K\x15\xe5%\xd5:\x1b\xff\x15\x12\x9aJ\x20=\x8fybr\x0a\xbe\xea\x97&i=\x10\x12\xe5~\x9a\xe2\xe1\x80\xf5\x00\"\xdb\xfd\xdal,\xea(\x92\xaaHq\x02\xd2\xd8\xf6\xa7\xcc\xc1\x15\x18\xa8\xa6+.y\xad\x17\xd5\x8c\xba\xa3D\x1d\x05\xdb\x95D\xfb\x82Sh\xa8\x8b\x19Ej\xaa)\x17]=p\x95\x15wIwY\xdb\x91\x86\xe0E\x11m8\xf0\x94\xf3\x94\xbcm\xc8!\xda\xd5\xf0th\x1f\x8c\xdc\x89\xdc,&\x9f\xc6\x1d\xa9\xed9$\xb9\x83h\xde\xf6\x1d\xb2\x8d\xdcO[\xeb\xdcx\x03\xdd\xbe\xa1\xf1\xcbf\x03\x80\xb1\xc5<\x05\xe3+:\x05A\xe8\xae\x14\xf0\xe5*\xb4\x93\xeeW\xbfG\xa7\xa7\"\xe1K\xe4\xf1\x10\x8avk\x82\xbb\xe6L\x9dL\x86W\x98T\xbd\x18yBnKK\xaa\xa4`D\xb1D\xa5a<\x20V\xc1d\x0f\xb9\xd3\xe8\xcb\xb4\\m6\x16}\x05Sl\xf7\x13!\x91\xa9\xd3*\x98\xce*L:\x0a\xd6\x9d<5\xdf}F\x10\x85\x9cU0\xb6\x9a\x8a\x82)\x07\xde0\x05\x0d\x0f\xd2]\x94g\x95h{\xa8;\xd6\xd2\x97U\xaf\x20t5g\xc5\xeb\x07\x7fu;4\xde\x95\xbd\xe3\xfc\xf9\xb5\x92\x82\x11\xc5\xda\x9bM\x92m\xd9\xb7\xd0\xad\xf9m\xb2\x8d\xa2`\xd9W\xc5\x04\xe3\x97\xcd\x06\x00c\x8by\x0av\xd1-\x13\x90w\x1d\xad3\xb4\x1eL\xaaCuI\xa4o\xd2\x92\x9c\xb2\xa1\xce\x97!)\xd8t\x1d[\"/\xfb\x9b\x94\xa4\xa44\xaa\x07\x82\xaa`\xb2\x87\x99\xa2\xe6\xcd\x99\xae\xcd\xc6\xa2\xaf`J\xb2\xd4\xca\x0c\x83[\xd8\x81c\x8b\xf5\x18\x1a\x86^\x1c\x8cw/\x9efM\x12\xe7\x8f\xb1\x0a\xc6VSQ0\xe6\xc0A\xd2{\x0c\xe9\x83\xf9\xbc\"\xcch\x16\xb3Q\xd4\xa6\xcd\xeb\xf1\xe6\xc6\xc1-\xab\x9c\xcf\xbc\xa3\x13\x07;\xd5\xa6$%)\xfa\xe2\xe9\x13\xe8\xc43\xb2\xa9\xaa`\xeb\xa5\x04\xeb\x97\xc9\x06\x00c\x8by\x0a&\x04\x09\xed\x9e`P@M\xa22\xf8\xc2\xdc\xea\xcb_\x88\x16\xe6\x93\xc4\xb4\x99d4\xb5PR\xb0\x90X\x15E\x8a\xe4\x9f\x09j\xa5H\xf1@P\x15L\xf6\x90;\x95\xbeL\xcdE#U\xb0M!\x0a\xd6defp\xf1\xca\xfd\x04\xf2v\xbc\xce\x1c\x0c\xbd8\x18\x89\xfe\xf3U\x89\xe4~\x82\xa8`\x15\xd4FS\xcd\xe1\x0a\xd6\x1d7\xa7\xfb\xcc\xd4\x99#\x9af\xb1\x83\x06\xeeo\xaf\xc0\xdd\xa4\xf3{p\xf2\xc6\x91\xec\x06\xad\x85\xa4N\x1f\xdd\xd0J\xd1\xeb[\xd0\x96\xd7Cl\xb0\x82m\x91\x12\x8c_P0`\xfc0O\xc1D\xc48\x98\x97v\xbe\xaey\x98QX(-\x09\x81\x04\xda\xabI!W\xb3\x90*)\x98\xde\x8c\x03I;\x927h\xa5H\xf1@P\x15L\xf6\xe0\xa5#\xbd**R\x11\x15,\x11\xfb\x16\xa6\x87(\x18\x9f\x9cA\xc6\x7f\xf9\xe24\x8e\xc5S\x15E\x11\xa6\xcdA\xc3\xd1Q\xb0b\x1a\x8bC\x19Th32\xf0\x88Y|CS\xcd\xe1\x0av\xda\x9ad\xb5f(]\xd9\xb0\x9c\xa2\xb7\x11\x8f\x90x\xd5^\x1a\xb4B\x1b_\xa1oto\x93\x86\xbd\x92:\xe5\xec\xd1J\xd1/\xb2\xaff\xffB\xf62\\\xc1\x18\xbf\xa0`\xc0\xf8a\xae\x82\x09\x81vO\xe0\x1a\xbez\xdc\x07\xba\xbaOVx\xc3\xf4\"\x84\xe4\xd9\xc9\xf4\xedb\xeb\xe2]\xdbR\xf1P\xabe\xb0\x85\xde\xafc\xe2M\x04yN\xbeo\xf2\xba\x80/>\xb7\x05\x9d\xce\x8d'7\x10U\x0f\x03->_B\xae\xef(\x8f4\x1e\x96\xc6m\xf0n\x88[Jo52\xd9\x14\xc4{\x91-\xd4\xc5\xcc\xa4m\xdbfZ\x1f\xf5tjl}\x8fMs\xef\xcf\xb7VP\xf3\xae8\xe5\xce`\xa9Uoz\xbd\xae\x82%\x16{\xebr\xc5\xdb\x98\xc5\x09\xa5u\xb3\x13\xbb5\x85T\x8b\xce\x1e\xf8tB\x93\xd7\x17\x18Q\x17\x0c\xa1\xed\xce='\xf68I\\~\xafs\xfe\xde\x13'v8\xc5\x11\xe3<+\x95YyN~\xdb\xfc7\xae\xe2\xe4\xcfo\xff\xf2\x95\xec6\x12\xeb\xba\x9d\xb3Y\xfc\xa1\xd1\xed\x9f\xb7Q\x9b\x1b\xe8\x8b_\xb4\xad\xda\xd8\xd6\xf6\xa9\xd6\xaf6\x1b\x00\x8c)\xe6*X\x80\x84\xc1H\xd7'\xd8T\xe5\xf1\xb6\x87\xbd\x06K\x13DE\x10J\x9fLHZ\xec\x99\x1a\x9fq&\x8e\x06\xaaC\xfa7\xca\xef\"\xad\xa5\xf9x\x13\xdfN\x9e\xfd\x90\xcfz8)\xe6\xb3\xba\x91\xc6\x83@\xe7\x83\x09d\xaa\x95&\x9b\xc4\xc0\x14j\xfbh'\xf9\xa3+#q\xf2\xec\x0d\xd8@k\xdb\xb58e\xf2t9\x98\xb7\xe91)\xf8u4Qwrm\xb5\xb5K~\xbaN\x97\xa4`\x95\x19\xc5)\xf1\xc9\x19b\xe4j\x20\x7fJb\x06\xee4\xb2\x85T\x8b\xce\x1e\xf8X<\xd9\x17\x9f\x11\x12\x08\xd3\xe7v\xc3Zi>\xd8\xc1\x8d{Wd\xe7l\x94\xa2\xf3\x9e$7y9\xe5\x94ix\x05o\xb2\x7fEf\x7f\xd1^Z\x834\xdc\xfc\xa5\xf4\xfeA\xf4\x91\x98\xd8\xac\xf5\x1b\x92\x0d\x00\xc6\x12s\x15\xec\xdef]<\xd5\xa5\xaa\xf8\\\xed\x8c[\x09:iUz\xba\x0e\x9d\xc3q\xb7\x04\x13\xf3\x83\x83\x83\xfc\xc9\x85SF\xfa\x8b'\x00\xb8W\x00\x05\x1bCJS\xc8\xd3u\x92\xf5\x1fM\x81\x06\xcf\xb4\xc8O\xd7i9\xa3\xabq#\xa4N\x9c?\x86\x84\xa4\xd1\xe8\x20\x00\xc4\"\xa0`\xb1\xcf\xe98q\xf8\xd8\x1e\x17\xe6N\x08\x00\xdc\x93\x80\x82\xc5>Bnb~\x95\xaf*?Q\xef\xf7\xe3\x00pO\x03\x0av/\xe0\x9d\x9d\x1c\x9f<\xe7\x0e\x1e\x9c\x01\x00\xf7\x08\xa0`\x00\x00\xc4.\xa0`\x00\x00\xc4.CC\xff\x1f\xf8\xaa\xf0z\xf7O\xc9\x8f\x00\x00\x00\x00IEND\xaeB`\x82",
- "analysis/help.html": "<!--{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\"Title\":\x20\"Static\x20analysis\x20features\x20of\x20godoc\"\x0a}-->\x0a\x0a<style>\x0a\x20\x20span.err\x20{\x20'font-size:120%;\x20color:darkred;\x20background-color:\x20yellow;\x20}\x0a\x20\x20img.ss\x20{\x20margin-left:\x201in;\x20}\x20/*\x20screenshot\x20*/\x0a\x20\x20img.dotted\x20{\x20border:\x20thin\x20dotted;\x20}\x0a</style>\x0a\x0a<!--\x20Images\x20were\x20grabbed\x20from\x20Chrome/Linux\x20at\x20150%\x20zoom,\x20and\x20are\x0a\x20\x20\x20\x20\x20displayed\x20at\x2066%\x20of\x20natural\x20size.\x20\x20This\x20allows\x20users\x20to\x20zoom\x20a\x0a\x20\x20\x20\x20\x20little\x20before\x20seeing\x20pixels.\x20-->\x0a\x0a<p>\x0a\x20\x20When\x20invoked\x20with\x20the\x20<code>-analysis</code>\x20flag,\x20godoc\x20performs\x0a\x20\x20static\x20analysis\x20on\x20the\x20Go\x20packages\x20it\x20indexes\x20and\x20displays\x20the\x0a\x20\x20results\x20in\x20the\x20source\x20and\x20package\x20views.\x20\x20This\x20document\x20provides\x20a\x0a\x20\x20brief\x20tour\x20of\x20these\x20features.\x0a</p>\x0a\x0a<h2>Type\x20analysis\x20features</h2>\x0a<p>\x0a\x20\x20<code>godoc\x20-analysis=type</code>\x20performs\x20static\x20checking\x20similar\x0a\x20\x20to\x20that\x20done\x20by\x20a\x20compiler:\x20it\x20detects\x20ill-formed\x20programs,\x20resolves\x0a\x20\x20each\x20identifier\x20to\x20the\x20entity\x20it\x20denotes,\x20computes\x20the\x20type\x20of\x20each\x0a\x20\x20expression\x20and\x20the\x20method\x20set\x20of\x20each\x20type,\x20and\x20determines\x20which\x0a\x20\x20types\x20are\x20assignable\x20to\x20each\x20interface\x20type.\x0a\x0a\x20\x20<b>Type\x20analysis</b>\x20is\x20relatively\x20quick,\x20requiring\x20about\x2010\x20seconds\x20for\x0a\x20\x20the\x20>200\x20packages\x20of\x20the\x20standard\x20library,\x20for\x20example.\x0a</p>\x0a\x0a<h3>Compiler\x20errors</h3>\x0a<p>\x0a\x20\x20If\x20any\x20source\x20file\x20contains\x20a\x20compilation\x20error,\x20the\x20source\x20view\x0a\x20\x20will\x20highlight\x20the\x20errant\x20location\x20in\x20red.\x20\x20Hovering\x20over\x20it\x0a\x20\x20displays\x20the\x20error\x20message.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='811'\x20src='error1.png'><br/>\x0a\x0a<h3>Identifier\x20resolution</h3>\x0a<p>\x0a\x20\x20In\x20the\x20source\x20view,\x20every\x20referring\x20identifier\x20is\x20annotated\x20with\x0a\x20\x20information\x20about\x20the\x20language\x20entity\x20it\x20refers\x20to:\x20a\x20package,\x0a\x20\x20constant,\x20variable,\x20type,\x20function\x20or\x20statement\x20label.\x0a\x0a\x20\x20Hovering\x20over\x20the\x20identifier\x20reveals\x20the\x20entity's\x20kind\x20and\x20type\x0a\x20\x20(e.g.\x20<code>var\x20x\x20int</code>\x20or\x20<code>func\x20f\x0a\x20\x20func(int)\x20string</code>).\x0a</p>\x0a<img\x20class=\"ss\"\x20width='652'\x20src='ident-field.png'><br/>\x0a<br/>\x0a<img\x20class=\"ss\"\x20width='652'\x20src='ident-func.png'>\x0a<p>\x0a\x20\x20Clicking\x20the\x20link\x20takes\x20you\x20to\x20the\x20entity's\x20definition.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='652'\x20src='ident-def.png'><br/>\x0a\x0a<h3>Type\x20information:\x20size/alignment,\x20method\x20set,\x20interfaces</h3>\x0a<p>\x0a\x20\x20Clicking\x20on\x20the\x20identifier\x20that\x20defines\x20a\x20named\x20type\x20causes\x20a\x20panel\x0a\x20\x20to\x20appear,\x20displaying\x20information\x20about\x20the\x20named\x20type,\x20including\x0a\x20\x20its\x20size\x20and\x20alignment\x20in\x20bytes,\x20its\x0a\x20\x20<a\x20href='http://golang.org/ref/spec#Method_sets'>method\x20set</a>,\x20and\x20its\x0a\x20\x20<i>implements</i>\x20relation:\x20the\x20set\x20of\x20types\x20T\x20that\x20are\x20assignable\x20to\x0a\x20\x20or\x20from\x20this\x20type\x20U\x20where\x20at\x20least\x20one\x20of\x20T\x20or\x20U\x20is\x20an\x20interface.\x0a\x0a\x20\x20This\x20example\x20shows\x20information\x20about\x20<code>net/rpc.methodType</code>.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='470'\x20src='typeinfo-src.png'>\x0a<p>\x0a\x20\x20The\x20method\x20set\x20includes\x20not\x20only\x20the\x20declared\x20methods\x20of\x20the\x20type,\x0a\x20\x20but\x20also\x20any\x20methods\x20\"promoted\"\x20from\x20anonymous\x20fields\x20of\x20structs,\x0a\x20\x20such\x20as\x20<code>sync.Mutex</code>\x20in\x20this\x20example.\x0a\x0a\x20\x20In\x20addition,\x20the\x20receiver\x20type\x20is\x20displayed\x20as\x20<code>*T</code>\x20or\x0a\x20\x20<code>T</code>\x20depending\x20on\x20whether\x20it\x20requires\x20the\x20address\x20or\x20just\x0a\x20\x20a\x20copy\x20of\x20the\x20receiver\x20value.\x0a</p>\x0a<p>\x0a\x20\x20The\x20method\x20set\x20and\x20<i>implements</i>\x20relation\x20are\x20also\x20available\x0a\x20\x20via\x20the\x20package\x20view.\x0a</p>\x0a<img\x20class=\"ss\x20dotted\"\x20width='716'\x20src='typeinfo-pkg.png'>\x0a\x0a<h2>Pointer\x20analysis\x20features</h2>\x0a<p>\x0a\x20\x20<code>godoc\x20-analysis=pointer</code>\x20additionally\x20performs\x20a\x20precise\x0a\x20\x20whole-program\x20<b>pointer\x20analysis</b>.\x20\x20In\x20other\x20words,\x20it\x0a\x20\x20approximates\x20the\x20set\x20of\x20memory\x20locations\x20to\x20which\x20each\x0a\x20\x20reference—not\x20just\x20vars\x20of\x20kind\x20<code>*T</code>,\x20but\x20also\x0a\x20\x20<code>[]T</code>,\x20<code>func</code>,\x20<code>map</code>,\x0a\x20\x20<code>chan</code>,\x20and\x20<code>interface</code>—may\x20refer.\x20\x20This\x0a\x20\x20information\x20reveals\x20the\x20possible\x20destinations\x20of\x20each\x20dynamic\x20call\x0a\x20\x20(via\x20a\x20<code>func</code>\x20variable\x20or\x20interface\x20method),\x20and\x20the\x0a\x20\x20relationship\x20between\x20send\x20and\x20receive\x20operations\x20on\x20the\x20same\x0a\x20\x20channel.\x0a</p>\x0a<p>\x0a\x20\x20Compared\x20to\x20type\x20analysis,\x20pointer\x20analysis\x20requires\x20more\x20time\x20and\x0a\x20\x20memory,\x20and\x20is\x20impractical\x20for\x20code\x20bases\x20exceeding\x20a\x20million\x20lines.\x0a</p>\x0a\x0a<h3>Call\x20graph\x20navigation</h3>\x0a<p>\x0a\x20\x20When\x20pointer\x20analysis\x20is\x20complete,\x20the\x20source\x20view\x20annotates\x20the\x0a\x20\x20code\x20with\x20<b>callers</b>\x20and\x20<b>callees</b>\x20information:\x20callers\x0a\x20\x20information\x20is\x20associated\x20with\x20the\x20<code>func</code>\x20keyword\x20that\x0a\x20\x20declares\x20a\x20function,\x20and\x20callees\x20information\x20is\x20associated\x20with\x20the\x0a\x20\x20open\x20paren\x20'<span\x20style=\"color:\x20dark-blue\"><code>(</code></span>'\x20of\x0a\x20\x20a\x20function\x20call.\x0a</p>\x0a<p>\x0a\x20\x20In\x20this\x20example,\x20hovering\x20over\x20the\x20declaration\x20of\x20the\x0a\x20\x20<code>rot13</code>\x20function\x20(defined\x20in\x20strings/strings_test.go)\x0a\x20\x20reveals\x20that\x20it\x20is\x20called\x20in\x20exactly\x20one\x20place.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='612'\x20src='callers1.png'>\x0a<p>\x0a\x20\x20Clicking\x20the\x20link\x20navigates\x20to\x20the\x20sole\x20caller.\x20\x20(If\x20there\x20were\x0a\x20\x20multiple\x20callers,\x20a\x20list\x20of\x20choices\x20would\x20be\x20displayed\x20first.)\x0a</p>\x0a<img\x20class=\"ss\"\x20width='680'\x20src='callers2.png'>\x0a<p>\x0a\x20\x20Notice\x20that\x20hovering\x20over\x20this\x20call\x20reveals\x20that\x20there\x20are\x2019\x0a\x20\x20possible\x20callees\x20at\x20this\x20site,\x20of\x20which\x20our\x20<code>rot13</code>\x0a\x20\x20function\x20was\x20just\x20one:\x20this\x20is\x20a\x20dynamic\x20call\x20through\x20a\x20variable\x20of\x0a\x20\x20type\x20<code>func(rune)\x20rune</code>.\x0a\x0a\x20\x20Clicking\x20on\x20the\x20call\x20brings\x20up\x20the\x20list\x20of\x20all\x2019\x20potential\x20callees,\x0a\x20\x20shown\x20truncated.\x20\x20Many\x20of\x20them\x20are\x20anonymous\x20functions.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='564'\x20src='call3.png'>\x0a<p>\x0a\x20\x20Pointer\x20analysis\x20gives\x20a\x20very\x20precise\x20approximation\x20of\x20the\x20call\x0a\x20\x20graph\x20compared\x20to\x20type-based\x20techniques.\x0a\x0a\x20\x20As\x20a\x20case\x20in\x20point,\x20the\x20next\x20example\x20shows\x20the\x20dynamic\x20call\x20inside\x0a\x20\x20the\x20<code>testing</code>\x20package\x20responsible\x20for\x20calling\x20all\x0a\x20\x20user-defined\x20functions\x20named\x20<code>Example<i>XYZ</i></code>.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='361'\x20src='call-eg.png'>\x0a<p>\x0a\x20\x20Recall\x20that\x20all\x20such\x20functions\x20have\x20type\x20<code>func()</code>,\x0a\x20\x20i.e.\x20no\x20arguments\x20and\x20no\x20results.\x20\x20A\x20type-based\x20approximation\x20could\x0a\x20\x20only\x20conclude\x20that\x20this\x20call\x20might\x20dispatch\x20to\x20any\x20function\x20matching\x0a\x20\x20that\x20type—and\x20these\x20are\x20very\x20numerous\x20in\x20most\x0a\x20\x20programs—but\x20pointer\x20analysis\x20can\x20track\x20the\x20flow\x20of\x20specific\x0a\x20\x20<code>func</code>\x20values\x20through\x20the\x20testing\x20package.\x0a\x0a\x20\x20As\x20an\x20indication\x20of\x20its\x20precision,\x20the\x20result\x20contains\x20only\x0a\x20\x20functions\x20whose\x20name\x20starts\x20with\x20<code>Example</code>.\x0a</p>\x0a\x0a<h3>Intra-package\x20call\x20graph</h3>\x0a<p>\x0a\x20\x20The\x20same\x20call\x20graph\x20information\x20is\x20presented\x20in\x20a\x20very\x20different\x20way\x0a\x20\x20in\x20the\x20package\x20view.\x20\x20For\x20each\x20package,\x20an\x20interactive\x20tree\x20view\x0a\x20\x20allows\x20exploration\x20of\x20the\x20call\x20graph\x20as\x20it\x20relates\x20to\x20just\x20that\x0a\x20\x20package;\x20all\x20functions\x20from\x20other\x20packages\x20are\x20elided.\x0a\x0a\x20\x20The\x20roots\x20of\x20the\x20tree\x20are\x20the\x20external\x20entry\x20points\x20of\x20the\x20package:\x0a\x20\x20not\x20only\x20its\x20exported\x20functions,\x20but\x20also\x20any\x20unexported\x20or\x0a\x20\x20anonymous\x20functions\x20that\x20are\x20called\x20(dynamically)\x20from\x20outside\x20the\x0a\x20\x20package.\x0a</p>\x0a<p>\x0a\x20\x20This\x20example\x20shows\x20the\x20entry\x20points\x20of\x20the\x0a\x20\x20<code>path/filepath</code>\x20package,\x20with\x20the\x20call\x20graph\x20for\x0a\x20\x20<code>Glob</code>\x20expanded\x20several\x20levels\x0a</p>\x0a<img\x20class=\"ss\x20dotted\"\x20width='501'\x20src='ipcg-pkg.png'>\x0a<p>\x0a\x20\x20Notice\x20that\x20the\x20nodes\x20for\x20Glob\x20and\x20Join\x20appear\x20multiple\x20times:\x20the\x0a\x20\x20tree\x20is\x20a\x20partial\x20unrolling\x20of\x20a\x20cyclic\x20graph;\x20the\x20full\x20unrolling\x0a\x20\x20is\x20in\x20general\x20infinite.\x0a</p>\x0a<p>\x0a\x20\x20For\x20each\x20function\x20documented\x20in\x20the\x20package\x20view,\x20another\x0a\x20\x20interactive\x20tree\x20view\x20allows\x20exploration\x20of\x20the\x20same\x20graph\x20starting\x0a\x20\x20at\x20that\x20function.\x0a\x0a\x20\x20This\x20is\x20a\x20portion\x20of\x20the\x20internal\x20graph\x20of\x0a\x20\x20<code>net/http.ListenAndServe</code>.\x0a</p>\x0a<img\x20class=\"ss\x20dotted\"\x20width='455'\x20src='ipcg-func.png'>\x0a\x0a<h3>Channel\x20peers\x20(send\x20\xe2\x86\x94\x20receive)</h3>\x0a<p>\x0a\x20\x20Because\x20concurrent\x20Go\x20programs\x20use\x20channels\x20to\x20pass\x20not\x20just\x20values\x0a\x20\x20but\x20also\x20control\x20between\x20different\x20goroutines,\x20it\x20is\x20natural\x20when\x0a\x20\x20reading\x20Go\x20code\x20to\x20want\x20to\x20navigate\x20from\x20a\x20channel\x20send\x20to\x20the\x0a\x20\x20corresponding\x20receive\x20so\x20as\x20to\x20understand\x20the\x20sequence\x20of\x20events.\x0a</p>\x0a<p>\x0a\x20\x20Godoc\x20annotates\x20every\x20channel\x20operation—make,\x20send,\x20range,\x0a\x20\x20receive,\x20close—with\x20a\x20link\x20to\x20a\x20panel\x20displaying\x20information\x0a\x20\x20about\x20other\x20operations\x20that\x20might\x20alias\x20the\x20same\x20channel.\x0a</p>\x0a<p>\x0a\x20\x20This\x20example,\x20from\x20the\x20tests\x20of\x20<code>net/http</code>,\x20shows\x20a\x20send\x0a\x20\x20operation\x20on\x20a\x20<code>chan\x20bool</code>.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='811'\x20src='chan1.png'>\x0a<p>\x0a\x20\x20Clicking\x20on\x20the\x20<code><-</code>\x20send\x20operator\x20reveals\x20that\x20this\x0a\x20\x20channel\x20is\x20made\x20at\x20a\x20unique\x20location\x20(line\x20332)\x20and\x20that\x20there\x20are\x0a\x20\x20three\x20receive\x20operations\x20that\x20might\x20read\x20this\x20value.\x0a\x0a\x20\x20It\x20hardly\x20needs\x20pointing\x20out\x20that\x20some\x20channel\x20element\x20types\x20are\x0a\x20\x20very\x20widely\x20used\x20(e.g.\x20struct{},\x20bool,\x20int,\x20interface{})\x20and\x20that\x20a\x0a\x20\x20typical\x20Go\x20program\x20might\x20contain\x20dozens\x20of\x20receive\x20operations\x20on\x20a\x0a\x20\x20value\x20of\x20type\x20<code>chan\x20bool</code>;\x20yet\x20the\x20pointer\x20analysis\x20is\x0a\x20\x20able\x20to\x20distinguish\x20operations\x20on\x20channels\x20at\x20a\x20much\x20finer\x20precision\x0a\x20\x20than\x20based\x20on\x20their\x20type\x20alone.\x0a</p>\x0a<p>\x0a\x20\x20Notice\x20also\x20that\x20the\x20send\x20occurs\x20in\x20a\x20different\x20(anonymous)\x20function\x0a\x20\x20from\x20the\x20outer\x20one\x20containing\x20the\x20<code>make</code>\x20and\x20the\x20receive\x0a\x20\x20operations.\x0a</p>\x0a<p>\x0a\x20\x20Here's\x20another\x20example\x20of\x20send\x20on\x20a\x20different\x20<code>chan\x0a\x20\x20bool</code>,\x20also\x20in\x20package\x20<code>net/http</code>:\x0a</p>\x0a<img\x20class=\"ss\"\x20width='774'\x20src='chan2a.png'>\x0a<p>\x0a\x20\x20The\x20analysis\x20finds\x20just\x20one\x20receive\x20operation\x20that\x20might\x20receive\x0a\x20\x20from\x20this\x20channel,\x20in\x20the\x20test\x20for\x20this\x20feature.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='737'\x20src='chan2b.png'>\x0a\x0a<h2>Known\x20issues</h2>\x0a<p>\x0a\x20\x20All\x20analysis\x20results\x20pertain\x20to\x20exactly\x0a\x20\x20one\x20configuration\x20(e.g.\x20amd64\x20linux).\x20\x20Files\x20that\x20are\x20conditionally\x0a\x20\x20compiled\x20based\x20on\x20different\x20platforms\x20or\x20build\x20tags\x20are\x20not\x20visible\x0a\x20\x20to\x20the\x20analysis.\x0a</p>\x0a<p>\x0a\x20\x20Files\x20that\x20<code>import\x20\"C\"</code>\x20require\x0a\x20\x20preprocessing\x20by\x20the\x20cgo\x20tool.\x20\x20The\x20file\x20offsets\x20after\x20preprocessing\x0a\x20\x20do\x20not\x20align\x20with\x20the\x20unpreprocessed\x20file,\x20so\x20markup\x20is\x20misaligned.\x0a</p>\x0a<p>\x0a\x20\x20Files\x20are\x20not\x20periodically\x20re-analyzed.\x0a\x20\x20If\x20the\x20files\x20change\x20underneath\x20the\x20running\x20server,\x20the\x20displayed\x0a\x20\x20markup\x20is\x20misaligned.\x0a</p>\x0a<p>\x0a\x20\x20Additional\x20issues\x20are\x20listed\x20at\x0a\x20\x20<a\x20href='https://go.googlesource.com/tools/+/master/godoc/analysis/README'>tools/godoc/analysis/README</a>.\x0a</p>\x0a",
+ "analysis/help.html": "<!--{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\"Title\":\x20\"Static\x20analysis\x20features\x20of\x20godoc\"\x0a}-->\x0a\x0a<style>\x0a\x20\x20span.err\x20{\x20'font-size:120%;\x20color:darkred;\x20background-color:\x20yellow;\x20}\x0a\x20\x20img.ss\x20{\x20margin-left:\x201in;\x20}\x20/*\x20screenshot\x20*/\x0a\x20\x20img.dotted\x20{\x20border:\x20thin\x20dotted;\x20}\x0a</style>\x0a\x0a<!--\x20Images\x20were\x20grabbed\x20from\x20Chrome/Linux\x20at\x20150%\x20zoom,\x20and\x20are\x0a\x20\x20\x20\x20\x20displayed\x20at\x2066%\x20of\x20natural\x20size.\x20\x20This\x20allows\x20users\x20to\x20zoom\x20a\x0a\x20\x20\x20\x20\x20little\x20before\x20seeing\x20pixels.\x20-->\x0a\x0a<p>\x0a\x20\x20When\x20invoked\x20with\x20the\x20<code>-analysis</code>\x20flag,\x20godoc\x20performs\x0a\x20\x20static\x20analysis\x20on\x20the\x20Go\x20packages\x20it\x20indexes\x20and\x20displays\x20the\x0a\x20\x20results\x20in\x20the\x20source\x20and\x20package\x20views.\x20\x20This\x20document\x20provides\x20a\x0a\x20\x20brief\x20tour\x20of\x20these\x20features.\x0a</p>\x0a\x0a<h2>Type\x20analysis\x20features</h2>\x0a<p>\x0a\x20\x20<code>godoc\x20-analysis=type</code>\x20performs\x20static\x20checking\x20similar\x0a\x20\x20to\x20that\x20done\x20by\x20a\x20compiler:\x20it\x20detects\x20ill-formed\x20programs,\x20resolves\x0a\x20\x20each\x20identifier\x20to\x20the\x20entity\x20it\x20denotes,\x20computes\x20the\x20type\x20of\x20each\x0a\x20\x20expression\x20and\x20the\x20method\x20set\x20of\x20each\x20type,\x20and\x20determines\x20which\x0a\x20\x20types\x20are\x20assignable\x20to\x20each\x20interface\x20type.\x0a\x0a\x20\x20<b>Type\x20analysis</b>\x20is\x20relatively\x20quick,\x20requiring\x20about\x2010\x20seconds\x20for\x0a\x20\x20the\x20>200\x20packages\x20of\x20the\x20standard\x20library,\x20for\x20example.\x0a</p>\x0a\x0a<h3>Compiler\x20errors</h3>\x0a<p>\x0a\x20\x20If\x20any\x20source\x20file\x20contains\x20a\x20compilation\x20error,\x20the\x20source\x20view\x0a\x20\x20will\x20highlight\x20the\x20errant\x20location\x20in\x20red.\x20\x20Hovering\x20over\x20it\x0a\x20\x20displays\x20the\x20error\x20message.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='811'\x20src='error1.png'><br/>\x0a\x0a<h3>Identifier\x20resolution</h3>\x0a<p>\x0a\x20\x20In\x20the\x20source\x20view,\x20every\x20referring\x20identifier\x20is\x20annotated\x20with\x0a\x20\x20information\x20about\x20the\x20language\x20entity\x20it\x20refers\x20to:\x20a\x20package,\x0a\x20\x20constant,\x20variable,\x20type,\x20function\x20or\x20statement\x20label.\x0a\x0a\x20\x20Hovering\x20over\x20the\x20identifier\x20reveals\x20the\x20entity's\x20kind\x20and\x20type\x0a\x20\x20(e.g.\x20<code>var\x20x\x20int</code>\x20or\x20<code>func\x20f\x0a\x20\x20func(int)\x20string</code>).\x0a</p>\x0a<img\x20class=\"ss\"\x20width='652'\x20src='ident-field.png'><br/>\x0a<br/>\x0a<img\x20class=\"ss\"\x20width='652'\x20src='ident-func.png'>\x0a<p>\x0a\x20\x20Clicking\x20the\x20link\x20takes\x20you\x20to\x20the\x20entity's\x20definition.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='652'\x20src='ident-def.png'><br/>\x0a\x0a<h3>Type\x20information:\x20size/alignment,\x20method\x20set,\x20interfaces</h3>\x0a<p>\x0a\x20\x20Clicking\x20on\x20the\x20identifier\x20that\x20defines\x20a\x20named\x20type\x20causes\x20a\x20panel\x0a\x20\x20to\x20appear,\x20displaying\x20information\x20about\x20the\x20named\x20type,\x20including\x0a\x20\x20its\x20size\x20and\x20alignment\x20in\x20bytes,\x20its\x0a\x20\x20<a\x20href='https://golang.org/ref/spec#Method_sets'>method\x20set</a>,\x20and\x20its\x0a\x20\x20<i>implements</i>\x20relation:\x20the\x20set\x20of\x20types\x20T\x20that\x20are\x20assignable\x20to\x0a\x20\x20or\x20from\x20this\x20type\x20U\x20where\x20at\x20least\x20one\x20of\x20T\x20or\x20U\x20is\x20an\x20interface.\x0a\x0a\x20\x20This\x20example\x20shows\x20information\x20about\x20<code>net/rpc.methodType</code>.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='470'\x20src='typeinfo-src.png'>\x0a<p>\x0a\x20\x20The\x20method\x20set\x20includes\x20not\x20only\x20the\x20declared\x20methods\x20of\x20the\x20type,\x0a\x20\x20but\x20also\x20any\x20methods\x20\"promoted\"\x20from\x20anonymous\x20fields\x20of\x20structs,\x0a\x20\x20such\x20as\x20<code>sync.Mutex</code>\x20in\x20this\x20example.\x0a\x0a\x20\x20In\x20addition,\x20the\x20receiver\x20type\x20is\x20displayed\x20as\x20<code>*T</code>\x20or\x0a\x20\x20<code>T</code>\x20depending\x20on\x20whether\x20it\x20requires\x20the\x20address\x20or\x20just\x0a\x20\x20a\x20copy\x20of\x20the\x20receiver\x20value.\x0a</p>\x0a<p>\x0a\x20\x20The\x20method\x20set\x20and\x20<i>implements</i>\x20relation\x20are\x20also\x20available\x0a\x20\x20via\x20the\x20package\x20view.\x0a</p>\x0a<img\x20class=\"ss\x20dotted\"\x20width='716'\x20src='typeinfo-pkg.png'>\x0a\x0a<h2>Pointer\x20analysis\x20features</h2>\x0a<p>\x0a\x20\x20<code>godoc\x20-analysis=pointer</code>\x20additionally\x20performs\x20a\x20precise\x0a\x20\x20whole-program\x20<b>pointer\x20analysis</b>.\x20\x20In\x20other\x20words,\x20it\x0a\x20\x20approximates\x20the\x20set\x20of\x20memory\x20locations\x20to\x20which\x20each\x0a\x20\x20reference—not\x20just\x20vars\x20of\x20kind\x20<code>*T</code>,\x20but\x20also\x0a\x20\x20<code>[]T</code>,\x20<code>func</code>,\x20<code>map</code>,\x0a\x20\x20<code>chan</code>,\x20and\x20<code>interface</code>—may\x20refer.\x20\x20This\x0a\x20\x20information\x20reveals\x20the\x20possible\x20destinations\x20of\x20each\x20dynamic\x20call\x0a\x20\x20(via\x20a\x20<code>func</code>\x20variable\x20or\x20interface\x20method),\x20and\x20the\x0a\x20\x20relationship\x20between\x20send\x20and\x20receive\x20operations\x20on\x20the\x20same\x0a\x20\x20channel.\x0a</p>\x0a<p>\x0a\x20\x20Compared\x20to\x20type\x20analysis,\x20pointer\x20analysis\x20requires\x20more\x20time\x20and\x0a\x20\x20memory,\x20and\x20is\x20impractical\x20for\x20code\x20bases\x20exceeding\x20a\x20million\x20lines.\x0a</p>\x0a\x0a<h3>Call\x20graph\x20navigation</h3>\x0a<p>\x0a\x20\x20When\x20pointer\x20analysis\x20is\x20complete,\x20the\x20source\x20view\x20annotates\x20the\x0a\x20\x20code\x20with\x20<b>callers</b>\x20and\x20<b>callees</b>\x20information:\x20callers\x0a\x20\x20information\x20is\x20associated\x20with\x20the\x20<code>func</code>\x20keyword\x20that\x0a\x20\x20declares\x20a\x20function,\x20and\x20callees\x20information\x20is\x20associated\x20with\x20the\x0a\x20\x20open\x20paren\x20'<span\x20style=\"color:\x20dark-blue\"><code>(</code></span>'\x20of\x0a\x20\x20a\x20function\x20call.\x0a</p>\x0a<p>\x0a\x20\x20In\x20this\x20example,\x20hovering\x20over\x20the\x20declaration\x20of\x20the\x0a\x20\x20<code>rot13</code>\x20function\x20(defined\x20in\x20strings/strings_test.go)\x0a\x20\x20reveals\x20that\x20it\x20is\x20called\x20in\x20exactly\x20one\x20place.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='612'\x20src='callers1.png'>\x0a<p>\x0a\x20\x20Clicking\x20the\x20link\x20navigates\x20to\x20the\x20sole\x20caller.\x20\x20(If\x20there\x20were\x0a\x20\x20multiple\x20callers,\x20a\x20list\x20of\x20choices\x20would\x20be\x20displayed\x20first.)\x0a</p>\x0a<img\x20class=\"ss\"\x20width='680'\x20src='callers2.png'>\x0a<p>\x0a\x20\x20Notice\x20that\x20hovering\x20over\x20this\x20call\x20reveals\x20that\x20there\x20are\x2019\x0a\x20\x20possible\x20callees\x20at\x20this\x20site,\x20of\x20which\x20our\x20<code>rot13</code>\x0a\x20\x20function\x20was\x20just\x20one:\x20this\x20is\x20a\x20dynamic\x20call\x20through\x20a\x20variable\x20of\x0a\x20\x20type\x20<code>func(rune)\x20rune</code>.\x0a\x0a\x20\x20Clicking\x20on\x20the\x20call\x20brings\x20up\x20the\x20list\x20of\x20all\x2019\x20potential\x20callees,\x0a\x20\x20shown\x20truncated.\x20\x20Many\x20of\x20them\x20are\x20anonymous\x20functions.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='564'\x20src='call3.png'>\x0a<p>\x0a\x20\x20Pointer\x20analysis\x20gives\x20a\x20very\x20precise\x20approximation\x20of\x20the\x20call\x0a\x20\x20graph\x20compared\x20to\x20type-based\x20techniques.\x0a\x0a\x20\x20As\x20a\x20case\x20in\x20point,\x20the\x20next\x20example\x20shows\x20the\x20dynamic\x20call\x20inside\x0a\x20\x20the\x20<code>testing</code>\x20package\x20responsible\x20for\x20calling\x20all\x0a\x20\x20user-defined\x20functions\x20named\x20<code>Example<i>XYZ</i></code>.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='361'\x20src='call-eg.png'>\x0a<p>\x0a\x20\x20Recall\x20that\x20all\x20such\x20functions\x20have\x20type\x20<code>func()</code>,\x0a\x20\x20i.e.\x20no\x20arguments\x20and\x20no\x20results.\x20\x20A\x20type-based\x20approximation\x20could\x0a\x20\x20only\x20conclude\x20that\x20this\x20call\x20might\x20dispatch\x20to\x20any\x20function\x20matching\x0a\x20\x20that\x20type—and\x20these\x20are\x20very\x20numerous\x20in\x20most\x0a\x20\x20programs—but\x20pointer\x20analysis\x20can\x20track\x20the\x20flow\x20of\x20specific\x0a\x20\x20<code>func</code>\x20values\x20through\x20the\x20testing\x20package.\x0a\x0a\x20\x20As\x20an\x20indication\x20of\x20its\x20precision,\x20the\x20result\x20contains\x20only\x0a\x20\x20functions\x20whose\x20name\x20starts\x20with\x20<code>Example</code>.\x0a</p>\x0a\x0a<h3>Intra-package\x20call\x20graph</h3>\x0a<p>\x0a\x20\x20The\x20same\x20call\x20graph\x20information\x20is\x20presented\x20in\x20a\x20very\x20different\x20way\x0a\x20\x20in\x20the\x20package\x20view.\x20\x20For\x20each\x20package,\x20an\x20interactive\x20tree\x20view\x0a\x20\x20allows\x20exploration\x20of\x20the\x20call\x20graph\x20as\x20it\x20relates\x20to\x20just\x20that\x0a\x20\x20package;\x20all\x20functions\x20from\x20other\x20packages\x20are\x20elided.\x0a\x0a\x20\x20The\x20roots\x20of\x20the\x20tree\x20are\x20the\x20external\x20entry\x20points\x20of\x20the\x20package:\x0a\x20\x20not\x20only\x20its\x20exported\x20functions,\x20but\x20also\x20any\x20unexported\x20or\x0a\x20\x20anonymous\x20functions\x20that\x20are\x20called\x20(dynamically)\x20from\x20outside\x20the\x0a\x20\x20package.\x0a</p>\x0a<p>\x0a\x20\x20This\x20example\x20shows\x20the\x20entry\x20points\x20of\x20the\x0a\x20\x20<code>path/filepath</code>\x20package,\x20with\x20the\x20call\x20graph\x20for\x0a\x20\x20<code>Glob</code>\x20expanded\x20several\x20levels\x0a</p>\x0a<img\x20class=\"ss\x20dotted\"\x20width='501'\x20src='ipcg-pkg.png'>\x0a<p>\x0a\x20\x20Notice\x20that\x20the\x20nodes\x20for\x20Glob\x20and\x20Join\x20appear\x20multiple\x20times:\x20the\x0a\x20\x20tree\x20is\x20a\x20partial\x20unrolling\x20of\x20a\x20cyclic\x20graph;\x20the\x20full\x20unrolling\x0a\x20\x20is\x20in\x20general\x20infinite.\x0a</p>\x0a<p>\x0a\x20\x20For\x20each\x20function\x20documented\x20in\x20the\x20package\x20view,\x20another\x0a\x20\x20interactive\x20tree\x20view\x20allows\x20exploration\x20of\x20the\x20same\x20graph\x20starting\x0a\x20\x20at\x20that\x20function.\x0a\x0a\x20\x20This\x20is\x20a\x20portion\x20of\x20the\x20internal\x20graph\x20of\x0a\x20\x20<code>net/http.ListenAndServe</code>.\x0a</p>\x0a<img\x20class=\"ss\x20dotted\"\x20width='455'\x20src='ipcg-func.png'>\x0a\x0a<h3>Channel\x20peers\x20(send\x20\xe2\x86\x94\x20receive)</h3>\x0a<p>\x0a\x20\x20Because\x20concurrent\x20Go\x20programs\x20use\x20channels\x20to\x20pass\x20not\x20just\x20values\x0a\x20\x20but\x20also\x20control\x20between\x20different\x20goroutines,\x20it\x20is\x20natural\x20when\x0a\x20\x20reading\x20Go\x20code\x20to\x20want\x20to\x20navigate\x20from\x20a\x20channel\x20send\x20to\x20the\x0a\x20\x20corresponding\x20receive\x20so\x20as\x20to\x20understand\x20the\x20sequence\x20of\x20events.\x0a</p>\x0a<p>\x0a\x20\x20Godoc\x20annotates\x20every\x20channel\x20operation—make,\x20send,\x20range,\x0a\x20\x20receive,\x20close—with\x20a\x20link\x20to\x20a\x20panel\x20displaying\x20information\x0a\x20\x20about\x20other\x20operations\x20that\x20might\x20alias\x20the\x20same\x20channel.\x0a</p>\x0a<p>\x0a\x20\x20This\x20example,\x20from\x20the\x20tests\x20of\x20<code>net/http</code>,\x20shows\x20a\x20send\x0a\x20\x20operation\x20on\x20a\x20<code>chan\x20bool</code>.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='811'\x20src='chan1.png'>\x0a<p>\x0a\x20\x20Clicking\x20on\x20the\x20<code><-</code>\x20send\x20operator\x20reveals\x20that\x20this\x0a\x20\x20channel\x20is\x20made\x20at\x20a\x20unique\x20location\x20(line\x20332)\x20and\x20that\x20there\x20are\x0a\x20\x20three\x20receive\x20operations\x20that\x20might\x20read\x20this\x20value.\x0a\x0a\x20\x20It\x20hardly\x20needs\x20pointing\x20out\x20that\x20some\x20channel\x20element\x20types\x20are\x0a\x20\x20very\x20widely\x20used\x20(e.g.\x20struct{},\x20bool,\x20int,\x20interface{})\x20and\x20that\x20a\x0a\x20\x20typical\x20Go\x20program\x20might\x20contain\x20dozens\x20of\x20receive\x20operations\x20on\x20a\x0a\x20\x20value\x20of\x20type\x20<code>chan\x20bool</code>;\x20yet\x20the\x20pointer\x20analysis\x20is\x0a\x20\x20able\x20to\x20distinguish\x20operations\x20on\x20channels\x20at\x20a\x20much\x20finer\x20precision\x0a\x20\x20than\x20based\x20on\x20their\x20type\x20alone.\x0a</p>\x0a<p>\x0a\x20\x20Notice\x20also\x20that\x20the\x20send\x20occurs\x20in\x20a\x20different\x20(anonymous)\x20function\x0a\x20\x20from\x20the\x20outer\x20one\x20containing\x20the\x20<code>make</code>\x20and\x20the\x20receive\x0a\x20\x20operations.\x0a</p>\x0a<p>\x0a\x20\x20Here's\x20another\x20example\x20of\x20send\x20on\x20a\x20different\x20<code>chan\x0a\x20\x20bool</code>,\x20also\x20in\x20package\x20<code>net/http</code>:\x0a</p>\x0a<img\x20class=\"ss\"\x20width='774'\x20src='chan2a.png'>\x0a<p>\x0a\x20\x20The\x20analysis\x20finds\x20just\x20one\x20receive\x20operation\x20that\x20might\x20receive\x0a\x20\x20from\x20this\x20channel,\x20in\x20the\x20test\x20for\x20this\x20feature.\x0a</p>\x0a<img\x20class=\"ss\"\x20width='737'\x20src='chan2b.png'>\x0a\x0a<h2>Known\x20issues</h2>\x0a<p>\x0a\x20\x20All\x20analysis\x20results\x20pertain\x20to\x20exactly\x0a\x20\x20one\x20configuration\x20(e.g.\x20amd64\x20linux).\x20\x20Files\x20that\x20are\x20conditionally\x0a\x20\x20compiled\x20based\x20on\x20different\x20platforms\x20or\x20build\x20tags\x20are\x20not\x20visible\x0a\x20\x20to\x20the\x20analysis.\x0a</p>\x0a<p>\x0a\x20\x20Files\x20that\x20<code>import\x20\"C\"</code>\x20require\x0a\x20\x20preprocessing\x20by\x20the\x20cgo\x20tool.\x20\x20The\x20file\x20offsets\x20after\x20preprocessing\x0a\x20\x20do\x20not\x20align\x20with\x20the\x20unpreprocessed\x20file,\x20so\x20markup\x20is\x20misaligned.\x0a</p>\x0a<p>\x0a\x20\x20Files\x20are\x20not\x20periodically\x20re-analyzed.\x0a\x20\x20If\x20the\x20files\x20change\x20underneath\x20the\x20running\x20server,\x20the\x20displayed\x0a\x20\x20markup\x20is\x20misaligned.\x0a</p>\x0a<p>\x0a\x20\x20Additional\x20issues\x20are\x20listed\x20at\x0a\x20\x20<a\x20href='https://go.googlesource.com/tools/+/master/godoc/analysis/README'>tools/godoc/analysis/README</a>.\x0a</p>\x0a",
"analysis/ident-def.png": "\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\x00\x03\xd2\x00\x00\x00\xf5\x08\x03\x00\x00\x00\x8b\x0c=\xff\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x02\xfdPLTE!#\x20#$\"$%#($#&'%'(&-'\")+(4,\",.+01/>/\"241A2%685G6$8:7;<:Q9$>@=W>#AB@CEB\\B'cC$GIF\x00f\x00\x00g\x00\x00h\x00gF'\x00i\x01KLJ\x00j\x02\x02k\x03\x04l\x05mJ&\x06m\x06sJ(OQN\x0bo\x08\x0ep\x0awN&\x0aq\x16TUS\x10q\x0c\x0cr\x17\x0fs\x18~R%WYV\x12u\x1a,`\xae7]\xad\x15v\x1c\x86T)\x16w\x1d[]Z9a\xab\x19y\x1e^_];b\xac<c\xad\x8dZ(>e\xaf)z\x20ac`+{\"(|*\x92]%cebAh\xb2egd+\x7f,\x9a_)Li\xafDl\xb0.\x81.ikh0\x830Ho\xb31\x841/\x858Jq\xb6mol\xa3f);\x86:Us\xb3\xabg->\x88<Wu\xb5\xaej(sur@\x8b>B\x8c@\xb3m,vxu@\x8dGC\x8dA\\z\xbbxzwI\x8dH]}\xb7K\x8fJ\xb9r*_\x80\xbaM\x91L}\x7f|\xc1s-O\x93MP\x94Oc\x84\xbeN\x95U\x81\x83\x80\xc7x+W\x95VX\x97Xm\x87\xbcY\x98Y\xd1z(Z\x99Z[\x9aZ\x87\x89\x86\\\x9b[r\x8c\xc2\xd6~-^\x9d]\\\x9dc\x8a\x8c\x89d\x9ddz\x8f\xbff\x9ff\x8f\x91\x8eh\xa1h\xde\x85+~\x93\xc4j\xa3j\x92\x94\x91k\xa4ky\x97\xc6s\xa4lq\xa5s\x95\x97\x94\xec\x8a,\x83\x9b\xc5\x98\x9a\x96\x9a\x9b\x98u\xaaw\x86\x9e\xc8\xf1\x8e/\x9b\x9d\x9a~\xaay\x88\xa0\xcb\x9e\xa0\x9d\x80\xad|\xf7\x93-\xa0\xa2\x9f\xfd\x92/\x81\xb0\x85\xa2\xa4\xa1\x92\xa5\xca\xff\x952\x82\xb2\x86\xa4\xa6\xa3\x83\xb3\x87\x8b\xb2\x88\xa6\xa8\xa5\x96\xa9\xce\x8d\xb5\x8a\xa9\xab\xa8\x8e\xb6\x8c\x9e\xad\xcd\x8d\xb8\x93\xac\xae\xab\x95\xb8\x95\xa1\xb1\xd1\x97\xba\x96\xaf\xb1\xae\x98\xbb\x98\xa4\xb3\xd4\xb2\xb4\xb0\xa5\xb5\xd5\x9b\xbe\x9a\xb5\xb7\xb4\xac\xb7\xd1\xa3\xbf\x9d\xa2\xc0\xa4\xb7\xb9\xb6\xa4\xc2\xa6\xb9\xbb\xb8\xa5\xc4\xa8\xb1\xbd\xd7\xbb\xbd\xba\xa8\xc7\xaa\xb7\xbf\xd4\xaf\xc6\xab\xbe\xc0\xbd\xb9\xc1\xd6\xc0\xc2\xbe\xb2\xc9\xae\xc1\xc3\xc0\xbb\xc3\xd8\xbc\xc4\xd9\xb1\xcc\xb6\xc4\xc6\xc3\xb3\xcd\xb8\xb9\xcc\xb8\xc1\xc8\xde\xbb\xca\xde\xc7\xc9\xc6\xbb\xce\xba\xbd\xcf\xbb\xca\xcc\xc8\xc7\xcb\xdb\xbf\xd2\xbe\xc4\xcf\xdd\xcd\xcf\xcc\xcb\xcf\xdf\xc8\xd3\xc1\xc6\xd4\xc7\xcd\xd1\xe1\xd0\xd2\xcf\xc8\xd6\xca\xd2\xd2\xdd\xd2\xd4\xd1\xca\xd8\xcc\xd3\xd5\xd2\xce\xd6\xdf\xcc\xda\xcd\xd5\xd7\xd4\xd6\xd8\xd5\xcd\xdc\xcf\xd4\xdb\xd0\xd8\xd9\xe3\xd2\xdb\xe3\xd5\xdc\xd1\xd9\xdb\xd8\xd7\xde\xd3\xd5\xdf\xda\xdc\xde\xdb\xda\xdf\xe2\xd7\xe1\xdc\xe0\xde\xe2\xde\xe0\xdd\xd8\xe2\xdd\xdd\xe2\xe5\xe0\xe2\xdf\xe2\xe4\xe1\xdf\xe5\xe7\xe4\xe6\xe3\xe8\xe5\xea\xe2\xe7\xea\xe5\xe7\xe4\xe6\xe8\xe5\xe4\xe9\xec\xe7\xe9\xe6\xfe\xff\xfc\x93\x8dkM\x00\x00\x20\x00IDATx^\xed\x9d\x7ft\x14\xd5\xdd\xff\x07%\x064*\x15\xe1y<\xb3=lbJ0@\x94\x86F\xac(\x98o\xe5\xb4\x86\xf4\x89i\xfa\x8d\xf6\xa4R\x7f`A\x10\x1a\xcd\x93\xc3\xb1\"\x02\x15\x1e\x8c\xf5l\xea\x81\xd8\xb4\x91`h\xf6(\xb89r\xb2\x12\xe4\xc8\xba(<\x12\xda''\x94\xd2\xf6!\xc5\x08\xc8S(\xc8\x93v\xd7\xd4\xb3\xde\xef\xf9\xde{\xe7\xd7\xbd\xb3wfv\x03a\xc8\xec\xe7\xf5\xc7\xee\xec\xe43\xf7\xde\xf9\xcc\xbc\xf7\xde;;\x99\xb7\xf4\xff.\x00\x04\x00\xc0e\x86\xe4$[;\x9c\x0a\x07\x00\xe0R\x03\x92\x06\x00O\x01\x92\x06\x00O\x01\x92\x06\x00O\x01\x92\x06\x00O1\x92$]_/Z\x04\x00\x80\xc1=I\xf7w4\x07Zv\x9dS?u\x07\x82\xb6\xd1\x98?\xc9\xeb\x04\x8b\x84_\xcb\x849\xed\xc9\xdbX\xf0\xb1O.u\x8a\xb1'\xc5\x12\x9e\x95\xe5\x90S\xccP\xe9$;]\x82\x17z\x17\x14\x16TE\xca\xf6!\x94h\x983\xcd7\xed\x9eF\x84\xa2\xb2\\\x95@!\x1c\x11q*\xc7\x0e\xf1n\xa6\xb8\xf3\xa8\x0eW\x9f\xd7\xe7\x14\xa5s\xb6\xbe(wA\xc2)*\x89\xc8\xf4\x9dN!\x19\x85k\x92>\x11\x08\xf6\x1c\xeb\xd9\xd2<@?\x9d\x0b\xecjr\xd8\x00\xd5\xe6\x9e\x16,\x12\xce\xbf*\xb7G\xbb\x96\xca\x0d\xc9\x1bYp\xa0\xce\xef\x14\xe2\x80}\x09\x91^\xe5\xfdT\xd4\xd7h\x13\x96\x1aZa&\x06\xf6E\x0b\x16\x1d\xc6\x8a\xf6=\xda\xb9}\x11\xfd\xee\x88\xc8\x0b\xda\xba\xda\x17\xcaQ\x14\x8f\xc8\xb9Q\x14\x0b\xc9\x91\xb8p\xe3T\x11\xeff\x8a\xe9;\x1e\x8d\xb6\xc9\xfb\x9c\xa2t\xaa\x8b\xda\xea\xf2\xce;E%\xd1\x95\xa7}kZ$*\xc3pM\xd2\x87\x02\xc7\xf0\xeb@\xa0\x87~\x0av\x1cs\x92t\x9f\xbcZ\xb0\xa8\x10\x91\xf1\xa9\x8d\xbb\xc4?\xa3TiL\xe9\x9c\xb4\xc3\xb6\x84\x7f[\xa2-\xf9/\\\xd2Faf\x0a\xd7\xe2\x97\x9aj\xb2XG$\xbd\xba\x80\x088>\x05\xa7'&\xd7\xd6\xe1\xfeT\x8eYm\x9b\"\xe2\xddL9}\x07S\x97\xf4y\xb9\x15%\x86\xd2\\\xbdc\xb7NT&\xe1\x9a\xa4\x11\xed\x9eO\x06\xfa\xc9[w\xd3@\xbf\x93\xa4\xeb\xfc\xa7\x04\x8b\x0a\x8a\xa4\xe3\x05+Q\xaa\xa4|NZb[B\xf9\xc5\x94t\xb9\xbd\xa4K\xe8\xe0\xa4W\xee\xc4\xfd\\\x05]]QC$\x1d\xc9\x8f\x8d(I\xf7\xc9\x17:\x80\xb6NT&\xe1\x9e\xa41\xb1\xa3-A\xf2\x15{.p\x089I\xfac\xdf*\xc1\xa2\x8a\"i\xb4\xb2\x90\xbcn\xaf\xca\xbbg\xb5r&\xf7-*\xf4\x15-\xa0\xfao+\xcf+k\xa3+O\xd5\xce\x98\xb2H\x1d92\xb1\x1a\xbd>Yn\xe8\xab-\xc9\xadI\xb0\x01\x03Kf\xf8J\x16\xf6\x9aK\x88\xd6\x94\xf8\x0a\x17\x94p%\xec\x94\x15\xca\xc9\x07\xff\xda\xd5\xf7\xe4\xd5\x1c\xa7\x7f\x10\xd4\xb6R\xf6\xb5'\x07\x1c\xc6mX\x88f\xcarA\x9c+\xcc\x0c\x95\xf4\xd2\"\xba\xf3]x\xbb\xaaEt\xf5\xa2*\"\xe9\x8f\xcbC\x02IkU\xf0\x15\xa7\x91(v\xad\x06\x99\xb37\xa2F\x99\x8c\xfe\xd9D\xe9\x92^)\xcb\xdb\x8d)\xb8\x20\x0f\x83\x85\xcan\xae6\x1f\x00\xb59\x89\"\xdf\x8b3gDV\x15T\x0fp\xdb\x9d\xcf\x93\xd5\xcb\x15\\\xa2D\x87%SpQ\xd2'\x03\x18zy,\xd8\x81\x1c%]\xef;.XTQ%\xdd.\xe3\xe3]/\xaf\xeaj\x9d^NN\x87H~\xf9\xab\x91F<\x9e#\x1d{CW\x83\xff)\xbc\xd4WpO\xfb\xce\x1a\x99\x9e\x93L\xacN<\x14*\x99\x93?su\xad|\x9c\x0d\xe8\x92\xeb#\xa1\x85\xf2A\xbe\x84\xdf\xcaO\x85\"\xed\x852W\x02\x9e\xe4\xce\xa9\x8eF\xa3t\x1a\xe0\x97KC;\x8b\x16Y\xd5\xf6\xa7\x90O\x9e\xde\xf8\xeb|>\x20q\xa0Q>\x80\xde\x94\xb7\xf7\xf2\x85\x99\xa1\x92>^\"\xd74\x1e\x18$\x9f\xcb\x96\xd1\xd5\xcb\xca\xa8\xa4[\x17(\x92\x8e\x7f\xac\x10g\xab\xe0*N#Q\xecZ\x9dX\xb4\xa0\xe1o\xe8o\x0d\x05\xd1\x18\x9b(\xa4KZ\xb9\xa6\xa0N\xc1Ey@\xbd\xd1\x90\xdc\x18\x8d\x9e6\x1d\x00\xbd9\x91\x02yu\xb5\\\xb8\xb1\xa8\x95\xdb\x0co\x17U\x06Bl\xa2\x84\x87%SpQ\xd2\xe8d\x7fwK3\xd6tO`\xc0Q\xd2\xc7}\xcf\x0a\x165TIw\xe1\xc9t\x97\xfc&\"g\x12\xfe\xe6\x8e\x97\xd4\xe0\xb3x0t\x1e\xa1w\xe9\x85\xdf\x88\xdc\x85{\xb29\xf8,O\x94\xf9i\xbc\x1e\xcbQ.\x93\xbe`\x80\x0b\x88\x85H\xb7R\xb6\x10q%\xb4M\xa7=I\x81\xf9\xdca\x06\xdeEg\xf1I<\x1dY\xd6\xe6\x9f\x82\xcf\xdc\xda\xa4\x80\xba\x8a\xb337\x9a\x0b3C%\x8d\xceo\xac\xf0\xc9S\xc8V\xa5\xb5tum)\x95\xf4\xe9\xdc\xd3T\xd2\xd5j\xffU\xc5WaT\x9cN\xa2\x98\xb5,\xf5K\xf1\xcb2\xf2\xd3\"\x93(\xc4\x0c\xbc\xa9\xee\x1a\xed\xb2\xce\x0c\xbc\xf5\x03\xc04\xa7\xa8\x16o\xb9\x13\xd5%\xff|\x99\xab\xcdm\xf4DY\x1d\x96\x8c\xc0MIc\xe2-\x1d\xe4\x12Y\"\x918\xda\x94\xb0;\x00+}\x1f\x0b\x165\xf4^\xfa<Zz\xc7\x20.-1\xb3\x9e\x9c;\xfa\x15\xd0\xfa2\xfaVZ\x87\xce\xcb\xf4\xb7\xae\x06rr1\xb1\x1c\xe5~u\x18\xc0\x06\x9cm\xab\xb9\xa3@.C\\\x09\x9f\xce(Y\xd9\xde\x9b\x184\x15\xc0J\x9al\xdbhS\x9b\xbfN\x18\x10/+z4\xa903\x8a\xa4It\xe4G\xe4\xb4W{\xe9\xa5J/\x8d\x16\xb4RI\xf7\xedT\xe8\xe3\xab0*N#Q\xecZ\x96Hn\x0c\xc5\xf2\xa2dQO\x14A\x20i\xab\xac\xb3\x92\xd6\x0e\x80\xd1\x1cT\x14\xc2\x85\x0d\xa0\xb5\xcb\x90\x99dI[\x1d\x96\x8c\xc05I\xc7\x15\x01\xef\x0f$\x8e\x044\xfa-\xa3O\xf9\xeb\x05\x8b:\xaa\xa4\xd7\x16\xe0\xb3Z\xed\x92p/\xd1*\xeb?\xe0T)\xf2x\xb4\x02\xf5\xca\xf4\xbc\xa3'\x17\x13\xcbQ\xaeM\\\x99\x80\x83E%k;\xa3\xd5e\x88+\x01\x9do_R*\x17\xbdj*\xc0|y\xcc\xae6\xab\x80\x90~a\xc9A\xd2\x07\xe9\x148Q\x857\xabR6]\xa8\xcc\xa5Q\xa8\xcc<\x97f\xab0*N#Q\xdc\xce3$\x0a;Qg!9\xa6F\xa2\x08\x02I[e\x9d\x95\xb4v\x00\x8c\xe6\xa0\xa2w\xd1A\x1fB\xebR\x91\xb4\xd5a\xc9\x08\xdc\x92t\xa2i\x07}\xc7\x92N\x9c$t\x07N\x9e\xb4\xee\xa6W\xc9}\x82E\x1dE\xd2\x83\xd3\xf1!\xad\xbd\xa3\x97r\x96\xac5:\x9f;\xe8[I\x1d:+\xd3k?tV\xc7\xc4r\x94/R\x17\x98\x80\xd2\x0a\"\x8eE\xf8LeK\xe8%\xb7\xbc\xfc=\x94\xdbf.\x81\x9c\\\xed\xa4\xab1Ne\x8b\xda,\x02N\x155\x94\x9c5\x17f\x90\xa0sk*\xe9\xa2\x95tM\xc3\x1c<\xc4V\xa4P^\xa3H:\x96\x171I\x9a\xad\xc2\xa88\x8dD\xb1k9V-D\x8b\xe8UK#Q\x04^\xd2\x0dvYg%\xad\x1d\x00\xa39\xa8(\x82\x0e\xfaS\x904I\x94\xd5a\xc9\x08\xdc\x924j\xee\x20\xafd\xe0\xad`;\x97>M\x07\x89\xe6E\x03E\xd2\xeb\xc8\x99\x19Q\xe6h\x0dx\x1e\x1a+\xaa&C\xafU\xab\xc8\xd0\x92\xac\x0d\x91\xc1iE\x09\x9e2\xf6\xe5*\xa7\xb2\x1e\xcb\xa1\x7f\xdb3\x01%\xe4,K\x94\x913\x95)\xa1Q~\x97\x04T\x9b\x07\x0e\xd5\xd5\xb8\xa5\xca\x8c\x95\x11\x8e\xb06q\xc0`E\x03\xaa\xafN\x98\x0a3h#\xbd\xe5Y\xf9u\xbcX8\x93\xdc\x9e\x91\x202X\x9dO\x7f\x97\xce\xabW$\x8d\xea\x96\x99$\xcdVaT\x9cN\xa2\x98\xb5\x1c\x07\xfc\xa7\xfc\x07\xc8\x02\x93(\xc4H:\x0f\x7f\xfb$*\xec\xb2\xceJZ;\x00LsR\x90\xb4\x9e(\xab\xc3\x92\x11\xb8&\xe9C\x81\x8e\x9ec=-\xea\xddc\x89\xfe\xee@\xff\x19\xcb\xe0\xb5F\xcf\xbcV\xd0I+w\x8f=\xa5\xdc=\xb6N^\x16\xea\xac\x97\xb7#2\xc1+m\xebZEg\x7f\xb5\xf2\xba\xaeu2\xb9zt8ofc\xc34\xd9\xf7\xe6a.V'q\x80^9U\xe6\xebF@\xa3\xbc\xa4mc\xb9\\\xf8\xea\x01\xb6\x84F9\xbf\xa1\x0b\x07\x98\xef\xbal\xf4\xff\xba\xb3:\xefSr\xa5\xb7\xee\x00\xea\xad\xf3EO\x89k\x13\x07\xc4\xf7=;\xfd8\xea+X\xbd/\xce\x14\xc6\x12\x91\x17\x84\xde\xac($+\x0b\xe5\xa2\x86wq@\x1fY[\xd3\x19\xed\xac\x91;\xc9\xddc\xa1\x18\x8a\xe6\x99\x7f\xc4\xd2\xab\xe0*N#Q\xecZ\x96\xc4\x8c\x9a\x19\xf4\x1b\x88I\xd4\xa7\xe4\xee\xb1\xd6h\x94\x0c0\xaa\xa6\xbf\xba\xb1\xc2:\xeb\xea\x15\xef\x03\x09\xd3\x01\xd0\x9bsxZk,\xe4\xeb\x8d-\xab\xe6\x86+\x83\xfb\xa2Q\x7f]4JO#=QV\x87%#pM\xd2\xa8\x7fGK`\xebnu\x12\xd7O\xa6\xd2\xcdV\xa1\xa7\xf3j\x05\x8b\x06\xca=\xde%\xea=\xde\x91\xea\xc2)U]t\xb1o\xc9\xcc\xfc\x8aN\xb2\x94x\xbd,\xaf\xac\x8d\x9es}\x0b\xa7\x95\xac}\xd3'?\xcb\xc7j\xf4*3\xbdGM\x85%Z\xe7\xf8\xa7/m\x9f\xe3\xabfK\xd8^\xd5X\xe2+\xaaN:u\xe2\xab\xa6\xe5V\x1f\xa4\xf7x\xcb\xbe\xc3\xe4\xb7S\x8b\xda\xc4\x01\x11\x99\xfcB\xbbJV\xee\xd0\xd6\x0a\xe3\xe8,\xcf+x\x94\xca\xaa\xa2\xbd\xa14o\xfaB\xf2M\x97h\x98S\x20\x17\xcciL\xd0\x12B\xb8[,2Of\xb4*\xf8\x8a\xd3H\x14\xbb\x96\xa5\xd5\xdfJ\xdf\x99D\xd5\xa9\xb3f\xd2\xe7\xf6U\xe7\xe6\xd7\xbch\x99\x87x\x01\x8d\xf4\xfd\xd9t\x00\xb4\xe6$\x8a\xf0\xfe\xe4\xcby!\xd3\x1c\xfc\xb7j\x15\xf4\xd8\xeb\x89\xb2:,\x19\x81{\x92N\x83\x17\xe5?\x09\x16\x01\x00HfDH\x1a\xfe\xab\x12\x00ReDH\x1a\x00\x80T\x01I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x18I\x92\x86[\xbd\x01\xc0\x11\xf7$m\x18\xe8\xc4\x9a\x94\xe7\x149\xf8BX\x1b\xe8\x10\x86\xcdF%U\xb7\x98\xa1\xe3\xd8t\xc7\x00\x95\xa1\xda\xf5\xa4it#&\xd5FR\xd2O\xeaP\xf7-\xf3pM\xd2\x8c\x81\xce\xb9\xc0\xfe~\xccI\x87-\xac\x0dt\x08\x86\x8d\x8a#i\xfa\xac\xa4f\x95s\x018:\xc08\xed\xdb\x85\xda\xf5\xa4it#F\xd8H\xcb\xec\x08\x92j\x9f\xc9\xa1\xee[\xe6\xe1\x9a\xa4\x19\x03\x9ds\x81\xa3N\xd1\xc8\xde@\x87\x90\xfa\x03^\xd3\xf5YI\xd1*g\xe88:\xc08\xec\xdbE\xb0\xebI\xc3\x15\xc3\x0aQ#\xad\xb3\x93\x9cT\xa7L\x0ey\xdf2\x0c\xd7$\xcd\x18\xe8\xa4&i;\x03\x9d\xf4H\xd7g%E\xab\x9c\x8b\xc0\x10\x0b\xbb\x08v=\x17A\xd2\"\xacw(9\xa9N;?\xe4}\xcb0\xdc\x934\xd2\x0dtR\x92\xb4\xad\x81\x8ea\xa3\xc2\xf8\xc2\xe0\xc5\xd6\xfa\x99\xd3\xc8\x13|\x18\xff\x16\x0bC\x1a\xc3\xf6\x85s\x96\x11\xb9\xc50\xf6,|a\x9a\xf5\x8c\x85\x05\x8f\xd8\xd5\xc5\xc2\x01F\x14\xc0{\xdeh\xa4c\xd7s\xe1F7LR\x11c\xb6#l$\xbfC\xf6\x16<\xe2Lr\xf8W\xd5\xcf\xa0\x15w\xe1\xb8\x17\xd5\xbd0`\xf7-\xa3qQ\xd2\xba\x81\xce\xb9@\xc7\x96@s\xd8\xf4\xe0;\x13\xb6\x06:\x86\x8d\x0a\xe3\x0bC\x16K66\x96\xe4\x1df\xfd[,\x0ci\x0c\xdb\x17\xd6YF\xe8\x16\xc3\xd8\xb3p\x85\xe9^/\x16\x16<\x16\xae.B\x07\x18a\x00\xe7y\xa3\x93\x8e]\xcf\x85\x1b\xdd0Ie\xcdv\x84\x8d\xe4Z\xe6`\xc1#\xce$\x87_.\x0f\x85\xcap\xc5\xb1}%\xf5\xa7\xd5\xbd0`\xf7-\xa3qQ\xd2\xba\x81\xce\xb9@S\xf7\xd1\x9e\x96f\xbb+\xde\xf6\x06:\x04\xed\xd1\xaf\x86/\x0c\xf2\x97\xe0\xd1\xfd\xf9\x12b\xe7h<\xe1V<\xc2cm_\x8c\x12\xc4n1\x9c=\x8b^\x18\xe3\xf5\"\xb6\xe0\xb1vu\x11<[^\x1c\xc0\xec\x1bK\x1av=\x17ntc$\x95\xdbcq#\x99\xa7\xf7:Y\xf0Xd\xd2\xc0_\x8aO\x90\xf8\x1cr4\x1bIg\xfe\x94\xf9\x87Lc\xdf2\x1a7%\x8d\xd4\xe7x'\xba\xc9\xc9\x15k\xdam\x13ho\xa0C\xd0\xcf(\xf2\x9coE\xbc\xfe\xff\x20\xafm\xf2ygI\xb3\xb6/z\x09\x16n1\x9c=\x8b^\x18\xe3\xf5\x82\x84\x16<\xd6\xae.\xa9K\xda\xd87\x964\xecz.\xdc\xe8\xc6H*\xb7\xc7\xe2F\xea-s\xb6\xe0\xb1\xc8\xa4\x81\xffE\xf2J\x8f\xe6q\xf90\x8a\xe7\x9b\x1f\x00\xca\xec[&\xe3\x9a\xa4\x0d\x03\x1dmMx\xabe\xb0\x93\x81\x0eA?\xa3\x0c\xf1*C\xc1(yb\xbf\x93\xa4Y\xdb\x17=\xd6\xca-\x86\xb5g\xd1\x0bc\xbc^\x90\xd0\x82\xc7\xda\xd5%uI3{\xc1\x90\x86]\xcf\x85\x1b\xdd\x18I\xe5\xf6X\xdcH\xbde\xce\x16<\x16\x994`\x8e&\xfa\xd1Z\xf4\xee\x14\xf3\x97\xa3\xb1o\x19\x8d[\x92f\x0ctPG\x90.\xee\x08Z\x87;\x18\xe8\x10D\x92\xa6\x97\xd1\xa8E\xad\xe1\xdf\xc2\xf8\xac0\xb0\xb6/z\x09\x16n1\x9c=\x8b^\x18\xe3\xf5\x82\x84\x16<\xd6\xae.\xc9\x0e0\x16\x01v\x92N\xcd\xae\xe7\xc2\x8dn\x8c\xa4r{l#i\xd2\xb2\x14,x\xc4\x994\xf0\xd3_.\xe9\xd1D;\x0b\x07\x05\x16\x96\xfa\xbee4nI\x9a5\xd0\x09n!\x8bg\x02\xfb-\x83\x9d\x0ct\x08\"I\x17\xe1\xb3v\xe0\x8ej\xc4\xfa\xb7\x88\x0diX\xdb\x17\xa3\x04\xb1[\x0cg\xcf\xa2\x17\xc6x\xbd0=\x0e\xe3\x16c\xed\xea\x92\xec\x00c\x11`!\xe94\xecz.\xdc\xe8\xc6H*\xb7\xc7\xe2F\xea-K\xc1\x82G\x9cI\x03\xffL\xe2\x98[J\x8e&\x1a,\xdc9-\xf9\xc1\xfb\xfa\xbea>\xdex\xa1\xb7\xc3\x8dT\\\x934c\xa0s4\xb0\xed\xd0\xd1\xbd\x81\xa0\xf5\x90\xc9\xc1@\x87\xb1Q\xe1|a\xc85\xd2\xf69S\xc8\x06\x8c\x7f\x0b\x12\x19\xd2\x18\xb6/l\x09b\xb7\x18\xce\x9e\xc5(L\xf7z\xb1\xb4\xe0\x11\xb9\xba\x88\x1d`\x84\x01\xdc\xbe\xb1\xa4l\xd7\x83.\xdc\xe8\x86M\xaaa\xb6c\xd5Hc\x87\x9c-x\x04\x994U\\s\x20R5E9\xfc\xeb\xee\x98\x92|\xba\xe8\xfb\x86Y(/H\xfa{f\xe0\x9a\xa4Y\x03\x9d\x13\x1d\xcd\x81`\xb7\xb5\xa2\x9d\x0ct\x18\x1b\x15\xce\x17\xc6\xbf\xba\xbe`F-=\xffY\xff\x16\x91!\x8da\xfb\xc2\x95\x20t\x8b\xe1\xecY\x8c\xc2t\xeb\x19\x0b\x0b\x1e\xb1\xab\x8b\xd8\x01F\x18\xc0{\xde0\xa4l\xd7C\xb80\xa3\x1b.\xa9\x86\xd9\x8eU#\x99\x1dr\xb4\xe0\x11d\x92\xa3\xb4qI\xbeZ1\xb9\x81PtEE\xdb7L\xfb\xf4vA@&\xe0\x9e\xa4\xd3`\xa8\x06:p\xbf\xd10py$5\xe6\xcfL\xc3\xab\x14\x18\x11\x92\x1e\xea\x7fU^\x1eg\x9f\xc7\xb8<\x92\x1a\x82+\xdbV\x8c\x08I\x0f\x95\xcb\xe3\xec\xf3\x18\x97AR\x1b#\xa8\xba\xc1)(c\xf1\xb0\xa4\x95\x0b5\xc0E\xe5rHjL._Yt\xde)*c\xf1\xb0\xa4\xe9\x85\x1a\xe1\x8df\xc0\x90\xb9,\x92\xda\x90_m\xbe\x11\x1e\xd0\xf1\xb0\xa4\x01\x20\x13\x01I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x00I\x03\x80\xa7\x18I\x92\x1e\xea\xad\xde\x00\x90A\xb8'i\xc3@\x07\xd3\xb3\xad\xa9e\xaf}\xbc\x83\x81\x0e\x00\x00\x04\xd7$\xcd\x18\xe8\xa0xG`\xf7\x91\xbd\x81C\xf6[\xd8\x1b\xe8\x00\x00@pM\xd2\x8c\x81\x0e\xea\x20\x9e\x1b'\xe9\xa25N\x06:\x00\x00\x20\x17%\xcd\x18\xe8\xf4+O\x1d;g\x1f\x7f\xf1\x0ct\x00\xc0\xc3\xb8'i\xa4\x1b\xe8\xec\x0e\x0c:\x19\xb99\x18\xe8\x00\x00\xa0\xe2\xa2\xa4u\x03\x9d`\xb0gk\xa0y\xd7\x85\x18\xe8\x00\x00\xa0\xe0\xa2\xa4u\x03\x9d\x16j\xa0\xd3\xdcra\x06:\x00\x00\x20w%\x8d\xb4\xe7x\xd3\xbez\x20\xb0\xc7&\xd0\xd9@\x07\x00\x00\xe4\xa2\xa4\x19\x03\x1d\xd59'l\xe3\xb6\x91\x82\x81\x0e\x00\x00\xc8=I\xb3\x06:{\x9b\xa8\xbc;.\xcc@\x07\x00\x00\xe4\x9e\xa4Y\x03\x1d\xc5:\xe7\\\xc0\xfa\xf6\xb1T\x0ct\x00\x00@.J\x9a1\xd0A{\x03{\x8fv7m\x15\xd8\xb4\xaa8\x19\xe8\x00\x00\xa0\xe2\x9a\xa4Y\x03\x1dt(\x18\xd8\xb2\xd7Z\xd1\x8e\x06:\x00\x00\xa8\xb8'\xe94\x18\xaa\x81\x0e\x00d\x1e#B\xd2\xf0_\x95\x00\x90*#B\xd2\x00\x00\xa4\x0aH\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\xc5H\x924\xdc\xea\x0d\x00\x8e\xb8'i\xdd@g\xb0)\xa0\xd0l\xbf\x81\x9d\x81N\xef\x82\xc2\x82\xaaH\xd9>\xf36\xb6<+\xcb!\xa7\x18\xf2\x88a\xb9\xd4)\xe6\xa2\x11\x99\xbe\xd3)D\xe7l}Q\xee\x02\xc7\x87%#T%\x93\xfd\x0c\xe1\xd7*\xa7P\x0b,\x12U\x87\x8b\xcc\x13\xfd\xf7z\xaa-\xa3\xa4\x91_-;\x96\x15'\x93\xda1\xf6\x14\xaeI\xda0\xd0\x89\x05\xf6\xf6c\xf6*\x0f\xe8\xb7\xc6\xc6@\xa7\xd7\xf7h\xe7\xf6E\xa9\x1e\xbdH\xaf\xf2~*\xeak\xb4\x8f\xa4\x1c\xa8\xf3;\x85\xa4\x88V\xb1\xf5\xda\xae\xbc\xd4v\x81P]\xd4V\x97w\xde)\x0a\xa1\xbe\xd5r$\x86bQyuj*H\xc6\"Q\xc7\xa3\xd16Y\xf4-\x9aj\xcb\x14\x04\xf9\x15'J\xcf\x8ee\xc5\xc9\xa4r\x8c-j\x1b\xa9\xb8&i\xc6@\xa7\x9b<\xd9d\xa0i\x97\xfd\x06v\x06:5\xd5\xe4\xb5.EI\xff\xdb\x12m\xc9\xefx\xb8\x09\x8d\x17K\xd2F\xc5\x96kS\xed\xdb\x10:/\xb7\xa2\x84\xfd\xb3\xcfUZe\xf2\xa0\x89\x04\x8e\x1f2V\x89:(RV\xea-SH\xce\xaf8Qlv\x84\x15\x0bq>\xc6V\xb5\x8dP\\\x934c\xa0C\xd9\xb6\xc5\xfa\xa9&\x14;\x03\x9d\x92\x06\xf2\xda+w\xa2T(wK\xd2\xe5\xc2sG\xbc\xd6\x91>9\xd51\xfa\xa5\x96t\xea-SH\xce\xafsJ.\xa6\xa4\x9dk\x1bQ\xb8'i\xa4\x1b\xe8\x10z\x02'\xeccm\x0dt\x96\x16\x1d&o]\xb4k\xd8^\x95w\xcfj\xa5\x93`\x165v\xca\x0a\xe5\xe4\x83\x7f\xed\xea{\xf2j\x8e[\xc5\xe2q[\xed\x8c)\x8b\xd4\x81a[y^Y\x9b\xb2\xbaoQ\xa1\xafh\xc1)\xb4R\x96\xb7+\x93\xc1D\x91\xef\xc5\x993\"\xab\x0a\xaa\x07\xd8\xc2V\xca\xbev\xb5\x0a\xb6\xe2\x81%3|%\x0b{M\xcd9\x9f'\x1bs\x07\xad6\xa6\x04\x86\xc1Be\xb3\xd5\\l\xafO\x96\x1b\xfajKrk\xb8\xce\xde\x90\xb4Q\x18\xd7^\xa39\\\x09\xd1\x9a\x12_\xe1\x82\x12R\x84\x91(#\x96\x90\xac\xac\xb4Z\xc6\xe5W\xcb\x998Q|v\x8c\x8a\x99F\x1a\xb0\x8d\xf4\xaf\xaa\x9f1m\xa12\xe70\x92\xaa\x1d7\xfe|\xf0\x04.JZ7\xd0!\xb4t\xd8\xc6:\x18\xe8\x1c/\x91k\x1a\x0f(\xdd|\xbd\xbc\xaa\xabuzy\x82_\xd4\x19\xd8\x17\x9dS\x1d\x8dF\xffL>\xf8\xe5\xd2\xd0\xce\xa2E\xa6\xcd\x0c\xfa\x0a\xeei\xdfY#\xd3S\xae\xce\xdf\xd0\xd5\xe0\x7f\x8a,F\xf2\xcb_\x8d4b\x89(35:\x19\x8c\x14\xc8\xab\xab\xe5\xc2\x8dE\xadla\x7f\x0a\xf9\xe4\xe9\x8d\xbf\xce_\xc4W\xdc%\xd7GB\x0b\xe5\x83\xa6\xe6\xf4F\xa3Z\x9f\xa2\xd7\xc6\x94\xc0\xd2\x1b\x0d\xc9\x8d\xd1\xe8i.6\x1e\x0a\x95\xcc\xc9\x9f\xb9\xbaV\xe6\x12dH\x9a)\x8cm\xaf\xd1\x1c\xb6\x84\xdf\xcaO\x85\"\xed\x852I\x89\x91(#\x96\x20\xe8,\xd3i\x19\x9b_=g\xe2Dq\xd91*f\x1bi\xc0n\xe6\x97\xcbC\xa1\xb2<\xf2\x9d\xaf7\x879n\xdc\x01\xf0\x04.JZ7\xd0\xc1\x1c\xa13k\x1b\x1c\x0ct\xceo\xac\xf0\xc9S\xc8wx\x97\xfc&\"G<\xc4-r0\x03\xef\xa2\xb3\xf8\\\x9a\x8e\xacb\xab\xe6\xe0^#QFN\xb9w\xe5\x08~\x8d\xc8]\xf8\xfc,\xa9\xc1\x1a\x19\x0c\x91\x0b@\xf4$\xa3#\xc7\xa2Z\\\xc8NTW\xcf\x17\xe6\x9f\x82O\xe2\xda\xe9|\xc5\xb1\x10\x19\x0e\x94-\xe4\xd7\x12r\x95\x93\x96\xa9\x8d+\x81A\x1f\xde\xb2\xb1\xa8\\\xa6\xdd.\x17\xc9\x0e\xbc\x8d\xc2\x98\xf6\xf2\xcd\xd1Jh\x9bNt\xd2V@%\xad'\x8a\x8bu\x18x;\xb6\x8c\xc9/w\x00\xc4\x89\xd2\xb3\x83\x8c\x8a\xd9F\x1a\xb0\x9b\xf9K\xf1\xde\xc7\xe7T\x98\x92\xaa\x1f7\x18x\xb38\x15\xeeL\\\xed\x9d;Z\xec\xe3R0\xd0\x89G~D\x0e\xd6\xd2;\x06\x13\x98\x99\xf5\xdc\"\x07#i\xf2'z`\x85\xb1\xe7\xe5v\xf2\xd6@\x02\xea\xcb\xe8\xaa\xd2:r\xf6\x19\x97H\x19I\x87\xf0y6\x80\xd6.\xe3\x0b\xa3\x8f\x1dO:w\xce\xb6\xd5\xdcQ\x20\x97\x99\xd6\"\xfd\xa4ej\xe3J`\xd0\x85\xc3\xc6\xa2r\x7f\xb2\x05\x20'i\xbd0\xa6\xbd|s\xb4\x12>\x9dQ\xb2\xb2\xbd7A\xc7>F\xa2\xb8X\x07I;\xb5\x8c\xcd/w\x00\xc4\x89\x12I\x9am$\x03\xb3\x99\xffE\xf2\xda&\x9f\xe7\x93\x0a\x92\x16\xe2T\xb8\x1d\x8c\x81\x0e\xa6\xc9\xfa\xb1\xfc\x14\x07\x03\x9d\x83\xf4rY\xa2\x0a\x7f-\x97\xa9s#~\x91\xc3ty\x8c\x1eXal\xaf\x1cEZ@\xd5\xa3t\xd5\xa3\x15\x9aD\xcc%\x14\xbd\x8b\x0e\xfa\x10Z\xb7\x8c/Lx\xee\x1c,*Y\xdb\x19\xad\xb6\x964S\x1bW\x02\x83.\x1c6\x16\x95\x0b\xe6\x84m\xf2\xdf\xf1kL&\xb3Ha{\xf9\xe6\xe8%\x9co_R*\x17\xbd\x8a\xb8\xcd\xb8X\x07I;\xb5\x8c\xcd/w\x00\xc4\x89\x12I\x9am\xa4\x01\xbb\x992X\x8f\xe2\xafa\xab\xa4\x82\xa4\x0d\x9c\x0a\xb7\x815\xd0!~\x1bG\xec\xc3\x1d\x0ct\x8aV\xd2\xb7\x869xPyG/\xe5,\xb7\xc8A\x0fa;\xe93\x8c\x03+\x8c=KU\x80\xe8\xe5\x9b\xfa;\xe8\xaa\x92:2t3\xf5\xd2\xb4\x9b)\x8a\xa0\x83~E\"laI\xe7\x0e\xa9\xb8\xb4\x82\x0c\x0c\x17\x95\xf1k\x09Z/m\xd4\xe6(i6\x16\x95\x9b\xe6\xdc\x84(m\xaf\xa2\x1fF\xd2F{\xf9\xe6h%\xf4\x92\xdby\xfe\x1e\xca\xe5\xbf\x09\xb8X\xa7^\xda\xa1el~\xb9\x03\x20N\x94H\xd2l#\x0d\xd8\xcd\xfc\xf4B];\x1e\x90$%\xb5\xc1tX\xbc\x81[\x92f\x0dt\xc8TZ\xbbL&\xc6\xc9@\xa7p&\x99\xd7&\xc8Y\x13Qfc\x0d\x1b\xb9E\x8e\xeaj\\\x8c2\xd1\xd5\xcfTqlE\x09.\xb7/\x97\x04t\xd1\x80\x10\x19\xdb\xc7\x8a\xaa\xc9@o\x15\xb9\xee\x9e\xb7\x16\xd7[a\x92\x08[\x18+H\xbd\xe2\x12rz'\xca\xcaL\xcdA\xfaI\xcb\xd4\xe6(i6V\xd8\xe3\x0cL#\xe3\x9a\xfai\x03\xc8B\xd2\\s\xf4\x12\x1a\xe5w\xc9[u=\xb7\x19\x17\xeb\x20i\xc7\x961\xf9\xe5\x0e\x808Q\"I\xb3\x8d4`7\xf3\xcf$\x17>J\xab\xf9\xe6\x18\xc7\xcdt\x00F>\xaeI\x9a5\xd0A=\x01;oig\x03\x9dB\xb9\xa8\xe1\xdd\xcejz\x93\xe0:yY\xa8\xb3^\xde\xce/\xb24\xfa\x7f\x8dc?%\xd7=\xeb\x0e\xa0\xde:_\xf4\x94E\xec\xe1\xbc\x99\x8d\x0d\xd3d\xdf\x9b\x87q7\"\xaf\xebZ'\xd7\x92\xd5\x91\xdc\xd2\xb6\xaeUt\"X5\xfd\xd5\x8d\x15$\xe0\xf0\xb4\xd6X\xc8\xd7\x1b[V}\x9c)\x8c\xabB\xaf\x18\x9f\x88K\xda6\x96\xcb\x85\xaf\x1e`\xd7\x0e\xee\x8bF\xfdu\xd1(I\x89^\x1b_\x82\x8er]\xf9\x00\x9d\xb5\xe8\xb1\x89\x03\xf4\xeam\xd2\xa5\x86\x90\xfclW=9g\x99\xc2\xd8\xf6\x1a\xcdaKh\x94\xf3\x1b\xba\xf0^D\xb860M\xff\x94\xdc\xc4\xd5\x1a\x8d\xf2\xfd[:-c\xf3\xcb\x1e\x00Q\xa2\x98\xec0\x15\x1b\x8dda\xf3\xeb\x97k\x0eD\xaa\xa6\xf4!\xee\x10\xea\xc7\x8d\xad\xcd\x1b\xb8&i\xce@\xe7\xc8\x16\xdbPG\x03\x9d\x8a\xf6\x86\xd2\xbc\xe9\xea\x8f\x8f\x91\xea\xc2)U]\xe6E\x86\xf8\xaai\xb9\xd5\x07\xe9\xfd\xbf\xb2\xef0\xf9\xb9\xf3Y\xab\xd8\xbe\x85\xd3J\xd6\xbe\xe9#\x01\x89\xd7\xcb\xf2\xca\xda\x94+\x00}Kf\xe6Wt\xd2\xa5\xea\xdc\xfc\x9a\x17e\xb9\xbeH\x96C\xf9r^H\x99\x0dj\x85\xf1Uh\x15\xa3D\xeb\x1c\xff\xf4\xa5\xeds|\xd5\xec\xda\xdf\xaa\xd3I\xf2U\xa1\xd7\xc6\x97\xa0\x11/\xa0\x91>\xfa\xd3\x8b\x1e\xdb\xabl\xff(2\xd3U\x9eWNv\xcd(\x8ck\xaf\xd1\x1c\xb6\x84\xedU\x8d%\xbe\xa2\xea\x08\xdf\x06\xa6\xe9uj{\xb9\xee7\xbd\x961\xf9e\x0f\x80(QLv\x98\x8a\x8dF\xb2\xb0\xf9-m\\\x92?\xa3V\xb9\xd8b\x1cB\xed\xb8q\x87\xc5\x1b\xb8'\xe94\x00\x03\x1d\x00H\x95\x11!i\xf8\xafJ\x00H\x95\x11!i\x00\x00R\x05$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9e\x02$\x0d\x00\x9eb$I\x1an\xf5\x06\x00G\xdc\x93\xb4n\xa0\x83\xd0\xc0\xae-\x81-\xbb\x06\x1c6\xb03\xd0\xd1\xb9\xf4\x86)\xe9x\xde0\x88Mf:\xc9\xff\x0c\x92'\xd8\x1a\x8e@\x89\x869\xd3|\xd3\xeei$O'\x91\xab\x12\xd4\x09\xc7\xf4\xbf\x84Cm\x83\x90\xce\xd2\xdc\xb2Py\xd2\x7f\xa4\xda\x93\x86\xa5\x0d0\xcc\xb8&i\xc3@\x07\x9dk\xda\xdas\xb4gK\x93\xfd\x93M\xec\x0ct\xd26\xc5\xb9\x88\xa4\xe3y\xc3\x206\x99\x19\xd8\x17-Xt\x98s\x04\x8a\xc8\x0b\xda\xba\xda\x17\xcaQ\x14\x8f\xc8\xb9Q\x14\x0b\xc9\x11\xf3\x03#\x84m\x18\x9a/\xcc\xebrm\xe7Z9\xdd\xc7\xf8\xa7ai\x03\x0c3\xaeI\x9a1\xd0\x09\xb7\x90\xe7\xfe\x0c\xb6\x84m7\xb03\xd0I\xdf\x14\xe7\"\x92\xba\xe7\x0d\x83\xb5\xc9L\xe1Z\xc49\x02\xad.\x20\x02\x8eOYM\x9e\x08X[\x87\xd0\xc7r\xf2v\xa26\x0c\xc9\x17\xa6On\xc0\xaf\xab\xd3\x954J\xc7\xff\x02\x18V\\\x934c\xa0\xd3\xb1\x95\xae\x08\xda?\x9d\xdf\xce@'\xe9\xa9\x9f\x97=\xd6&3T\xd2\x8c#P5}\xbe&\xaa\xa8!\x92\x8e\xe4\xc7\x84\x92\x161\xa4\x07_>5\x9d|\x81\xfcI\xe6\x1f\xcf\x97\x0a\x20\xe9\xcb\x04\xf7$\x8dt\x03\x9d3M\xe13\xf13\xe1\xa63v\xb1v\x06:\xe9\x99\xe2\xe8\xf67HhSC\xe6\xaa\x8d\xa8QV&\xe5Z\x09\x16\xce2ix\xde0\x01\xbc\xc9\x8c\x09*i\xc6\x11\xa8Jy\xb2\xe6\xa2*\"\xe9\x8f\xcbCI\x926\xda`a\xd7\xc3y\xff\xe8\xf0N8\x1a\xd3\xd6\xd2\xb7\xd7\x8f[l\xc6fG\xe8\xa5\xc3x\xd3\x20\xab\x03\x00\x0c'.J\xda0\xd0\x89u\xe0\xa5\x0e\xfb#og\xa0\x93\x96)\x8ea\x7f#\xb6\xa9\x89E\x0b\x1a\xfe\x86\xfe\xd6P\x10\x8d1%X9\xcb\xa4\xe1y\xc3X\xf0\xb0&3f\xa8\xa4\x19G\xa0\xb2et\xf5\xb22*\xe9\xd6\x05\xc9\xbd\xb4\xde\x06\x0b\xbb\x1e\xd6\xfb\xc7\x80w\xc2QQ\x1f\x96\xaf\x20\xda\x8c\xcd\x8e\xd0K\x87\xf1\xa6\xb1:\x00\xc0\xb0\xe2\xa2\xa4u\x03\x9dxGKO\x7fOK\x87\x9du\xa5\x83\x81N\x1a\xa68\x8c\xfd\x8d\x85MM\xfdR\xfc\xb2\xac\xde\\\x82\xd0Y\x06\xa5\xeey\xc3\xd9\xc98\x0c\xbc\x19G\xa0R\xe5\xf9\x89\xb5\xa5T\xd2\xa7sO\x8b\x06\xde\xda\xd3p\x85v=\\\xc5:&o\x1a\x85\xc3\xca\xc5\xf4x<n\xb5\x19\x93\x1d\x0b/\x1d\xe3\x01\xc1V\x0eF\xc0p\xe2\xa6\xa4\x91\xfa\x1c\xefp\x0b\xbd\x00\xd4bg0\xedd\xa0\x93\xb2)\x0ek\x7fcaS\x13\xc9\x8d\xa1X^\xd4\\\x82\xd0Y\x06\xa5\xeey\xc3\xd9\xc98I\x1a\xe9\x8e@j/\xbdT\xe9\xa5\xd1\x82V[I3\x15\xeb)\xe1*6\xe0\xbdi\x14\xfeN{\xe9e\xf4w2\x8b\xcd\x8c\xecXx\xe9\x18\x92\xb6r0\x02\x86\x13\xd7$m\x18\xe8$\x02\xfb\xb5E\xcbh\x07\x03\x9d4LqX\xfb\x1b\x0bG\x95Da'\xea,L\x98K\x10:\xcb\xa0\xd4=o8;\x19\x07I3\x8e@UJ\xf3\x17*si\x14*\xb3\x954S\xb1\x9e\x12\xaeb\x1d\x937\x8d\x0a\xad\xfdt\xa7\xbc.n\xb1\x19\x93\x1d\x0b/\x1d\xa7\x03\x00\x0c/nI\x9a1\xd0\xd1$\xfd\xa1\x8d\xa4\x1d\x0ct\xd20\xc5a\xedo\xacljV-D\x8b\xe8\x158\xde\xd5E\xe4,\x83R\xf7\xbc\xe1\xecdD\x92N\xd0\x89/\x15\x15\xe3\x08T\xad|\x93\x94\xd7(\x92\x8e\xe5E\xd2\x914I\x09W\xb1\x8e\xc9\x9bF\xe5\xa9\"\xf2\x85\xd7I\xae4\x887c\xb2c\xe1\xa5cx\xd3X9\x18\x01\xc3\x89[\x92f\x0dtv\xa8\x03\xef\x1d\x96\xc1N\x06:\xe9\x98\xe20\xf67V65\x07\xfc\xa7\xfc\xc4\x07\x83/A\xe8,\x83R\xf7\xbc\xe1\xecdD\x92n#\xaeUg\xe5\xd7\x11\xe7\x08\xb4:\x9ff'\xaf^\x914\xaa[\x96\xa2\xa4\xf5\x94p\x15\xeb\x98\xbciT\x94\xdf\xa5\xab\x89\xa4\xc5\x9b1\xd9\xb1\xf0\xd21\xbci\xf8\x03\xf0\xf1F\xb8\xbd\xecR\xe0\x9a\xa4\x19\x03\x9dXsK\xf7\xb1\xee\x96f\xebK\xdeN\x06:\xe9\x98\xe2\xb0\xf67\x1665\x89\x1953\x94\x11\x83^\x82\xd8Y&-\xcf\x1b\xc6\xbf\x855\x99a\x1a&/\x08\xbdYQH\x9c\\\x18G\xa0\x88\\\xd3\x19\xed\xac\x91;\xc9\xddc\xa1\x18\x8a\xe6\x99$m\xb4\xc1\xc2\xae\x875\x8e1\xe0L|\x0cZ\xe5\xfa\xaez\xe5\xee1\xe1fLv,\xbct\x18o\x1a\xee\x00,\x94\x17\x98\x8b\x02\x86\x01\xd7$\xcd\x1a\xe8\xc4\xf7l\x09l\xddcm\x8b\xe5h\xa0\x93\x96)\x0ec\x7fceS\xd3\xea\xd7\xee\x9e\xd2J\x10;\xcb\xa4\xe3y\xc3\x04p&3\x0c\x9d\xe5y\x05\x8f\x1e&K\x8c#P\xa2aN\x81\\0\xa71\x81\xc5M~\x0dNT\x14\xf1_\x05F\x1b,\xecz8\xef\x1f\x1d\xce\xc4\x87\xa1\xb34\xaf\xa2\xab\xa4\xcbj3dd\xc7\xc2K\x87\xf5\xa6a\x0f@\xfbt\xe6\x072`\xd8pO\xd2i\x00\x06:\x00\x90*#B\xd2\xf0_\x95\x00\x90*#B\xd2\x00\x00\xa4\x0aH\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\xda]6_0N5\x00\x19\x06H\xda]\x9c\x04\xeb\x8cS\x0d@\x86\x01\x92v\x17'\xc1:\xe3T\x03\x90a\x80\xa4\xdd\xc5I\xb0\xce8\xd5\x00d\x18\x20iwq\x12\xac3N5\x00\x19\x06H\xda]\x9c\x04\xeb\x8cS\x0d\x97\x92K\xfbD\xa2K[\xdb\x88\x01$\xed.N\x82u\xc6\xa9\x86KF\x7f\xe5u\xd2\\\x84\xe6I9\xf3{\x9cb/\x1c\xb56\x20\x09\xefK:8\xf9\xa4S\xc8\xa5\xe1\xcc\xe4`\xf2J'\xc1:\xa3\x16\xe4\xfan&&N\x0ct\x1c%\xffa\x17\x98\x983@W\x09\xf7\xf8\xe2\xa0\xd5\x06$\xe1\x9e\xa4\x19\x03\x9d\xc4\xfe\xad\x81\xad\xf6\xdf\xec\xff\xbb\xe1;\xb3\x9f\xfe\xc26D\xcc\x1ai\x85\xbatd\xb44\xd164\x897\x8a1_\xff\xfe[_:\x05j|2\xab\xf8\x01\xeb\xbf\xae\x19\xf5\\\xd2\xbaoK\x84\xeb\xff\xaf\x93pu~v\xa5t=\xb7B-[\xdfM!\x1d9C\x12\xd7\x99\x1f\xe4d\xdf\x9e\xe2\xf8\xb6[\xda\xa6-\x86%\xe595\xc2=\xbe80\xb5\x01<\xaeI\x9a1\xd0I\x04\x03\x1f\x1e\xd9\x1b\xd8k\x17\xfe\xe4}o\xbd0\xfb3\xbb\x081\xebG5\xe9\xcb\xbb+\xb3l\"\x05|\xf6\x9b\xe2\xb7?z\xff\xf9\xe2\xd7\x9c\x025\xbe\xfc\xdd\x86;m\xfe\x1c\xbcb\xbdy\xd5+\xdf\x96\x1e\xfc\xc9\xe37KwY(8\x99\x9f\xdcr%\xf7\x99\x16\xc3\xee\xa6\x88`\xf6\x96\xe4\x95;l3N\xb8\xed\xba\x97*\xb3\x1d|\x8d4\xf6Hacq\xb7\xba$\xd8\xe3\x8b\x03S\x1b\xc0\xe3\x9a\xa4\x19\x03\x9d\xfd\x813t\xc5\x80u\xf4g\xc5o\xa0/\xfea\xfdw+\x8e\x8c^\xcc|Z\x91\xaa\xa4ONUF\xb1\x1f\x14\xff\x11\xebtC\xf1_\xec\xc3\x19^\xb3\x934Z\x9ee\x1e+n~B\xfa)\xd6\xe5\xad\xd2\xcf\xc4\x02\x16\xf0\x7f\x92%\xcd\xef\xa6\x08QW;y\x9e`%\xcb\x19i=J\xd8\x1c\x15\x0eC\xc7\xecb\xf2\x1e_\x1c\x98*\x00\x1e\xd7$\xcd\x18\xe8\xa8\xce9M6\x9d\xc6'\xc5\xefY\xff\xd1\x86\xca\xeb\xd832eIwK\xdd\xf4\x9dJ\x1a}~\xf7\x06\xfbp\x06{I\x0f\\WiZ\xa3J\xfa\x97W\xdd*\xd6\xaf\x00\x81\xa4\xf9\xddL\x95IN\x92>$\xa51\\\x17K:y\x8f/\x0e\x20iK\xdc\x934\xd2\x0dt\xb6*c\xa8\xa0\xd5\xec\xe8\x8b\xef\x14S^FhCq\xf1;\xea\x8cuC\xf1\xac\xb7^~`\xf6\xd3\xffCc>y\xe6;\xb3\xeeS\x97\x0d\xe2c\xb5\xde\xab\x7f\xfe\xb8\xab\xbf\xa5\x0e\xbc\x9b&gOxD\xd1\xc0\xa1o\xe5\x8c\xbe\xee\xf6~n\xa3\xf5\x13\x82=ROp\xc2K\x9a\xa4\xd1\x86\xef\xe0\x97/\xdf\xfe\xf1\xdd\x0f\xbc\xfc\x99\xd61\x82\x00\x00\x0f\xfaIDAT9\x0d1j\xfb\xf2\xad\xc7f?\xfc\x06\x9dl\xff\xcf\xf3\xdf\xbd\xf7\x19e\xe0\xad\xc7\xfeqVq\xf1\xa6O\x9e\xbf\x7f\xf6\xd3\xff\xa4\x1b.\xcf6=`M\x95\xf4\xe6[\xc7\x90\xd7\x07o\xbc\xea\xfao\xfc\x92*\xf5g7\x8f\xb9r\xccW\x7fN\x16\xbfw\xc3U7|\x8f\xae\xfc\xf9\xd7\xae\x19s\xb3:\xf0\xd6b\x91\xb1\x9b-xZ\xbe\x02\xad\xc0\xaf-l\x1d\xe7\xb2%iT3Y\xfa\x814:\xf0\xc8\x84\xec\xdbq\xcf\x19\x94\x14&\xd1\x10-%\xfb\xaf\x90\xa4\xe5\x87\xe6\x8f\xcf\xbe}0\x9e\xa3\x04<BJ\x987n\xf4\xb8\xb9\xea\x04\x99\xc9\x19\x93I<\x81\xd6E\xd6\xcd\x8c\x8a\x93\xf6\x18\x09kc\x16q\xc0K\x93\xb2'\xe1\xf4#~-K\x18$m\x85\x8b\x92\xd6\x0dtv5\xd1g`\x06\x04\xd3=\x85?|\xf4N\xf1k\x1f}\xf4W,\x9b\x8ff\xbd\xa6\xceX\xff\xfb\x9dY\xc5\xf7\xbd\xf6\x9b\xbb\x9f!\x11\xffy\xf7c\xbf\xf9\xe05<:\xe7\xd9\xab\x9d[\x87\xc6N\x08\x04o\x93\xa8\xa4+G=\x14\\\x9f3\x89\x0cE\xc3c'\xad\xe9X!\xad\xe16\xea\x9f/M\x94n\x92\xe6\x1d\xd3%\xfdV1\x1e\xf3\xbfP\xfc\xf2\xfbo\xdc\xf70\xb9D\xc7\xd4\xf6\xc2\x9d\x9b\xde\xdft\xe7\xf3x\xe9\x93\xbb\x1fx\xfb\xbd\xa7\x8b\xa9\xa4\xf5\xd8\xcf\xdfy\xe7\xfe\xef\xdf\xfd\xdd\x97\x9f\xff\xfa_i\xd9\xbb\xccg\xa2&\xe9\xefIX\x9d\xb7H\xb7>\xfe\xbd17\xfc\x0a\x7f\xfe\xc9U7|\xfb\x89\xbb$\xa2\xe4[\xae\xbc\xeb\xf1\xbb\xae\xbc\x85\xc8\xfc\xaa\xeb\x1f|\xfc_%*i=\x16\x19\xbb9\x10\x1e\xbb\xfc$:\xb9\xfc\xea0\xdfi\xef\x0d\x87\xb3\xe8\xd5\xb3\x9e\xe6+\xa4\x9c\x15k\xc6~\x8bxz\x87'L\x0d\x87\xc3\xf4\xaa\xa4\x9e\x92XK\xf3W&\x8c\x1d\xf7\xc8|\xe9(\xda\x1fn\x96V\x84\xc3D\xbbA\xa92\xdc2w\xd4\x1e\x12\xcb\xe4\xcc\xc8\xe4\xe0\x89\xf0\xe4\xab\xf5J\xe39\x93w\x1dS\x87\xfaI{,\xae\x8d\xad\x18Uf-\x0f.\xcf\x9a\x8f\xb8\xe60\xf0\xb5\x01<.JZ7\xd0\x19h\x0a\x9e\x8c\xf5\x07\x03-\xd6\xa1\xc6\xc0\xfbNr\xadJ\x19\xde\xdey/\xee'\x9f\xbf\x0f/}~\xff\xd3\xb8K\xfc\xe2\x1d\xf3\xf5\xb3\xa0tDY\x98:\x9e\\\x86\x9b\x94E\xd7\x05\x10\x19\xb8\xe1n+6\xee\xf68y\xda\xb0\xd9_o\xdbhi4\x1d2\xa8\x92~\x1fO\xa6\xdf+~\x1b/\xfd\x1e\x0f\x12\xd8\xda\xde/\xfe\x80\x86\xbd\x8f\xd0\x8f\xbfOV>LZ\xc6\xc4\"\xf4p\xf1\x93\xff@_\xaa\xd7\x01\x8eJ\xa6\xef-M\xd2O\xe0\xc9\xf4\xe3\xd2\x83x\xe9\xdf\xa5\x1f\xe2\x81\xf85\xff\x8a\x85\xfd\xcb\x1f\xbeB\xfe\xf2\xc4f\xf5\xf5\xc6k\xb1\xee\x7fu\x03\x91\xb4\x11\x8b\x98\xddD\x95d,=O0\xd6\xcdV/\x88g]\x8d\xc51?\x87.\xeb\x03o&%x\xadt\x1b>&\xf4\x9a\x98>\xf0\x1eh!\x02\xba\x89\xfc\x0e\xcc\xe4\x8c\xd9l.\xee\xce\x99A\xfa6\xfc\xf1\x9b\xcab\xd2\x1e[\xd6\xa6/n\x93\xc8L\xacC\xb9\xa6\xcd\x04\xe8\x98j\x038\xdc\x944R\x9f\xe3\x8d\xce\x04q\x7f\xbd;hs\x94\xc4\x92~A[|\xbf\xf8\x0f\xc2\xcd\xb6\xaa\xe7\xfa9z\x16\xa1\xe5D\xd2\xf3\xc6\xc7\x071\xe3*\xc9\xb9\xb3_\xb0\xd1\x99GF\x8f\x93\xc6e->\xa3K\xfa\xed\xe2\xcf\xd0\xf3\xf7\x7f\xf1O\xccw_\xe0j{A\xf9\xcd\xea\x81\x0d\xe8\xb3\xe2\xb7\xc8\xd2&\xd2\x1c&\x16K\xfa\xce\xbf\x1ae\x1fUNd\x03M\xd2\x0fJ\xafl\xbe\xf9\xda_\xfe\x0as\xcd-\x9b\xb5\xb5\xb4;V~\xb3\xba\xfe\x96\xcd\xaf\xd0N{\xf3]D\xd2F,2v\x13w\xa1\xd9\x03h\x20[\xf0@t]\xd2D\xee\xea5\x05]\xd2LJ\xf0Z\xe3\x82\x961\x97>\xf9\xd2\xed\xe3\xc7J7!.g\xccfG\xb7\xe9?Fc\x06r\xc6\xaf\xdf\xa16)i\x8f-k\xd3\x17+o\xa2o\x13\xcd\x01:|m\x00\x8fk\x926\x0ct\xc8[\xecL\x025\xd9xb\x89%\xad/\xbeQ\xfc\xb9p\xb3\xdd\xea\xa8O\x1d\x99\xd2Sy\x92:\x89\xc4]\xcezI\xf4\xe8\xf059\x01<\x97^\x9f\xf3\x9c.\xe9_\xdcM:[\x85\xa7\xb9\xda~\xfc4}{\xfa1\xdc'\x7fD\x96hs\x98X\xfc\xe11\xa6\xec=\x92i'5\xf1~\xe3\xaa\xcd\x9boP[\xf6Uu\x1c\xaep\xe3W\xe9\xdbWo\xdc\xfcS\xe9'\xba\xa4\x8dXd\xec&\x1e\x92\xe6lE[s\x12(\x09]\xd2\xe4\xdd,i&%\xf8\xc3$}#]\xd2{\xae\x1b\xb7xk\xf86\"5&g\xdcfxv\xbbG\xfb\xc3\x1e\xc9\xb0\x0aO\xdac\xcb\xda\xf4\xc5\xa9J\x07\xff\xcd\xc9\xa6\x00\x16\xa66\x80\xc7-I3\x06:\xf8D$K\x87\x02\xfcU*\x0e\x93\xa47\x99$\xfdA\xf1\xef\x85\x9b\x0d(SHtF\"\x17[\x10\xbd<6\x7f\xfc^\xcaI2\xb6\xfbP\xb4U\x9c^\xf1&\xf3{E\xd2_\xdc\xf7\x0c\xe9y\x7fO\xf9\x8c\xab\xed\x85\xfb\xc9\x95\xb1/\xef\x7f\x01\xfd\xaf2\x91\x7fA\xe9\xa5\xf5X,\xe9g\x98\xa2W\x8c6\xfd\xca\xab]\xf1\x1es\xf3\xe6\xcd_\xbb\xf6\xa7\x94\x9f\xf3\xbd\xf4\xb5\xf4\xedZ\xbd\x97\xa6\x97\xc7\x8cXd\xec&\xe6\xa1\xb9h\xeeC(\x19KI\x07\x8er)\xc1k\xbf\xa5o\xa4Kz\xe2d\xd2'~\x8bH\x9a\xc9\x19\xb7\x99\xc5\x15\xef\xe4=\xb6\xacM_\xac\x1cO\xdf\xc6W\x9a\x02X\xe0\x8a\xb7%nI\x9a5\xd0\xe9\x09\xe0\xa3{\xae\xd9\xee\xde\x01C\xd2\xb3\x7f\x815\xf6\x98I\xd2\xff\xb8\xefIr\xd9\xea\xe5\x97\xcd\xdb\xcd\x1b\xaf\xf4X\x93\xc7\xe1a\xf4\xa1,r*w(\x97\x83\x97?G~a\x99J\x84\xfbP\x92\x06\x8e]}\x8c\xbe+\x92\xdeD$\xfc\x8123\xde\xf4\x1b\xae\xb6\xf7\xe9\xdaw\xc8\\\xfa\xb1\xfb\xb1\x82\xff2[\xf9\x86\xd1cyI'&~\x13\xf1\xa8\xe2\xbd\x8b\xbc>Af\xd1x\xf9\xdb\x9b7\xbf2\xe6_H7}\xeb7\xc8\xac\x99\xac\xfd\xa1\xf48\xee\xb0\xafy\x85\\##\x926bI)\xdan\xe2\x0e;\xab?Kt\xb2\x8b$=u*B'H6\x98\x94p?m\xe9\x92\x1eGt\x95\x98D$\xcd\xe4\x8c\xdb\xccB\xd2\xc9{lY\x1b3\xb5'c\xf5f\xa5r\x8b_\xda@\xd2\x96\xb8&i\xc6@\xe7P`\xcf\xb1\xfdMA\xeb\xc9\x91r\xc5\xfbw\xf4~\xd0\x1f\xdf\xf7\x9b\xd7\x1e+\x9e\xf5\xf6\x7f\xff\xf5\xa3Y\x1b~\xf7\xe5\x1f6\xcc\"W\xc2\xffs\xf6\x03o\xbd\xff\xb22\x9de94J\xb9\x9a\xdd\x9d=n\xc5\xf2\x9cQW\x90;[\x16K\xf3Z\x82\x95\x12\xb9\xddjG\xd6\xc4\x97\xb6=\xa4L\xb4\x05pw\x8fm\xfa\xfa\xf3\xef\xbc\xf7\x02\x15+S\xdb\xf3\xc5\x9b\xde\xdbTL\xaex\xffq\xf6w_\xdbt/i\x19\x13\xfb\xcf\xff\xfa\xe8\xfbO~\xf4\xd1'j\x81k$\xf3o\xef\xf4\xee\xb1'nQ\xee\x1e\xbbK\xfa\xda\x0f\x1f\xbf\x85^\xf8\xfa\xc9U\xd7\x7f\xef\x89[%r\xa3\xe8\xd7\xa4\xbb\x1e\xc7\x7f\xc1K?\xbd\xea\x9ao\xdf5F\xba\xf2\xc1\x9f1\xb1\xa4\x14m7\xb1\x84\xc6\xdd>\xce<\xee\x8e\xef\x0e\x87\xb3*\xc3\xe1s\xa8?<\xbar7\xda[9\x9a^\xc7^\x91\xb5f\xebm\xd9\xe4\xcbKO\xc9\xe0nz\x1d\x9c\xce\x83\x95+\xde\xbbIi+\xa4y/=7I\xcaY\xb3\x9b\xcb\x19\x93I2\xb9\xd1G\xd8\xc6D@\xb0\xc7\xe2\xda\xd8\x8a\xd1\xfcQ\x8b\x83\x8bG\xcd7\xad\xe5\xd8\x9b4\x9e\x07T\\\x934k\xa0\xf3aK\x20\xf8\xa1`\x02\xa8\xf2\xf9\xbdtf:\xeb/\xe4\xc3'O\xce\xbe\xfb\xe9M\xc5\xc5\x1b6\x90U\x7f\xb8\x1b\xbf\x92\x1bA\xfe\xf2\xccw\xef~\xec\xbd\xe4m\x97g)\x87\xfe\xd0\xdc\x9c\xf1\x8b\x03WH?\xc0\xcb\x1d\xb7\xe5\\=U\xf9\x11\xfc\xd0\xbcqc'oM\xdeL\x81\xde\xe3]|\xbf\xfaE\xf1\xc1\x93\xdf\xb9\xf7\xc7\xef\xd3E\xa3\xb6/\xdexx\xf6\xc3o\xd0/\x9bO\x9e\xbe\xf7\xfe_\xbc=\x8b6G\x8b\xfd#3\xa9&\x17\xaf\x92n\xf2R\xee\xf1\xbeV\xf9\xd9y\xf3\x13\xff2f\xcc\x8dO\xd0\xc5\x9f\xdd|\xcdU7>N\x96~E\x7f\x97\xfe\x15]\xf9\xd51\xd7~\xe3\xc1+\xa5[\x99XZ\x8c\xb6\x9bx\xae\x9b\xc5\xff$\x87\xd93J\x99\xbc\x06\xd0\x0f\xf0\xeb\xe8\xeel\xfcJ\xf2\x10\x7f('{\xaa2%\xd5R\xb2_\x09%=k\xfcj\xbax\x05\xf9\x95+\xb1fBV\xce\xbc\xc0\xf8\xd1\xb8_gs\xc6d\x12\x9d\xb8b\xde\xfe\x13\xf8\x20\xc6\xfb\xf7\xce\xcb\xd2\xe6P\x82=\x16\xd7\xc6,\xe2\xea\xd6\xdf\x94}\xd3K\x09\xd3Z\x0e\xad6\x20\x09\xf7$}\x89xht\x8bS\xc8\xa5\xa2yt\xa5\xf9\x8e\x89\x8b\xf6\x9fX\x97\xc1nn\x9dH/y\xcd\x95\xa4\xafh\x17\xcaE{|\x91Pk\x03\x92\xf0\xbc\xa4\xd1\x9aq'\x9cB.\x0dg\xae{.y\xa5\x93`\x9dQ\x0b\xba\x1cv\xf3\xc4~\xdc;\xf7\x7fxL\xfb,\xdc\xe3\x8b\x06\xad\x0dH\xc2\xfb\x92\xbe\xbcq\x12\xac3N5\x00\x19\x06H\xda]\x9c\x04\xeb\x8cS\x0d@\x86\x01\x92v\x17'\xc1:\xe3T\x03\x90a\x80\xa4\xdd\xc5I\xb0\xce8\xd5\x00d\x18\x20iwq\x12\xac3N5\x00\x19\x06H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\x1a\x00<\x05H\xda+\xc0\x0d\xcf\x00\x05$\xed\x09\xc0N\x06\xd0\x00I{\x01\xb0\x93\x01t\\\x95tw@\xfd\x97\x9d\xfe`\xa0\xc3\x9d{\xf0\x19g\x19\x0b\xb7\x18{S\x9c!\x13\xbc:\xf5\x07\xe2i\x8d\xac\x94$)\xfb\x90\x20\x00\xecd\x00\x1d7%}.\xb0K\xf9\xef\xf9c\x81\xf0\xa1p@\xff\x07\x9eK\x09\xe3,c\xe1\x16\xe3`\x8a3T\x16KN\x06\x19\x06Z#\x8f\x86\xc3/\x09\x9f\xe6\x01v2\x80\x8e\x9b\x92\x0ev\x1c\xa3\x92N4\x93\xff\xdf\xdf\xd5\xec\xca\x05\x1e\xbdRk\xb7\x18{\x07\x8d!\xb2\\Z\xee\x14b`dF\xfc\x80\x1exl\x0f\xa0\xe3\xa2\xa4\xbb\x9b\x06\xfa\xa9\xa4{\x02\xe4\xa1\x93\xd4\x1e\xcbE\xac\xddb\x86E\xd2+\xec\xad&\xad\x00I\x03\x0e\xb8'\xe9s\x81CH\x91t\x872\x11\xdc&x\xfc\xf4\xf0b8\xcb\xb0n1,\"S\x1cda\xd7#r\xcd1\x1b\xe8\x18\xacQ<>\x8e\x8eU*\x96\xc6\xb2\xf3\x0e\xc6\xe8\x86i$A,^\xb0\x93\x01t\xdc\x9341\xb7S$\xbdUyn\xd6.\xcbg\x80\x0d\x1b\xba\xb3\x0c\xeb\x16\xc3\x204\xc5\xb1\xb2\xeb\x11\xb9\xe6\x98\x0dt\x0c\xd6K\xd4\xa65\xde\xb4^!\x10g\xfe\xc8\x18\xdd\xb0\x8dDBI\x83\x9d\x0c\xc0\xe2\x9a\xa4{\x88\xf7\xac\"\xe9&\xe5\x91v{\xec-\x92\x87\x09\xedi\xb8\xc2\x81\xb7\xd8\x14Gl\xd7c\xe5\x9a\xc3\x19\xe8\x18\x04\xd4\xe7\x92\x9e8\xa2`z\xcc\x10kt\xc34R$\xe9\xb9\x12\xd8\xc9\x00\x06nI\x1a\xcf\x9c\x13\x89\xc4\xd1\xa6DBw\xae\x0c_\xfa^\x1a\xd9K\xda\xc2\x14Gl\xd7c\xe5\x9a\xc3\x19\xe8\x184)\xcf\xca=\"i\xf0O\xb6e\x8dn\x1c$\x0dv2\x00\x8b[\x92>\x12\xd0\xe8G\x1d\x8a\x96\x82\x97|.M\xb0\x93\xb4\x85)\x8e\xd8\xae\xc7\xca5\x873\xd01\xd8u\xb5\xa2\xcdpP\xc1\xf4+\x14\xfb\x08}\x07I#\xb0\x93\x01\x18\xdc\x92t\xe2$\xa1;p\xf2d\x02\x8f\xc1\xc9\xaf\xc1\xe7\xdc\xb9\xe2m'i\x0bS\x1c\xb1]\x8f\x95k\x0eg\xa0\x932\xe9I\x1a\xaex\x03:nIZ\xa1_\xfd]\x9atQ;\xdc\xf9]\xdav.-6\xc5\xe1\xedz\x8e<\xa7\x0c\x99\xad\\s\xac$m\xbf\xb7\x20i`\x88\xb8)\xe9D\x7fw\xa0\x9fx;\x1f\x0d\xec8\x12&~k\x97\x18\xc3Y\x86u\x8ba\x10\x9a\xe2\x98\xecz\xe6J\xb7+\xc1\"\xd7\x1c\x93\x81\x0eCp\xac\xcd%-\xd6\xe8\x86i\xe41r\xf7\xd8\xfap8)S`'\x03\xe8\xb8)\xe9~2\x95\xa6\xbf\xb8\xf6o\x0b\x04]\xb8\xc7\xdbp\x96a\xddbXD\xa68\x88\xb7\xeb\x09\xe4h\x96Z\x02\xd7\x1c\xde@\x87e[\x8e\xcdm\xd9\xac\xd1\x8d\xd1Hz\x8f7!\xc9\xf9\x0d\xecd\x00\x1d7%\x0d\\4\xc0N\x06\xd0\x00I{\x04\xb0\x93\x01\x14@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)\\\x95\xb4n\xa0\x83P\xd8\x95G\x9a\x8c\x14\x86\xc9\xc4\x07\xf0\"nJZ7\xd0A\xe8D\x20\x13\x1e\x9e\xb5c\xafS\x84\x05\xc3d\xe2\x03x\x117%\xad\x19\xe8\x20\xd4\xdf\x92\x11\x92\x9e\x9c\xf4\xec\x82\x94\x19\x16\xc7\x0f\xc0\x8b\xb8(i\xdd@\x07\x85\x03\xe1\x8c\x90\xf4$\x9040\xec\xb8'i\xc3@\x07\xc5\x06\xb4\xc7\xf3{\x82\xfdWH\xd2\xf2C\xf3\xc7g\xdf>\x88?5M\xce\x9e\xf0\xc8\x00\x1e\x93\xa8O\x19\x9aD\x9eC4\xaa\x09\x1d\x19-M\xe4bY\xd7\x1c\x96\xe13\xf1\x01\xbc\x88{\x926\x0ct\x08^\x92t\xac\xa5\xf9+\x13\xc6\x8e{d\xbe\x84\xd5Y9\xea\xa1\xe0\xfa\x9cI\x094\xb0+<aj8L\xae\x03\xf6\x87G\xaf@hwe\x16\x17\xcb\xb9\xe6\x18\x0c\xa3\x89\x0f\xe0E\\\x934c\xa0C\xf0\x92\xa41\x93\xa4\xdb\xce\xe1\x81\x08\xe9\x9b\xc9\xc3\x06\xf7H\xf4\xa9\x89\xc6\xc0\x9b}\xa6\xaf\x1ekr\xcdQ\x19F\x13\x1f\xc0\x8b\xb8%i\xd6@\x87\xe05Ig\xa9\xc3\xe7y\xe3\xe3\x83\x98q\x95t\xadX\xd2Z\xac\xc95Ga8M|\x00/\xe2\x96\xa4Y\x03\x1d\x82\xd7$=I[Pg\xd0\xf4\xf1\x9d\x16\x92\xd6bM\xcf\xe3W\x18N\x13\x1f\xc0\x8b\xb8%i\xd6@\x87\xe05Ik\x13\xe2\xf9\xe3\xf7RN\xd2\xb5D\xd2\xd4\x82\x80\x8awy\x16\x1f+\x94\xb4\x8b&>\xc0\x88\xc4-I+xw.\xad\xf5\xc7\x1dR\x0by[\xfe\x1cy\x9d:\x15\xa1\x13tE\xf6b\xfc\xad69\x8b\x8f\x15JzXM|\x00\x0f\xe2\xa6\xa4u\x03\x9dX\x7f\x7f`G\xbf\xc9ay\xe42\xb8\x9b^\xdbVT\xb6X\x9a\xd7\x12\xacT\x9cgWd\xad\xd9z[\xf61\xbc45\xe7\xb9\xe7&KW\x04z\x98X\xd65\x87a\x18M|\x00/\xe2\xa6\xa4u\x03\x9d\xbd\xca\xac\xfa\x9c\xd3\x06#\x84\xfd\x8a\xe5\xcd7\x95O\x1d\xb7\xe5\\=U1\xcb\x89?\x94\x93=\x95\x0eG\x0eM\xcd\x1e{\xfbbI\xfa\x01\x13\xcb\xba\xe6\xb0\x0c\x9f\x89\x0f\xe0E\xdc\x944\x00\x00\x17\x1d\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x0a\x904\x00x\x8a\x8c\x96\xf4<)g>X\x02\x00\xde\"\xa3%\xdd\xbf#01g\xc0)\x0a\x00F\x12\xaeJZ3\xd0\x19\x08\xb7\x04\x82\xfb\x07\x1d\xa2\x87\x85\xb0\xb4\xdf)\x04\x00F\x12nJZ3\xd09\x17\x08v\x1f\xd9\xdb\xb4\xd5\x0dM\xef\x91v;\x85\x00\xc0H\xc2MIk\x06:;Z\xc8\xf3\xc7\xce\x04\x86\xea\x18u!\x80\xa4\x01\x8f\xe1\xa2\xa4u\x03\x9d\xad-\xf4\xf3\x0e\xdd\xc6\xf2\x12\x02\x92\x06<\x86{\x926\x0ct\xfa\x8f\xd1\x15\xbb\xb6\xd8\xc6\x0f\x0f\xddR\xd8)\x04\x00F\x12\xeeI\x9a7\xd0A(\xd1\xec\x86\xb8\xe29\x93w\x1dK8E\x01\xc0\x88\xc15I\x9b\x0ctp'\x1d8c\x1d=|l3\x1e\xfc\x07\x00\x1e\xc0-I\x9b\x0dt\xd0\xee\xc0\x11\xdb\x0d\x86\x89\x81\x9c\xf1\xebw\xb8R3\x00\x0c\x0bnI\xdad\xa03\xb8#\xe0\xcem\\{\xa4\x0e\xa7\x10\x00\x18I\xb8%i\xde@g\x20\xd8\xc4=\x8e\xfe\xd2\x01W\xbc\x01\x8f\xe1\x96\xa4\x15\xd4\xb9\xf4\x99\xe6-\xe7\xb0\xcac\x0e\xd1\xc3\x01H\x1a\xf0\x18nJZ3\xd09\x1ah>\xd2\xdf\xdf\xbf\xab\xc5i\x83a`7H\x1a\xf0\x16nJZ3\xd0\xd9\xa6\xce\xaa/\xf9\xad&\xf1\xfe\xbd\xf3\xb2\\\x1a\xf1\x03\xc0\xf0\xe0\xa6\xa4]g\xae$}\xe5\x92\x7f\x8f\x00\xc0\xb0\x92\xd1\x92\xee\xff\xf0\x98S\x08\x00\x8c02Z\xd2\x00\xe0=@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)@\xd2\x00\xe0)\xfe??\xe8B\x81\x97E\xcd\x14\x00\x00\x00\x00IEND\xaeB`\x82",
@@ -45,9 +45,11 @@
"error.html": "<!--\x0a\x09Copyright\x202009\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a\x09Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a\x09license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a-->\x0a\x0a<p>\x0a<span\x20class=\"alert\"\x20style=\"font-size:120%\">{{html\x20.}}</span>\x0a</p>\x0a",
- "example.html": "<div\x20id=\"example_{{.Name}}\"\x20class=\"toggle\">\x0a\x09<div\x20class=\"collapsed\">\x0a\x09\x09<p\x20class=\"exampleHeading\x20toggleButton\">\xe2\x96\xb9\x20<span\x20class=\"text\">Example{{example_suffix\x20.Name}}</span></p>\x0a\x09</div>\x0a\x09<div\x20class=\"expanded\">\x0a\x09\x09<p\x20class=\"exampleHeading\x20toggleButton\">\xe2\x96\xbe\x20<span\x20class=\"text\">Example{{example_suffix\x20.Name}}</span></p>\x0a\x09\x09{{with\x20.Doc}}<p>{{html\x20.}}</p>{{end}}\x0a\x09\x09{{$output\x20:=\x20.Output}}\x0a\x09\x09{{with\x20.Play}}\x0a\x09\x09\x09<div\x20class=\"play\">\x0a\x09\x09\x09\x09<div\x20class=\"input\"><textarea\x20class=\"code\"\x20spellcheck=\"false\">{{html\x20.}}</textarea></div>\x0a\x09\x09\x09\x09<div\x20class=\"output\"><pre>{{html\x20$output}}</pre></div>\x0a\x09\x09\x09\x09<div\x20class=\"buttons\">\x0a\x09\x09\x09\x09\x09<a\x20class=\"run\"\x20title=\"Run\x20this\x20code\x20[shift-enter]\">Run</a>\x0a\x09\x09\x09\x09\x09<a\x20class=\"fmt\"\x20title=\"Format\x20this\x20code\">Format</a>\x0a\x09\x09\x09\x09\x09{{if\x20not\x20$.GoogleCN}}\x0a\x09\x09\x09\x09\x09<a\x20class=\"share\"\x20title=\"Share\x20this\x20code\">Share</a>\x0a\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09</div>\x0a\x09\x09\x09</div>\x0a\x09\x09{{else}}\x0a\x09\x09\x09<p>Code:</p>\x0a\x09\x09\x09<pre\x20class=\"code\">{{.Code}}</pre>\x0a\x09\x09\x09{{with\x20.Output}}\x0a\x09\x09\x09<p>Output:</p>\x0a\x09\x09\x09<pre\x20class=\"output\">{{html\x20.}}</pre>\x0a\x09\x09\x09{{end}}\x0a\x09\x09{{end}}\x0a\x09</div>\x0a</div>\x0a",
+ "example.html": "<div\x20id=\"example_{{.Name}}\"\x20class=\"toggle\">\x0a\x09<div\x20class=\"collapsed\">\x0a\x09\x09<p\x20class=\"exampleHeading\x20toggleButton\">\xe2\x96\xb9\x20<span\x20class=\"text\">Example{{example_suffix\x20.Name}}</span></p>\x0a\x09</div>\x0a\x09<div\x20class=\"expanded\">\x0a\x09\x09<p\x20class=\"exampleHeading\x20toggleButton\">\xe2\x96\xbe\x20<span\x20class=\"text\">Example{{example_suffix\x20.Name}}</span></p>\x0a\x09\x09{{with\x20.Doc}}<p>{{html\x20.}}</p>{{end}}\x0a\x09\x09{{$output\x20:=\x20.Output}}\x0a\x09\x09{{with\x20.Play}}\x0a\x09\x09\x09<div\x20class=\"play\">\x0a\x09\x09\x09\x09<div\x20class=\"input\"><textarea\x20class=\"code\"\x20spellcheck=\"false\">{{html\x20.}}</textarea></div>\x0a\x09\x09\x09\x09<div\x20class=\"output\"><pre>{{html\x20$output}}</pre></div>\x0a\x09\x09\x09\x09<div\x20class=\"buttons\">\x0a\x09\x09\x09\x09\x09<a\x20class=\"run\"\x20title=\"Run\x20this\x20code\x20[shift-enter]\">Run</a>\x0a\x09\x09\x09\x09\x09<a\x20class=\"fmt\"\x20title=\"Format\x20this\x20code\">Format</a>\x0a\x09\x09\x09\x09\x09<a\x20class=\"share\"\x20title=\"Share\x20this\x20code\">Share</a>\x0a\x09\x09\x09\x09</div>\x0a\x09\x09\x09</div>\x0a\x09\x09{{else}}\x0a\x09\x09\x09<p>Code:</p>\x0a\x09\x09\x09<pre\x20class=\"code\">{{.Code}}</pre>\x0a\x09\x09\x09{{with\x20.Output}}\x0a\x09\x09\x09<p>Output:</p>\x0a\x09\x09\x09<pre\x20class=\"output\">{{html\x20.}}</pre>\x0a\x09\x09\x09{{end}}\x0a\x09\x09{{end}}\x0a\x09</div>\x0a</div>\x0a",
- "godoc.html": "<!DOCTYPE\x20html>\x0a<html>\x0a<head>\x0a<meta\x20http-equiv=\"Content-Type\"\x20content=\"text/html;\x20charset=utf-8\">\x0a<meta\x20name=\"viewport\"\x20content=\"width=device-width,\x20initial-scale=1\">\x0a<meta\x20name=\"theme-color\"\x20content=\"#375EAB\">\x0a{{with\x20.Tabtitle}}\x0a\x20\x20<title>{{html\x20.}}\x20-\x20Go\x20Documentation\x20Server</title>\x0a{{else}}\x0a\x20\x20<title>Go\x20Documentation\x20Server</title>\x0a{{end}}\x0a<link\x20type=\"text/css\"\x20rel=\"stylesheet\"\x20href=\"/lib/godoc/style.css\">\x0a{{if\x20.TreeView}}\x0a<link\x20rel=\"stylesheet\"\x20href=\"/lib/godoc/jquery.treeview.css\">\x0a{{end}}\x0a<script>window.initFuncs\x20=\x20[];</script>\x0a<script\x20src=\"/lib/godoc/jquery.js\"\x20defer></script>\x0a{{if\x20.TreeView}}\x0a<script\x20src=\"/lib/godoc/jquery.treeview.js\"\x20defer></script>\x0a<script\x20src=\"/lib/godoc/jquery.treeview.edit.js\"\x20defer></script>\x0a{{end}}\x0a\x0a{{if\x20.Playground}}\x0a<script\x20src=\"/lib/godoc/playground.js\"\x20defer></script>\x0a{{end}}\x0a{{with\x20.Version}}<script>var\x20goVersion\x20=\x20{{printf\x20\"%q\"\x20.}};</script>{{end}}\x0a<script\x20src=\"/lib/godoc/godocs.js\"\x20defer></script>\x0a</head>\x0a<body>\x0a\x0a<div\x20id='lowframe'\x20style=\"position:\x20fixed;\x20bottom:\x200;\x20left:\x200;\x20height:\x200;\x20width:\x20100%;\x20border-top:\x20thin\x20solid\x20grey;\x20background-color:\x20white;\x20overflow:\x20auto;\">\x0a...\x0a</div><!--\x20#lowframe\x20-->\x0a\x0a<div\x20id=\"topbar\"{{if\x20.Title}}\x20class=\"wide\"{{end}}><div\x20class=\"container\">\x0a<div\x20class=\"top-heading\"\x20id=\"heading-wide\"><a\x20href=\"/pkg/\">Go\x20Documentation\x20Server</a></div>\x0a<div\x20class=\"top-heading\"\x20id=\"heading-narrow\"><a\x20href=\"/pkg/\">GoDoc</a></div>\x0a<a\x20href=\"#\"\x20id=\"menu-button\"><span\x20id=\"menu-button-arrow\">▽</span></a>\x0a<form\x20method=\"GET\"\x20action=\"/search\">\x0a<div\x20id=\"menu\">\x0a{{if\x20(and\x20.Playground\x20.Title)}}\x0a<a\x20id=\"playgroundButton\"\x20href=\"http://play.golang.org/\"\x20title=\"Show\x20Go\x20Playground\">Play</a>\x0a{{end}}\x0a<span\x20class=\"search-box\"><input\x20type=\"search\"\x20id=\"search\"\x20name=\"q\"\x20placeholder=\"Search\"\x20aria-label=\"Search\"\x20required><button\x20type=\"submit\"><span><!--\x20magnifying\x20glass:\x20--><svg\x20width=\"24\"\x20height=\"24\"\x20viewBox=\"0\x200\x2024\x2024\"><title>submit\x20search</title><path\x20d=\"M15.5\x2014h-.79l-.28-.27C15.41\x2012.59\x2016\x2011.11\x2016\x209.5\x2016\x205.91\x2013.09\x203\x209.5\x203S3\x205.91\x203\x209.5\x205.91\x2016\x209.5\x2016c1.61\x200\x203.09-.59\x204.23-1.57l.27.28v.79l5\x204.99L20.49\x2019l-4.99-5zm-6\x200C7.01\x2014\x205\x2011.99\x205\x209.5S7.01\x205\x209.5\x205\x2014\x207.01\x2014\x209.5\x2011.99\x2014\x209.5\x2014z\"/><path\x20d=\"M0\x200h24v24H0z\"\x20fill=\"none\"/></svg></span></button></span>\x0a</div>\x0a</form>\x0a\x0a</div></div>\x0a\x0a{{if\x20.Playground}}\x0a<div\x20id=\"playground\"\x20class=\"play\">\x0a\x09<div\x20class=\"input\"><textarea\x20class=\"code\"\x20spellcheck=\"false\">package\x20main\x0a\x0aimport\x20\"fmt\"\x0a\x0afunc\x20main()\x20{\x0a\x09fmt.Println(\"Hello,\x20\xe4\xb8\x96\xe7\x95\x8c\")\x0a}</textarea></div>\x0a\x09<div\x20class=\"output\"></div>\x0a\x09<div\x20class=\"buttons\">\x0a\x09\x09<a\x20class=\"run\"\x20title=\"Run\x20this\x20code\x20[shift-enter]\">Run</a>\x0a\x09\x09<a\x20class=\"fmt\"\x20title=\"Format\x20this\x20code\">Format</a>\x0a\x09\x09{{if\x20not\x20$.GoogleCN}}\x0a\x09\x09<a\x20class=\"share\"\x20title=\"Share\x20this\x20code\">Share</a>\x0a\x09\x09{{end}}\x0a\x09</div>\x0a</div>\x0a{{end}}\x0a\x0a<div\x20id=\"page\"{{if\x20.Title}}\x20class=\"wide\"{{end}}>\x0a<div\x20class=\"container\">\x0a\x0a{{if\x20or\x20.Title\x20.SrcPath}}\x0a\x20\x20<h1>\x0a\x20\x20\x20\x20{{html\x20.Title}}\x0a\x20\x20\x20\x20{{html\x20.SrcPath\x20|\x20srcBreadcrumb}}\x0a\x20\x20</h1>\x0a{{end}}\x0a\x0a{{with\x20.Subtitle}}\x0a\x20\x20<h2>{{html\x20.}}</h2>\x0a{{end}}\x0a\x0a{{with\x20.SrcPath}}\x0a\x20\x20<h2>\x0a\x20\x20\x20\x20Documentation:\x20{{html\x20.\x20|\x20srcToPkgLink}}\x0a\x20\x20</h2>\x0a{{end}}\x0a\x0a{{/*\x20The\x20Table\x20of\x20Contents\x20is\x20automatically\x20inserted\x20in\x20this\x20<div>.\x0a\x20\x20\x20\x20\x20Do\x20not\x20delete\x20this\x20<div>.\x20*/}}\x0a<div\x20id=\"nav\"></div>\x0a\x0a{{/*\x20Body\x20is\x20HTML-escaped\x20elsewhere\x20*/}}\x0a{{printf\x20\"%s\"\x20.Body}}\x0a\x0a<div\x20id=\"footer\">\x0aBuild\x20version\x20{{html\x20.Version}}.<br>\x0aExcept\x20as\x20<a\x20href=\"https://developers.google.com/site-policies#restrictions\">noted</a>,\x0athe\x20content\x20of\x20this\x20page\x20is\x20licensed\x20under\x20the\x0aCreative\x20Commons\x20Attribution\x203.0\x20License,\x0aand\x20code\x20is\x20licensed\x20under\x20a\x20<a\x20href=\"/LICENSE\">BSD\x20license</a>.<br>\x0a<a\x20href=\"/doc/tos.html\">Terms\x20of\x20Service</a>\x20|\x0a<a\x20href=\"http://www.google.com/intl/en/policies/privacy/\">Privacy\x20Policy</a>\x0a</div>\x0a\x0a</div><!--\x20.container\x20-->\x0a</div><!--\x20#page\x20-->\x0a</body>\x0a</html>\x0a",
+ "favicon.ico": "\x00\x00\x01\x00\x02\x00\x20\x20\x00\x00\x01\x00\x20\x00\xa8\x10\x00\x00&\x00\x00\x00\x10\x10\x00\x00\x01\x00\x08\x00h\x05\x00\x00\xce\x10\x00\x00(\x00\x00\x00\x20\x00\x00\x00@\x00\x00\x00\x01\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xda\xc1e\xff\xc6\xb0\\\xff\xc6\xb0\\\xff\xdf\xc6h\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xe6\xe1\xcd\xff\xfb\xfc\xff\xff\xfb\xfc\xff\xff\xe2\xda\xbc\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xf8\xdcs\xff\xe6\xcck\xff\xf1\xf0\xea\xff\xfb\xfc\xff\xff\xfb\xfc\xff\xff\xe9\xe5\xd7\xff\xe4\xcaj\xff\xf8\xdcs\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfb\xdeu\xff\xa9\x9a`\xff\x94\x93|\xff\x94\x9f\xb7\xff\x9a\xa6\xc1\xff\x9b\xa7\xc2\xff\x93\x9c\xb0\xff\x96\x93z\xff\xb0\x9f_\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xf8\xdcy\xffv\x8d\xc0\xfft\x8c\xc3\xfft\x8c\xc3\xfft\x8c\xc3\xfft\x8c\xc3\xfft\x8c\xc3\xfft\x8c\xc3\xff|\x8f\xb7\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe2{\xff\xfe\xee\xb1\xff\xff\xf7\xd9\xff\xff\xf9\xe3\xff\xff\xf5\xcf\xff\xa0\xa9\xb5\xfft\x8c\xc3\xffSb\x85\xff39I\xff5<L\xffYj\x90\xfft\x8c\xc3\xff\xb0\xb4\xb2\xff\xff\xf2\xc3\xff\xff\xf3\xc9\xff\xfe\xee\xaf\xff\xfe\xe3\x7f\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe4\x84\xff\xff\xfa\xe8\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xcc\xca\xbd\xff\x1f\x20#\xff\x1f\x20#\xff\x1f\x20#\xff'(*\xff\xdd\xde\xd7\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xf4\xff\xfe\xe8\x98\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xffTN8\xff\xfe\xe1v\xff\xfe\xe1v\xff\xff\xf8\xde\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa0\x97v\xffLG4\xffQK5\xff\xb3\xad\x9a\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xf7\xff\xfe\xe4\x85\xff\xfe\xe1v\xff\xfe\xe1v\xffTN8\xffTN8\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xf3TN8\xff\xf8\xdct\xff\xfe\xe8\x95\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf6\xf6\xf6\xff_ac\xff(*.\xff{}\x7f\xff\xfe\xfb\xef\xff\xfe\xe1w\xff\xfe\xe7\x91\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xd4\xd4\xd5\xff@AD\xff126\xff\xb3\xb4\xb5\xff\xff\xf1\xc1\xff\xfe\xe1v\xff\xf8\xdct\xffTN8\xffTN8\xf3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN8\xdaTN8\xff\xeb\xd0o\xff\xfe\xee\xb3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x97\x98\x9a\xff\x11\x13\x17\xff\x11\x13\x17\xff\x11\x13\x17\xff\xc1\xc2\xc3\xff\xfe\xe5\x86\xff\xfe\xee\xb1\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffPQT\xff\x11\x13\x17\xff\x11\x13\x17\xff\x1f!%\xff\xfc\xf5\xdd\xff\xfe\xe1v\xff\xea\xd0o\xffTN8\xffSN8\xd9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00UO7\xafTN8\xff\xca\xb5c\xff\xfe\xef\xb4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x96\x96\x98\xff\x11\x13\x17\xff\x11\x13\x17\xff\x11\x13\x17\xff\xc0\xc0\xc1\xff\xfe\xe5\x87\xff\xfe\xee\xb1\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffOPS\xff\x11\x13\x17\xff\x11\x13\x17\xff\x1e\x20$\xff\xfb\xf4\xdc\xff\xfe\xe1v\xff\xc9\xb3b\xffTN8\xffTN8\xb1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TN9UWR9\xf4\x90\x81N\xff\xe2\xc9l\xff\xfe\xe8\x97\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf5\xf5\xf5\xff[\\_\xff%&*\xffvwy\xff\xfe\xfb\xf1\xff\xfe\xe1w\xff\xfe\xe7\x93\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xd1\xd2\xd2\xff;=@\xff,.1\xff\xb0\xb0\xb2\xff\xff\xf2\xc2\xff\xfe\xe1v\xff\xd8\xc0h\xffxmE\xffTN8\xf8TO9g\x00\x00\x00\x00TL9CWP9\xfe\xd2\xbbf\xff\xed\xd2p\xff\xfc\xdfv\xff\xfd\xe0v\xff\xff\xf8\xe0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xee\xb3\xff\xfe\xe1v\xff\xfe\xe1v\xff\xff\xf7\xd9\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xf8\xff\xfe\xe5\x86\xff\xfe\xe1v\xff\xfe\xe1v\xff\xf7\xdbs\xff\xa2\x91T\xffTN8\xffUO8WUN8\xbb\x82vI\xff\xf3\xd8s\xff\x9d\x8dS\xff\x9d\x8eR\xff\xf6\xdas\xff\xfd\xe3\x84\xff\xff\xfa\xea\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf4\xcb\xff\xfe\xe1w\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe3\x81\xff\xff\xf8\xe0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\xf6\xff\xfe\xe9\x9a\xff\xfe\xe0v\xff\xd6\xbeg\xff\x98\x89Q\xff\xb1\x9fY\xff\xf5\xd9s\xffTN8\xffUN8\xd0TO8\xe0kb@\xff\xfa\xdet\xff\xf9\xddu\xff\xfe\xe1v\xff\xc9\xb3b\xff\x92\x83N\xff\xf5\xdby\xff\xfe\xef\xb4\xff\xff\xf7\xdd\xff\xff\xf9\xe5\xff\xff\xf5\xd2\xff\xfe\xea\xa0\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1w\xff\xfe\xeb\xa3\xff\xff\xf3\xc7\xff\xff\xf4\xcc\xff\xfe\xee\xb3\xff\xfe\xe3\x80\xff\xf5\xd9s\xff\xa1\x91T\xff\xe9\xcfn\xff\xf7\xdbs\xff\xed\xd2p\xff\xdb\xc3i\xffTN8\xffTN8\xf5UN7\xc1TN8\xff~rG\xff\xb2\xa0[\xff\x8c~M\xffTN8\xffTN8\xffh_?\xff\xc0\xac_\xff\xf9\xdct\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xfe\xe1v\xff\xf8\xdct\xff\xc0\xac_\xffg_?\xffTN8\xff]U;\xff\xa3\x92U\xff\xaa\x99W\xffe]>\xffTN8\xffSM8\xd6TL9CTN8\xfcTN8\xffTN8\xffTN8\xffTN8\xffTO8\xe3TN8\xffTN8\xff_W;\xff\x92\x84O\xff\xb4\xa1[\xff\xd4\xbdg\xff\xe7\xcdm\xff\xf1\xd6q\xff\xfa\xdet\xff\xfa\xdet\xff\xf1\xd6q\xff\xe7\xcdm\xff\xd4\xbdg\xff\xb4\xa1[\xff\x92\x84O\xff_W;\xffTN8\xffTN8\xffUN9\xdcTN8\xffTN8\xffTN8\xffTN8\xffTN8\xfeUO8W\x00\x00\x00\x00TO6=TO9\xb9TN8\xdeUN8\xcaUN8i\x00\x00\x00\x02SM9YSN8\xdfTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffTN8\xffSN8\xdcSM8V\x00\x00\x00\x01TN8[TN8\xc3SN8\xdfTN8\xc0TM8I\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00UUU\x03RN7ATO8\x92TO8\xbcTN8\xdeTN8\xeeTN8\xeeTN8\xffTN8\xffTN8\xf1TN8\xeeTN8\xe3TO8\xbcTN7\x8fTO6=\x80\x80\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\xc0\x00\x00\x03\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x01\xff\x00\x00\xff\xff\xff\xff\xff(\x00\x00\x00\x10\x00\x00\x00\x20\x00\x00\x00\x01\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x13\x17\x00\x1f\x20#\x00.,'\x00WN1\x00TN8\x00ue7\x00xh4\x00{sN\x00\xaa\x92H\x00\xa5\x92T\x00t\x8c\xc3\x00\xc8\xa7N\x00\x8e\x99\xa6\x00\xa6\xa3\x89\x00\xc8\xb3r\x00\xc2\xb2z\x00\xcf\xbby\x00\xca\xbf\x8f\x00\xcc\xc1\x96\x00\xf3\xd5t\x00\xff\xddw\x00\xff\xdfw\x00\xff\xdfx\x00\xff\xe1u\x00\xff\xe0y\x00\xfe\xe1v\x00\xe7\xe1\xd2\x00\xf5\xf6\xfb\x00\xf7\xfa\xff\x00\xfb\xfc\xff\x00\xfb\xfe\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x04\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x04\x20\x20\x04\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x04\x20\x20\x04\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x04\x20\x20\x04\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x04\x20\x20\x04\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x04\x20\x20\x04\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x04\x20\x20\x04\x19\x19\x19\x19\x19\x10\x0f\x19\x15\x14\x14\x19\x04\x20\x20\x04\x19\x19\x19\x18\x0a\x1d\x1d\x0a\x19\x14\x14\x19\x04\x20\x20\x04\x19\x15\x19\x19\x0d\x0a\x0a\x0c\x19\x17\x16\x19\x04\x20\x20\x04\x19\x13\x1f\x1f\x0e\x01\x01\x0e\x1f\x1f\x13\x19\x04\x20\x20\x04\x19\x1a\x1e\x00\x02\x19\x19\x1a\x1d\x00\x02\x19\x04\x20\x04\x06\x0b\x1a\x1f\x00\x02\x19\x19\x1a\x1f\x00\x02\x0b\x05\x04\x04\x0b\x08\x0e\x1b\x1c\x0e\x19\x19\x0e\x1f\x1f\x0e\x08\x0b\x04\x20\x04\x07\x08\x0b\x0b\x19\x19\x19\x19\x12\x11\x09\x07\x04\x20\x20\x20\x20\x20\x07\x07\x03\x04\x04\x03\x07\x07\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\xf0\x0f\x00\x00\xff\xff\x00\x00",
+
+ "godoc.html": "<!DOCTYPE\x20html>\x0a<html>\x0a<head>\x0a<meta\x20http-equiv=\"Content-Type\"\x20content=\"text/html;\x20charset=utf-8\">\x0a<meta\x20name=\"viewport\"\x20content=\"width=device-width,\x20initial-scale=1\">\x0a<meta\x20name=\"theme-color\"\x20content=\"#375EAB\">\x0a{{with\x20.Tabtitle}}\x0a\x20\x20<title>{{html\x20.}}\x20-\x20Go\x20Documentation\x20Server</title>\x0a{{else}}\x0a\x20\x20<title>Go\x20Documentation\x20Server</title>\x0a{{end}}\x0a<link\x20type=\"text/css\"\x20rel=\"stylesheet\"\x20href=\"/lib/godoc/style.css\">\x0a{{if\x20.TreeView}}\x0a<link\x20rel=\"stylesheet\"\x20href=\"/lib/godoc/jquery.treeview.css\">\x0a{{end}}\x0a<script>window.initFuncs\x20=\x20[];</script>\x0a<script\x20src=\"/lib/godoc/jquery.js\"\x20defer></script>\x0a{{if\x20.TreeView}}\x0a<script\x20src=\"/lib/godoc/jquery.treeview.js\"\x20defer></script>\x0a<script\x20src=\"/lib/godoc/jquery.treeview.edit.js\"\x20defer></script>\x0a{{end}}\x0a\x0a{{if\x20.Playground}}\x0a<script\x20src=\"/lib/godoc/playground.js\"\x20defer></script>\x0a{{end}}\x0a{{with\x20.Version}}<script>var\x20goVersion\x20=\x20{{printf\x20\"%q\"\x20.}};</script>{{end}}\x0a<script\x20src=\"/lib/godoc/godocs.js\"\x20defer></script>\x0a</head>\x0a<body>\x0a\x0a<div\x20id='lowframe'\x20style=\"position:\x20fixed;\x20bottom:\x200;\x20left:\x200;\x20height:\x200;\x20width:\x20100%;\x20border-top:\x20thin\x20solid\x20grey;\x20background-color:\x20white;\x20overflow:\x20auto;\">\x0a...\x0a</div><!--\x20#lowframe\x20-->\x0a\x0a<div\x20id=\"topbar\"{{if\x20.Title}}\x20class=\"wide\"{{end}}><div\x20class=\"container\">\x0a<div\x20class=\"top-heading\"\x20id=\"heading-wide\"><a\x20href=\"/pkg/\">Go\x20Documentation\x20Server</a></div>\x0a<div\x20class=\"top-heading\"\x20id=\"heading-narrow\"><a\x20href=\"/pkg/\">GoDoc</a></div>\x0a<a\x20href=\"#\"\x20id=\"menu-button\"><span\x20id=\"menu-button-arrow\">▽</span></a>\x0a<form\x20method=\"GET\"\x20action=\"/search\">\x0a<div\x20id=\"menu\">\x0a{{if\x20(and\x20.Playground\x20.Title)}}\x0a<a\x20id=\"playgroundButton\"\x20href=\"https://play.golang.org/\"\x20title=\"Show\x20Go\x20Playground\">Play</a>\x0a{{end}}\x0a<span\x20class=\"search-box\"><input\x20type=\"search\"\x20id=\"search\"\x20name=\"q\"\x20placeholder=\"Search\"\x20aria-label=\"Search\"\x20required><button\x20type=\"submit\"><span><!--\x20magnifying\x20glass:\x20--><svg\x20width=\"24\"\x20height=\"24\"\x20viewBox=\"0\x200\x2024\x2024\"><title>submit\x20search</title><path\x20d=\"M15.5\x2014h-.79l-.28-.27C15.41\x2012.59\x2016\x2011.11\x2016\x209.5\x2016\x205.91\x2013.09\x203\x209.5\x203S3\x205.91\x203\x209.5\x205.91\x2016\x209.5\x2016c1.61\x200\x203.09-.59\x204.23-1.57l.27.28v.79l5\x204.99L20.49\x2019l-4.99-5zm-6\x200C7.01\x2014\x205\x2011.99\x205\x209.5S7.01\x205\x209.5\x205\x2014\x207.01\x2014\x209.5\x2011.99\x2014\x209.5\x2014z\"/><path\x20d=\"M0\x200h24v24H0z\"\x20fill=\"none\"/></svg></span></button></span>\x0a</div>\x0a</form>\x0a\x0a</div></div>\x0a\x0a{{if\x20.Playground}}\x0a<div\x20id=\"playground\"\x20class=\"play\">\x0a\x09<div\x20class=\"input\"><textarea\x20class=\"code\"\x20spellcheck=\"false\">package\x20main\x0a\x0aimport\x20\"fmt\"\x0a\x0afunc\x20main()\x20{\x0a\x09fmt.Println(\"Hello,\x20\xe4\xb8\x96\xe7\x95\x8c\")\x0a}</textarea></div>\x0a\x09<div\x20class=\"output\"></div>\x0a\x09<div\x20class=\"buttons\">\x0a\x09\x09<a\x20class=\"run\"\x20title=\"Run\x20this\x20code\x20[shift-enter]\">Run</a>\x0a\x09\x09<a\x20class=\"fmt\"\x20title=\"Format\x20this\x20code\">Format</a>\x0a\x09\x09<a\x20class=\"share\"\x20title=\"Share\x20this\x20code\">Share</a>\x0a\x09</div>\x0a</div>\x0a{{end}}\x0a\x0a<div\x20id=\"page\"{{if\x20.Title}}\x20class=\"wide\"{{end}}>\x0a<div\x20class=\"container\">\x0a\x0a{{if\x20or\x20.Title\x20.SrcPath}}\x0a\x20\x20<h1>\x0a\x20\x20\x20\x20{{html\x20.Title}}\x0a\x20\x20\x20\x20{{html\x20.SrcPath\x20|\x20srcBreadcrumb}}\x0a\x20\x20</h1>\x0a{{end}}\x0a\x0a{{with\x20.Subtitle}}\x0a\x20\x20<h2>{{html\x20.}}</h2>\x0a{{end}}\x0a\x0a{{with\x20.SrcPath}}\x0a\x20\x20<h2>\x0a\x20\x20\x20\x20Documentation:\x20{{html\x20.\x20|\x20srcToPkgLink}}\x0a\x20\x20</h2>\x0a{{end}}\x0a\x0a{{/*\x20The\x20Table\x20of\x20Contents\x20is\x20automatically\x20inserted\x20in\x20this\x20<div>.\x0a\x20\x20\x20\x20\x20Do\x20not\x20delete\x20this\x20<div>.\x20*/}}\x0a<div\x20id=\"nav\"></div>\x0a\x0a{{/*\x20Body\x20is\x20HTML-escaped\x20elsewhere\x20*/}}\x0a{{printf\x20\"%s\"\x20.Body}}\x0a\x0a<div\x20id=\"footer\">\x0aBuild\x20version\x20{{html\x20.Version}}.<br>\x0aExcept\x20as\x20<a\x20href=\"https://developers.google.com/site-policies#restrictions\">noted</a>,\x0athe\x20content\x20of\x20this\x20page\x20is\x20licensed\x20under\x20the\x0aCreative\x20Commons\x20Attribution\x203.0\x20License,\x0aand\x20code\x20is\x20licensed\x20under\x20a\x20<a\x20href=\"/LICENSE\">BSD\x20license</a>.<br>\x0a<a\x20href=\"https://golang.org/doc/tos.html\">Terms\x20of\x20Service</a>\x20|\x0a<a\x20href=\"https://www.google.com/intl/en/policies/privacy/\">Privacy\x20Policy</a>\x0a</div>\x0a\x0a</div><!--\x20.container\x20-->\x0a</div><!--\x20#page\x20-->\x0a</body>\x0a</html>\x0a",
"godocs.js": "//\x20Copyright\x202012\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a//\x20Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a//\x20license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a\x0a/*\x20A\x20little\x20code\x20to\x20ease\x20navigation\x20of\x20these\x20documents.\x0a\x20*\x0a\x20*\x20On\x20window\x20load\x20we:\x0a\x20*\x20\x20+\x20Generate\x20a\x20table\x20of\x20contents\x20(generateTOC)\x0a\x20*\x20\x20+\x20Bind\x20foldable\x20sections\x20(bindToggles)\x0a\x20*\x20\x20+\x20Bind\x20links\x20to\x20foldable\x20sections\x20(bindToggleLinks)\x0a\x20*/\x0a\x0a(function()\x20{\x0a\x20\x20'use\x20strict';\x0a\x0a\x20\x20//\x20Mobile-friendly\x20topbar\x20menu\x0a\x20\x20$(function()\x20{\x0a\x20\x20\x20\x20var\x20menu\x20=\x20$('#menu');\x0a\x20\x20\x20\x20var\x20menuButton\x20=\x20$('#menu-button');\x0a\x20\x20\x20\x20var\x20menuButtonArrow\x20=\x20$('#menu-button-arrow');\x0a\x20\x20\x20\x20menuButton.click(function(event)\x20{\x0a\x20\x20\x20\x20\x20\x20menu.toggleClass('menu-visible');\x0a\x20\x20\x20\x20\x20\x20menuButtonArrow.toggleClass('vertical-flip');\x0a\x20\x20\x20\x20\x20\x20event.preventDefault();\x0a\x20\x20\x20\x20\x20\x20return\x20false;\x0a\x20\x20\x20\x20});\x0a\x20\x20});\x0a\x0a\x20\x20/*\x20Generates\x20a\x20table\x20of\x20contents:\x20looks\x20for\x20h2\x20and\x20h3\x20elements\x20and\x20generates\x0a\x20\x20\x20*\x20links.\x20\"Decorates\"\x20the\x20element\x20with\x20id==\"nav\"\x20with\x20this\x20table\x20of\x20contents.\x0a\x20\x20\x20*/\x0a\x20\x20function\x20generateTOC()\x20{\x0a\x20\x20\x20\x20if\x20($('#manual-nav').length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20For\x20search,\x20we\x20send\x20the\x20toc\x20precomputed\x20from\x20server-side.\x0a\x20\x20\x20\x20//\x20TODO:\x20Ideally,\x20this\x20should\x20always\x20be\x20precomputed\x20for\x20all\x20pages,\x20but\x20then\x0a\x20\x20\x20\x20//\x20we\x20need\x20to\x20do\x20HTML\x20parsing\x20on\x20the\x20server-side.\x0a\x20\x20\x20\x20if\x20(location.pathname\x20===\x20'/search')\x20{\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20nav\x20=\x20$('#nav');\x0a\x20\x20\x20\x20if\x20(nav.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20toc_items\x20=\x20[];\x0a\x20\x20\x20\x20$(nav)\x0a\x20\x20\x20\x20\x20\x20.nextAll('h2,\x20h3')\x0a\x20\x20\x20\x20\x20\x20.each(function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20node\x20=\x20this;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(node.id\x20==\x20'')\x20node.id\x20=\x20'tmp_'\x20+\x20toc_items.length;\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20link\x20=\x20$('<a/>')\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.attr('href',\x20'#'\x20+\x20node.id)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.text($(node).text());\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20item;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20($(node).is('h2'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20item\x20=\x20$('<dt/>');\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20h3\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20item\x20=\x20$('<dd\x20class=\"indent\"/>');\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20item.append(link);\x0a\x20\x20\x20\x20\x20\x20\x20\x20toc_items.push(item);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20if\x20(toc_items.length\x20<=\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20var\x20dl1\x20=\x20$('<dl/>');\x0a\x20\x20\x20\x20var\x20dl2\x20=\x20$('<dl/>');\x0a\x0a\x20\x20\x20\x20var\x20split_index\x20=\x20toc_items.length\x20/\x202\x20+\x201;\x0a\x20\x20\x20\x20if\x20(split_index\x20<\x208)\x20{\x0a\x20\x20\x20\x20\x20\x20split_index\x20=\x20toc_items.length;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20split_index;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20dl1.append(toc_items[i]);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20for\x20(;\x20/*\x20keep\x20using\x20i\x20*/\x20i\x20<\x20toc_items.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20dl2.append(toc_items[i]);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20tocTable\x20=\x20$('<table\x20class=\"unruled\"/>').appendTo(nav);\x0a\x20\x20\x20\x20var\x20tocBody\x20=\x20$('<tbody/>').appendTo(tocTable);\x0a\x20\x20\x20\x20var\x20tocRow\x20=\x20$('<tr/>').appendTo(tocBody);\x0a\x0a\x20\x20\x20\x20//\x201st\x20column\x0a\x20\x20\x20\x20$('<td\x20class=\"first\"/>')\x0a\x20\x20\x20\x20\x20\x20.appendTo(tocRow)\x0a\x20\x20\x20\x20\x20\x20.append(dl1);\x0a\x20\x20\x20\x20//\x202nd\x20column\x0a\x20\x20\x20\x20$('<td/>')\x0a\x20\x20\x20\x20\x20\x20.appendTo(tocRow)\x0a\x20\x20\x20\x20\x20\x20.append(dl2);\x0a\x20\x20}\x0a\x0a\x20\x20function\x20bindToggle(el)\x20{\x0a\x20\x20\x20\x20$('.toggleButton',\x20el).click(function()\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20($(this).closest('.toggle,\x20.toggleVisible')[0]\x20!=\x20el)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Only\x20trigger\x20the\x20closest\x20toggle\x20header.\x0a\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20($(el).is('.toggle'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$(el)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.addClass('toggleVisible')\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.removeClass('toggle');\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$(el)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.addClass('toggle')\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.removeClass('toggleVisible');\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20function\x20bindToggles(selector)\x20{\x0a\x20\x20\x20\x20$(selector).each(function(i,\x20el)\x20{\x0a\x20\x20\x20\x20\x20\x20bindToggle(el);\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20function\x20bindToggleLink(el,\x20prefix)\x20{\x0a\x20\x20\x20\x20$(el).click(function()\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20href\x20=\x20$(el).attr('href');\x0a\x20\x20\x20\x20\x20\x20var\x20i\x20=\x20href.indexOf('#'\x20+\x20prefix);\x0a\x20\x20\x20\x20\x20\x20if\x20(i\x20<\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20var\x20id\x20=\x20'#'\x20+\x20prefix\x20+\x20href.slice(i\x20+\x201\x20+\x20prefix.length);\x0a\x20\x20\x20\x20\x20\x20if\x20($(id).is('.toggle'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$(id)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.find('.toggleButton')\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.first()\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.click();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x20\x20function\x20bindToggleLinks(selector,\x20prefix)\x20{\x0a\x20\x20\x20\x20$(selector).each(function(i,\x20el)\x20{\x0a\x20\x20\x20\x20\x20\x20bindToggleLink(el,\x20prefix);\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20function\x20setupDropdownPlayground()\x20{\x0a\x20\x20\x20\x20if\x20(!$('#page').is('.wide'))\x20{\x0a\x20\x20\x20\x20\x20\x20return;\x20//\x20don't\x20show\x20on\x20front\x20page\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20var\x20button\x20=\x20$('#playgroundButton');\x0a\x20\x20\x20\x20var\x20div\x20=\x20$('#playground');\x0a\x20\x20\x20\x20var\x20setup\x20=\x20false;\x0a\x20\x20\x20\x20button.toggle(\x0a\x20\x20\x20\x20\x20\x20function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20button.addClass('active');\x0a\x20\x20\x20\x20\x20\x20\x20\x20div.show();\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(setup)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20setup\x20=\x20true;\x0a\x20\x20\x20\x20\x20\x20\x20\x20playground({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20codeEl:\x20$('.code',\x20div),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20outputEl:\x20$('.output',\x20div),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20runEl:\x20$('.run',\x20div),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20fmtEl:\x20$('.fmt',\x20div),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20shareEl:\x20$('.share',\x20div),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20shareRedirect:\x20'//play.golang.org/p/',\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20},\x0a\x20\x20\x20\x20\x20\x20function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20button.removeClass('active');\x0a\x20\x20\x20\x20\x20\x20\x20\x20div.hide();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20);\x0a\x20\x20\x20\x20$('#menu').css('min-width',\x20'+=60');\x0a\x0a\x20\x20\x20\x20//\x20Hide\x20inline\x20playground\x20if\x20we\x20click\x20somewhere\x20on\x20the\x20page.\x0a\x20\x20\x20\x20//\x20This\x20is\x20needed\x20in\x20mobile\x20devices,\x20where\x20the\x20\"Play\"\x20button\x0a\x20\x20\x20\x20//\x20is\x20not\x20clickable\x20once\x20the\x20playground\x20opens\x20up.\x0a\x20\x20\x20\x20$('#page').click(function()\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(button.hasClass('active'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20button.click();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20function\x20setupInlinePlayground()\x20{\x0a\x20\x20\x20\x20'use\x20strict';\x0a\x20\x20\x20\x20//\x20Set\x20up\x20playground\x20when\x20each\x20element\x20is\x20toggled.\x0a\x20\x20\x20\x20$('div.play').each(function(i,\x20el)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Set\x20up\x20playground\x20for\x20this\x20example.\x0a\x20\x20\x20\x20\x20\x20var\x20setup\x20=\x20function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20code\x20=\x20$('.code',\x20el);\x0a\x20\x20\x20\x20\x20\x20\x20\x20playground({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20codeEl:\x20code,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20outputEl:\x20$('.output',\x20el),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20runEl:\x20$('.run',\x20el),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20fmtEl:\x20$('.fmt',\x20el),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20shareEl:\x20$('.share',\x20el),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20shareRedirect:\x20'//play.golang.org/p/',\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Make\x20the\x20code\x20textarea\x20resize\x20to\x20fit\x20content.\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20resize\x20=\x20function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20code.height(0);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20var\x20h\x20=\x20code[0].scrollHeight;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20code.height(h\x20+\x2020);\x20//\x20minimize\x20bouncing.\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20code.closest('.input').height(h);\x0a\x20\x20\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20\x20\x20code.on('keydown',\x20resize);\x0a\x20\x20\x20\x20\x20\x20\x20\x20code.on('keyup',\x20resize);\x0a\x20\x20\x20\x20\x20\x20\x20\x20code.keyup();\x20//\x20resize\x20now.\x0a\x20\x20\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20If\x20example\x20already\x20visible,\x20set\x20up\x20playground\x20now.\x0a\x20\x20\x20\x20\x20\x20if\x20($(el).is(':visible'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20setup();\x0a\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Otherwise,\x20set\x20up\x20playground\x20when\x20example\x20is\x20expanded.\x0a\x20\x20\x20\x20\x20\x20var\x20built\x20=\x20false;\x0a\x20\x20\x20\x20\x20\x20$(el)\x0a\x20\x20\x20\x20\x20\x20\x20\x20.closest('.toggle')\x0a\x20\x20\x20\x20\x20\x20\x20\x20.click(function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Only\x20set\x20up\x20once.\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!built)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20setup();\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20built\x20=\x20true;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20//\x20fixFocus\x20tries\x20to\x20put\x20focus\x20to\x20div#page\x20so\x20that\x20keyboard\x20navigation\x20works.\x0a\x20\x20function\x20fixFocus()\x20{\x0a\x20\x20\x20\x20var\x20page\x20=\x20$('div#page');\x0a\x20\x20\x20\x20var\x20topbar\x20=\x20$('div#topbar');\x0a\x20\x20\x20\x20page.css('outline',\x200);\x20//\x20disable\x20outline\x20when\x20focused\x0a\x20\x20\x20\x20page.attr('tabindex',\x20-1);\x20//\x20and\x20set\x20tabindex\x20so\x20that\x20it\x20is\x20focusable\x0a\x20\x20\x20\x20$(window)\x0a\x20\x20\x20\x20\x20\x20.resize(function(evt)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20only\x20focus\x20page\x20when\x20the\x20topbar\x20is\x20at\x20fixed\x20position\x20(that\x20is,\x20it's\x20in\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20front\x20of\x20page,\x20and\x20keyboard\x20event\x20will\x20go\x20to\x20the\x20former\x20by\x20default.)\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20by\x20focusing\x20page,\x20keyboard\x20event\x20will\x20go\x20to\x20page\x20so\x20that\x20up/down\x20arrow,\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20space,\x20etc.\x20will\x20work\x20as\x20expected.\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(topbar.css('position')\x20==\x20'fixed')\x20page.focus();\x0a\x20\x20\x20\x20\x20\x20})\x0a\x20\x20\x20\x20\x20\x20.resize();\x0a\x20\x20}\x0a\x0a\x20\x20function\x20toggleHash()\x20{\x0a\x20\x20\x20\x20var\x20id\x20=\x20window.location.hash.substring(1);\x0a\x20\x20\x20\x20//\x20Open\x20all\x20of\x20the\x20toggles\x20for\x20a\x20particular\x20hash.\x0a\x20\x20\x20\x20var\x20els\x20=\x20$(\x0a\x20\x20\x20\x20\x20\x20document.getElementById(id),\x0a\x20\x20\x20\x20\x20\x20$('a[name]').filter(function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20$(this).attr('name')\x20==\x20id;\x0a\x20\x20\x20\x20\x20\x20})\x0a\x20\x20\x20\x20);\x0a\x0a\x20\x20\x20\x20while\x20(els.length)\x20{\x0a\x20\x20\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20els.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20el\x20=\x20$(els[i]);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(el.is('.toggle'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20el.find('.toggleButton')\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.first()\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.click();\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20els\x20=\x20el.parent();\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20function\x20personalizeInstallInstructions()\x20{\x0a\x20\x20\x20\x20var\x20prefix\x20=\x20'?download=';\x0a\x20\x20\x20\x20var\x20s\x20=\x20window.location.search;\x0a\x20\x20\x20\x20if\x20(s.indexOf(prefix)\x20!=\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20No\x20'download'\x20query\x20string;\x20detect\x20\"test\"\x20instructions\x20from\x20User\x20Agent.\x0a\x20\x20\x20\x20\x20\x20if\x20(navigator.platform.indexOf('Win')\x20!=\x20-1)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$('.testUnix').hide();\x0a\x20\x20\x20\x20\x20\x20\x20\x20$('.testWindows').show();\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$('.testUnix').show();\x0a\x20\x20\x20\x20\x20\x20\x20\x20$('.testWindows').hide();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20filename\x20=\x20s.substr(prefix.length);\x0a\x20\x20\x20\x20var\x20filenameRE\x20=\x20/^go1\\.\\d+(\\.\\d+)?([a-z0-9]+)?\\.([a-z0-9]+)(-[a-z0-9]+)?(-osx10\\.[68])?\\.([a-z.]+)$/;\x0a\x20\x20\x20\x20var\x20m\x20=\x20filenameRE.exec(filename);\x0a\x20\x20\x20\x20if\x20(!m)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Can't\x20interpret\x20file\x20name;\x20bail.\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20$('.downloadFilename').text(filename);\x0a\x20\x20\x20\x20$('.hideFromDownload').hide();\x0a\x0a\x20\x20\x20\x20var\x20os\x20=\x20m[3];\x0a\x20\x20\x20\x20var\x20ext\x20=\x20m[6];\x0a\x20\x20\x20\x20if\x20(ext\x20!=\x20'tar.gz')\x20{\x0a\x20\x20\x20\x20\x20\x20$('#tarballInstructions').hide();\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(os\x20!=\x20'darwin'\x20||\x20ext\x20!=\x20'pkg')\x20{\x0a\x20\x20\x20\x20\x20\x20$('#darwinPackageInstructions').hide();\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(os\x20!=\x20'windows')\x20{\x0a\x20\x20\x20\x20\x20\x20$('#windowsInstructions').hide();\x0a\x20\x20\x20\x20\x20\x20$('.testUnix').show();\x0a\x20\x20\x20\x20\x20\x20$('.testWindows').hide();\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(ext\x20!=\x20'msi')\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$('#windowsInstallerInstructions').hide();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20if\x20(ext\x20!=\x20'zip')\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$('#windowsZipInstructions').hide();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20$('.testUnix').hide();\x0a\x20\x20\x20\x20\x20\x20$('.testWindows').show();\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20download\x20=\x20'https://dl.google.com/go/'\x20+\x20filename;\x0a\x0a\x20\x20\x20\x20var\x20message\x20=\x20$(\x0a\x20\x20\x20\x20\x20\x20'<p\x20class=\"downloading\">'\x20+\x0a\x20\x20\x20\x20\x20\x20\x20\x20'Your\x20download\x20should\x20begin\x20shortly.\x20'\x20+\x0a\x20\x20\x20\x20\x20\x20\x20\x20'If\x20it\x20does\x20not,\x20click\x20<a>this\x20link</a>.</p>'\x0a\x20\x20\x20\x20);\x0a\x20\x20\x20\x20message.find('a').attr('href',\x20download);\x0a\x20\x20\x20\x20message.insertAfter('#nav');\x0a\x0a\x20\x20\x20\x20window.location\x20=\x20download;\x0a\x20\x20}\x0a\x0a\x20\x20function\x20updateVersionTags()\x20{\x0a\x20\x20\x20\x20var\x20v\x20=\x20window.goVersion;\x0a\x20\x20\x20\x20if\x20(/^go[0-9.]+$/.test(v))\x20{\x0a\x20\x20\x20\x20\x20\x20$('.versionTag')\x0a\x20\x20\x20\x20\x20\x20\x20\x20.empty()\x0a\x20\x20\x20\x20\x20\x20\x20\x20.text(v);\x0a\x20\x20\x20\x20\x20\x20$('.whereTag').hide();\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20function\x20addPermalinks()\x20{\x0a\x20\x20\x20\x20function\x20addPermalink(source,\x20parent)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20id\x20=\x20source.attr('id');\x0a\x20\x20\x20\x20\x20\x20if\x20(id\x20==\x20''\x20||\x20id.indexOf('tmp_')\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Auto-generated\x20permalink.\x0a\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20if\x20(parent.find('>\x20.permalink').length)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Already\x20attached.\x0a\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20parent\x0a\x20\x20\x20\x20\x20\x20\x20\x20.append('\x20')\x0a\x20\x20\x20\x20\x20\x20\x20\x20.append($(\"<a\x20class='permalink'>¶</a>\").attr('href',\x20'#'\x20+\x20id));\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20$('#page\x20.container')\x0a\x20\x20\x20\x20\x20\x20.find('h2[id],\x20h3[id]')\x0a\x20\x20\x20\x20\x20\x20.each(function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20el\x20=\x20$(this);\x0a\x20\x20\x20\x20\x20\x20\x20\x20addPermalink(el,\x20el);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20$('#page\x20.container')\x0a\x20\x20\x20\x20\x20\x20.find('dl[id]')\x0a\x20\x20\x20\x20\x20\x20.each(function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20el\x20=\x20$(this);\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Add\x20the\x20anchor\x20to\x20the\x20\"dt\"\x20element.\x0a\x20\x20\x20\x20\x20\x20\x20\x20addPermalink(el,\x20el.find('>\x20dt').first());\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20$('.js-expandAll').click(function()\x20{\x0a\x20\x20\x20\x20if\x20($(this).hasClass('collapsed'))\x20{\x0a\x20\x20\x20\x20\x20\x20toggleExamples('toggle');\x0a\x20\x20\x20\x20\x20\x20$(this).text('(Collapse\x20All)');\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20toggleExamples('toggleVisible');\x0a\x20\x20\x20\x20\x20\x20$(this).text('(Expand\x20All)');\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20$(this).toggleClass('collapsed');\x0a\x20\x20});\x0a\x0a\x20\x20function\x20toggleExamples(className)\x20{\x0a\x20\x20\x20\x20//\x20We\x20need\x20to\x20explicitly\x20iterate\x20through\x20divs\x20starting\x20with\x20\"example_\"\x0a\x20\x20\x20\x20//\x20to\x20avoid\x20toggling\x20Overview\x20and\x20Index\x20collapsibles.\x0a\x20\x20\x20\x20$(\"[id^='example_']\").each(function()\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Check\x20for\x20state\x20and\x20click\x20it\x20only\x20if\x20required.\x0a\x20\x20\x20\x20\x20\x20if\x20($(this).hasClass(className))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20$(this)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.find('.toggleButton')\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.first()\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20.click();\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a\x0a\x20\x20$(document).ready(function()\x20{\x0a\x20\x20\x20\x20generateTOC();\x0a\x20\x20\x20\x20addPermalinks();\x0a\x20\x20\x20\x20bindToggles('.toggle');\x0a\x20\x20\x20\x20bindToggles('.toggleVisible');\x0a\x20\x20\x20\x20bindToggleLinks('.exampleLink',\x20'example_');\x0a\x20\x20\x20\x20bindToggleLinks('.overviewLink',\x20'');\x0a\x20\x20\x20\x20bindToggleLinks('.examplesLink',\x20'');\x0a\x20\x20\x20\x20bindToggleLinks('.indexLink',\x20'');\x0a\x20\x20\x20\x20setupDropdownPlayground();\x0a\x20\x20\x20\x20setupInlinePlayground();\x0a\x20\x20\x20\x20fixFocus();\x0a\x20\x20\x20\x20setupTypeInfo();\x0a\x20\x20\x20\x20setupCallgraphs();\x0a\x20\x20\x20\x20toggleHash();\x0a\x20\x20\x20\x20personalizeInstallInstructions();\x0a\x20\x20\x20\x20updateVersionTags();\x0a\x0a\x20\x20\x20\x20//\x20godoc.html\x20defines\x20window.initFuncs\x20in\x20the\x20<head>\x20tag,\x20and\x20root.html\x20and\x0a\x20\x20\x20\x20//\x20codewalk.js\x20push\x20their\x20on-page-ready\x20functions\x20to\x20the\x20list.\x0a\x20\x20\x20\x20//\x20We\x20execute\x20those\x20functions\x20here,\x20to\x20avoid\x20loading\x20jQuery\x20until\x20the\x20page\x0a\x20\x20\x20\x20//\x20content\x20is\x20loaded.\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20window.initFuncs.length;\x20i++)\x20window.initFuncs[i]();\x0a\x20\x20});\x0a\x0a\x20\x20//\x20--\x20analysis\x20---------------------------------------------------------\x0a\x0a\x20\x20//\x20escapeHTML\x20returns\x20HTML\x20for\x20s,\x20with\x20metacharacters\x20quoted.\x0a\x20\x20//\x20It\x20is\x20safe\x20for\x20use\x20in\x20both\x20elements\x20and\x20attributes\x0a\x20\x20//\x20(unlike\x20the\x20\"set\x20innerText,\x20read\x20innerHTML\"\x20trick).\x0a\x20\x20function\x20escapeHTML(s)\x20{\x0a\x20\x20\x20\x20return\x20s\x0a\x20\x20\x20\x20\x20\x20.replace(/&/g,\x20'&')\x0a\x20\x20\x20\x20\x20\x20.replace(/\\\"/g,\x20'"')\x0a\x20\x20\x20\x20\x20\x20.replace(/\\'/g,\x20''')\x0a\x20\x20\x20\x20\x20\x20.replace(/</g,\x20'<')\x0a\x20\x20\x20\x20\x20\x20.replace(/>/g,\x20'>');\x0a\x20\x20}\x0a\x0a\x20\x20//\x20makeAnchor\x20returns\x20HTML\x20for\x20an\x20<a>\x20element,\x20given\x20an\x20anchorJSON\x20object.\x0a\x20\x20function\x20makeAnchor(json)\x20{\x0a\x20\x20\x20\x20var\x20html\x20=\x20escapeHTML(json.Text);\x0a\x20\x20\x20\x20if\x20(json.Href\x20!=\x20'')\x20{\x0a\x20\x20\x20\x20\x20\x20html\x20=\x20\"<a\x20href='\"\x20+\x20escapeHTML(json.Href)\x20+\x20\"'>\"\x20+\x20html\x20+\x20'</a>';\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20return\x20html;\x0a\x20\x20}\x0a\x0a\x20\x20function\x20showLowFrame(html)\x20{\x0a\x20\x20\x20\x20var\x20lowframe\x20=\x20document.getElementById('lowframe');\x0a\x20\x20\x20\x20lowframe.style.height\x20=\x20'200px';\x0a\x20\x20\x20\x20lowframe.innerHTML\x20=\x0a\x20\x20\x20\x20\x20\x20\"<p\x20style='text-align:\x20left;'>\"\x20+\x0a\x20\x20\x20\x20\x20\x20html\x20+\x0a\x20\x20\x20\x20\x20\x20'</p>\\n'\x20+\x0a\x20\x20\x20\x20\x20\x20\"<div\x20onclick='hideLowFrame()'\x20style='position:\x20absolute;\x20top:\x200;\x20right:\x200;\x20cursor:\x20pointer;'>\xe2\x9c\x98</div>\";\x0a\x20\x20}\x0a\x0a\x20\x20document.hideLowFrame\x20=\x20function()\x20{\x0a\x20\x20\x20\x20var\x20lowframe\x20=\x20document.getElementById('lowframe');\x0a\x20\x20\x20\x20lowframe.style.height\x20=\x20'0px';\x0a\x20\x20};\x0a\x0a\x20\x20//\x20onClickCallers\x20is\x20the\x20onclick\x20action\x20for\x20the\x20'func'\x20tokens\x20of\x20a\x0a\x20\x20//\x20function\x20declaration.\x0a\x20\x20document.onClickCallers\x20=\x20function(index)\x20{\x0a\x20\x20\x20\x20var\x20data\x20=\x20document.ANALYSIS_DATA[index];\x0a\x20\x20\x20\x20if\x20(data.Callers.length\x20==\x201\x20&&\x20data.Callers[0].Sites.length\x20==\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20document.location\x20=\x20data.Callers[0].Sites[0].Href;\x20//\x20jump\x20to\x20sole\x20caller\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20html\x20=\x0a\x20\x20\x20\x20\x20\x20'Callers\x20of\x20<code>'\x20+\x20escapeHTML(data.Callee)\x20+\x20'</code>:<br/>\\n';\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20data.Callers.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20caller\x20=\x20data.Callers[i];\x0a\x20\x20\x20\x20\x20\x20html\x20+=\x20'<code>'\x20+\x20escapeHTML(caller.Func)\x20+\x20'</code>';\x0a\x20\x20\x20\x20\x20\x20var\x20sites\x20=\x20caller.Sites;\x0a\x20\x20\x20\x20\x20\x20if\x20(sites\x20!=\x20null\x20&&\x20sites.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20'\x20at\x20line\x20';\x0a\x20\x20\x20\x20\x20\x20\x20\x20for\x20(var\x20j\x20=\x200;\x20j\x20<\x20sites.length;\x20j++)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(j\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20',\x20';\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20'<code>'\x20+\x20makeAnchor(sites[j])\x20+\x20'</code>';\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20html\x20+=\x20'<br/>\\n';\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20showLowFrame(html);\x0a\x20\x20};\x0a\x0a\x20\x20//\x20onClickCallees\x20is\x20the\x20onclick\x20action\x20for\x20the\x20'('\x20token\x20of\x20a\x20function\x20call.\x0a\x20\x20document.onClickCallees\x20=\x20function(index)\x20{\x0a\x20\x20\x20\x20var\x20data\x20=\x20document.ANALYSIS_DATA[index];\x0a\x20\x20\x20\x20if\x20(data.Callees.length\x20==\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20document.location\x20=\x20data.Callees[0].Href;\x20//\x20jump\x20to\x20sole\x20callee\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20html\x20=\x20'Callees\x20of\x20this\x20'\x20+\x20escapeHTML(data.Descr)\x20+\x20':<br/>\\n';\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20data.Callees.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20html\x20+=\x20'<code>'\x20+\x20makeAnchor(data.Callees[i])\x20+\x20'</code><br/>\\n';\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20showLowFrame(html);\x0a\x20\x20};\x0a\x0a\x20\x20//\x20onClickTypeInfo\x20is\x20the\x20onclick\x20action\x20for\x20identifiers\x20declaring\x20a\x20named\x20type.\x0a\x20\x20document.onClickTypeInfo\x20=\x20function(index)\x20{\x0a\x20\x20\x20\x20var\x20data\x20=\x20document.ANALYSIS_DATA[index];\x0a\x20\x20\x20\x20var\x20html\x20=\x0a\x20\x20\x20\x20\x20\x20'Type\x20<code>'\x20+\x0a\x20\x20\x20\x20\x20\x20data.Name\x20+\x0a\x20\x20\x20\x20\x20\x20'</code>:\x20'\x20+\x0a\x20\x20\x20\x20\x20\x20' <small>(size='\x20+\x0a\x20\x20\x20\x20\x20\x20data.Size\x20+\x0a\x20\x20\x20\x20\x20\x20',\x20align='\x20+\x0a\x20\x20\x20\x20\x20\x20data.Align\x20+\x0a\x20\x20\x20\x20\x20\x20')</small><br/>\\n';\x0a\x20\x20\x20\x20html\x20+=\x20implementsHTML(data);\x0a\x20\x20\x20\x20html\x20+=\x20methodsetHTML(data);\x0a\x20\x20\x20\x20showLowFrame(html);\x0a\x20\x20};\x0a\x0a\x20\x20//\x20implementsHTML\x20returns\x20HTML\x20for\x20the\x20implements\x20relation\x20of\x20the\x0a\x20\x20//\x20specified\x20TypeInfoJSON\x20value.\x0a\x20\x20function\x20implementsHTML(info)\x20{\x0a\x20\x20\x20\x20var\x20html\x20=\x20'';\x0a\x20\x20\x20\x20if\x20(info.ImplGroups\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20info.ImplGroups.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20group\x20=\x20info.ImplGroups[i];\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20x\x20=\x20'<code>'\x20+\x20escapeHTML(group.Descr)\x20+\x20'</code>\x20';\x0a\x20\x20\x20\x20\x20\x20\x20\x20for\x20(var\x20j\x20=\x200;\x20j\x20<\x20group.Facts.length;\x20j++)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20var\x20fact\x20=\x20group.Facts[j];\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20var\x20y\x20=\x20'<code>'\x20+\x20makeAnchor(fact.Other)\x20+\x20'</code>';\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(fact.ByKind\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20escapeHTML(fact.ByKind)\x20+\x20'\x20type\x20'\x20+\x20y\x20+\x20'\x20implements\x20'\x20+\x20x;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20x\x20+\x20'\x20implements\x20'\x20+\x20y;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20'<br/>\\n';\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20return\x20html;\x0a\x20\x20}\x0a\x0a\x20\x20//\x20methodsetHTML\x20returns\x20HTML\x20for\x20the\x20methodset\x20of\x20the\x20specified\x0a\x20\x20//\x20TypeInfoJSON\x20value.\x0a\x20\x20function\x20methodsetHTML(info)\x20{\x0a\x20\x20\x20\x20var\x20html\x20=\x20'';\x0a\x20\x20\x20\x20if\x20(info.Methods\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20info.Methods.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20html\x20+=\x20'<code>'\x20+\x20makeAnchor(info.Methods[i])\x20+\x20'</code><br/>\\n';\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20return\x20html;\x0a\x20\x20}\x0a\x0a\x20\x20//\x20onClickComm\x20is\x20the\x20onclick\x20action\x20for\x20channel\x20\"make\"\x20and\x20\"<-\"\x0a\x20\x20//\x20send/receive\x20tokens.\x0a\x20\x20document.onClickComm\x20=\x20function(index)\x20{\x0a\x20\x20\x20\x20var\x20ops\x20=\x20document.ANALYSIS_DATA[index].Ops;\x0a\x20\x20\x20\x20if\x20(ops.length\x20==\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20document.location\x20=\x20ops[0].Op.Href;\x20//\x20jump\x20to\x20sole\x20element\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20html\x20=\x20'Operations\x20on\x20this\x20channel:<br/>\\n';\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20ops.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20html\x20+=\x0a\x20\x20\x20\x20\x20\x20\x20\x20makeAnchor(ops[i].Op)\x20+\x0a\x20\x20\x20\x20\x20\x20\x20\x20'\x20by\x20<code>'\x20+\x0a\x20\x20\x20\x20\x20\x20\x20\x20escapeHTML(ops[i].Fn)\x20+\x0a\x20\x20\x20\x20\x20\x20\x20\x20'</code><br/>\\n';\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(ops.length\x20==\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20html\x20+=\x20'(none)<br/>\\n';\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20showLowFrame(html);\x0a\x20\x20};\x0a\x0a\x20\x20$(window).load(function()\x20{\x0a\x20\x20\x20\x20//\x20Scroll\x20window\x20so\x20that\x20first\x20selection\x20is\x20visible.\x0a\x20\x20\x20\x20//\x20(This\x20means\x20we\x20don't\x20need\x20to\x20emit\x20id='L%d'\x20spans\x20for\x20each\x20line.)\x0a\x20\x20\x20\x20//\x20TODO(adonovan):\x20ideally,\x20scroll\x20it\x20so\x20that\x20it's\x20under\x20the\x20pointer,\x0a\x20\x20\x20\x20//\x20but\x20I\x20don't\x20know\x20how\x20to\x20get\x20the\x20pointer\x20y\x20coordinate.\x0a\x20\x20\x20\x20var\x20elts\x20=\x20document.getElementsByClassName('selection');\x0a\x20\x20\x20\x20if\x20(elts.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20elts[0].scrollIntoView();\x0a\x20\x20\x20\x20}\x0a\x20\x20});\x0a\x0a\x20\x20//\x20setupTypeInfo\x20populates\x20the\x20\"Implements\"\x20and\x20\"Method\x20set\"\x20toggle\x20for\x0a\x20\x20//\x20each\x20type\x20in\x20the\x20package\x20doc.\x0a\x20\x20function\x20setupTypeInfo()\x20{\x0a\x20\x20\x20\x20for\x20(var\x20i\x20in\x20document.ANALYSIS_DATA)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20data\x20=\x20document.ANALYSIS_DATA[i];\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20el\x20=\x20document.getElementById('implements-'\x20+\x20i);\x0a\x20\x20\x20\x20\x20\x20if\x20(el\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20el\x20!=\x20null\x20=>\x20data\x20is\x20TypeInfoJSON.\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(data.ImplGroups\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20el.innerHTML\x20=\x20implementsHTML(data);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20el.parentNode.parentNode.style.display\x20=\x20'block';\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20el\x20=\x20document.getElementById('methodset-'\x20+\x20i);\x0a\x20\x20\x20\x20\x20\x20if\x20(el\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20el\x20!=\x20null\x20=>\x20data\x20is\x20TypeInfoJSON.\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(data.Methods\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20el.innerHTML\x20=\x20methodsetHTML(data);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20el.parentNode.parentNode.style.display\x20=\x20'block';\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20function\x20setupCallgraphs()\x20{\x0a\x20\x20\x20\x20if\x20(document.CALLGRAPH\x20==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20document.getElementById('pkg-callgraph').style.display\x20=\x20'block';\x0a\x0a\x20\x20\x20\x20var\x20treeviews\x20=\x20document.getElementsByClassName('treeview');\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20treeviews.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20tree\x20=\x20treeviews[i];\x0a\x20\x20\x20\x20\x20\x20if\x20(tree.id\x20==\x20null\x20||\x20tree.id.indexOf('callgraph-')\x20!=\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20continue;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20var\x20id\x20=\x20tree.id.substring('callgraph-'.length);\x0a\x20\x20\x20\x20\x20\x20$(tree).treeview({\x20collapsed:\x20true,\x20animated:\x20'fast'\x20});\x0a\x20\x20\x20\x20\x20\x20document.cgAddChildren(tree,\x20tree,\x20[id]);\x0a\x20\x20\x20\x20\x20\x20tree.parentNode.parentNode.style.display\x20=\x20'block';\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x0a\x0a\x20\x20document.cgAddChildren\x20=\x20function(tree,\x20ul,\x20indices)\x20{\x0a\x20\x20\x20\x20if\x20(indices\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20indices.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20li\x20=\x20cgAddChild(tree,\x20ul,\x20document.CALLGRAPH[indices[i]]);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(i\x20==\x20indices.length\x20-\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20$(li).addClass('last');\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20$(tree).treeview({\x20animated:\x20'fast',\x20add:\x20ul\x20});\x0a\x20\x20};\x0a\x0a\x20\x20//\x20cgAddChild\x20adds\x20an\x20<li>\x20element\x20for\x20document.CALLGRAPH\x20node\x20cgn\x20to\x0a\x20\x20//\x20the\x20parent\x20<ul>\x20element\x20ul.\x20tree\x20is\x20the\x20tree's\x20root\x20<ul>\x20element.\x0a\x20\x20function\x20cgAddChild(tree,\x20ul,\x20cgn)\x20{\x0a\x20\x20\x20\x20var\x20li\x20=\x20document.createElement('li');\x0a\x20\x20\x20\x20ul.appendChild(li);\x0a\x20\x20\x20\x20li.className\x20=\x20'closed';\x0a\x0a\x20\x20\x20\x20var\x20code\x20=\x20document.createElement('code');\x0a\x0a\x20\x20\x20\x20if\x20(cgn.Callees\x20!=\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20$(li).addClass('expandable');\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Event\x20handlers\x20and\x20innerHTML\x20updates\x20don't\x20play\x20nicely\x20together,\x0a\x20\x20\x20\x20\x20\x20//\x20hence\x20all\x20this\x20explicit\x20DOM\x20manipulation.\x0a\x20\x20\x20\x20\x20\x20var\x20hitarea\x20=\x20document.createElement('div');\x0a\x20\x20\x20\x20\x20\x20hitarea.className\x20=\x20'hitarea\x20expandable-hitarea';\x0a\x20\x20\x20\x20\x20\x20li.appendChild(hitarea);\x0a\x0a\x20\x20\x20\x20\x20\x20li.appendChild(code);\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20childUL\x20=\x20document.createElement('ul');\x0a\x20\x20\x20\x20\x20\x20li.appendChild(childUL);\x0a\x20\x20\x20\x20\x20\x20childUL.setAttribute('style',\x20'display:\x20none;');\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20onClick\x20=\x20function()\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20document.cgAddChildren(tree,\x20childUL,\x20cgn.Callees);\x0a\x20\x20\x20\x20\x20\x20\x20\x20hitarea.removeEventListener('click',\x20onClick);\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20hitarea.addEventListener('click',\x20onClick);\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20li.appendChild(code);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20code.innerHTML\x20+=\x20' '\x20+\x20makeAnchor(cgn.Func);\x0a\x20\x20\x20\x20return\x20li;\x0a\x20\x20}\x0a})();\x0a",
@@ -81,7 +83,7 @@
"package.html": "<!--\x0a\x09Copyright\x202009\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a\x09Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a\x09license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a-->\x0a<!--\x0a\x09Note:\x20Static\x20(i.e.,\x20not\x20template-generated)\x20href\x20and\x20id\x0a\x09attributes\x20start\x20with\x20\"pkg-\"\x20to\x20make\x20it\x20impossible\x20for\x0a\x09them\x20to\x20conflict\x20with\x20generated\x20attributes\x20(some\x20of\x20which\x0a\x09correspond\x20to\x20Go\x20identifiers).\x0a-->\x0a{{with\x20.PDoc}}\x0a\x09<script>\x0a\x09document.ANALYSIS_DATA\x20=\x20{{$.AnalysisData}};\x0a\x09document.CALLGRAPH\x20=\x20{{$.CallGraph}};\x0a\x09</script>\x0a\x0a\x09{{if\x20$.IsMain}}\x0a\x09\x09{{/*\x20command\x20documentation\x20*/}}\x0a\x09\x09{{comment_html\x20.Doc}}\x0a\x09{{else}}\x0a\x09\x09{{/*\x20package\x20documentation\x20*/}}\x0a\x09\x09<div\x20id=\"short-nav\">\x0a\x09\x09\x09<dl>\x0a\x09\x09\x09<dd><code>import\x20\"{{html\x20.ImportPath}}\"</code></dd>\x0a\x09\x09\x09</dl>\x0a\x09\x09\x09<dl>\x0a\x09\x09\x09<dd><a\x20href=\"#pkg-overview\"\x20class=\"overviewLink\">Overview</a></dd>\x0a\x09\x09\x09<dd><a\x20href=\"#pkg-index\"\x20class=\"indexLink\">Index</a></dd>\x0a\x09\x09\x09{{if\x20$.Examples}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#pkg-examples\"\x20class=\"examplesLink\">Examples</a></dd>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09{{if\x20$.Dirs}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#pkg-subdirectories\">Subdirectories</a></dd>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09</dl>\x0a\x09\x09</div>\x0a\x09\x09<!--\x20The\x20package's\x20Name\x20is\x20printed\x20as\x20title\x20by\x20the\x20top-level\x20template\x20-->\x0a\x09\x09<div\x20id=\"pkg-overview\"\x20class=\"toggleVisible\">\x0a\x09\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Overview\x20section\">Overview\x20\xe2\x96\xb9</h2>\x0a\x09\x09\x09</div>\x0a\x09\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Overview\x20section\">Overview\x20\xe2\x96\xbe</h2>\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09{{example_html\x20$\x20\"\"}}\x0a\x09\x09\x09</div>\x0a\x09\x09</div>\x0a\x0a\x09\x09<div\x20id=\"pkg-index\"\x20class=\"toggleVisible\">\x0a\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Index\x20section\">Index\x20\xe2\x96\xb9</h2>\x0a\x09\x09</div>\x0a\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Index\x20section\">Index\x20\xe2\x96\xbe</h2>\x0a\x0a\x09\x09<!--\x20Table\x20of\x20contents\x20for\x20API;\x20must\x20be\x20named\x20manual-nav\x20to\x20turn\x20off\x20auto\x20nav.\x20-->\x0a\x09\x09\x09<div\x20id=\"manual-nav\">\x0a\x09\x09\x09<dl>\x0a\x09\x09\x09{{if\x20.Consts}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#pkg-constants\">Constants</a></dd>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09{{if\x20.Vars}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#pkg-variables\">Variables</a></dd>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09{{range\x20.Funcs}}\x0a\x09\x09\x09\x09{{$name_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#{{$name_html}}\">{{node_html\x20$\x20.Decl\x20false\x20|\x20sanitize}}</a></dd>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09{{range\x20.Types}}\x0a\x09\x09\x09\x09{{$tname_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#{{$tname_html}}\">type\x20{{$tname_html}}</a></dd>\x0a\x09\x09\x09\x09{{range\x20.Funcs}}\x0a\x09\x09\x09\x09\x09{{$name_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09\x09\x09<dd> \x20 \x20<a\x20href=\"#{{$name_html}}\">{{node_html\x20$\x20.Decl\x20false\x20|\x20sanitize}}</a></dd>\x0a\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09{{range\x20.Methods}}\x0a\x09\x09\x09\x09\x09{{$name_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09\x09\x09<dd> \x20 \x20<a\x20href=\"#{{$tname_html}}.{{$name_html}}\">{{node_html\x20$\x20.Decl\x20false\x20|\x20sanitize}}</a></dd>\x0a\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09{{if\x20$.Notes}}\x0a\x09\x09\x09\x09{{range\x20$marker,\x20$item\x20:=\x20$.Notes}}\x0a\x09\x09\x09\x09<dd><a\x20href=\"#pkg-note-{{$marker}}\">{{noteTitle\x20$marker\x20|\x20html}}s</a></dd>\x0a\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09</dl>\x0a\x09\x09\x09</div><!--\x20#manual-nav\x20-->\x0a\x0a\x09\x09{{if\x20$.Examples}}\x0a\x09\x09<div\x20id=\"pkg-examples\">\x0a\x09\x09\x09<h3>Examples</h3>\x0a\x09\x09\x09<div\x20class=\"js-expandAll\x20expandAll\x20collapsed\">(Expand\x20All)</div>\x0a\x09\x09\x09<dl>\x0a\x09\x09\x09{{range\x20$.Examples}}\x0a\x09\x09\x09<dd><a\x20class=\"exampleLink\"\x20href=\"#example_{{.Name}}\">{{example_name\x20.Name}}</a></dd>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09</dl>\x0a\x09\x09</div>\x0a\x09\x09{{end}}\x0a\x0a\x09\x09{{with\x20.Filenames}}\x0a\x09\x09\x09<h3>Package\x20files</h3>\x0a\x09\x09\x09<p>\x0a\x09\x09\x09<span\x20style=\"font-size:90%\">\x0a\x09\x09\x09{{range\x20.}}\x0a\x09\x09\x09\x09<a\x20href=\"{{.|srcLink|html}}\">{{.|filename|html}}</a>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09</span>\x0a\x09\x09\x09</p>\x0a\x09\x09{{end}}\x0a\x09\x09</div><!--\x20.expanded\x20-->\x0a\x09\x09</div><!--\x20#pkg-index\x20-->\x0a\x0a\x09\x09{{if\x20ne\x20$.CallGraph\x20\"null\"}}\x0a\x09\x09<div\x20id=\"pkg-callgraph\"\x20class=\"toggle\"\x20style=\"display:\x20none\">\x0a\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Internal\x20Call\x20Graph\x20section\">Internal\x20call\x20graph\x20\xe2\x96\xb9</h2>\x0a\x09\x09</div>\x20<!--\x20.expanded\x20-->\x0a\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Internal\x20Call\x20Graph\x20section\">Internal\x20call\x20graph\x20\xe2\x96\xbe</h2>\x0a\x09\x09\x09<p>\x0a\x09\x09\x09\x20\x20In\x20the\x20call\x20graph\x20viewer\x20below,\x20each\x20node\x0a\x09\x09\x09\x20\x20is\x20a\x20function\x20belonging\x20to\x20this\x20package\x0a\x09\x09\x09\x20\x20and\x20its\x20children\x20are\x20the\x20functions\x20it\x0a\x09\x09\x09\x20\x20calls—perhaps\x20dynamically.\x0a\x09\x09\x09</p>\x0a\x09\x09\x09<p>\x0a\x09\x09\x09\x20\x20The\x20root\x20nodes\x20are\x20the\x20entry\x20points\x20of\x20the\x0a\x09\x09\x09\x20\x20package:\x20functions\x20that\x20may\x20be\x20called\x20from\x0a\x09\x09\x09\x20\x20outside\x20the\x20package.\x0a\x09\x09\x09\x20\x20There\x20may\x20be\x20non-exported\x20or\x20anonymous\x0a\x09\x09\x09\x20\x20functions\x20among\x20them\x20if\x20they\x20are\x20called\x0a\x09\x09\x09\x20\x20dynamically\x20from\x20another\x20package.\x0a\x09\x09\x09</p>\x0a\x09\x09\x09<p>\x0a\x09\x09\x09\x20\x20Click\x20a\x20node\x20to\x20visit\x20that\x20function's\x20source\x20code.\x0a\x09\x09\x09\x20\x20From\x20there\x20you\x20can\x20visit\x20its\x20callers\x20by\x0a\x09\x09\x09\x20\x20clicking\x20its\x20declaring\x20<code>func</code>\x0a\x09\x09\x09\x20\x20token.\x0a\x09\x09\x09</p>\x0a\x09\x09\x09<p>\x0a\x09\x09\x09\x20\x20Functions\x20may\x20be\x20omitted\x20if\x20they\x20were\x0a\x09\x09\x09\x20\x20determined\x20to\x20be\x20unreachable\x20in\x20the\x0a\x09\x09\x09\x20\x20particular\x20programs\x20or\x20tests\x20that\x20were\x0a\x09\x09\x09\x20\x20analyzed.\x0a\x09\x09\x09</p>\x0a\x09\x09\x09<!--\x20Zero\x20means\x20show\x20all\x20package\x20entry\x20points.\x20-->\x0a\x09\x09\x09<ul\x20style=\"margin-left:\x200.5in\"\x20id=\"callgraph-0\"\x20class=\"treeview\"></ul>\x0a\x09\x09</div>\x0a\x09\x09</div>\x20<!--\x20#pkg-callgraph\x20-->\x0a\x09\x09{{end}}\x0a\x0a\x09\x09{{with\x20.Consts}}\x0a\x09\x09\x09<h2\x20id=\"pkg-constants\">Constants</h2>\x0a\x09\x09\x09{{range\x20.}}\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09{{end}}\x0a\x09\x09{{end}}\x0a\x09\x09{{with\x20.Vars}}\x0a\x09\x09\x09<h2\x20id=\"pkg-variables\">Variables</h2>\x0a\x09\x09\x09{{range\x20.}}\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09{{end}}\x0a\x09\x09{{end}}\x0a\x09\x09{{range\x20.Funcs}}\x0a\x09\x09\x09{{/*\x20Name\x20is\x20a\x20string\x20-\x20no\x20need\x20for\x20FSet\x20*/}}\x0a\x09\x09\x09{{$name_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09<h2\x20id=\"{{$name_html}}\">func\x20<a\x20href=\"{{posLink_url\x20$\x20.Decl}}\">{{$name_html}}</a>\x0a\x09\x09\x09\x09<a\x20class=\"permalink\"\x20href=\"#{{$name_html}}\">¶</a>\x0a\x09\x09\x09\x09{{$since\x20:=\x20since\x20\"func\"\x20\"\"\x20.Name\x20$.PDoc.ImportPath}}\x0a\x09\x09\x09\x09{{if\x20$since}}<span\x20title=\"Added\x20in\x20Go\x20{{$since}}\">{{$since}}</span>{{end}}\x0a\x09\x09\x09</h2>\x0a\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09{{example_html\x20$\x20.Name}}\x0a\x09\x09\x09{{callgraph_html\x20$\x20\"\"\x20.Name}}\x0a\x0a\x09\x09{{end}}\x0a\x09\x09{{range\x20.Types}}\x0a\x09\x09\x09{{$tname\x20:=\x20.Name}}\x0a\x09\x09\x09{{$tname_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09<h2\x20id=\"{{$tname_html}}\">type\x20<a\x20href=\"{{posLink_url\x20$\x20.Decl}}\">{{$tname_html}}</a>\x0a\x09\x09\x09\x09<a\x20class=\"permalink\"\x20href=\"#{{$tname_html}}\">¶</a>\x0a\x09\x09\x09\x09{{$since\x20:=\x20since\x20\"type\"\x20\"\"\x20.Name\x20$.PDoc.ImportPath}}\x0a\x09\x09\x09\x09{{if\x20$since}}<span\x20title=\"Added\x20in\x20Go\x20{{$since}}\">{{$since}}</span>{{end}}\x0a\x09\x09\x09</h2>\x0a\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x0a\x09\x09\x09{{range\x20.Consts}}\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09{{end}}\x0a\x0a\x09\x09\x09{{range\x20.Vars}}\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09{{end}}\x0a\x0a\x09\x09\x09{{example_html\x20$\x20$tname}}\x0a\x09\x09\x09{{implements_html\x20$\x20$tname}}\x0a\x09\x09\x09{{methodset_html\x20$\x20$tname}}\x0a\x0a\x09\x09\x09{{range\x20.Funcs}}\x0a\x09\x09\x09\x09{{$name_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09\x09<h3\x20id=\"{{$name_html}}\">func\x20<a\x20href=\"{{posLink_url\x20$\x20.Decl}}\">{{$name_html}}</a>\x0a\x09\x09\x09\x09\x09<a\x20class=\"permalink\"\x20href=\"#{{$name_html}}\">¶</a>\x0a\x09\x09\x09\x09\x09{{$since\x20:=\x20since\x20\"func\"\x20\"\"\x20.Name\x20$.PDoc.ImportPath}}\x0a\x09\x09\x09\x09\x09{{if\x20$since}}<span\x20title=\"Added\x20in\x20Go\x20{{$since}}\">{{$since}}</span>{{end}}\x0a\x09\x09\x09\x09</h3>\x0a\x09\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09{{example_html\x20$\x20.Name}}\x0a\x09\x09\x09\x09{{callgraph_html\x20$\x20\"\"\x20.Name}}\x0a\x09\x09\x09{{end}}\x0a\x0a\x09\x09\x09{{range\x20.Methods}}\x0a\x09\x09\x09\x09{{$name_html\x20:=\x20html\x20.Name}}\x0a\x09\x09\x09\x09<h3\x20id=\"{{$tname_html}}.{{$name_html}}\">func\x20({{html\x20.Recv}})\x20<a\x20href=\"{{posLink_url\x20$\x20.Decl}}\">{{$name_html}}</a>\x0a\x09\x09\x09\x09\x09<a\x20class=\"permalink\"\x20href=\"#{{$tname_html}}.{{$name_html}}\">¶</a>\x0a\x09\x09\x09\x09\x09{{$since\x20:=\x20since\x20\"method\"\x20.Recv\x20.Name\x20$.PDoc.ImportPath}}\x0a\x09\x09\x09\x09\x09{{if\x20$since}}<span\x20title=\"Added\x20in\x20Go\x20{{$since}}\">{{$since}}</span>{{end}}\x0a\x09\x09\x09\x09</h3>\x0a\x09\x09\x09\x09<pre>{{node_html\x20$\x20.Decl\x20true}}</pre>\x0a\x09\x09\x09\x09{{comment_html\x20.Doc}}\x0a\x09\x09\x09\x09{{$name\x20:=\x20printf\x20\"%s_%s\"\x20$tname\x20.Name}}\x0a\x09\x09\x09\x09{{example_html\x20$\x20$name}}\x0a\x09\x09\x09\x09{{callgraph_html\x20$\x20.Recv\x20.Name}}\x0a\x09\x09\x09{{end}}\x0a\x09\x09{{end}}\x0a\x09{{end}}\x0a\x0a\x09{{with\x20$.Notes}}\x0a\x09\x09{{range\x20$marker,\x20$content\x20:=\x20.}}\x0a\x09\x09\x09<h2\x20id=\"pkg-note-{{$marker}}\">{{noteTitle\x20$marker\x20|\x20html}}s</h2>\x0a\x09\x09\x09<ul\x20style=\"list-style:\x20none;\x20padding:\x200;\">\x0a\x09\x09\x09{{range\x20.}}\x0a\x09\x09\x09<li><a\x20href=\"{{posLink_url\x20$\x20.}}\"\x20style=\"float:\x20left;\">☞</a>\x20{{comment_html\x20.Body}}</li>\x0a\x09\x09\x09{{end}}\x0a\x09\x09\x09</ul>\x0a\x09\x09{{end}}\x0a\x09{{end}}\x0a{{end}}\x0a\x0a{{with\x20.PAst}}\x0a\x09{{range\x20$filename,\x20$ast\x20:=\x20.}}\x0a\x09\x09<a\x20href=\"{{$filename|srcLink|html}}\">{{$filename|filename|html}}</a>:<pre>{{node_html\x20$\x20$ast\x20false}}</pre>\x0a\x09{{end}}\x0a{{end}}\x0a\x0a{{with\x20.Dirs}}\x0a\x09{{/*\x20DirList\x20entries\x20are\x20numbers\x20and\x20strings\x20-\x20no\x20need\x20for\x20FSet\x20*/}}\x0a\x09{{if\x20$.PDoc}}\x0a\x09\x09<h2\x20id=\"pkg-subdirectories\">Subdirectories</h2>\x0a\x09{{end}}\x0a\x09<div\x20class=\"pkg-dir\">\x0a\x09\x09<table>\x0a\x09\x09\x09<tr>\x0a\x09\x09\x09\x09<th\x20class=\"pkg-name\">Name</th>\x0a\x09\x09\x09\x09<th\x20class=\"pkg-synopsis\">Synopsis</th>\x0a\x09\x09\x09</tr>\x0a\x0a\x09\x09\x09{{if\x20not\x20(or\x20(eq\x20$.Dirname\x20\"/src/cmd\")\x20$.DirFlat)}}\x0a\x09\x09\x09<tr>\x0a\x09\x09\x09\x09<td\x20colspan=\"2\"><a\x20href=\"..\">..</a></td>\x0a\x09\x09\x09</tr>\x0a\x09\x09\x09{{end}}\x0a\x0a\x09\x09\x09{{range\x20.List}}\x0a\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09{{if\x20$.DirFlat}}\x0a\x09\x09\x09\x09\x09{{if\x20.HasPkg}}\x0a\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\">\x0a\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Path}}</a>\x0a\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09{{else}}\x0a\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\"\x20style=\"padding-left:\x20{{multiply\x20.Depth\x2020}}px;\">\x0a\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Name}}</a>\x0a\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09<td\x20class=\"pkg-synopsis\">\x0a\x09\x09\x09\x09\x09\x09{{html\x20.Synopsis}}\x0a\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09</tr>\x0a\x09\x09\x09{{end}}\x0a\x09\x09</table>\x0a\x09</div>\x0a{{end}}\x0a",
- "packageroot.html": "<!--\x0a\x09Copyright\x202018\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a\x09Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a\x09license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a-->\x0a<!--\x0a\x09Note:\x20Static\x20(i.e.,\x20not\x20template-generated)\x20href\x20and\x20id\x0a\x09attributes\x20start\x20with\x20\"pkg-\"\x20to\x20make\x20it\x20impossible\x20for\x0a\x09them\x20to\x20conflict\x20with\x20generated\x20attributes\x20(some\x20of\x20which\x0a\x09correspond\x20to\x20Go\x20identifiers).\x0a-->\x0a{{with\x20.PAst}}\x0a\x09{{range\x20$filename,\x20$ast\x20:=\x20.}}\x0a\x09\x09<a\x20href=\"{{$filename|srcLink|html}}\">{{$filename|filename|html}}</a>:<pre>{{node_html\x20$\x20$ast\x20false}}</pre>\x0a\x09{{end}}\x0a{{end}}\x0a\x0a{{with\x20.Dirs}}\x0a\x09{{/*\x20DirList\x20entries\x20are\x20numbers\x20and\x20strings\x20-\x20no\x20need\x20for\x20FSet\x20*/}}\x0a\x09{{if\x20$.PDoc}}\x0a\x09\x09<h2\x20id=\"pkg-subdirectories\">Subdirectories</h2>\x0a\x09{{end}}\x0a\x09\x09<div\x20id=\"manual-nav\">\x0a\x09\x09\x09<dl>\x0a\x09\x09\x09\x09<dt><a\x20href=\"#stdlib\">Standard\x20library</a></dt>\x0a\x09\x09\x09\x09{{if\x20hasThirdParty\x20.List\x20}}\x0a\x09\x09\x09\x09\x09<dt><a\x20href=\"#thirdparty\">Third\x20party</a></dt>\x0a\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09<dt><a\x20href=\"#other\">Other\x20packages</a></dt>\x0a\x09\x09\x09\x09<dd><a\x20href=\"#subrepo\">Sub-repositories</a></dd>\x0a\x09\x09\x09\x09<dd><a\x20href=\"#community\">Community</a></dd>\x0a\x09\x09\x09</dl>\x0a\x09\x09</div>\x0a\x0a\x09\x09<div\x20id=\"stdlib\"\x20class=\"toggleVisible\">\x0a\x09\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Standard\x20library\x20section\">Standard\x20library\x20\xe2\x96\xb9</h2>\x0a\x09\x09\x09</div>\x0a\x09\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Standard\x20library\x20section\">Standard\x20library\x20\xe2\x96\xbe</h2>\x0a\x09\x09\x09\x09<img\x20alt=\"\"\x20class=\"gopher\"\x20src=\"/doc/gopher/pkg.png\"/>\x0a\x09\x09\x09\x09<div\x20class=\"pkg-dir\">\x0a\x09\x09\x09\x09\x09<table>\x0a\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-name\">Name</th>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-synopsis\">Synopsis</th>\x0a\x09\x09\x09\x09\x09\x09</tr>\x0a\x0a\x09\x09\x09\x09\x09\x09{{range\x20.List}}\x0a\x09\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09{{if\x20eq\x20.RootType\x20\"GOROOT\"}}\x0a\x09\x09\x09\x09\x09\x09\x09{{if\x20$.DirFlat}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20.HasPkg}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Path}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09{{else}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\"\x20style=\"padding-left:\x20{{multiply\x20.Depth\x2020}}px;\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Name}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-synopsis\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09{{html\x20.Synopsis}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09</tr>\x0a\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09</table>\x0a\x09\x09\x09\x09</div>\x20<!--\x20.pkg-dir\x20-->\x0a\x09\x09\x09</div>\x20<!--\x20.expanded\x20-->\x0a\x09\x09</div>\x20<!--\x20#stdlib\x20.toggleVisible\x20-->\x0a\x0a\x09{{if\x20hasThirdParty\x20.List\x20}}\x0a\x09\x09<div\x20id=\"thirdparty\"\x20class=\"toggleVisible\">\x0a\x09\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Third\x20party\x20section\">Third\x20party\x20\xe2\x96\xb9</h2>\x0a\x09\x09\x09</div>\x0a\x09\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Third\x20party\x20section\">Third\x20party\x20\xe2\x96\xbe</h2>\x0a\x09\x09\x09\x09<div\x20class=\"pkg-dir\">\x0a\x09\x09\x09\x09\x09<table>\x0a\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-name\">Name</th>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-synopsis\">Synopsis</th>\x0a\x09\x09\x09\x09\x09\x09</tr>\x0a\x0a\x09\x09\x09\x09\x09\x09{{range\x20.List}}\x0a\x09\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20eq\x20.RootType\x20\"GOPATH\"}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20$.DirFlat}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20.HasPkg}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Path}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{else}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\"\x20style=\"padding-left:\x20{{multiply\x20.Depth\x2020}}px;\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Name}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-synopsis\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09{{html\x20.Synopsis}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09</tr>\x0a\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09</table>\x0a\x09\x09\x09\x09</div>\x20<!--\x20.pkg-dir\x20-->\x0a\x09\x09\x09</div>\x20<!--\x20.expanded\x20-->\x0a\x09\x09</div>\x20<!--\x20#stdlib\x20.toggleVisible\x20-->\x0a\x09{{end}}\x0a\x0a\x09<h2\x20id=\"other\">Other\x20packages</h2>\x0a\x09<h3\x20id=\"subrepo\">Sub-repositories</h3>\x0a\x09<p>\x0a\x09These\x20packages\x20are\x20part\x20of\x20the\x20Go\x20Project\x20but\x20outside\x20the\x20main\x20Go\x20tree.\x0a\x09They\x20are\x20developed\x20under\x20looser\x20<a\x20href=\"/doc/go1compat\">compatibility\x20requirements</a>\x20than\x20the\x20Go\x20core.\x0a\x09Install\x20them\x20with\x20\"<a\x20href=\"/cmd/go/#hdr-Download_and_install_packages_and_dependencies\">go\x20get</a>\".\x0a\x09</p>\x0a\x09<ul>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/benchmarks\">benchmarks</a>\x20\xe2\x80\x94\x20benchmarks\x20to\x20measure\x20Go\x20as\x20it\x20is\x20developed.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/blog\">blog</a>\x20\xe2\x80\x94\x20<a\x20href=\"//blog.golang.org\">blog.golang.org</a>'s\x20implementation.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/build\">build</a>\x20\xe2\x80\x94\x20<a\x20href=\"//build.golang.org\">build.golang.org</a>'s\x20implementation.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/crypto\">crypto</a>\x20\xe2\x80\x94\x20additional\x20cryptography\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/debug\">debug</a>\x20\xe2\x80\x94\x20an\x20experimental\x20debugger\x20for\x20Go.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/image\">image</a>\x20\xe2\x80\x94\x20additional\x20imaging\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/mobile\">mobile</a>\x20\xe2\x80\x94\x20experimental\x20support\x20for\x20Go\x20on\x20mobile\x20platforms.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/net\">net</a>\x20\xe2\x80\x94\x20additional\x20networking\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/perf\">perf</a>\x20\xe2\x80\x94\x20packages\x20and\x20tools\x20for\x20performance\x20measurement,\x20storage,\x20and\x20analysis.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/pkgsite\">pkgsite</a>\x20\xe2\x80\x94\x20home\x20of\x20the\x20pkg.go.dev\x20website.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/review\">review</a>\x20\xe2\x80\x94\x20a\x20tool\x20for\x20working\x20with\x20Gerrit\x20code\x20reviews.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/sync\">sync</a>\x20\xe2\x80\x94\x20additional\x20concurrency\x20primitives.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/sys\">sys</a>\x20\xe2\x80\x94\x20packages\x20for\x20making\x20system\x20calls.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/text\">text</a>\x20\xe2\x80\x94\x20packages\x20for\x20working\x20with\x20text.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/time\">time</a>\x20\xe2\x80\x94\x20additional\x20time\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/tools\">tools</a>\x20\xe2\x80\x94\x20godoc,\x20goimports,\x20gorename,\x20and\x20other\x20tools.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/tour\">tour</a>\x20\xe2\x80\x94\x20<a\x20href=\"//tour.golang.org\">tour.golang.org</a>'s\x20implementation.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/exp\">exp</a>\x20\xe2\x80\x94\x20experimental\x20and\x20deprecated\x20packages\x20(handle\x20with\x20care;\x20may\x20change\x20without\x20warning).</li>\x0a\x09</ul>\x0a\x0a\x09<h3\x20id=\"community\">Community</h3>\x0a\x09<p>\x0a\x09These\x20services\x20can\x20help\x20you\x20find\x20Open\x20Source\x20packages\x20provided\x20by\x20the\x20community.\x0a\x09</p>\x0a\x09<ul>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev\">Pkg.go.dev</a>\x20-\x20the\x20Go\x20package\x20discovery\x20site.</li>\x0a\x09\x09<li><a\x20href=\"/wiki/Projects\">Projects\x20at\x20the\x20Go\x20Wiki</a>\x20-\x20a\x20curated\x20list\x20of\x20Go\x20projects.</li>\x0a\x09</ul>\x0a{{end}}\x0a",
+ "packageroot.html": "<!--\x0a\x09Copyright\x202018\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a\x09Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a\x09license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a-->\x0a<!--\x0a\x09Note:\x20Static\x20(i.e.,\x20not\x20template-generated)\x20href\x20and\x20id\x0a\x09attributes\x20start\x20with\x20\"pkg-\"\x20to\x20make\x20it\x20impossible\x20for\x0a\x09them\x20to\x20conflict\x20with\x20generated\x20attributes\x20(some\x20of\x20which\x0a\x09correspond\x20to\x20Go\x20identifiers).\x0a-->\x0a{{with\x20.PAst}}\x0a\x09{{range\x20$filename,\x20$ast\x20:=\x20.}}\x0a\x09\x09<a\x20href=\"{{$filename|srcLink|html}}\">{{$filename|filename|html}}</a>:<pre>{{node_html\x20$\x20$ast\x20false}}</pre>\x0a\x09{{end}}\x0a{{end}}\x0a\x0a{{with\x20.Dirs}}\x0a\x09{{/*\x20DirList\x20entries\x20are\x20numbers\x20and\x20strings\x20-\x20no\x20need\x20for\x20FSet\x20*/}}\x0a\x09{{if\x20$.PDoc}}\x0a\x09\x09<h2\x20id=\"pkg-subdirectories\">Subdirectories</h2>\x0a\x09{{end}}\x0a\x09\x09<div\x20id=\"manual-nav\">\x0a\x09\x09\x09<dl>\x0a\x09\x09\x09\x09<dt><a\x20href=\"#stdlib\">Standard\x20library</a></dt>\x0a\x09\x09\x09\x09{{if\x20hasThirdParty\x20.List\x20}}\x0a\x09\x09\x09\x09\x09<dt><a\x20href=\"#thirdparty\">Third\x20party</a></dt>\x0a\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09<dt><a\x20href=\"#other\">Other\x20packages</a></dt>\x0a\x09\x09\x09\x09<dd><a\x20href=\"#subrepo\">Sub-repositories</a></dd>\x0a\x09\x09\x09\x09<dd><a\x20href=\"#community\">Community</a></dd>\x0a\x09\x09\x09</dl>\x0a\x09\x09</div>\x0a\x0a\x09\x09<div\x20id=\"stdlib\"\x20class=\"toggleVisible\">\x0a\x09\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Standard\x20library\x20section\">Standard\x20library\x20\xe2\x96\xb9</h2>\x0a\x09\x09\x09</div>\x0a\x09\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Standard\x20library\x20section\">Standard\x20library\x20\xe2\x96\xbe</h2>\x0a\x09\x09\x09\x09<img\x20alt=\"\"\x20class=\"gopher\"\x20src=\"https://golang.org/doc/gopher/pkg.png\"/>\x0a\x09\x09\x09\x09<div\x20class=\"pkg-dir\">\x0a\x09\x09\x09\x09\x09<table>\x0a\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-name\">Name</th>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-synopsis\">Synopsis</th>\x0a\x09\x09\x09\x09\x09\x09</tr>\x0a\x0a\x09\x09\x09\x09\x09\x09{{range\x20.List}}\x0a\x09\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09{{if\x20eq\x20.RootType\x20\"GOROOT\"}}\x0a\x09\x09\x09\x09\x09\x09\x09{{if\x20$.DirFlat}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20.HasPkg}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Path}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09{{else}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\"\x20style=\"padding-left:\x20{{multiply\x20.Depth\x2020}}px;\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Name}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-synopsis\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09{{html\x20.Synopsis}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09</tr>\x0a\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09</table>\x0a\x09\x09\x09\x09</div>\x20<!--\x20.pkg-dir\x20-->\x0a\x09\x09\x09</div>\x20<!--\x20.expanded\x20-->\x0a\x09\x09</div>\x20<!--\x20#stdlib\x20.toggleVisible\x20-->\x0a\x0a\x09{{if\x20hasThirdParty\x20.List\x20}}\x0a\x09\x09<div\x20id=\"thirdparty\"\x20class=\"toggleVisible\">\x0a\x09\x09\x09<div\x20class=\"collapsed\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20show\x20Third\x20party\x20section\">Third\x20party\x20\xe2\x96\xb9</h2>\x0a\x09\x09\x09</div>\x0a\x09\x09\x09<div\x20class=\"expanded\">\x0a\x09\x09\x09\x09<h2\x20class=\"toggleButton\"\x20title=\"Click\x20to\x20hide\x20Third\x20party\x20section\">Third\x20party\x20\xe2\x96\xbe</h2>\x0a\x09\x09\x09\x09<div\x20class=\"pkg-dir\">\x0a\x09\x09\x09\x09\x09<table>\x0a\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-name\">Name</th>\x0a\x09\x09\x09\x09\x09\x09\x09<th\x20class=\"pkg-synopsis\">Synopsis</th>\x0a\x09\x09\x09\x09\x09\x09</tr>\x0a\x0a\x09\x09\x09\x09\x09\x09{{range\x20.List}}\x0a\x09\x09\x09\x09\x09\x09\x09<tr>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20eq\x20.RootType\x20\"GOPATH\"}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20$.DirFlat}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09{{if\x20.HasPkg}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Path}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{else}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-name\"\x20style=\"padding-left:\x20{{multiply\x20.Depth\x2020}}px;\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09<a\x20href=\"{{html\x20.Path}}/{{modeQueryString\x20$.Mode\x20|\x20html}}\">{{html\x20.Name}}</a>\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09<td\x20class=\"pkg-synopsis\">\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09\x09{{html\x20.Synopsis}}\x0a\x09\x09\x09\x09\x09\x09\x09\x09\x09</td>\x0a\x09\x09\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09\x09\x09</tr>\x0a\x09\x09\x09\x09\x09\x09{{end}}\x0a\x09\x09\x09\x09\x09</table>\x0a\x09\x09\x09\x09</div>\x20<!--\x20.pkg-dir\x20-->\x0a\x09\x09\x09</div>\x20<!--\x20.expanded\x20-->\x0a\x09\x09</div>\x20<!--\x20#stdlib\x20.toggleVisible\x20-->\x0a\x09{{end}}\x0a\x0a\x09<h2\x20id=\"other\">Other\x20packages</h2>\x0a\x09<h3\x20id=\"subrepo\">Sub-repositories</h3>\x0a\x09<p>\x0a\x09These\x20packages\x20are\x20part\x20of\x20the\x20Go\x20Project\x20but\x20outside\x20the\x20main\x20Go\x20tree.\x0a\x09They\x20are\x20developed\x20under\x20looser\x20<a\x20href=\"https://golang.org/doc/go1compat\">compatibility\x20requirements</a>\x20than\x20the\x20Go\x20core.\x0a\x09Install\x20them\x20with\x20\"<a\x20href=\"/cmd/go/#hdr-Download_and_install_packages_and_dependencies\">go\x20get</a>\".\x0a\x09</p>\x0a\x09<ul>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/benchmarks\">benchmarks</a>\x20\xe2\x80\x94\x20benchmarks\x20to\x20measure\x20Go\x20as\x20it\x20is\x20developed.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/blog\">blog</a>\x20\xe2\x80\x94\x20<a\x20href=\"//blog.golang.org\">blog.golang.org</a>'s\x20implementation.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/build\">build</a>\x20\xe2\x80\x94\x20<a\x20href=\"//build.golang.org\">build.golang.org</a>'s\x20implementation.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/crypto\">crypto</a>\x20\xe2\x80\x94\x20additional\x20cryptography\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/debug\">debug</a>\x20\xe2\x80\x94\x20an\x20experimental\x20debugger\x20for\x20Go.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/image\">image</a>\x20\xe2\x80\x94\x20additional\x20imaging\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/mobile\">mobile</a>\x20\xe2\x80\x94\x20experimental\x20support\x20for\x20Go\x20on\x20mobile\x20platforms.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/net\">net</a>\x20\xe2\x80\x94\x20additional\x20networking\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/perf\">perf</a>\x20\xe2\x80\x94\x20packages\x20and\x20tools\x20for\x20performance\x20measurement,\x20storage,\x20and\x20analysis.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/pkgsite\">pkgsite</a>\x20\xe2\x80\x94\x20home\x20of\x20the\x20pkg.go.dev\x20website.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/review\">review</a>\x20\xe2\x80\x94\x20a\x20tool\x20for\x20working\x20with\x20Gerrit\x20code\x20reviews.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/sync\">sync</a>\x20\xe2\x80\x94\x20additional\x20concurrency\x20primitives.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/sys\">sys</a>\x20\xe2\x80\x94\x20packages\x20for\x20making\x20system\x20calls.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/text\">text</a>\x20\xe2\x80\x94\x20packages\x20for\x20working\x20with\x20text.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/time\">time</a>\x20\xe2\x80\x94\x20additional\x20time\x20packages.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/tools\">tools</a>\x20\xe2\x80\x94\x20godoc,\x20goimports,\x20gorename,\x20and\x20other\x20tools.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/tour\">tour</a>\x20\xe2\x80\x94\x20<a\x20href=\"//tour.golang.org\">tour.golang.org</a>'s\x20implementation.</li>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev/golang.org/x/exp\">exp</a>\x20\xe2\x80\x94\x20experimental\x20and\x20deprecated\x20packages\x20(handle\x20with\x20care;\x20may\x20change\x20without\x20warning).</li>\x0a\x09</ul>\x0a\x0a\x09<h3\x20id=\"community\">Community</h3>\x0a\x09<p>\x0a\x09These\x20services\x20can\x20help\x20you\x20find\x20Open\x20Source\x20packages\x20provided\x20by\x20the\x20community.\x0a\x09</p>\x0a\x09<ul>\x0a\x09\x09<li><a\x20href=\"//pkg.go.dev\">Pkg.go.dev</a>\x20-\x20the\x20Go\x20package\x20discovery\x20site.</li>\x0a\x09\x09<li><a\x20href=\"/wiki/Projects\">Projects\x20at\x20the\x20Go\x20Wiki</a>\x20-\x20a\x20curated\x20list\x20of\x20Go\x20projects.</li>\x0a\x09</ul>\x0a{{end}}\x0a",
"play.js": "//\x20Copyright\x202012\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a//\x20Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a//\x20license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a\x0afunction\x20initPlayground(transport)\x20{\x0a\x20\x20'use\x20strict';\x0a\x0a\x20\x20function\x20text(node)\x20{\x0a\x20\x20\x20\x20var\x20s\x20=\x20'';\x0a\x20\x20\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20node.childNodes.length;\x20i++)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20n\x20=\x20node.childNodes[i];\x0a\x20\x20\x20\x20\x20\x20if\x20(n.nodeType\x20===\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(n.tagName\x20===\x20'BUTTON')\x20continue;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(n.tagName\x20===\x20'SPAN'\x20&&\x20n.className\x20===\x20'number')\x20continue;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(n.tagName\x20===\x20'DIV'\x20||\x20n.tagName\x20===\x20'BR'\x20||\x20n.tagName\x20===\x20'PRE')\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20s\x20+=\x20'\\n';\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20s\x20+=\x20text(n);\x0a\x20\x20\x20\x20\x20\x20\x20\x20continue;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20if\x20(n.nodeType\x20===\x203)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20s\x20+=\x20n.nodeValue;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20return\x20s.replace('\\xA0',\x20'\x20');\x20//\x20replace\x20non-breaking\x20spaces\x0a\x20\x20}\x0a\x0a\x20\x20//\x20When\x20presenter\x20notes\x20are\x20enabled,\x20the\x20index\x20passed\x0a\x20\x20//\x20here\x20will\x20identify\x20the\x20playground\x20to\x20be\x20synced\x0a\x20\x20function\x20init(code,\x20index)\x20{\x0a\x20\x20\x20\x20var\x20output\x20=\x20document.createElement('div');\x0a\x20\x20\x20\x20var\x20outpre\x20=\x20document.createElement('pre');\x0a\x20\x20\x20\x20var\x20running;\x0a\x0a\x20\x20\x20\x20if\x20($\x20&&\x20$(output).resizable)\x20{\x0a\x20\x20\x20\x20\x20\x20$(output).resizable({\x0a\x20\x20\x20\x20\x20\x20\x20\x20handles:\x20'n,w,nw',\x0a\x20\x20\x20\x20\x20\x20\x20\x20minHeight:\x2027,\x0a\x20\x20\x20\x20\x20\x20\x20\x20minWidth:\x20135,\x0a\x20\x20\x20\x20\x20\x20\x20\x20maxHeight:\x20608,\x0a\x20\x20\x20\x20\x20\x20\x20\x20maxWidth:\x20990,\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20function\x20onKill()\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(running)\x20running.Kill();\x0a\x20\x20\x20\x20\x20\x20if\x20(window.notesEnabled)\x20updatePlayStorage('onKill',\x20index);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20function\x20onRun(e)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20sk\x20=\x20e.shiftKey\x20||\x20localStorage.getItem('play-shiftKey')\x20===\x20'true';\x0a\x20\x20\x20\x20\x20\x20if\x20(running)\x20running.Kill();\x0a\x20\x20\x20\x20\x20\x20output.style.display\x20=\x20'block';\x0a\x20\x20\x20\x20\x20\x20outpre.textContent\x20=\x20'';\x0a\x20\x20\x20\x20\x20\x20run1.style.display\x20=\x20'none';\x0a\x20\x20\x20\x20\x20\x20var\x20options\x20=\x20{\x20Race:\x20sk\x20};\x0a\x20\x20\x20\x20\x20\x20running\x20=\x20transport.Run(text(code),\x20PlaygroundOutput(outpre),\x20options);\x0a\x20\x20\x20\x20\x20\x20if\x20(window.notesEnabled)\x20updatePlayStorage('onRun',\x20index,\x20e);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20function\x20onClose()\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(running)\x20running.Kill();\x0a\x20\x20\x20\x20\x20\x20output.style.display\x20=\x20'none';\x0a\x20\x20\x20\x20\x20\x20run1.style.display\x20=\x20'inline-block';\x0a\x20\x20\x20\x20\x20\x20if\x20(window.notesEnabled)\x20updatePlayStorage('onClose',\x20index);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(window.notesEnabled)\x20{\x0a\x20\x20\x20\x20\x20\x20playgroundHandlers.onRun.push(onRun);\x0a\x20\x20\x20\x20\x20\x20playgroundHandlers.onClose.push(onClose);\x0a\x20\x20\x20\x20\x20\x20playgroundHandlers.onKill.push(onKill);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20run1\x20=\x20document.createElement('button');\x0a\x20\x20\x20\x20run1.textContent\x20=\x20'Run';\x0a\x20\x20\x20\x20run1.className\x20=\x20'run';\x0a\x20\x20\x20\x20run1.addEventListener('click',\x20onRun,\x20false);\x0a\x20\x20\x20\x20var\x20run2\x20=\x20document.createElement('button');\x0a\x20\x20\x20\x20run2.className\x20=\x20'run';\x0a\x20\x20\x20\x20run2.textContent\x20=\x20'Run';\x0a\x20\x20\x20\x20run2.addEventListener('click',\x20onRun,\x20false);\x0a\x20\x20\x20\x20var\x20kill\x20=\x20document.createElement('button');\x0a\x20\x20\x20\x20kill.className\x20=\x20'kill';\x0a\x20\x20\x20\x20kill.textContent\x20=\x20'Kill';\x0a\x20\x20\x20\x20kill.addEventListener('click',\x20onKill,\x20false);\x0a\x20\x20\x20\x20var\x20close\x20=\x20document.createElement('button');\x0a\x20\x20\x20\x20close.className\x20=\x20'close';\x0a\x20\x20\x20\x20close.textContent\x20=\x20'Close';\x0a\x20\x20\x20\x20close.addEventListener('click',\x20onClose,\x20false);\x0a\x0a\x20\x20\x20\x20var\x20button\x20=\x20document.createElement('div');\x0a\x20\x20\x20\x20button.classList.add('buttons');\x0a\x20\x20\x20\x20button.appendChild(run1);\x0a\x20\x20\x20\x20//\x20Hack\x20to\x20simulate\x20insertAfter\x0a\x20\x20\x20\x20code.parentNode.insertBefore(button,\x20code.nextSibling);\x0a\x0a\x20\x20\x20\x20var\x20buttons\x20=\x20document.createElement('div');\x0a\x20\x20\x20\x20buttons.classList.add('buttons');\x0a\x20\x20\x20\x20buttons.appendChild(run2);\x0a\x20\x20\x20\x20buttons.appendChild(kill);\x0a\x20\x20\x20\x20buttons.appendChild(close);\x0a\x0a\x20\x20\x20\x20output.classList.add('output');\x0a\x20\x20\x20\x20output.appendChild(buttons);\x0a\x20\x20\x20\x20output.appendChild(outpre);\x0a\x20\x20\x20\x20output.style.display\x20=\x20'none';\x0a\x20\x20\x20\x20code.parentNode.insertBefore(output,\x20button.nextSibling);\x0a\x20\x20}\x0a\x0a\x20\x20var\x20play\x20=\x20document.querySelectorAll('div.playground');\x0a\x20\x20for\x20(var\x20i\x20=\x200;\x20i\x20<\x20play.length;\x20i++)\x20{\x0a\x20\x20\x20\x20init(play[i],\x20i);\x0a\x20\x20}\x0a}\x0a",
@@ -95,5 +97,5 @@
"searchtxt.html": "<!--\x0a\x09Copyright\x202009\x20The\x20Go\x20Authors.\x20All\x20rights\x20reserved.\x0a\x09Use\x20of\x20this\x20source\x20code\x20is\x20governed\x20by\x20a\x20BSD-style\x0a\x09license\x20that\x20can\x20be\x20found\x20in\x20the\x20LICENSE\x20file.\x0a-->\x0a{{$query_url\x20:=\x20urlquery\x20.Query}}\x0a{{with\x20.Textual}}\x0a\x09{{if\x20$.Complete}}\x0a\x09\x09<h2\x20id=\"Textual\">{{html\x20$.Found}}\x20textual\x20occurrences</h2>\x0a\x09{{else}}\x0a\x09\x09<h2\x20id=\"Textual\">More\x20than\x20{{html\x20$.Found}}\x20textual\x20occurrences</h2>\x0a\x09\x09<p>\x0a\x09\x09<span\x20class=\"alert\"\x20style=\"font-size:120%\">Not\x20all\x20files\x20or\x20lines\x20containing\x20\"{{html\x20$.Query}}\"\x20are\x20shown.</span>\x0a\x09\x09</p>\x0a\x09{{end}}\x0a\x09<p>\x0a\x09<table\x20class=\"layout\">\x0a\x09{{range\x20.}}\x0a\x09\x09{{$file\x20:=\x20.Filename}}\x0a\x09\x09<tr>\x0a\x09\x09<td\x20align=\"left\"\x20valign=\"top\">\x0a\x09\x09<a\x20href=\"{{queryLink\x20$file\x20$query_url\x200}}\">{{$file}}</a>:\x0a\x09\x09</td>\x0a\x09\x09<td\x20align=\"left\"\x20width=\"4\"></td>\x0a\x09\x09<th\x20align=\"left\"\x20valign=\"top\">{{len\x20.Lines}}</th>\x0a\x09\x09<td\x20align=\"left\"\x20width=\"4\"></td>\x0a\x09\x09<td\x20align=\"left\">\x0a\x09\x09{{range\x20.Lines}}\x0a\x09\x09\x09<a\x20href=\"{{queryLink\x20$file\x20$query_url\x20.}}\">{{html\x20.}}</a>\x0a\x09\x09{{end}}\x0a\x09\x09{{if\x20not\x20$.Complete}}\x0a\x09\x09\x09...\x0a\x09\x09{{end}}\x0a\x09\x09</td>\x0a\x09\x09</tr>\x0a\x09{{end}}\x0a\x09{{if\x20not\x20$.Complete}}\x0a\x09\x09<tr><td\x20align=\"left\">...</td></tr>\x0a\x09{{end}}\x0a\x09</table>\x0a\x09</p>\x0a{{end}}\x0a",
- "style.css": "body\x20{\x0a\x20\x20margin:\x200;\x0a\x20\x20font-family:\x20Arial,\x20sans-serif;\x0a\x20\x20background-color:\x20#fff;\x0a\x20\x20line-height:\x201.3;\x0a\x20\x20text-align:\x20center;\x0a\x20\x20color:\x20#222;\x0a}\x0atextarea\x20{\x0a\x20\x20/*\x20Inherit\x20text\x20color\x20from\x20body\x20avoiding\x20illegible\x20text\x20in\x20the\x20case\x20where\x20the\x0a\x20\x09*\x20user\x20has\x20inverted\x20the\x20browsers\x20custom\x20text\x20and\x20background\x20colors.\x20*/\x0a\x20\x20color:\x20inherit;\x0a}\x0apre,\x0acode\x20{\x0a\x20\x20font-family:\x20Menlo,\x20monospace;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0apre\x20{\x0a\x20\x20line-height:\x201.4;\x0a\x20\x20overflow-x:\x20auto;\x0a}\x0apre\x20.comment\x20{\x0a\x20\x20color:\x20#006600;\x0a}\x0apre\x20.highlight,\x0apre\x20.highlight-comment,\x0apre\x20.selection-highlight,\x0apre\x20.selection-highlight-comment\x20{\x0a\x20\x20background:\x20#ffff00;\x0a}\x0apre\x20.selection,\x0apre\x20.selection-comment\x20{\x0a\x20\x20background:\x20#ff9632;\x0a}\x0apre\x20.ln\x20{\x0a\x20\x20color:\x20#999;\x0a\x20\x20background:\x20#efefef;\x0a}\x0a.ln\x20{\x0a\x20\x20-webkit-user-select:\x20none;\x0a\x20\x20-moz-user-select:\x20none;\x0a\x20\x20-ms-user-select:\x20none;\x0a\x20\x20user-select:\x20none;\x0a\x0a\x20\x20/*\x20Ensure\x208\x20characters\x20in\x20the\x20document\x20-\x20which\x20due\x20to\x20floating\x0a\x20\x20\x20*\x20point\x20rendering\x20issues,\x20might\x20have\x20a\x20width\x20of\x20less\x20than\x201\x20each\x20-\x20are\x208\x0a\x20\x20\x20*\x20characters\x20wide,\x20so\x20a\x20tab\x20in\x20the\x209th\x20position\x20indents\x20properly.\x20See\x0a\x20\x20\x20*\x20https://github.com/webcompat/web-bugs/issues/17530#issuecomment-402675091\x0a\x20\x20\x20*\x20for\x20more\x20information.\x20*/\x0a\x20\x20display:\x20inline-block;\x0a\x20\x20width:\x208ch;\x0a}\x0a\x0a.search-nav\x20{\x0a\x20\x20margin-left:\x201.25rem;\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20column-gap:\x201.25rem;\x0a\x20\x20column-fill:\x20auto;\x0a\x20\x20column-width:\x2014rem;\x0a}\x0a\x0a.search-nav\x20.indent\x20{\x0a\x20\x20margin-left:\x201.25rem;\x0a}\x0a\x0aa,\x0a.exampleHeading\x20.text,\x0a.expandAll\x20{\x0a\x20\x20color:\x20#375eab;\x0a\x20\x20text-decoration:\x20none;\x0a}\x0aa:hover,\x0a.exampleHeading\x20.text:hover,\x0a.expandAll:hover\x20{\x0a\x20\x20text-decoration:\x20underline;\x0a}\x0a.article\x20a\x20{\x0a\x20\x20text-decoration:\x20underline;\x0a}\x0a.article\x20.title\x20a\x20{\x0a\x20\x20text-decoration:\x20none;\x0a}\x0a\x0a.permalink\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a:hover\x20>\x20.permalink\x20{\x0a\x20\x20display:\x20inline;\x0a}\x0a\x0ap,\x0ali\x20{\x0a\x20\x20max-width:\x2050rem;\x0a\x20\x20word-wrap:\x20break-word;\x0a}\x0ap,\x0apre,\x0aul,\x0aol\x20{\x0a\x20\x20margin:\x201.25rem;\x0a}\x0apre\x20{\x0a\x20\x20background:\x20#efefef;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0a\x0ah1,\x0ah2,\x0ah3,\x0ah4,\x0a.rootHeading\x20{\x0a\x20\x20margin:\x201.25rem\x200\x201.25rem;\x0a\x20\x20padding:\x200;\x0a\x20\x20color:\x20#375eab;\x0a\x20\x20font-weight:\x20bold;\x0a}\x0ah1\x20{\x0a\x20\x20font-size:\x201.75rem;\x0a\x20\x20line-height:\x201;\x0a}\x0ah1\x20.text-muted\x20{\x0a\x20\x20color:\x20#777;\x0a}\x0ah2\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20background:\x20#e0ebf5;\x0a\x20\x20padding:\x200.5rem;\x0a\x20\x20line-height:\x201.25;\x0a\x20\x20font-weight:\x20normal;\x0a\x20\x20overflow:\x20auto;\x0a\x20\x20overflow-wrap:\x20break-word;\x0a}\x0ah2\x20a\x20{\x0a\x20\x20font-weight:\x20bold;\x0a}\x0ah3\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20line-height:\x201.25;\x0a\x20\x20overflow:\x20auto;\x0a\x20\x20overflow-wrap:\x20break-word;\x0a}\x0ah3,\x0ah4\x20{\x0a\x20\x20margin:\x201.25rem\x200.3125rem;\x0a}\x0ah4\x20{\x0a\x20\x20font-size:\x201rem;\x0a}\x0a.rootHeading\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20margin:\x200;\x0a}\x0a\x0ah2\x20>\x20span,\x0ah3\x20>\x20span\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20margin:\x200\x2025px\x200\x200;\x0a\x20\x20font-weight:\x20normal;\x0a\x20\x20color:\x20#5279c7;\x0a}\x0a\x0adl\x20{\x0a\x20\x20margin:\x201.25rem;\x0a}\x0add\x20{\x0a\x20\x20margin:\x200\x200\x200\x201.25rem;\x0a}\x0adl,\x0add\x20{\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv#nav\x20table\x20td\x20{\x0a\x20\x20vertical-align:\x20top;\x0a}\x0a\x0a#pkg-index\x20h3\x20{\x0a\x20\x20font-size:\x201rem;\x0a}\x0a.pkg-dir\x20{\x0a\x20\x20padding:\x200\x200.625rem;\x0a}\x0a.pkg-dir\x20table\x20{\x0a\x20\x20border-collapse:\x20collapse;\x0a\x20\x20border-spacing:\x200;\x0a}\x0a.pkg-name\x20{\x0a\x20\x20padding-right:\x200.625rem;\x0a}\x0a.alert\x20{\x0a\x20\x20color:\x20#aa0000;\x0a}\x0a\x0a.top-heading\x20{\x0a\x20\x20float:\x20left;\x0a\x20\x20padding:\x201.313rem\x200;\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20font-weight:\x20normal;\x0a}\x0a.top-heading\x20a\x20{\x0a\x20\x20color:\x20#222;\x0a\x20\x20text-decoration:\x20none;\x0a}\x0a\x0a#pkg-examples\x20h3\x20{\x0a\x20\x20float:\x20left;\x0a}\x0a\x0a#pkg-examples\x20dl\x20{\x0a\x20\x20clear:\x20both;\x0a}\x0a\x0a.expandAll\x20{\x0a\x20\x20cursor:\x20pointer;\x0a\x20\x20float:\x20left;\x0a\x20\x20margin:\x201.25rem\x200;\x0a}\x0a\x0adiv#topbar\x20{\x0a\x20\x20background:\x20#e0ebf5;\x0a\x20\x20height:\x204rem;\x0a\x20\x20overflow:\x20hidden;\x0a}\x0a\x0adiv#page\x20{\x0a\x20\x20width:\x20100%;\x0a}\x0adiv#page\x20>\x20.container,\x0adiv#topbar\x20>\x20.container\x20{\x0a\x20\x20text-align:\x20left;\x0a\x20\x20margin-left:\x20auto;\x0a\x20\x20margin-right:\x20auto;\x0a\x20\x20padding:\x200\x201.25rem;\x0a}\x0adiv#topbar\x20>\x20.container,\x0adiv#page\x20>\x20.container\x20{\x0a\x20\x20max-width:\x2059.38rem;\x0a}\x0adiv#page.wide\x20>\x20.container,\x0adiv#topbar.wide\x20>\x20.container\x20{\x0a\x20\x20max-width:\x20none;\x0a}\x0adiv#plusone\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20clear:\x20right;\x0a\x20\x20margin-top:\x200.3125rem;\x0a}\x0a\x0adiv#footer\x20{\x0a\x20\x20text-align:\x20center;\x0a\x20\x20color:\x20#666;\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20margin:\x202.5rem\x200;\x0a}\x0a\x0adiv#menu\x20>\x20a,\x0ainput#search,\x0adiv#learn\x20.buttons\x20a,\x0adiv.play\x20.buttons\x20a,\x0adiv#blog\x20.read\x20a,\x0a#menu-button\x20{\x0a\x20\x20padding:\x200.625rem;\x0a\x0a\x20\x20text-decoration:\x20none;\x0a\x20\x20font-size:\x201rem;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0adiv#playground\x20.buttons\x20a,\x0adiv#menu\x20>\x20a,\x0ainput#search,\x0a#menu-button\x20{\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a}\x0adiv#playground\x20.buttons\x20a,\x0adiv#menu\x20>\x20a,\x0a#menu-button\x20{\x0a\x20\x20color:\x20white;\x0a\x20\x20background:\x20#375eab;\x0a}\x0a#playgroundButton.active\x20{\x0a\x20\x20background:\x20white;\x0a\x20\x20color:\x20#375eab;\x0a}\x0aa#start,\x0adiv#learn\x20.buttons\x20a,\x0adiv.play\x20.buttons\x20a,\x0adiv#blog\x20.read\x20a\x20{\x0a\x20\x20color:\x20#222;\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a\x20\x20background:\x20#e0ebf5;\x0a}\x0a.download\x20{\x0a\x20\x20width:\x209.375rem;\x0a}\x0a\x0adiv#menu\x20{\x0a\x20\x20text-align:\x20right;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20white-space:\x20nowrap;\x0a\x20\x20max-height:\x200;\x0a\x20\x20-moz-transition:\x20max-height\x200.25s\x20linear;\x0a\x20\x20transition:\x20max-height\x200.25s\x20linear;\x0a\x20\x20width:\x20100%;\x0a}\x0adiv#menu.menu-visible\x20{\x0a\x20\x20max-height:\x2031.25rem;\x0a}\x0adiv#menu\x20>\x20a,\x0a#menu-button\x20{\x0a\x20\x20margin:\x200.625rem\x200.125rem;\x0a\x20\x20padding:\x200.625rem;\x0a}\x0a::-webkit-input-placeholder\x20{\x0a\x20\x20color:\x20#7f7f7f;\x0a\x20\x20opacity:\x201;\x0a}\x0a::placeholder\x20{\x0a\x20\x20color:\x20#7f7f7f;\x0a\x20\x20opacity:\x201;\x0a}\x0a#menu\x20.search-box\x20{\x0a\x20\x20display:\x20inline-flex;\x0a\x20\x20width:\x208.75rem;\x0a}\x0ainput#search\x20{\x0a\x20\x20background:\x20white;\x0a\x20\x20color:\x20#222;\x0a\x20\x20box-sizing:\x20border-box;\x0a\x20\x20-webkit-appearance:\x20none;\x0a\x20\x20border-top-right-radius:\x200;\x0a\x20\x20border-bottom-right-radius:\x200;\x0a\x20\x20border-right:\x200;\x0a\x20\x20margin-right:\x200;\x0a\x20\x20flex-grow:\x201;\x0a\x20\x20max-width:\x20100%;\x0a\x20\x20min-width:\x205.625rem;\x0a}\x0ainput#search:-webkit-search-decoration\x20{\x0a\x20\x20-webkit-appearance:\x20none;\x0a}\x0ainput#search:-moz-ui-invalid\x20{\x0a\x20\x20box-shadow:\x20unset;\x0a}\x0ainput#search\x20+\x20button\x20{\x0a\x20\x20display:\x20inline;\x0a\x20\x20font-size:\x201em;\x0a\x20\x20background-color:\x20#375eab;\x0a\x20\x20color:\x20white;\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a\x20\x20border-top-left-radius:\x200;\x0a\x20\x20border-top-right-radius:\x200.3125rem;\x0a\x20\x20border-bottom-left-radius:\x200;\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a\x20\x20margin-left:\x200;\x0a\x20\x20cursor:\x20pointer;\x0a}\x0ainput#search\x20+\x20button\x20span\x20{\x0a\x20\x20display:\x20flex;\x0a}\x0ainput#search\x20+\x20button\x20svg\x20{\x0a\x20\x20fill:\x20white;\x0a}\x0a\x0a#menu-button\x20{\x0a\x20\x20display:\x20none;\x0a\x20\x20position:\x20absolute;\x0a\x20\x20right:\x200.3125rem;\x0a\x20\x20top:\x200;\x0a\x20\x20margin-right:\x200.3125rem;\x0a}\x0a#menu-button-arrow\x20{\x0a\x20\x20display:\x20inline-block;\x0a}\x0a.vertical-flip\x20{\x0a\x20\x20transform:\x20rotate(-180deg);\x0a}\x0a\x0adiv.left\x20{\x0a\x20\x20float:\x20left;\x0a\x20\x20clear:\x20left;\x0a\x20\x20margin-right:\x202.5%;\x0a}\x0adiv.right\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20clear:\x20right;\x0a\x20\x20margin-left:\x202.5%;\x0a}\x0adiv.left,\x0adiv.right\x20{\x0a\x20\x20width:\x2045%;\x0a}\x0a\x0adiv#learn,\x0adiv#about\x20{\x0a\x20\x20padding-top:\x201.25rem;\x0a}\x0adiv#learn\x20h2,\x0adiv#about\x20{\x0a\x20\x20margin:\x200;\x0a}\x0adiv#about\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20margin:\x200\x20auto\x201.875rem;\x0a}\x0adiv#gopher\x20{\x0a\x20\x20background:\x20url(/doc/gopher/frontpage.png)\x20no-repeat;\x0a\x20\x20background-position:\x20center\x20top;\x0a\x20\x20height:\x209.688rem;\x0a\x20\x20max-height:\x20200px;\x20/*\x20Setting\x20in\x20px\x20to\x20prevent\x20the\x20gopher\x20from\x20blowing\x20up\x20in\x20very\x20high\x20default\x20font-sizes\x20*/\x0a}\x0aa#start\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20padding:\x200.625rem;\x0a\x0a\x20\x20text-align:\x20center;\x0a\x20\x20text-decoration:\x20none;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0aa#start\x20.big\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20font-weight:\x20bold;\x0a\x20\x20font-size:\x201.25rem;\x0a}\x0aa#start\x20.desc\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20font-weight:\x20normal;\x0a\x20\x20margin-top:\x200.3125rem;\x0a}\x0a\x0adiv#learn\x20.popout\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20display:\x20block;\x0a\x20\x20cursor:\x20pointer;\x0a\x20\x20font-size:\x200.75rem;\x0a\x20\x20background:\x20url(/doc/share.png)\x20no-repeat;\x0a\x20\x20background-position:\x20right\x20center;\x0a\x20\x20padding:\x200.375rem\x201.688rem;\x0a}\x0adiv#learn\x20pre,\x0adiv#learn\x20textarea\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20margin:\x200;\x0a\x20\x20font-family:\x20Menlo,\x20monospace;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv#learn\x20.input\x20{\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20margin-top:\x200.625rem;\x0a\x20\x20height:\x209.375rem;\x0a\x0a\x20\x20border-top-left-radius:\x200.3125rem;\x0a\x20\x20border-top-right-radius:\x200.3125rem;\x0a}\x0adiv#learn\x20.input\x20textarea\x20{\x0a\x20\x20width:\x20100%;\x0a\x20\x20height:\x20100%;\x0a\x20\x20border:\x20none;\x0a\x20\x20outline:\x20none;\x0a\x20\x20resize:\x20none;\x0a}\x0adiv#learn\x20.output\x20{\x0a\x20\x20border-top:\x20none\x20!important;\x0a\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20height:\x203.688rem;\x0a\x20\x20overflow:\x20auto;\x0a\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a\x20\x20border-bottom-left-radius:\x200.3125rem;\x0a}\x0adiv#learn\x20.output\x20pre\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20border-radius:\x200;\x0a}\x0adiv#learn\x20.input,\x0adiv#learn\x20.input\x20textarea,\x0adiv#learn\x20.output,\x0adiv#learn\x20.output\x20pre\x20{\x0a\x20\x20background:\x20#ffffd8;\x0a}\x0adiv#learn\x20.input,\x0adiv#learn\x20.output\x20{\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a}\x0adiv#learn\x20.buttons\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20padding:\x201.25rem\x200\x200.625rem\x200;\x0a\x20\x20text-align:\x20right;\x0a}\x0adiv#learn\x20.buttons\x20a\x20{\x0a\x20\x20height:\x201rem;\x0a\x20\x20margin-left:\x200.3125rem;\x0a\x20\x20padding:\x200.625rem;\x0a}\x0adiv#learn\x20.toys\x20{\x0a\x20\x20margin-top:\x200.5rem;\x0a}\x0adiv#learn\x20.toys\x20select\x20{\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a\x20\x20margin:\x200;\x0a}\x0adiv#learn\x20.output\x20.exit\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a\x0adiv#video\x20{\x0a\x20\x20max-width:\x20100%;\x0a}\x0adiv#blog,\x0adiv#video\x20{\x0a\x20\x20margin-top:\x202.5rem;\x0a}\x0adiv#blog\x20>\x20a,\x0adiv#blog\x20>\x20div,\x0adiv#blog\x20>\x20h2,\x0adiv#video\x20>\x20a,\x0adiv#video\x20>\x20div,\x0adiv#video\x20>\x20h2\x20{\x0a\x20\x20margin-bottom:\x200.625rem;\x0a}\x0adiv#blog\x20.title,\x0adiv#video\x20.title\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20font-size:\x201.25rem;\x0a}\x0adiv#blog\x20.when\x20{\x0a\x20\x20color:\x20#666;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv#blog\x20.read\x20{\x0a\x20\x20text-align:\x20right;\x0a}\x0a\x0a@supports\x20(--c:\x200)\x20{\x0a\x20\x20[style*='--aspect-ratio-padding:']\x20{\x0a\x20\x20\x20\x20position:\x20relative;\x0a\x20\x20\x20\x20overflow:\x20hidden;\x0a\x20\x20\x20\x20padding-top:\x20var(--aspect-ratio-padding);\x0a\x20\x20}\x0a\x0a\x20\x20[style*='--aspect-ratio-padding:']\x20>\x20*\x20{\x0a\x20\x20\x20\x20position:\x20absolute;\x0a\x20\x20\x20\x20top:\x200;\x0a\x20\x20\x20\x20left:\x200;\x0a\x20\x20\x20\x20width:\x20100%;\x0a\x20\x20\x20\x20height:\x20100%;\x0a\x20\x20}\x0a}\x0a\x0a.toggleButton\x20{\x0a\x20\x20cursor:\x20pointer;\x0a}\x0a.toggle\x20>\x20.collapsed\x20{\x0a\x20\x20display:\x20block;\x0a}\x0a.toggle\x20>\x20.expanded\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a.toggleVisible\x20>\x20.collapsed\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a.toggleVisible\x20>\x20.expanded\x20{\x0a\x20\x20display:\x20block;\x0a}\x0a\x0atable.codetable\x20{\x0a\x20\x20margin-left:\x20auto;\x0a\x20\x20margin-right:\x20auto;\x0a\x20\x20border-style:\x20none;\x0a}\x0atable.codetable\x20td\x20{\x0a\x20\x20padding-right:\x200.625rem;\x0a}\x0ahr\x20{\x0a\x20\x20border-style:\x20none;\x0a\x20\x20border-top:\x200.0625rem\x20solid\x20black;\x0a}\x0a\x0aimg.gopher\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20margin-left:\x200.625rem;\x0a\x20\x20margin-bottom:\x200.625rem;\x0a\x20\x20z-index:\x20-1;\x0a}\x0ah2\x20{\x0a\x20\x20clear:\x20right;\x0a}\x0a\x0a/*\x20example\x20and\x20drop-down\x20playground\x20*/\x0adiv.play\x20{\x0a\x20\x20padding:\x200\x201.25rem\x202.5rem\x201.25rem;\x0a}\x0adiv.play\x20pre,\x0adiv.play\x20textarea,\x0adiv.play\x20.lines\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20margin:\x200;\x0a\x20\x20font-family:\x20Menlo,\x20monospace;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv.play\x20.input\x20{\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20margin-top:\x200.625rem;\x0a\x0a\x20\x20border-top-left-radius:\x200.3125rem;\x0a\x20\x20border-top-right-radius:\x200.3125rem;\x0a\x0a\x20\x20overflow:\x20hidden;\x0a}\x0adiv.play\x20.input\x20textarea\x20{\x0a\x20\x20width:\x20100%;\x0a\x20\x20height:\x20100%;\x0a\x20\x20border:\x20none;\x0a\x20\x20outline:\x20none;\x0a\x20\x20resize:\x20none;\x0a\x0a\x20\x20overflow:\x20hidden;\x0a}\x0adiv#playground\x20.input\x20textarea\x20{\x0a\x20\x20overflow:\x20auto;\x0a\x20\x20resize:\x20auto;\x0a}\x0adiv.play\x20.output\x20{\x0a\x20\x20border-top:\x20none\x20!important;\x0a\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20max-height:\x2012.5rem;\x0a\x20\x20overflow:\x20auto;\x0a\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a\x20\x20border-bottom-left-radius:\x200.3125rem;\x0a}\x0adiv.play\x20.output\x20pre\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20border-radius:\x200;\x0a}\x0adiv.play\x20.input,\x0adiv.play\x20.input\x20textarea,\x0adiv.play\x20.output,\x0adiv.play\x20.output\x20pre\x20{\x0a\x20\x20background:\x20#ffffd8;\x0a}\x0adiv.play\x20.input,\x0adiv.play\x20.output\x20{\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a}\x0adiv.play\x20.buttons\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20padding:\x201.25rem\x200\x200.625rem\x200;\x0a\x20\x20text-align:\x20right;\x0a}\x0adiv.play\x20.buttons\x20a\x20{\x0a\x20\x20height:\x201rem;\x0a\x20\x20margin-left:\x200.3125rem;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20cursor:\x20pointer;\x0a}\x0a.output\x20.stderr\x20{\x0a\x20\x20color:\x20#933;\x0a}\x0a.output\x20.system\x20{\x0a\x20\x20color:\x20#999;\x0a}\x0a\x0a/*\x20drop-down\x20playground\x20*/\x0adiv#playground\x20{\x0a\x20\x20/*\x20start\x20hidden;\x20revealed\x20by\x20javascript\x20*/\x0a\x20\x20display:\x20none;\x0a}\x0adiv#playground\x20{\x0a\x20\x20position:\x20absolute;\x0a\x20\x20top:\x203.938rem;\x0a\x20\x20right:\x201.25rem;\x0a\x20\x20padding:\x200\x200.625rem\x200.625rem\x200.625rem;\x0a\x20\x20z-index:\x201;\x0a\x20\x20text-align:\x20left;\x0a\x20\x20background:\x20#e0ebf5;\x0a\x0a\x20\x20border:\x200.0625rem\x20solid\x20#b0bbc5;\x0a\x20\x20border-top:\x20none;\x0a\x0a\x20\x20border-bottom-left-radius:\x200.3125rem;\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a}\x0adiv#playground\x20.code\x20{\x0a\x20\x20width:\x2032.5rem;\x0a\x20\x20height:\x2012.5rem;\x0a}\x0adiv#playground\x20.output\x20{\x0a\x20\x20height:\x206.25rem;\x0a}\x0a\x0a/*\x20Inline\x20runnable\x20snippets\x20(play.js/initPlayground)\x20*/\x0a#content\x20.code\x20pre,\x0a#content\x20.playground\x20pre,\x0a#content\x20.output\x20pre\x20{\x0a\x20\x20margin:\x200;\x0a\x20\x20padding:\x200;\x0a\x20\x20background:\x20none;\x0a\x20\x20border:\x20none;\x0a\x20\x20outline:\x200\x20solid\x20transparent;\x0a\x20\x20overflow:\x20auto;\x0a}\x0a#content\x20.playground\x20.number,\x0a#content\x20.code\x20.number\x20{\x0a\x20\x20color:\x20#999;\x0a}\x0a#content\x20.code,\x0a#content\x20.playground,\x0a#content\x20.output\x20{\x0a\x20\x20width:\x20auto;\x0a\x20\x20margin:\x201.25rem;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0a#content\x20.code,\x0a#content\x20.playground\x20{\x0a\x20\x20background:\x20#e9e9e9;\x0a}\x0a#content\x20.output\x20{\x0a\x20\x20background:\x20#202020;\x0a}\x0a#content\x20.output\x20.stdout,\x0a#content\x20.output\x20pre\x20{\x0a\x20\x20color:\x20#e6e6e6;\x0a}\x0a#content\x20.output\x20.stderr,\x0a#content\x20.output\x20.error\x20{\x0a\x20\x20color:\x20rgb(244,\x2074,\x2063);\x0a}\x0a#content\x20.output\x20.system,\x0a#content\x20.output\x20.exit\x20{\x0a\x20\x20color:\x20rgb(255,\x20209,\x2077);\x0a}\x0a#content\x20.buttons\x20{\x0a\x20\x20position:\x20relative;\x0a\x20\x20float:\x20right;\x0a\x20\x20top:\x20-3.125rem;\x0a\x20\x20right:\x201.875rem;\x0a}\x0a#content\x20.output\x20.buttons\x20{\x0a\x20\x20top:\x20-3.75rem;\x0a\x20\x20right:\x200;\x0a\x20\x20height:\x200;\x0a}\x0a#content\x20.buttons\x20.kill\x20{\x0a\x20\x20display:\x20none;\x0a\x20\x20visibility:\x20hidden;\x0a}\x0aa.error\x20{\x0a\x20\x20font-weight:\x20bold;\x0a\x20\x20color:\x20white;\x0a\x20\x20background-color:\x20darkred;\x0a\x20\x20border-bottom-left-radius:\x200.25rem;\x0a\x20\x20border-bottom-right-radius:\x200.25rem;\x0a\x20\x20border-top-left-radius:\x200.25rem;\x0a\x20\x20border-top-right-radius:\x200.25rem;\x0a\x20\x20padding:\x200.125rem\x200.25rem\x200.125rem\x200.25rem;\x20/*\x20TRBL\x20*/\x0a}\x0a\x0a#heading-narrow\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a\x0a.downloading\x20{\x0a\x20\x20background:\x20#f9f9be;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20text-align:\x20center;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0a\x0a@media\x20(max-width:\x2058.125em)\x20{\x0a\x20\x20#heading-wide\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x20\x20#heading-narrow\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(max-width:\x2047.5em)\x20{\x0a\x20\x20.container\x20.left,\x0a\x20\x20.container\x20.right\x20{\x0a\x20\x20\x20\x20width:\x20auto;\x0a\x20\x20\x20\x20float:\x20none;\x0a\x20\x20}\x0a\x0a\x20\x20div#about\x20{\x0a\x20\x20\x20\x20max-width:\x2031.25rem;\x0a\x20\x20\x20\x20text-align:\x20center;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(min-width:\x2043.75em)\x20and\x20(max-width:\x2062.5em)\x20{\x0a\x20\x20div#menu\x20>\x20a\x20{\x0a\x20\x20\x20\x20margin:\x200.3125rem\x200;\x0a\x20\x20\x20\x20font-size:\x200.875rem;\x0a\x20\x20}\x0a\x0a\x20\x20input#search\x20{\x0a\x20\x20\x20\x20font-size:\x200.875rem;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(max-width:\x2043.75em)\x20{\x0a\x20\x20body\x20{\x0a\x20\x20\x20\x20font-size:\x200.9375rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#playground\x20{\x0a\x20\x20\x20\x20left:\x200;\x0a\x20\x20\x20\x20right:\x200;\x0a\x20\x20}\x0a\x0a\x20\x20pre,\x0a\x20\x20code\x20{\x0a\x20\x20\x20\x20font-size:\x200.866rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#page\x20>\x20.container\x20{\x0a\x20\x20\x20\x20padding:\x200\x200.625rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#topbar\x20{\x0a\x20\x20\x20\x20height:\x20auto;\x0a\x20\x20\x20\x20padding:\x200.625rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#topbar\x20>\x20.container\x20{\x0a\x20\x20\x20\x20padding:\x200;\x0a\x20\x20}\x0a\x0a\x20\x20#heading-wide\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20}\x0a\x20\x20#heading-narrow\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x0a\x20\x20.top-heading\x20{\x0a\x20\x20\x20\x20float:\x20none;\x0a\x20\x20\x20\x20display:\x20inline-block;\x0a\x20\x20\x20\x20padding:\x200.75rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#menu\x20{\x0a\x20\x20\x20\x20padding:\x200;\x0a\x20\x20\x20\x20min-width:\x200;\x0a\x20\x20\x20\x20text-align:\x20left;\x0a\x20\x20\x20\x20float:\x20left;\x0a\x20\x20}\x0a\x0a\x20\x20div#menu\x20>\x20a\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20\x20\x20margin-left:\x200;\x0a\x20\x20\x20\x20margin-right:\x200;\x0a\x20\x20}\x0a\x0a\x20\x20#menu\x20.search-box\x20{\x0a\x20\x20\x20\x20display:\x20flex;\x0a\x20\x20\x20\x20width:\x20100%;\x0a\x20\x20}\x0a\x0a\x20\x20#menu-button\x20{\x0a\x20\x20\x20\x20display:\x20inline-block;\x0a\x20\x20}\x0a\x0a\x20\x20p,\x0a\x20\x20pre,\x0a\x20\x20ul,\x0a\x20\x20ol\x20{\x0a\x20\x20\x20\x20margin:\x200.625rem;\x0a\x20\x20}\x0a\x0a\x20\x20.pkg-synopsis\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x0a\x20\x20img.gopher\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(max-width:\x2030em)\x20{\x0a\x20\x20#heading-wide\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x20\x20#heading-narrow\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20print\x20{\x0a\x20\x20pre\x20{\x0a\x20\x20\x20\x20background:\x20#fff;\x0a\x20\x20\x20\x20border:\x200.0625rem\x20solid\x20#bbb;\x0a\x20\x20\x20\x20white-space:\x20pre-wrap;\x0a\x20\x20}\x0a}\x0a",
+ "style.css": "body\x20{\x0a\x20\x20margin:\x200;\x0a\x20\x20font-family:\x20Arial,\x20sans-serif;\x0a\x20\x20background-color:\x20#fff;\x0a\x20\x20line-height:\x201.3;\x0a\x20\x20text-align:\x20center;\x0a\x20\x20color:\x20#222;\x0a}\x0atextarea\x20{\x0a\x20\x20/*\x20Inherit\x20text\x20color\x20from\x20body\x20avoiding\x20illegible\x20text\x20in\x20the\x20case\x20where\x20the\x0a\x20\x09*\x20user\x20has\x20inverted\x20the\x20browsers\x20custom\x20text\x20and\x20background\x20colors.\x20*/\x0a\x20\x20color:\x20inherit;\x0a}\x0apre,\x0acode\x20{\x0a\x20\x20font-family:\x20Menlo,\x20monospace;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0apre\x20{\x0a\x20\x20line-height:\x201.4;\x0a\x20\x20overflow-x:\x20auto;\x0a}\x0apre\x20.comment\x20{\x0a\x20\x20color:\x20#006600;\x0a}\x0apre\x20.highlight,\x0apre\x20.highlight-comment,\x0apre\x20.selection-highlight,\x0apre\x20.selection-highlight-comment\x20{\x0a\x20\x20background:\x20#ffff00;\x0a}\x0apre\x20.selection,\x0apre\x20.selection-comment\x20{\x0a\x20\x20background:\x20#ff9632;\x0a}\x0apre\x20.ln\x20{\x0a\x20\x20color:\x20#999;\x0a\x20\x20background:\x20#efefef;\x0a}\x0a.ln\x20{\x0a\x20\x20-webkit-user-select:\x20none;\x0a\x20\x20-moz-user-select:\x20none;\x0a\x20\x20-ms-user-select:\x20none;\x0a\x20\x20user-select:\x20none;\x0a\x0a\x20\x20/*\x20Ensure\x208\x20characters\x20in\x20the\x20document\x20-\x20which\x20due\x20to\x20floating\x0a\x20\x20\x20*\x20point\x20rendering\x20issues,\x20might\x20have\x20a\x20width\x20of\x20less\x20than\x201\x20each\x20-\x20are\x208\x0a\x20\x20\x20*\x20characters\x20wide,\x20so\x20a\x20tab\x20in\x20the\x209th\x20position\x20indents\x20properly.\x20See\x0a\x20\x20\x20*\x20https://github.com/webcompat/web-bugs/issues/17530#issuecomment-402675091\x0a\x20\x20\x20*\x20for\x20more\x20information.\x20*/\x0a\x20\x20display:\x20inline-block;\x0a\x20\x20width:\x208ch;\x0a}\x0a\x0a.search-nav\x20{\x0a\x20\x20margin-left:\x201.25rem;\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20column-gap:\x201.25rem;\x0a\x20\x20column-fill:\x20auto;\x0a\x20\x20column-width:\x2014rem;\x0a}\x0a\x0a.search-nav\x20.indent\x20{\x0a\x20\x20margin-left:\x201.25rem;\x0a}\x0a\x0aa,\x0a.exampleHeading\x20.text,\x0a.expandAll\x20{\x0a\x20\x20color:\x20#375eab;\x0a\x20\x20text-decoration:\x20none;\x0a}\x0aa:hover,\x0a.exampleHeading\x20.text:hover,\x0a.expandAll:hover\x20{\x0a\x20\x20text-decoration:\x20underline;\x0a}\x0a.article\x20a\x20{\x0a\x20\x20text-decoration:\x20underline;\x0a}\x0a.article\x20.title\x20a\x20{\x0a\x20\x20text-decoration:\x20none;\x0a}\x0a\x0a.permalink\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a:hover\x20>\x20.permalink\x20{\x0a\x20\x20display:\x20inline;\x0a}\x0a\x0ap,\x0ali\x20{\x0a\x20\x20max-width:\x2050rem;\x0a\x20\x20word-wrap:\x20break-word;\x0a}\x0ap,\x0apre,\x0aul,\x0aol\x20{\x0a\x20\x20margin:\x201.25rem;\x0a}\x0apre\x20{\x0a\x20\x20background:\x20#efefef;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0a\x0ah1,\x0ah2,\x0ah3,\x0ah4,\x0a.rootHeading\x20{\x0a\x20\x20margin:\x201.25rem\x200\x201.25rem;\x0a\x20\x20padding:\x200;\x0a\x20\x20color:\x20#375eab;\x0a\x20\x20font-weight:\x20bold;\x0a}\x0ah1\x20{\x0a\x20\x20font-size:\x201.75rem;\x0a\x20\x20line-height:\x201;\x0a}\x0ah1\x20.text-muted\x20{\x0a\x20\x20color:\x20#777;\x0a}\x0ah2\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20background:\x20#e0ebf5;\x0a\x20\x20padding:\x200.5rem;\x0a\x20\x20line-height:\x201.25;\x0a\x20\x20font-weight:\x20normal;\x0a\x20\x20overflow:\x20auto;\x0a\x20\x20overflow-wrap:\x20break-word;\x0a}\x0ah2\x20a\x20{\x0a\x20\x20font-weight:\x20bold;\x0a}\x0ah3\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20line-height:\x201.25;\x0a\x20\x20overflow:\x20auto;\x0a\x20\x20overflow-wrap:\x20break-word;\x0a}\x0ah3,\x0ah4\x20{\x0a\x20\x20margin:\x201.25rem\x200.3125rem;\x0a}\x0ah4\x20{\x0a\x20\x20font-size:\x201rem;\x0a}\x0a.rootHeading\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20margin:\x200;\x0a}\x0a\x0ah2\x20>\x20span,\x0ah3\x20>\x20span\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20margin:\x200\x2025px\x200\x200;\x0a\x20\x20font-weight:\x20normal;\x0a\x20\x20color:\x20#5279c7;\x0a}\x0a\x0adl\x20{\x0a\x20\x20margin:\x201.25rem;\x0a}\x0add\x20{\x0a\x20\x20margin:\x200\x200\x200\x201.25rem;\x0a}\x0adl,\x0add\x20{\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv#nav\x20table\x20td\x20{\x0a\x20\x20vertical-align:\x20top;\x0a}\x0a\x0a#pkg-index\x20h3\x20{\x0a\x20\x20font-size:\x201rem;\x0a}\x0a.pkg-dir\x20{\x0a\x20\x20padding:\x200\x200.625rem;\x0a}\x0a.pkg-dir\x20table\x20{\x0a\x20\x20border-collapse:\x20collapse;\x0a\x20\x20border-spacing:\x200;\x0a}\x0a.pkg-name\x20{\x0a\x20\x20padding-right:\x200.625rem;\x0a}\x0a.alert\x20{\x0a\x20\x20color:\x20#aa0000;\x0a}\x0a\x0a.top-heading\x20{\x0a\x20\x20float:\x20left;\x0a\x20\x20padding:\x201.313rem\x200;\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20font-weight:\x20normal;\x0a}\x0a.top-heading\x20a\x20{\x0a\x20\x20color:\x20#222;\x0a\x20\x20text-decoration:\x20none;\x0a}\x0a\x0a#pkg-examples\x20h3\x20{\x0a\x20\x20float:\x20left;\x0a}\x0a\x0a#pkg-examples\x20dl\x20{\x0a\x20\x20clear:\x20both;\x0a}\x0a\x0a.expandAll\x20{\x0a\x20\x20cursor:\x20pointer;\x0a\x20\x20float:\x20left;\x0a\x20\x20margin:\x201.25rem\x200;\x0a}\x0a\x0adiv#topbar\x20{\x0a\x20\x20background:\x20#e0ebf5;\x0a\x20\x20height:\x204rem;\x0a\x20\x20overflow:\x20hidden;\x0a}\x0a\x0adiv#page\x20{\x0a\x20\x20width:\x20100%;\x0a}\x0adiv#page\x20>\x20.container,\x0adiv#topbar\x20>\x20.container\x20{\x0a\x20\x20text-align:\x20left;\x0a\x20\x20margin-left:\x20auto;\x0a\x20\x20margin-right:\x20auto;\x0a\x20\x20padding:\x200\x201.25rem;\x0a}\x0adiv#topbar\x20>\x20.container,\x0adiv#page\x20>\x20.container\x20{\x0a\x20\x20max-width:\x2059.38rem;\x0a}\x0adiv#page.wide\x20>\x20.container,\x0adiv#topbar.wide\x20>\x20.container\x20{\x0a\x20\x20max-width:\x20none;\x0a}\x0adiv#plusone\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20clear:\x20right;\x0a\x20\x20margin-top:\x200.3125rem;\x0a}\x0a\x0adiv#footer\x20{\x0a\x20\x20text-align:\x20center;\x0a\x20\x20color:\x20#666;\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20margin:\x202.5rem\x200;\x0a}\x0a\x0adiv#menu\x20>\x20a,\x0ainput#search,\x0adiv#learn\x20.buttons\x20a,\x0adiv.play\x20.buttons\x20a,\x0adiv#blog\x20.read\x20a,\x0a#menu-button\x20{\x0a\x20\x20padding:\x200.625rem;\x0a\x0a\x20\x20text-decoration:\x20none;\x0a\x20\x20font-size:\x201rem;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0adiv#playground\x20.buttons\x20a,\x0adiv#menu\x20>\x20a,\x0ainput#search,\x0a#menu-button\x20{\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a}\x0adiv#playground\x20.buttons\x20a,\x0adiv#menu\x20>\x20a,\x0a#menu-button\x20{\x0a\x20\x20color:\x20white;\x0a\x20\x20background:\x20#375eab;\x0a}\x0a#playgroundButton.active\x20{\x0a\x20\x20background:\x20white;\x0a\x20\x20color:\x20#375eab;\x0a}\x0aa#start,\x0adiv#learn\x20.buttons\x20a,\x0adiv.play\x20.buttons\x20a,\x0adiv#blog\x20.read\x20a\x20{\x0a\x20\x20color:\x20#222;\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a\x20\x20background:\x20#e0ebf5;\x0a}\x0a.download\x20{\x0a\x20\x20width:\x209.375rem;\x0a}\x0a\x0adiv#menu\x20{\x0a\x20\x20text-align:\x20right;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20white-space:\x20nowrap;\x0a\x20\x20max-height:\x200;\x0a\x20\x20-moz-transition:\x20max-height\x200.25s\x20linear;\x0a\x20\x20transition:\x20max-height\x200.25s\x20linear;\x0a\x20\x20width:\x20100%;\x0a}\x0adiv#menu.menu-visible\x20{\x0a\x20\x20max-height:\x2031.25rem;\x0a}\x0adiv#menu\x20>\x20a,\x0a#menu-button\x20{\x0a\x20\x20margin:\x200.625rem\x200.125rem;\x0a\x20\x20padding:\x200.625rem;\x0a}\x0a::-webkit-input-placeholder\x20{\x0a\x20\x20color:\x20#7f7f7f;\x0a\x20\x20opacity:\x201;\x0a}\x0a::placeholder\x20{\x0a\x20\x20color:\x20#7f7f7f;\x0a\x20\x20opacity:\x201;\x0a}\x0a#menu\x20.search-box\x20{\x0a\x20\x20display:\x20inline-flex;\x0a\x20\x20width:\x208.75rem;\x0a}\x0ainput#search\x20{\x0a\x20\x20background:\x20white;\x0a\x20\x20color:\x20#222;\x0a\x20\x20box-sizing:\x20border-box;\x0a\x20\x20-webkit-appearance:\x20none;\x0a\x20\x20border-top-right-radius:\x200;\x0a\x20\x20border-bottom-right-radius:\x200;\x0a\x20\x20border-right:\x200;\x0a\x20\x20margin-right:\x200;\x0a\x20\x20flex-grow:\x201;\x0a\x20\x20max-width:\x20100%;\x0a\x20\x20min-width:\x205.625rem;\x0a}\x0ainput#search:-webkit-search-decoration\x20{\x0a\x20\x20-webkit-appearance:\x20none;\x0a}\x0ainput#search:-moz-ui-invalid\x20{\x0a\x20\x20box-shadow:\x20unset;\x0a}\x0ainput#search\x20+\x20button\x20{\x0a\x20\x20display:\x20inline;\x0a\x20\x20font-size:\x201em;\x0a\x20\x20background-color:\x20#375eab;\x0a\x20\x20color:\x20white;\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a\x20\x20border-top-left-radius:\x200;\x0a\x20\x20border-top-right-radius:\x200.3125rem;\x0a\x20\x20border-bottom-left-radius:\x200;\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a\x20\x20margin-left:\x200;\x0a\x20\x20cursor:\x20pointer;\x0a}\x0ainput#search\x20+\x20button\x20span\x20{\x0a\x20\x20display:\x20flex;\x0a}\x0ainput#search\x20+\x20button\x20svg\x20{\x0a\x20\x20fill:\x20white;\x0a}\x0a\x0a#menu-button\x20{\x0a\x20\x20display:\x20none;\x0a\x20\x20position:\x20absolute;\x0a\x20\x20right:\x200.3125rem;\x0a\x20\x20top:\x200;\x0a\x20\x20margin-right:\x200.3125rem;\x0a}\x0a#menu-button-arrow\x20{\x0a\x20\x20display:\x20inline-block;\x0a}\x0a.vertical-flip\x20{\x0a\x20\x20transform:\x20rotate(-180deg);\x0a}\x0a\x0adiv.left\x20{\x0a\x20\x20float:\x20left;\x0a\x20\x20clear:\x20left;\x0a\x20\x20margin-right:\x202.5%;\x0a}\x0adiv.right\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20clear:\x20right;\x0a\x20\x20margin-left:\x202.5%;\x0a}\x0adiv.left,\x0adiv.right\x20{\x0a\x20\x20width:\x2045%;\x0a}\x0a\x0adiv#learn,\x0adiv#about\x20{\x0a\x20\x20padding-top:\x201.25rem;\x0a}\x0adiv#learn\x20h2,\x0adiv#about\x20{\x0a\x20\x20margin:\x200;\x0a}\x0adiv#about\x20{\x0a\x20\x20font-size:\x201.25rem;\x0a\x20\x20margin:\x200\x20auto\x201.875rem;\x0a}\x0adiv#gopher\x20{\x0a\x20\x20background:\x20url(https://golang.org/doc/gopher/frontpage.png)\x20no-repeat;\x0a\x20\x20background-position:\x20center\x20top;\x0a\x20\x20height:\x209.688rem;\x0a\x20\x20max-height:\x20200px;\x20/*\x20Setting\x20in\x20px\x20to\x20prevent\x20the\x20gopher\x20from\x20blowing\x20up\x20in\x20very\x20high\x20default\x20font-sizes\x20*/\x0a}\x0aa#start\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20padding:\x200.625rem;\x0a\x0a\x20\x20text-align:\x20center;\x0a\x20\x20text-decoration:\x20none;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0aa#start\x20.big\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20font-weight:\x20bold;\x0a\x20\x20font-size:\x201.25rem;\x0a}\x0aa#start\x20.desc\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20font-weight:\x20normal;\x0a\x20\x20margin-top:\x200.3125rem;\x0a}\x0a\x0adiv#learn\x20.popout\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20display:\x20block;\x0a\x20\x20cursor:\x20pointer;\x0a\x20\x20font-size:\x200.75rem;\x0a\x20\x20background:\x20url(/doc/share.png)\x20no-repeat;\x0a\x20\x20background-position:\x20right\x20center;\x0a\x20\x20padding:\x200.375rem\x201.688rem;\x0a}\x0adiv#learn\x20pre,\x0adiv#learn\x20textarea\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20margin:\x200;\x0a\x20\x20font-family:\x20Menlo,\x20monospace;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv#learn\x20.input\x20{\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20margin-top:\x200.625rem;\x0a\x20\x20height:\x209.375rem;\x0a\x0a\x20\x20border-top-left-radius:\x200.3125rem;\x0a\x20\x20border-top-right-radius:\x200.3125rem;\x0a}\x0adiv#learn\x20.input\x20textarea\x20{\x0a\x20\x20width:\x20100%;\x0a\x20\x20height:\x20100%;\x0a\x20\x20border:\x20none;\x0a\x20\x20outline:\x20none;\x0a\x20\x20resize:\x20none;\x0a}\x0adiv#learn\x20.output\x20{\x0a\x20\x20border-top:\x20none\x20!important;\x0a\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20height:\x203.688rem;\x0a\x20\x20overflow:\x20auto;\x0a\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a\x20\x20border-bottom-left-radius:\x200.3125rem;\x0a}\x0adiv#learn\x20.output\x20pre\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20border-radius:\x200;\x0a}\x0adiv#learn\x20.input,\x0adiv#learn\x20.input\x20textarea,\x0adiv#learn\x20.output,\x0adiv#learn\x20.output\x20pre\x20{\x0a\x20\x20background:\x20#ffffd8;\x0a}\x0adiv#learn\x20.input,\x0adiv#learn\x20.output\x20{\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a}\x0adiv#learn\x20.buttons\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20padding:\x201.25rem\x200\x200.625rem\x200;\x0a\x20\x20text-align:\x20right;\x0a}\x0adiv#learn\x20.buttons\x20a\x20{\x0a\x20\x20height:\x201rem;\x0a\x20\x20margin-left:\x200.3125rem;\x0a\x20\x20padding:\x200.625rem;\x0a}\x0adiv#learn\x20.toys\x20{\x0a\x20\x20margin-top:\x200.5rem;\x0a}\x0adiv#learn\x20.toys\x20select\x20{\x0a\x20\x20font-size:\x200.875rem;\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a\x20\x20margin:\x200;\x0a}\x0adiv#learn\x20.output\x20.exit\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a\x0adiv#video\x20{\x0a\x20\x20max-width:\x20100%;\x0a}\x0adiv#blog,\x0adiv#video\x20{\x0a\x20\x20margin-top:\x202.5rem;\x0a}\x0adiv#blog\x20>\x20a,\x0adiv#blog\x20>\x20div,\x0adiv#blog\x20>\x20h2,\x0adiv#video\x20>\x20a,\x0adiv#video\x20>\x20div,\x0adiv#video\x20>\x20h2\x20{\x0a\x20\x20margin-bottom:\x200.625rem;\x0a}\x0adiv#blog\x20.title,\x0adiv#video\x20.title\x20{\x0a\x20\x20display:\x20block;\x0a\x20\x20font-size:\x201.25rem;\x0a}\x0adiv#blog\x20.when\x20{\x0a\x20\x20color:\x20#666;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv#blog\x20.read\x20{\x0a\x20\x20text-align:\x20right;\x0a}\x0a\x0a@supports\x20(--c:\x200)\x20{\x0a\x20\x20[style*='--aspect-ratio-padding:']\x20{\x0a\x20\x20\x20\x20position:\x20relative;\x0a\x20\x20\x20\x20overflow:\x20hidden;\x0a\x20\x20\x20\x20padding-top:\x20var(--aspect-ratio-padding);\x0a\x20\x20}\x0a\x0a\x20\x20[style*='--aspect-ratio-padding:']\x20>\x20*\x20{\x0a\x20\x20\x20\x20position:\x20absolute;\x0a\x20\x20\x20\x20top:\x200;\x0a\x20\x20\x20\x20left:\x200;\x0a\x20\x20\x20\x20width:\x20100%;\x0a\x20\x20\x20\x20height:\x20100%;\x0a\x20\x20}\x0a}\x0a\x0a.toggleButton\x20{\x0a\x20\x20cursor:\x20pointer;\x0a}\x0a.toggle\x20>\x20.collapsed\x20{\x0a\x20\x20display:\x20block;\x0a}\x0a.toggle\x20>\x20.expanded\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a.toggleVisible\x20>\x20.collapsed\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a.toggleVisible\x20>\x20.expanded\x20{\x0a\x20\x20display:\x20block;\x0a}\x0a\x0atable.codetable\x20{\x0a\x20\x20margin-left:\x20auto;\x0a\x20\x20margin-right:\x20auto;\x0a\x20\x20border-style:\x20none;\x0a}\x0atable.codetable\x20td\x20{\x0a\x20\x20padding-right:\x200.625rem;\x0a}\x0ahr\x20{\x0a\x20\x20border-style:\x20none;\x0a\x20\x20border-top:\x200.0625rem\x20solid\x20black;\x0a}\x0a\x0aimg.gopher\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20margin-left:\x200.625rem;\x0a\x20\x20margin-bottom:\x200.625rem;\x0a\x20\x20z-index:\x20-1;\x0a}\x0ah2\x20{\x0a\x20\x20clear:\x20right;\x0a}\x0a\x0a/*\x20example\x20and\x20drop-down\x20playground\x20*/\x0adiv.play\x20{\x0a\x20\x20padding:\x200\x201.25rem\x202.5rem\x201.25rem;\x0a}\x0adiv.play\x20pre,\x0adiv.play\x20textarea,\x0adiv.play\x20.lines\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20margin:\x200;\x0a\x20\x20font-family:\x20Menlo,\x20monospace;\x0a\x20\x20font-size:\x200.875rem;\x0a}\x0adiv.play\x20.input\x20{\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20margin-top:\x200.625rem;\x0a\x0a\x20\x20border-top-left-radius:\x200.3125rem;\x0a\x20\x20border-top-right-radius:\x200.3125rem;\x0a\x0a\x20\x20overflow:\x20hidden;\x0a}\x0adiv.play\x20.input\x20textarea\x20{\x0a\x20\x20width:\x20100%;\x0a\x20\x20height:\x20100%;\x0a\x20\x20border:\x20none;\x0a\x20\x20outline:\x20none;\x0a\x20\x20resize:\x20none;\x0a\x0a\x20\x20overflow:\x20hidden;\x0a}\x0adiv#playground\x20.input\x20textarea\x20{\x0a\x20\x20overflow:\x20auto;\x0a\x20\x20resize:\x20auto;\x0a}\x0adiv.play\x20.output\x20{\x0a\x20\x20border-top:\x20none\x20!important;\x0a\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20max-height:\x2012.5rem;\x0a\x20\x20overflow:\x20auto;\x0a\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a\x20\x20border-bottom-left-radius:\x200.3125rem;\x0a}\x0adiv.play\x20.output\x20pre\x20{\x0a\x20\x20padding:\x200;\x0a\x20\x20border-radius:\x200;\x0a}\x0adiv.play\x20.input,\x0adiv.play\x20.input\x20textarea,\x0adiv.play\x20.output,\x0adiv.play\x20.output\x20pre\x20{\x0a\x20\x20background:\x20#ffffd8;\x0a}\x0adiv.play\x20.input,\x0adiv.play\x20.output\x20{\x0a\x20\x20border:\x200.0625rem\x20solid\x20#375eab;\x0a}\x0adiv.play\x20.buttons\x20{\x0a\x20\x20float:\x20right;\x0a\x20\x20padding:\x201.25rem\x200\x200.625rem\x200;\x0a\x20\x20text-align:\x20right;\x0a}\x0adiv.play\x20.buttons\x20a\x20{\x0a\x20\x20height:\x201rem;\x0a\x20\x20margin-left:\x200.3125rem;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20cursor:\x20pointer;\x0a}\x0a.output\x20.stderr\x20{\x0a\x20\x20color:\x20#933;\x0a}\x0a.output\x20.system\x20{\x0a\x20\x20color:\x20#999;\x0a}\x0a\x0a/*\x20drop-down\x20playground\x20*/\x0adiv#playground\x20{\x0a\x20\x20/*\x20start\x20hidden;\x20revealed\x20by\x20javascript\x20*/\x0a\x20\x20display:\x20none;\x0a}\x0adiv#playground\x20{\x0a\x20\x20position:\x20absolute;\x0a\x20\x20top:\x203.938rem;\x0a\x20\x20right:\x201.25rem;\x0a\x20\x20padding:\x200\x200.625rem\x200.625rem\x200.625rem;\x0a\x20\x20z-index:\x201;\x0a\x20\x20text-align:\x20left;\x0a\x20\x20background:\x20#e0ebf5;\x0a\x0a\x20\x20border:\x200.0625rem\x20solid\x20#b0bbc5;\x0a\x20\x20border-top:\x20none;\x0a\x0a\x20\x20border-bottom-left-radius:\x200.3125rem;\x0a\x20\x20border-bottom-right-radius:\x200.3125rem;\x0a}\x0adiv#playground\x20.code\x20{\x0a\x20\x20width:\x2032.5rem;\x0a\x20\x20height:\x2012.5rem;\x0a}\x0adiv#playground\x20.output\x20{\x0a\x20\x20height:\x206.25rem;\x0a}\x0a\x0a/*\x20Inline\x20runnable\x20snippets\x20(play.js/initPlayground)\x20*/\x0a#content\x20.code\x20pre,\x0a#content\x20.playground\x20pre,\x0a#content\x20.output\x20pre\x20{\x0a\x20\x20margin:\x200;\x0a\x20\x20padding:\x200;\x0a\x20\x20background:\x20none;\x0a\x20\x20border:\x20none;\x0a\x20\x20outline:\x200\x20solid\x20transparent;\x0a\x20\x20overflow:\x20auto;\x0a}\x0a#content\x20.playground\x20.number,\x0a#content\x20.code\x20.number\x20{\x0a\x20\x20color:\x20#999;\x0a}\x0a#content\x20.code,\x0a#content\x20.playground,\x0a#content\x20.output\x20{\x0a\x20\x20width:\x20auto;\x0a\x20\x20margin:\x201.25rem;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0a#content\x20.code,\x0a#content\x20.playground\x20{\x0a\x20\x20background:\x20#e9e9e9;\x0a}\x0a#content\x20.output\x20{\x0a\x20\x20background:\x20#202020;\x0a}\x0a#content\x20.output\x20.stdout,\x0a#content\x20.output\x20pre\x20{\x0a\x20\x20color:\x20#e6e6e6;\x0a}\x0a#content\x20.output\x20.stderr,\x0a#content\x20.output\x20.error\x20{\x0a\x20\x20color:\x20rgb(244,\x2074,\x2063);\x0a}\x0a#content\x20.output\x20.system,\x0a#content\x20.output\x20.exit\x20{\x0a\x20\x20color:\x20rgb(255,\x20209,\x2077);\x0a}\x0a#content\x20.buttons\x20{\x0a\x20\x20position:\x20relative;\x0a\x20\x20float:\x20right;\x0a\x20\x20top:\x20-3.125rem;\x0a\x20\x20right:\x201.875rem;\x0a}\x0a#content\x20.output\x20.buttons\x20{\x0a\x20\x20top:\x20-3.75rem;\x0a\x20\x20right:\x200;\x0a\x20\x20height:\x200;\x0a}\x0a#content\x20.buttons\x20.kill\x20{\x0a\x20\x20display:\x20none;\x0a\x20\x20visibility:\x20hidden;\x0a}\x0aa.error\x20{\x0a\x20\x20font-weight:\x20bold;\x0a\x20\x20color:\x20white;\x0a\x20\x20background-color:\x20darkred;\x0a\x20\x20border-bottom-left-radius:\x200.25rem;\x0a\x20\x20border-bottom-right-radius:\x200.25rem;\x0a\x20\x20border-top-left-radius:\x200.25rem;\x0a\x20\x20border-top-right-radius:\x200.25rem;\x0a\x20\x20padding:\x200.125rem\x200.25rem\x200.125rem\x200.25rem;\x20/*\x20TRBL\x20*/\x0a}\x0a\x0a#heading-narrow\x20{\x0a\x20\x20display:\x20none;\x0a}\x0a\x0a.downloading\x20{\x0a\x20\x20background:\x20#f9f9be;\x0a\x20\x20padding:\x200.625rem;\x0a\x20\x20text-align:\x20center;\x0a\x20\x20border-radius:\x200.3125rem;\x0a}\x0a\x0a@media\x20(max-width:\x2058.125em)\x20{\x0a\x20\x20#heading-wide\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x20\x20#heading-narrow\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(max-width:\x2047.5em)\x20{\x0a\x20\x20.container\x20.left,\x0a\x20\x20.container\x20.right\x20{\x0a\x20\x20\x20\x20width:\x20auto;\x0a\x20\x20\x20\x20float:\x20none;\x0a\x20\x20}\x0a\x0a\x20\x20div#about\x20{\x0a\x20\x20\x20\x20max-width:\x2031.25rem;\x0a\x20\x20\x20\x20text-align:\x20center;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(min-width:\x2043.75em)\x20and\x20(max-width:\x2062.5em)\x20{\x0a\x20\x20div#menu\x20>\x20a\x20{\x0a\x20\x20\x20\x20margin:\x200.3125rem\x200;\x0a\x20\x20\x20\x20font-size:\x200.875rem;\x0a\x20\x20}\x0a\x0a\x20\x20input#search\x20{\x0a\x20\x20\x20\x20font-size:\x200.875rem;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(max-width:\x2043.75em)\x20{\x0a\x20\x20body\x20{\x0a\x20\x20\x20\x20font-size:\x200.9375rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#playground\x20{\x0a\x20\x20\x20\x20left:\x200;\x0a\x20\x20\x20\x20right:\x200;\x0a\x20\x20}\x0a\x0a\x20\x20pre,\x0a\x20\x20code\x20{\x0a\x20\x20\x20\x20font-size:\x200.866rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#page\x20>\x20.container\x20{\x0a\x20\x20\x20\x20padding:\x200\x200.625rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#topbar\x20{\x0a\x20\x20\x20\x20height:\x20auto;\x0a\x20\x20\x20\x20padding:\x200.625rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#topbar\x20>\x20.container\x20{\x0a\x20\x20\x20\x20padding:\x200;\x0a\x20\x20}\x0a\x0a\x20\x20#heading-wide\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20}\x0a\x20\x20#heading-narrow\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x0a\x20\x20.top-heading\x20{\x0a\x20\x20\x20\x20float:\x20none;\x0a\x20\x20\x20\x20display:\x20inline-block;\x0a\x20\x20\x20\x20padding:\x200.75rem;\x0a\x20\x20}\x0a\x0a\x20\x20div#menu\x20{\x0a\x20\x20\x20\x20padding:\x200;\x0a\x20\x20\x20\x20min-width:\x200;\x0a\x20\x20\x20\x20text-align:\x20left;\x0a\x20\x20\x20\x20float:\x20left;\x0a\x20\x20}\x0a\x0a\x20\x20div#menu\x20>\x20a\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20\x20\x20margin-left:\x200;\x0a\x20\x20\x20\x20margin-right:\x200;\x0a\x20\x20}\x0a\x0a\x20\x20#menu\x20.search-box\x20{\x0a\x20\x20\x20\x20display:\x20flex;\x0a\x20\x20\x20\x20width:\x20100%;\x0a\x20\x20}\x0a\x0a\x20\x20#menu-button\x20{\x0a\x20\x20\x20\x20display:\x20inline-block;\x0a\x20\x20}\x0a\x0a\x20\x20p,\x0a\x20\x20pre,\x0a\x20\x20ul,\x0a\x20\x20ol\x20{\x0a\x20\x20\x20\x20margin:\x200.625rem;\x0a\x20\x20}\x0a\x0a\x20\x20.pkg-synopsis\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x0a\x20\x20img.gopher\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20(max-width:\x2030em)\x20{\x0a\x20\x20#heading-wide\x20{\x0a\x20\x20\x20\x20display:\x20none;\x0a\x20\x20}\x0a\x20\x20#heading-narrow\x20{\x0a\x20\x20\x20\x20display:\x20block;\x0a\x20\x20}\x0a}\x0a\x0a@media\x20print\x20{\x0a\x20\x20pre\x20{\x0a\x20\x20\x20\x20background:\x20#fff;\x0a\x20\x20\x20\x20border:\x200.0625rem\x20solid\x20#bbb;\x0a\x20\x20\x20\x20white-space:\x20pre-wrap;\x0a\x20\x20}\x0a}\x0a",
}
diff --git a/godoc/static/style.css b/godoc/static/style.css
index c495bad..9e04825 100644
--- a/godoc/static/style.css
+++ b/godoc/static/style.css
@@ -404,7 +404,7 @@
margin: 0 auto 1.875rem;
}
div#gopher {
- background: url(/doc/gopher/frontpage.png) no-repeat;
+ background: url(https://golang.org/doc/gopher/frontpage.png) no-repeat;
background-position: center top;
height: 9.688rem;
max-height: 200px; /* Setting in px to prevent the gopher from blowing up in very high default font-sizes */
diff --git a/gopls/doc/commands.md b/gopls/doc/commands.md
index 1d7aa58..c9cc41a 100644
--- a/gopls/doc/commands.md
+++ b/gopls/doc/commands.md
@@ -286,4 +286,9 @@
}
```
+### ****
+Identifier: `gopls.workspace_metadata`
+
+
+
<!-- END Commands: DO NOT MANUALLY EDIT THIS SECTION -->
diff --git a/gopls/doc/emacs.md b/gopls/doc/emacs.md
index 471dbf1..486f493 100644
--- a/gopls/doc/emacs.md
+++ b/gopls/doc/emacs.md
@@ -121,9 +121,9 @@
See [settings] for information about available gopls settings.
LSP server settings are controlled by the `eglot-workspace-configuration`
-variable, which can be set either globally in `.emacs` (as below) or in a
-`.dir-locals.el` file in the project root.
+variable, which can be set either globally in `.emacs` or in a `.dir-locals.el` file in the project root.
+`.emacs`:
```elisp
(setq-default eglot-workspace-configuration
'((:gopls .
@@ -131,6 +131,12 @@
(matcher . "CaseSensitive")))))
```
+`.dir-locals.el`:
+```elisp
+((nil (eglot-workspace-configuration . ((gopls . ((staticcheck . t)
+ (matcher . "CaseSensitive")))))))
+```
+
### Organizing imports with Eglot
`gopls` provides the import-organizing functionality of `goimports` as an LSP
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index f48dc3f..f0e7314 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -209,6 +209,15 @@
* `"Fuzzy"`
Default: `"Fuzzy"`.
+##### **experimentalPostfixCompletions** *bool*
+
+**This setting is experimental and may be deleted.**
+
+experimentalPostfixCompletions enables artifical method snippets
+such as "someSlice.sort!".
+
+Default: `false`.
+
#### Diagnostic
##### **analyses** *map[string]bool*
diff --git a/gopls/go.mod b/gopls/go.mod
index 1cb1f87..e26a512 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -8,9 +8,11 @@
github.com/sergi/go-diff v1.1.0
golang.org/x/mod v0.4.1
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
- golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399
+ golang.org/x/tools v0.1.1-0.20210408020845-b261fe96097f
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
- honnef.co/go/tools v0.1.1
- mvdan.cc/gofumpt v0.1.0
+ honnef.co/go/tools v0.1.3
+ mvdan.cc/gofumpt v0.1.1
mvdan.cc/xurls/v2 v2.2.0
)
+
+replace golang.org/x/tools => ../
diff --git a/gopls/go.sum b/gopls/go.sum
index 0a53270..87e9da3 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -33,32 +33,21 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399 h1:O5bm8buX/OaamnfcBrkjn0SPUIU30jFmaS8lP+ikkxs=
-golang.org/x/tools v0.1.1-0.20210319172145-bda8f5cee399/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@@ -71,9 +60,9 @@
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-honnef.co/go/tools v0.1.1 h1:EVDuO03OCZwpV2t/tLLxPmPiomagMoBOgfPt0FM+4IY=
-honnef.co/go/tools v0.1.1/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
-mvdan.cc/gofumpt v0.1.0 h1:hsVv+Y9UsZ/mFZTxJZuHVI6shSQCtzZ11h1JEFPAZLw=
-mvdan.cc/gofumpt v0.1.0/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
+honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o=
+honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
+mvdan.cc/gofumpt v0.1.1 h1:bi/1aS/5W00E2ny5q65w9SnKpWEF/UIOqDYBILpo9rA=
+mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
diff --git a/gopls/integration/parse/parse.go b/gopls/integration/parse/parse.go
deleted file mode 100644
index 5f52128..0000000
--- a/gopls/integration/parse/parse.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2019 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 parse provides functions to parse LSP logs.
-// Fully processed logs are returned by ToRLog().
-package parse
-
-import (
- "bufio"
- "encoding/json"
- "errors"
- "fmt"
- "log"
- "os"
- "regexp"
- "strings"
-)
-
-// MsgType is the type of message.
-type MsgType int
-
-const (
- // ClRequest from client to server has method and id
- ClRequest MsgType = iota
- // ClResponse from server to client
- ClResponse
- // SvRequest from server to client, has method and id
- SvRequest
- // SvResponse from client to server
- SvResponse
- // ToServer notification has method, but no id
- ToServer
- // ToClient notification
- ToClient
- // ReportErr is an error message
- ReportErr // errors have method and id
-)
-
-// Logmsg is the type of a parsed log entry.
-type Logmsg struct {
- Type MsgType
- Method string
- ID string // for requests/responses. Client and server request ids overlap
- Elapsed string // for responses
- Hdr string // header. do we need to keep all these strings?
- Rest string // the unparsed result, with newlines or not
- Body interface{} // the parsed result
-}
-
-// ReadLogs from a file. Most users should use ToRlog().
-func ReadLogs(fname string) ([]*Logmsg, error) {
- byid := make(map[string]int)
- msgs := []*Logmsg{}
- fd, err := os.Open(fname)
- if err != nil {
- return nil, err
- }
- defer fd.Close()
- logrdr := bufio.NewScanner(fd)
- logrdr.Buffer(nil, 1<<25) // a large buffer, for safety
- logrdr.Split(scanLogs)
- for i := 0; logrdr.Scan(); i++ {
- flds := strings.SplitN(logrdr.Text(), "\n", 2)
- if len(flds) == 1 {
- flds = append(flds, "") // for Errors
- }
- msg, err := parselog(flds[0], flds[1])
- if err != nil {
- return nil, fmt.Errorf("failed to parse %q: %v", logrdr.Text(), err)
- }
- switch msg.Type {
- case ClRequest, SvRequest:
- v, err := msg.unmarshal(Requests(msg.Method))
- if err != nil {
- return nil, fmt.Errorf("%v for %s, %T", err, msg.Method, Requests(msg.Method))
- }
- msg.Body = v
- case ClResponse, SvResponse:
- v, err := msg.doresponse()
- if err != nil {
- return nil, fmt.Errorf("%v %s", err, msg.Method)
- }
- msg.Body = v
- case ToServer, ToClient:
- v, err := msg.unmarshal(Notifs(msg.Method))
- if err != nil && Notifs(msg.Method) != nil {
- return nil, fmt.Errorf("%s/%T: %v", msg.Method, Notifs(msg.Method), err)
- }
- msg.Body = v
- case ReportErr:
- msg.Body = msg.Rest // save cause
- }
- byid[msg.ID]++
- msgs = append(msgs, msg)
- }
- if err = logrdr.Err(); err != nil {
- return msgs, err
- }
- return msgs, nil
-}
-
-// parse a single log message, given first line, and the rest
-func parselog(first, rest string) (*Logmsg, error) {
- if strings.HasPrefix(rest, "Params: ") {
- rest = rest[8:]
- } else if strings.HasPrefix(rest, "Result: ") {
- rest = rest[8:]
- }
- msg := &Logmsg{Hdr: first, Rest: rest}
- fixid := func(s string) string {
- // emacs does (n)., gopls does (n)'.
- s = strings.Trim(s, "()'.{)")
- return s
- }
- flds := strings.Fields(first)
- chk := func(s string, n int) bool { return strings.Contains(first, s) && len(flds) == n }
- // gopls and emacs differ in how they report elapsed time
- switch {
- case chk("Sending request", 9):
- msg.Type = ClRequest
- msg.Method = flds[6][1:]
- msg.ID = fixid(flds[8][:len(flds[8])-2])
- case chk("Received response", 11):
- msg.Type = ClResponse
- msg.Method = flds[6][1:]
- msg.ID = fixid(flds[8])
- msg.Elapsed = flds[10]
- case chk("Received request", 9):
- msg.Type = SvRequest
- msg.Method = flds[6][1:]
- msg.ID = fixid(flds[8])
- case chk("Sending response", 11), // gopls
- chk("Sending response", 13): // emacs
- msg.Type = SvResponse
- msg.Method = flds[6][1:]
- msg.ID = fixid(flds[8][:len(flds[8])-1])
- msg.Elapsed = flds[10]
- case chk("Sending notification", 7):
- msg.Type = ToServer
- msg.Method = strings.Trim(flds[6], ".'")
- if len(flds) == 9 {
- log.Printf("len=%d method=%s %q", len(flds), msg.Method, first)
- }
- case chk("Received notification", 7):
- msg.Type = ToClient
- msg.Method = flds[6][1 : len(flds[6])-2]
- case strings.HasPrefix(first, "[Error - "):
- msg.Type = ReportErr
- both := flds[5]
- idx := strings.Index(both, "#") // relies on ID.Number
- msg.Method = both[:idx]
- msg.ID = fixid(both[idx+1:])
- msg.Rest = strings.Join(flds[6:], " ")
- msg.Rest = `"` + msg.Rest + `"`
- default:
- return nil, fmt.Errorf("surprise, first=%q with %d flds", first, len(flds))
- }
- return msg, nil
-}
-
-// unmarshal into a proposed type
-func (l *Logmsg) unmarshal(p interface{}) (interface{}, error) {
- r := []byte(l.Rest)
- if err := json.Unmarshal(r, p); err != nil {
- // need general alternatives, but for now
- // if p is *[]foo and rest is {}, return an empty p (or *p?)
- // or, cheat:
- if l.Rest == "{}" {
- return nil, nil
- }
- return nil, err
- }
- return p, nil
-}
-
-func (l *Logmsg) doresponse() (interface{}, error) {
- for _, x := range Responses(l.Method) {
- v, err := l.unmarshal(x)
- if err == nil {
- return v, nil
- }
- if x == nil {
- return new(interface{}), nil
- }
- }
- // failure!
- rr := Responses(l.Method)
- for _, x := range rr {
- log.Printf("tried %T", x)
- }
- log.Fatalf("(%d) doresponse failed for %s %q", len(rr), l.Method, l.Rest)
- return nil, nil
-}
-
-// be a little forgiving in separating log records
-var recSep = regexp.MustCompile("\n\n\n|\r\n\r\n\r\n")
-
-// return offset of start of next record, contents of record, error
-func scanLogs(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
- got := recSep.FindIndex(b)
- if got == nil {
- if atEOF && len(b) > 0 {
- return 0, nil, errors.New("malformed log: all logs should end with a separator")
- }
- return 0, nil, nil
- }
- return got[1], b[:got[0]], nil
-}
-
-// String returns a user-useful versin of a Direction
-func (d MsgType) String() string {
- switch d {
- case ClRequest:
- return "clrequest"
- case ClResponse:
- return "clresponse"
- case SvRequest:
- return "svrequest"
- case SvResponse:
- return "svresponse"
- case ToServer:
- return "toserver"
- case ToClient:
- return "toclient"
- case ReportErr:
- return "reporterr"
- }
- return fmt.Sprintf("dirname: %d unknown", d)
-}
diff --git a/gopls/integration/parse/protocol.go b/gopls/integration/parse/protocol.go
deleted file mode 100644
index d1e90ed..0000000
--- a/gopls/integration/parse/protocol.go
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright 2019 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 parse
-
-import (
- "log"
-
- p "golang.org/x/tools/internal/lsp/protocol"
-)
-
-// Requests and notifications are fixed types
-// Responses may be one of several types
-
-// Requests returns a pointer to a type suitable for Unmarshal
-func Requests(m string) interface{} {
- // these are in the documentation's order
- switch m {
- case "initialize":
- return new(p.InitializeParams)
- case "shutdown":
- return new(struct{})
- case "window/showMessgeRequest":
- return new(p.ShowMessageRequestParams)
- case "client/registerCapability":
- return new(p.RegistrationParams)
- case "client/unregisterCapability":
- return new(p.UnregistrationParams)
- case "workspace/workspaceFolders":
- return nil
- case "workspace/configuration":
- return new(p.ConfigurationParams)
- case "workspace/symbol":
- return new(p.WorkspaceSymbolParams)
- case "workspace/executeCommand":
- return new(p.ExecuteCommandParams)
- case "workspace/applyEdit":
- return new(p.ApplyWorkspaceEditParams)
- case "textDocument/willSaveWaitUntil":
- return new(p.WillSaveTextDocumentParams)
- case "textDocument/completion":
- return new(p.CompletionParams)
- case "completionItem/resolve":
- return new(p.CompletionItem)
- case "textDocument/hover":
- return new(p.TextDocumentPositionParams)
- case "textDocument/signatureHelp":
- return new(p.TextDocumentPositionParams)
- case "textDocument/declaration":
- return new(p.TextDocumentPositionParams)
- case "textDocument/definition":
- return new(p.TextDocumentPositionParams)
- case "textDocument/typeDefinition":
- return new(p.TextDocumentPositionParams)
- case "textDocument/implementation":
- return new(p.TextDocumentPositionParams)
- case "textDocument/references":
- return new(p.ReferenceParams)
- case "textDocument/documentHighlight":
- return new(p.TextDocumentPositionParams)
- case "textDocument/documentSymbol":
- return new(p.DocumentSymbolParams)
- case "textDocument/codeAction":
- return new(p.CodeActionParams)
- case "textDocument/codeLens":
- return new(p.CodeLensParams)
- case "codeLens/resolve":
- return new(p.CodeLens)
- case "textDocument/documentLink":
- return new(p.DocumentLinkParams)
- case "documentLink/resolve":
- return new(p.DocumentLink)
- case "textDocument/documentColor":
- return new(p.DocumentColorParams)
- case "textDocument/colorPressentation":
- return new(p.ColorPresentationParams)
- case "textDocument/formatting":
- return new(p.DocumentFormattingParams)
- case "textDocument/rangeFormatting":
- return new(p.DocumentRangeFormattingParams)
- case "textDocument/typeFormatting":
- return new(p.DocumentOnTypeFormattingParams)
- case "textDocument/rename":
- return new(p.RenameParams)
- case "textDocument/prepareRename":
- return new(p.TextDocumentPositionParams)
- case "textDocument/foldingRange":
- return new(p.FoldingRangeParams)
- case "textDocument/incomingCalls":
- return new(p.CallHierarchyIncomingCallsParams)
- case "textDocument/outgoingCalls":
- return new(p.CallHierarchyOutgoingCallsParams)
- }
- log.Fatalf("request(%s) undefined", m)
- return ""
-}
-
-// Notifs returns a pointer to a type suitable for Unmarshal
-func Notifs(m string) interface{} {
- switch m {
- case "$/cancelRequest":
- return new(p.CancelParams)
- case "$/setTraceNotification":
- return new(struct{ Value string })
- case "client/registerCapability": // why is this a notification? (serer->client rpc)
- return new(p.RegistrationParams)
- case "initialized":
- return new(p.InitializedParams)
- case "exit":
- return nil
- case "window/showMessage":
- return new(p.ShowMessageParams)
- case "window/logMessage":
- return new(p.LogMessageParams)
- case "telemetry/event":
- return new(interface{}) // any
- case "workspace/didChangeWorkspaceFolders":
- return new(p.DidChangeWorkspaceFoldersParams)
- case "workspace/didChangeConfiguration":
- return new(p.DidChangeConfigurationParams)
- case "workspace/didChangeWatchedFiles":
- return new(p.DidChangeWatchedFilesParams)
- case "textDocument/didOpen":
- return new(p.DidOpenTextDocumentParams)
- case "textDocument/didChange":
- return new(p.DidChangeTextDocumentParams)
- case "textDocument/willSave":
- return new(p.WillSaveTextDocumentParams)
- case "textDocument/didSave":
- return new(p.DidSaveTextDocumentParams)
- case "textDocument/didClose":
- return new(p.DidCloseTextDocumentParams)
- case "textDocument/willClose":
- return new(p.DidCloseTextDocumentParams)
- case "textDocument/publishDiagnostics":
- return new(p.PublishDiagnosticsParams)
- }
- log.Fatalf("notif(%s) undefined", m)
- return ""
-}
-
-// Responses returns a slice of types, one of which should be
-// suitable for Unmarshal
-func Responses(m string) []interface{} {
- switch m {
- case "initialize":
- return []interface{}{new(p.InitializeResult)}
- case "shutdown":
- return []interface{}{nil}
- case "window/showMessageRequest":
- return []interface{}{new(p.MessageActionItem), nil}
- case "client/registerCapability":
- return []interface{}{nil}
- case "client/unregisterCapability":
- return []interface{}{nil}
- case "workspace/workspaceFolder":
- return []interface{}{new([]p.WorkspaceFolder), nil}
- case "workspace/configuration":
- return []interface{}{new([]interface{}), new(interface{})}
- case "workspace/symbol":
- return []interface{}{new([]p.SymbolInformation), nil}
- case "workspace/executeCommand":
- return []interface{}{new(interface{}), nil}
- case "workspace/applyEdit":
- return []interface{}{new(p.ApplyWorkspaceEditResponse)}
- case "textDocument/willSaveWaitUntil":
- return []interface{}{new([]p.TextEdit), nil}
- case "textDocument/completion":
- return []interface{}{new(p.CompletionList), new([]p.CompletionItem), nil}
- case "completionItem/resolve":
- return []interface{}{new(p.CompletionItem)}
- case "textDocument/hover":
- return []interface{}{new(p.Hover), nil}
- case "textDocument/signatureHelp":
- return []interface{}{new(p.SignatureHelp), nil}
- case "textDocument/declaration":
- return []interface{}{new(p.Location), new([]p.Location), new([]p.LocationLink), nil}
- case "textDocument/definition":
- return []interface{}{new([]p.Location), new([]p.Location), new([]p.LocationLink), nil}
- case "textDocument/typeDefinition":
- return []interface{}{new([]p.Location), new([]p.LocationLink), new(p.Location), nil}
- case "textDocument/implementation":
- return []interface{}{new(p.Location), new([]p.Location), new([]p.LocationLink), nil}
- case "textDocument/references":
- return []interface{}{new([]p.Location), nil}
- case "textDocument/documentHighlight":
- return []interface{}{new([]p.DocumentHighlight), nil}
- case "textDocument/documentSymbol":
- return []interface{}{new([]p.DocumentSymbol), new([]p.SymbolInformation), nil}
- case "textDocument/codeAction":
- return []interface{}{new([]p.CodeAction), new(p.Command), nil}
- case "textDocument/codeLens":
- return []interface{}{new([]p.CodeLens), nil}
- case "codelens/resolve":
- return []interface{}{new(p.CodeLens)}
- case "textDocument/documentLink":
- return []interface{}{new([]p.DocumentLink), nil}
- case "documentLink/resolve":
- return []interface{}{new(p.DocumentLink)}
- case "textDocument/documentColor":
- return []interface{}{new([]p.ColorInformation)}
- case "textDocument/colorPresentation":
- return []interface{}{new([]p.ColorPresentation)}
- case "textDocument/formatting":
- return []interface{}{new([]p.TextEdit), nil}
- case "textDocument/rangeFormatting":
- return []interface{}{new([]p.TextEdit), nil}
- case "textDocument/onTypeFormatting":
- return []interface{}{new([]p.TextEdit), nil}
- case "textDocument/rename":
- return []interface{}{new(p.WorkspaceEdit), nil}
- case "textDocument/prepareRename":
- return []interface{}{new(p.Range), nil}
- case "textDocument/foldingRange":
- return []interface{}{new([]p.FoldingRange), nil}
- case "callHierarchy/incomingCalls":
- return []interface{}{new([]p.CallHierarchyIncomingCall), nil}
- case "callHierarchy/outgoingCalls":
- return []interface{}{new([]p.CallHierarchyOutgoingCall), nil}
- }
- log.Fatalf("responses(%q) undefined", m)
- return nil
-}
-
-// Msgtype given method names. Note that mSrv|mCl is possible
-type Msgtype int
-
-const (
- // Mnot for notifications
- Mnot Msgtype = 1
- // Mreq for requests
- Mreq Msgtype = 2
- // Msrv for messages from the server
- Msrv Msgtype = 4
- // Mcl for messages from the client
- Mcl Msgtype = 8
-)
-
-// IsNotify says if the message is a notification
-func IsNotify(msg string) bool {
- m, ok := fromMethod[msg]
- if !ok {
- log.Fatalf("%q", msg)
- }
- return m&Mnot != 0
-}
-
-// FromServer says if the message is from the server
-func FromServer(msg string) bool {
- m, ok := fromMethod[msg]
- if !ok {
- log.Fatalf("%q", msg)
- }
- return m&Msrv != 0
-}
-
-// FromClient says if the message is from the client
-func FromClient(msg string) bool {
- m, ok := fromMethod[msg]
- if !ok {
- log.Fatalf("%q", msg)
- }
- return m&Mcl != 0
-}
-
-// rpc name to message type
-var fromMethod = map[string]Msgtype{
- "$/cancelRequest": Mnot | Msrv | Mcl,
- "initialize": Mreq | Msrv,
- "initialized": Mnot | Mcl,
- "shutdown": Mreq | Mcl,
- "exit": Mnot | Mcl,
- "window/showMessage": Mreq | Msrv,
- "window/logMessage": Mnot | Msrv,
- "telemetry'event": Mnot | Msrv,
- "client/registerCapability": Mreq | Msrv,
- "client/unregisterCapability": Mreq | Msrv,
- "workspace/workspaceFolders": Mreq | Msrv,
- "workspace/workspaceDidChangeWorkspaceFolders": Mnot | Mcl,
- "workspace/didChangeConfiguration": Mnot | Mcl,
- "workspace/configuration": Mreq | Msrv,
- "workspace/didChangeWatchedFiles": Mnot | Mcl,
- "workspace/symbol": Mreq | Mcl,
- "workspace/executeCommand": Mreq | Mcl,
- "workspace/applyEdit": Mreq | Msrv,
- "textDocument/didOpen": Mnot | Mcl,
- "textDocument/didChange": Mnot | Mcl,
- "textDocument/willSave": Mnot | Mcl,
- "textDocument/willSaveWaitUntil": Mreq | Mcl,
- "textDocument/didSave": Mnot | Mcl,
- "textDocument/didClose": Mnot | Mcl,
- "textDocument/publishDiagnostics": Mnot | Msrv,
- "textDocument/completion": Mreq | Mcl,
- "completionItem/resolve": Mreq | Mcl,
- "textDocument/hover": Mreq | Mcl,
- "textDocument/signatureHelp": Mreq | Mcl,
- "textDocument/declaration": Mreq | Mcl,
- "textDocument/definition": Mreq | Mcl,
- "textDocument/typeDefinition": Mreq | Mcl,
- "textDocument/implementation": Mreq | Mcl,
- "textDocument/references": Mreq | Mcl,
- "textDocument/documentHighlight": Mreq | Mcl,
- "textDocument/documentSymbol": Mreq | Mcl,
- "textDocument/codeAction": Mreq | Mcl,
- "textDocument/codeLens": Mreq | Mcl,
- "codeLens/resolve": Mreq | Mcl,
- "textDocument/documentLink": Mreq | Mcl,
- "documentLink/resolve": Mreq | Mcl,
- "textDocument/documentColor": Mreq | Mcl,
- "textDocument/colorPresentation": Mreq | Mcl,
- "textDocument/formatting": Mreq | Mcl,
- "textDocument/rangeFormatting": Mreq | Mcl,
- "textDocument/onTypeFormatting": Mreq | Mcl,
- "textDocument/rename": Mreq | Mcl,
- "textDocument/prepareRename": Mreq | Mcl,
- "textDocument/foldingRange": Mreq | Mcl,
- "callHierarchy/incomingCalls": Mreq | Mcl,
- "callHierarchy/outgoingCalls": Mreq | Mcl,
-}
diff --git a/gopls/integration/parse/rlog.go b/gopls/integration/parse/rlog.go
deleted file mode 100644
index aebb1e2..0000000
--- a/gopls/integration/parse/rlog.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2019 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 parse
-
-import (
- "fmt"
- "log"
- "strings"
-)
-
-// Rlog contains the processed logs
-type Rlog struct {
- Logs []*Logmsg // In the order in the log file
- ServerCall map[string]*Logmsg // ID->Request, client->server
- ServerReply map[string]*Logmsg // ID->Response, server->client (includes Errors)
- ClientCall map[string]*Logmsg
- ClientReply map[string]*Logmsg
- ClientNotifs []*Logmsg
- ServerNotifs []*Logmsg
- Histogram *LogHist
-}
-
-func newRlog(x []*Logmsg) *Rlog {
- return &Rlog{Logs: x,
- ServerCall: make(map[string]*Logmsg),
- ServerReply: make(map[string]*Logmsg),
- ClientCall: make(map[string]*Logmsg),
- ClientReply: make(map[string]*Logmsg),
- ClientNotifs: []*Logmsg{},
- ServerNotifs: []*Logmsg{},
- Histogram: &LogHist{},
- }
-}
-
-// Counts returns a one-line summary of an Rlog
-func (r *Rlog) Counts() string {
- return fmt.Sprintf("logs:%d srvC:%d srvR:%d clC:%d clR:%d clN:%d srvN:%d",
- len(r.Logs),
- len(r.ServerCall), len(r.ServerReply), len(r.ClientCall), len(r.ClientReply),
- len(r.ClientNotifs), len(r.ServerNotifs))
-}
-
-// ToRlog reads a log file and returns a *Rlog
-func ToRlog(fname string) (*Rlog, error) {
- x, err := ReadLogs(fname)
- if err != nil {
- return nil, err
- }
- ans := newRlog(x)
- for _, l := range x {
- switch l.Type {
- case ClRequest:
- ans.ServerCall[l.ID] = l
- case ClResponse:
- ans.ServerReply[l.ID] = l
- if l.Type != ReportErr {
- n := 0
- fmt.Sscanf(l.Elapsed, "%d", &n)
- ans.Histogram.add(n)
- }
- case SvRequest:
- ans.ClientCall[l.ID] = l
- case SvResponse:
- ans.ClientReply[l.ID] = l
- case ToClient:
- ans.ClientNotifs = append(ans.ClientNotifs, l)
- case ToServer:
- ans.ServerNotifs = append(ans.ServerNotifs, l)
- case ReportErr:
- ans.ServerReply[l.ID] = l
- l.Method = ans.ServerCall[l.ID].Method // Method not in log message
- default:
- log.Fatalf("eh? %s/%s (%s)", l.Type, l.Method, l.ID)
- }
- }
- return ans, nil
-}
-
-// LogHist gets ints, and puts them into buckets:
-// <=10, <=30, 100, 300, 1000, ...
-// It produces a historgram of elapsed times in milliseconds
-type LogHist struct {
- cnts []int
-}
-
-func (l *LogHist) add(n int) {
- if n < 0 {
- n = 0
- }
- bucket := 0
- for ; n > 0; n /= 10 {
- if n < 10 {
- break
- }
- if n < 30 {
- bucket++
- break
- }
- bucket += 2
- }
- if len(l.cnts) <= bucket {
- for j := len(l.cnts); j < bucket+10; j++ {
- l.cnts = append(l.cnts, 0)
- }
- }
- l.cnts[bucket]++
-}
-
-// String returns a string describing a histogram
-func (l *LogHist) String() string {
- top := len(l.cnts) - 1
- for ; top > 0 && l.cnts[top] == 0; top-- {
- }
- labs := []string{"10", "30"}
- out := strings.Builder{}
- out.WriteByte('[')
- for i := 0; i <= top; i++ {
- label := labs[i%2]
- labs[i%2] += "0"
- fmt.Fprintf(&out, "%s:%d ", label, l.cnts[i])
- }
- out.WriteByte(']')
- return out.String()
-}
diff --git a/gopls/integration/replay/README.md b/gopls/integration/replay/README.md
deleted file mode 100644
index 7c32277..0000000
--- a/gopls/integration/replay/README.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Replaying Logs
-
-The LSP log replayer takes a log from a gopls session, starts up an instance of gopls,
-and tries to replay the session. It produces a log from the replayed session and reports
-some comparative statistics of the two logs.
-
-```replay -log <logfile>```
-
-The `logfile` should be the log produced by gopls. It will have a name like
-`/tmp/gopls-89775` or, on a Mac, `$TMPDIR/gopls-29388`.
-
-If `replay` cannot find a copy of gopls to execute, use `-cmd <path to gopls>`.
-It looks in the same places where `go install` would put its output,
-namely `$GOBIN/gopls`, `$GOPATH/bin/gopls`, `$HOME/go/bin/gopls`.
-
-The log for the replayed session is saved in `/tmp/seen`.
-
-There is also a boolean argument `-cmp` which compares the log file
-with `/tmp/seen` without invoking gopls and rerunning the session.
-
-The output is fairly cryptic, and generated by logging. Ideas for better output would be welcome.
-Here's an example, with intermingled comments:
-
-```
-main.go:50: old 1856, hist:[10:177 30:1 100:0 300:3 1000:4 ]
-```
-This says that the original log had 1856 records in it. The histogram is
-counting how long RPCs took, in milliseconds. In this case 177 took no more
-than 10ms, and 4 took between 300ms and 1000ms.
-```
-main.go:53: calling mimic
-main.go:293: mimic 1856
-```
-This is a reminder that it's replaying in a new session, with a log file
-containing 1856 records
-```
-main.go:61: new 1846, hist:[10:181 30:1 100:1 300:1 1000:1 ]
-```
-The new session produced 1846 log records (that's 10 fewer),
-and a vaguely similar histogram.
-```
-main.go:96: old: clrequest:578 clresponse:185 svrequest:2 svresponse:2 toserver:244 toclient:460 reporterr:385
-main.go:96: new: clrequest:571 clresponse:185 svrequest:2 svresponse:2 toserver:241 toclient:460 reporterr:385
-```
-The first line is for the original log, the second for the new log. The new log has 7 fewer RPC requests
-from the client *clrequest* (578 vs 571), the same number of client responses *clresponse*, 3 fewer
-notifications *toserver* from the client, the same number from the server *toclient* to the client, and
-the same number of errors *reporterr*. (That's mysterious, but a look at the ends of the log files shows
-that the original session ended with several RPCs that don't show up, for whatever reason, in the new session.)
-
-Finally, there are counts of the various notifications seen, in the new log and the old log, and
-which direction they went. (The 3 fewer notifications in the summary above can be seen here to be from cancels
-and a didChange.)
-```
-main.go:107: counts of notifications
-main.go:110: '$/cancelRequest'. new toserver 1
-main.go:110: '$/cancelRequest'. old toserver 3
-main.go:110: 'initialized'. new toserver 1
-main.go:110: 'initialized'. old toserver 1
-main.go:110: 'textDocument/didChange'. new toserver 231
-main.go:110: 'textDocument/didChange'. old toserver 232
-main.go:110: 'textDocument/didOpen'. new toserver 1
-main.go:110: 'textDocument/didOpen'. old toserver 1
-main.go:110: 'textDocument/didSave'. new toserver 7
-main.go:110: 'textDocument/didSave'. old toserver 7
-main.go:110: 'textDocument/publishDiagnostics'. new toclient 182
-main.go:110: 'textDocument/publishDiagnostics'. old toclient 182
-main.go:110: 'window/logMessage'. new toclient 278
-main.go:110: 'window/logMessage'. old toclient 278
-```
-### Caveats
-Replay cannot restore the exact environment gopls saw for the original session.
-For instance, the first didOpen message in the new session will see the file
-as it was left by the original session.
-
-Gopls invokes various tools, and the environment they see could have changed too.
-
-Replay will use the gopls it finds (or is given). It has no way of using
-the same version that created the original session.
diff --git a/gopls/integration/replay/main.go b/gopls/integration/replay/main.go
deleted file mode 100644
index 35cd1d5..0000000
--- a/gopls/integration/replay/main.go
+++ /dev/null
@@ -1,292 +0,0 @@
-// Copyright 2019 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.
-
-// Replay logs. See README.md
-package main
-
-import (
- "bufio"
- "context"
- "flag"
- "fmt"
- exec "golang.org/x/sys/execabs"
- "log"
- "os"
- "sort"
- "strconv"
- "strings"
-
- "golang.org/x/tools/gopls/integration/parse"
- "golang.org/x/tools/internal/fakenet"
- "golang.org/x/tools/internal/jsonrpc2"
- p "golang.org/x/tools/internal/lsp/protocol"
-)
-
-var (
- command = flag.String("cmd", "", "location of server to send to, looks for gopls")
- cmp = flag.Bool("cmp", false, "only compare log and /tmp/seen")
- logrdr *bufio.Scanner
- msgs []*parse.Logmsg
- // requests and responses/errors, by id
- clreq = make(map[string]*parse.Logmsg)
- clresp = make(map[string]*parse.Logmsg)
- svreq = make(map[string]*parse.Logmsg)
- svresp = make(map[string]*parse.Logmsg)
-)
-
-func main() {
- log.SetFlags(log.Lshortfile)
- flag.Usage = func() {
- fmt.Fprintln(flag.CommandLine.Output(), "replay [options] <logfile>")
- flag.PrintDefaults()
- }
- flag.Parse()
- if flag.NArg() != 1 {
- flag.Usage()
- os.Exit(2)
- }
- logf := flag.Arg(0)
-
- orig, err := parse.ToRlog(logf)
- if err != nil {
- log.Fatalf("error parsing logfile %q: %v", logf, err)
- }
- ctx := context.Background()
- msgs = orig.Logs
- log.Printf("old %d, hist:%s", len(msgs), orig.Histogram)
-
- if !*cmp {
- log.Print("calling mimic")
- mimic(ctx)
- }
- seen, err := parse.ToRlog("/tmp/seen")
- if err != nil {
- log.Fatal(err)
- }
- newMsgs := seen.Logs
- log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
-
- ok := make(map[string]int)
- f := func(x []*parse.Logmsg, label string, diags map[p.DocumentURI][]p.Diagnostic) {
- counts := make(map[parse.MsgType]int)
- for _, l := range x {
- if l.Method == "window/logMessage" {
- // don't care
- //continue
- }
- if l.Method == "textDocument/publishDiagnostics" {
- v, ok := l.Body.(*p.PublishDiagnosticsParams)
- if !ok {
- log.Fatalf("got %T expected PublishDiagnosticsParams", l.Body)
- }
- diags[v.URI] = v.Diagnostics
- }
- counts[l.Type]++
- // notifications only
- if l.Type != parse.ToServer && l.Type != parse.ToClient {
- continue
- }
- s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Type)
- if i := strings.Index(s, "notification"); i != -1 {
- s = s[i+12:]
- }
- if len(s) > 120 {
- s = s[:120]
- }
- ok[s]++
- }
- msg := ""
- for i := parse.ClRequest; i <= parse.ReportErr; i++ {
- msg += fmt.Sprintf("%s:%d ", i, counts[i])
- }
- log.Printf("%s: %s", label, msg)
- }
- mdiags := make(map[p.DocumentURI][]p.Diagnostic)
- f(msgs, "old", mdiags)
- vdiags := make(map[p.DocumentURI][]p.Diagnostic)
- f(newMsgs, "new", vdiags)
- buf := []string{}
- for k := range ok {
- buf = append(buf, fmt.Sprintf("%s %d", k, ok[k]))
- }
- if len(buf) > 0 {
- log.Printf("counts of notifications")
- sort.Strings(buf)
- for _, k := range buf {
- log.Print(k)
- }
- }
- buf = buf[0:0]
- for k, v := range mdiags {
- va := vdiags[k]
- if len(v) != len(va) {
- buf = append(buf, fmt.Sprintf("new has %d, old has %d for %s",
- len(va), len(v), k))
- }
- }
- for ka := range vdiags {
- if _, ok := mdiags[ka]; !ok {
- buf = append(buf, fmt.Sprintf("new diagnostics, but no old ones, for %s",
- ka))
- }
- }
- if len(buf) > 0 {
- log.Print("diagnostics differ:")
- for _, s := range buf {
- log.Print(s)
- }
- }
-}
-
-func send(ctx context.Context, l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
- if id == nil {
- // need to use the number version of ID
- n, err := strconv.Atoi(l.ID)
- if err != nil {
- n = 0
- }
- nid := jsonrpc2.NewIntID(int64(n))
- id = &nid
- }
- var msg jsonrpc2.Message
- var err error
- switch l.Type {
- case parse.ClRequest:
- msg, err = jsonrpc2.NewCall(*id, l.Method, l.Body)
- case parse.SvResponse:
- msg, err = jsonrpc2.NewResponse(*id, l.Body, nil)
- case parse.ToServer:
- msg, err = jsonrpc2.NewNotification(l.Method, l.Body)
- default:
- log.Fatalf("sending %s", l.Type)
- }
- if err != nil {
- log.Fatal(err)
- }
- stream.Write(ctx, msg)
-}
-
-func respond(ctx context.Context, c *jsonrpc2.Call, stream jsonrpc2.Stream) {
- // c is a server request
- // pick out the id, and look for the response in msgs
- id := c.ID()
- idstr := fmt.Sprint(id)
- for _, l := range msgs {
- if l.ID == idstr && l.Type == parse.SvResponse {
- // check that the methods match?
- // need to send back the same ID we got.
- send(ctx, l, stream, &id)
- return
- }
- }
- log.Fatalf("no response found %q %+v %+v", c.Method(), c.ID(), c)
-}
-
-func findgopls() string {
- totry := [][]string{{"GOBIN", "/gopls"}, {"GOPATH", "/bin/gopls"}, {"HOME", "/go/bin/gopls"}}
- // looks in the places go install would install:
- // GOBIN, else GOPATH/bin, else HOME/go/bin
- ok := func(s string) bool {
- fd, err := os.Open(s)
- if err != nil {
- return false
- }
- fi, err := fd.Stat()
- if err != nil {
- return false
- }
- return fi.Mode()&0111 != 0
- }
- for _, t := range totry {
- g := os.Getenv(t[0])
- if g != "" && ok(g+t[1]) {
- gopls := g + t[1]
- log.Printf("using gopls at %s", gopls)
- return gopls
- }
- }
- log.Fatal("could not find gopls")
- return ""
-}
-
-func mimic(ctx context.Context) {
- log.Printf("mimic %d", len(msgs))
- if *command == "" {
- *command = findgopls()
- }
- cmd := exec.Command(*command, "-logfile", "/tmp/seen", "-rpc.trace")
- toServer, err := cmd.StdinPipe()
- if err != nil {
- log.Fatal(err)
- }
- fromServer, err := cmd.StdoutPipe()
- if err != nil {
- log.Fatal(err)
- }
- err = cmd.Start()
- if err != nil {
- log.Fatal(err)
- }
- conn := fakenet.NewConn("stdio", fromServer, toServer)
- stream := jsonrpc2.NewHeaderStream(conn)
- rchan := make(chan jsonrpc2.Message, 10) // do we need buffering?
- rdr := func() {
- for {
- msg, _, err := stream.Read(ctx)
- if err != nil {
- rchan <- nil // close it instead?
- return
- }
- rchan <- msg
- }
- }
- go rdr()
- // send as many as possible: all clrequests and toservers up to a clresponse
- // and loop
- seenids := make(map[string]bool) // id's that have been responded to:
-big:
- for _, l := range msgs {
- switch l.Type {
- case parse.ToServer: // just send these as we get to them
- send(ctx, l, stream, nil)
- case parse.ClRequest:
- send(ctx, l, stream, nil) // for now, wait for a response, to make sure code is ok
- fallthrough
- case parse.ClResponse, parse.ReportErr: // don't go past these until they're received
- if seenids[l.ID] {
- break // onward, as it has been received already
- }
- done:
- for {
- msg := <-rchan
- if msg == nil {
- break big
- }
- // if it's svrequest, do something
- // if it's clresponse or reporterr, add to seenids, and if it
- // is l.id, break out of the loop, and continue the outer loop
-
- switch msg := msg.(type) {
- case *jsonrpc2.Call:
- if parse.FromServer(msg.Method()) {
- respond(ctx, msg, stream)
- continue done // still waiting
- }
- case *jsonrpc2.Response:
- id := fmt.Sprint(msg.ID())
- seenids[id] = true
- if id == l.ID {
- break done
- }
- }
- }
- case parse.SvRequest: // not ours to send
- continue
- case parse.SvResponse: // sent by us, if the request arrives
- continue
- case parse.ToClient: // we don't send these
- continue
- }
- }
-}
diff --git a/gopls/internal/coverage/coverage.go b/gopls/internal/coverage/coverage.go
new file mode 100644
index 0000000..7bb3640
--- /dev/null
+++ b/gopls/internal/coverage/coverage.go
@@ -0,0 +1,261 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go.1.16
+// +build go.1.16
+
+// Running this program in the tools directory will produce a coverage file /tmp/cover.out
+// and a coverage report for all the packages under internal/lsp, accumulated by all the tests
+// under gopls.
+//
+// -o controls where the coverage file is written, defaulting to /tmp/cover.out
+// -i coverage-file will generate the report from an existing coverage file
+// -v controls verbosity (0: only report coverage, 1: report as each directory is finished,
+// 2: report on each test, 3: more details, 4: too much)
+// -t tests only tests packages in the given comma-separated list of directories in gopls.
+// The names should start with ., as in ./internal/regtest/bench
+// -run tests. If set, -run tests is passed on to the go test command.
+//
+// Despite gopls' use of goroutines, the counts are almost deterministic.
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ "golang.org/x/tools/cover"
+)
+
+var (
+ proFile = flag.String("i", "", "existing profile file")
+ outFile = flag.String("o", "/tmp/cover.out", "where to write the coverage file")
+ verbose = flag.Int("v", 0, "how much detail to print as tests are running")
+ tests = flag.String("t", "", "list of tests to run")
+ run = flag.String("run", "", "value of -run to pass to go test")
+)
+
+func main() {
+ log.SetFlags(log.Lshortfile)
+ flag.Parse()
+
+ if *proFile != "" {
+ report(*proFile)
+ return
+ }
+
+ checkCwd()
+ // find the packages under gopls containing tests
+ tests := listDirs("gopls")
+ tests = onlyTests(tests)
+ tests = realTestName(tests)
+
+ // report coverage for packages under internal/lsp
+ parg := "golang.org/x/tools/internal/lsp/..."
+
+ accum := []string{}
+ seen := make(map[string]bool)
+ now := time.Now()
+ for _, toRun := range tests {
+ if excluded(toRun) {
+ continue
+ }
+ x := runTest(toRun, parg)
+ if *verbose > 0 {
+ fmt.Printf("finished %s %.1fs\n", toRun, time.Since(now).Seconds())
+ }
+ lines := bytes.Split(x, []byte{'\n'})
+ for _, l := range lines {
+ if len(l) == 0 {
+ continue
+ }
+ if !seen[string(l)] {
+ // not accumulating counts, so only works for mode:set
+ seen[string(l)] = true
+ accum = append(accum, string(l))
+ }
+ }
+ }
+ sort.Strings(accum[1:])
+ if err := os.WriteFile(*outFile, []byte(strings.Join(accum, "\n")), 0644); err != nil {
+ log.Print(err)
+ }
+ report(*outFile)
+}
+
+type result struct {
+ Time time.Time
+ Test string
+ Action string
+ Package string
+ Output string
+ Elapsed float64
+}
+
+func runTest(tName, parg string) []byte {
+ args := []string{"test", "-short", "-coverpkg", parg, "-coverprofile", *outFile,
+ "-json"}
+ if *run != "" {
+ args = append(args, fmt.Sprintf("-run=%s", *run))
+ }
+ args = append(args, tName)
+ cmd := exec.Command("go", args...)
+ cmd.Dir = "./gopls"
+ ans, err := cmd.Output()
+ if *verbose > 1 {
+ got := strings.Split(string(ans), "\n")
+ for _, g := range got {
+ if g == "" {
+ continue
+ }
+ var m result
+ if err := json.Unmarshal([]byte(g), &m); err != nil {
+ log.Printf("%T/%v", err, err) // shouldn't happen
+ continue
+ }
+ maybePrint(m)
+ }
+ }
+ if err != nil {
+ log.Printf("%s: %q, cmd=%s", tName, ans, cmd.String())
+ }
+ buf, err := os.ReadFile(*outFile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return buf
+}
+
+func report(fn string) {
+ profs, err := cover.ParseProfiles(fn)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for _, p := range profs {
+ statements, counts := 0, 0
+ for _, x := range p.Blocks {
+ statements += x.NumStmt
+ if x.Count != 0 {
+ counts += x.NumStmt // sic: if any were executed, all were
+ }
+ }
+ pc := 100 * float64(counts) / float64(statements)
+ fmt.Printf("%3.0f%% %3d/%3d %s\n", pc, counts, statements, p.FileName)
+ }
+}
+
+var todo []string // tests to run
+
+func excluded(tname string) bool {
+ if *tests == "" { // run all tests
+ return false
+ }
+ if todo == nil {
+ todo = strings.Split(*tests, ",")
+ }
+ for _, nm := range todo {
+ if tname == nm { // run this test
+ return false
+ }
+ }
+ // not in list, skip it
+ return true
+}
+
+// should m.Package be printed sometime?
+func maybePrint(m result) {
+ switch m.Action {
+ case "pass", "fail", "skip":
+ fmt.Printf("%s %s %.3f\n", m.Action, m.Test, m.Elapsed)
+ case "run":
+ if *verbose > 2 {
+ fmt.Printf("%s %s %.3f\n", m.Action, m.Test, m.Elapsed)
+ }
+ case "output":
+ if *verbose > 3 {
+ fmt.Printf("%s %s %q %.3f\n", m.Action, m.Test, m.Output, m.Elapsed)
+ }
+ default:
+ log.Fatalf("unknown action %s\n", m.Action)
+ }
+}
+
+// return only the directories that contain tests
+func onlyTests(s []string) []string {
+ ans := []string{}
+outer:
+ for _, d := range s {
+ files, err := os.ReadDir(d)
+ if err != nil {
+ log.Fatalf("%s: %v", d, err)
+ }
+ for _, de := range files {
+ if strings.Contains(de.Name(), "_test.go") {
+ ans = append(ans, d)
+ continue outer
+ }
+ }
+ }
+ return ans
+}
+
+// replace the prefix gopls/ with ./ as the tests are run in the gopls directory
+func realTestName(p []string) []string {
+ ans := []string{}
+ for _, x := range p {
+ x = x[len("gopls/"):]
+ ans = append(ans, "./"+x)
+ }
+ return ans
+}
+
+// make sure we start in a tools directory
+func checkCwd() {
+ dir, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ // we expect to be a the root of golang.org/x/tools
+ cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", "golang.org/x/tools")
+ buf, err := cmd.Output()
+ buf = bytes.Trim(buf, "\n \t") // remove \n at end
+ if err != nil {
+ log.Fatal(err)
+ }
+ if string(buf) != dir {
+ log.Fatalf("wrong directory: in %q, should be in %q", dir, string(buf))
+ }
+ // and we expect gopls and internal/lsp as subdirectories
+ _, err = os.Stat("gopls")
+ if err != nil {
+ log.Fatalf("expected a gopls directory, %v", err)
+ }
+ _, err = os.Stat("internal/lsp")
+ if err != nil {
+ log.Fatalf("expected to see internal/lsp, %v", err)
+ }
+}
+
+func listDirs(dir string) []string {
+ ans := []string{}
+ f := func(path string, dirEntry os.DirEntry, err error) error {
+ if strings.HasSuffix(path, "/testdata") || strings.HasSuffix(path, "/typescript") {
+ return filepath.SkipDir
+ }
+ if dirEntry.IsDir() {
+ ans = append(ans, path)
+ }
+ return nil
+ }
+ filepath.WalkDir(dir, f)
+ return ans
+}
diff --git a/gopls/internal/regtest/codelens/codelens_test.go b/gopls/internal/regtest/codelens/codelens_test.go
index 88d0e04..87d0863 100644
--- a/gopls/internal/regtest/codelens/codelens_test.go
+++ b/gopls/internal/regtest/codelens/codelens_test.go
@@ -275,9 +275,6 @@
}
func TestGCDetails(t *testing.T) {
- if testing.Short() {
- t.Skip("Flaky test -- see golang.org/issue/44099")
- }
testenv.NeedsGo1Point(t, 15)
if runtime.GOOS == "android" {
t.Skipf("the gc details code lens doesn't work on Android")
diff --git a/gopls/internal/regtest/completion/postfix_snippet_test.go b/gopls/internal/regtest/completion/postfix_snippet_test.go
new file mode 100644
index 0000000..4c59ef9
--- /dev/null
+++ b/gopls/internal/regtest/completion/postfix_snippet_test.go
@@ -0,0 +1,402 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "strings"
+ "testing"
+
+ . "golang.org/x/tools/gopls/internal/regtest"
+ "golang.org/x/tools/internal/lsp/source"
+)
+
+func TestPostfixSnippetCompletion(t *testing.T) {
+ const mod = `
+-- go.mod --
+module mod.com
+
+go 1.12
+`
+
+ cases := []struct {
+ name string
+ before, after string
+ }{
+ {
+ name: "sort",
+ before: `
+package foo
+
+func _() {
+ var foo []int
+ foo.sort
+}
+`,
+ after: `
+package foo
+
+import "sort"
+
+func _() {
+ var foo []int
+ sort.Slice(foo, func(i, j int) bool {
+ $0
+})
+}
+`,
+ },
+ {
+ name: "sort_renamed_sort_package",
+ before: `
+package foo
+
+import blahsort "sort"
+
+var j int
+
+func _() {
+ var foo []int
+ foo.sort
+}
+`,
+ after: `
+package foo
+
+import blahsort "sort"
+
+var j int
+
+func _() {
+ var foo []int
+ blahsort.Slice(foo, func(i, j2 int) bool {
+ $0
+})
+}
+`,
+ },
+ {
+ name: "last",
+ before: `
+package foo
+
+func _() {
+ var s struct { i []int }
+ s.i.last
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var s struct { i []int }
+ s.i[len(s.i)-1]
+}
+`,
+ },
+ {
+ name: "reverse",
+ before: `
+package foo
+
+func _() {
+ var foo []int
+ foo.reverse
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo []int
+ for i, j := 0, len(foo)-1; i < j; i, j = i+1, j-1 {
+ foo[i], foo[j] = foo[j], foo[i]
+}
+
+}
+`,
+ },
+ {
+ name: "slice_range",
+ before: `
+package foo
+
+func _() {
+ type myThing struct{}
+ var foo []myThing
+ foo.range
+}
+`,
+ after: `
+package foo
+
+func _() {
+ type myThing struct{}
+ var foo []myThing
+ for i, mt := range foo {
+ $0
+}
+}
+`,
+ },
+ {
+ name: "append_stmt",
+ before: `
+package foo
+
+func _() {
+ var foo []int
+ foo.append
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo []int
+ foo = append(foo, $0)
+}
+`,
+ },
+ {
+ name: "append_expr",
+ before: `
+package foo
+
+func _() {
+ var foo []int
+ var _ []int = foo.append
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo []int
+ var _ []int = append(foo, $0)
+}
+`,
+ },
+ {
+ name: "slice_copy",
+ before: `
+package foo
+
+func _() {
+ var foo []int
+ foo.copy
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo []int
+ fooCopy := make([]int, len(foo))
+copy(fooCopy, foo)
+
+}
+`,
+ },
+ {
+ name: "map_range",
+ before: `
+package foo
+
+func _() {
+ var foo map[string]int
+ foo.range
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo map[string]int
+ for k, v := range foo {
+ $0
+}
+}
+`,
+ },
+ {
+ name: "map_clear",
+ before: `
+package foo
+
+func _() {
+ var foo map[string]int
+ foo.clear
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo map[string]int
+ for k := range foo {
+ delete(foo, k)
+}
+
+}
+`,
+ },
+ {
+ name: "map_keys",
+ before: `
+package foo
+
+func _() {
+ var foo map[string]int
+ foo.keys
+}
+`,
+ after: `
+package foo
+
+func _() {
+ var foo map[string]int
+ keys := make([]string, 0, len(foo))
+for k := range foo {
+ keys = append(keys, k)
+}
+
+}
+`,
+ },
+ {
+ name: "var",
+ before: `
+package foo
+
+func foo() (int, error) { return 0, nil }
+
+func _() {
+ foo().var
+}
+`,
+ after: `
+package foo
+
+func foo() (int, error) { return 0, nil }
+
+func _() {
+ i, err := foo()
+}
+`,
+ },
+ {
+ name: "var_single_value",
+ before: `
+package foo
+
+func foo() error { return nil }
+
+func _() {
+ foo().var
+}
+`,
+ after: `
+package foo
+
+func foo() error { return nil }
+
+func _() {
+ err := foo()
+}
+`,
+ },
+ {
+ name: "var_same_type",
+ before: `
+package foo
+
+func foo() (int, int) { return 0, 0 }
+
+func _() {
+ foo().var
+}
+`,
+ after: `
+package foo
+
+func foo() (int, int) { return 0, 0 }
+
+func _() {
+ i, i2 := foo()
+}
+`,
+ },
+ {
+ name: "print_scalar",
+ before: `
+package foo
+
+func _() {
+ var foo int
+ foo.print
+}
+`,
+ after: `
+package foo
+
+import "fmt"
+
+func _() {
+ var foo int
+ fmt.Printf("foo: %v\n", foo)
+}
+`,
+ },
+ {
+ name: "print_multi",
+ before: `
+package foo
+
+func foo() (int, error) { return 0, nil }
+
+func _() {
+ foo().print
+}
+`,
+ after: `
+package foo
+
+import "fmt"
+
+func foo() (int, error) { return 0, nil }
+
+func _() {
+ fmt.Println(foo())
+}
+`,
+ },
+ }
+
+ r := WithOptions(Options(func(o *source.Options) {
+ o.ExperimentalPostfixCompletions = true
+ }))
+ r.Run(t, mod, func(t *testing.T, env *Env) {
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ c.before = strings.Trim(c.before, "\n")
+ c.after = strings.Trim(c.after, "\n")
+
+ env.CreateBuffer("foo.go", c.before)
+
+ pos := env.RegexpSearch("foo.go", "\n}")
+ completions := env.Completion("foo.go", pos)
+ if len(completions.Items) != 1 {
+ t.Fatalf("expected one completion, got %v", completions.Items)
+ }
+
+ env.AcceptCompletion("foo.go", pos, completions.Items[0])
+
+ if buf := env.Editor.BufferText("foo.go"); buf != c.after {
+ t.Errorf("\nGOT:\n%s\nEXPECTED:\n%s", buf, c.after)
+ }
+ })
+ }
+ })
+}
diff --git a/gopls/internal/regtest/diagnostics/diagnostics_test.go b/gopls/internal/regtest/diagnostics/diagnostics_test.go
index 1d7cb09..3a4beee 100644
--- a/gopls/internal/regtest/diagnostics/diagnostics_test.go
+++ b/gopls/internal/regtest/diagnostics/diagnostics_test.go
@@ -572,12 +572,8 @@
WithOptions(EditorConfig{
Env: map[string]string{"GO111MODULE": go111module},
}).Run(t, files, func(t *testing.T, env *Env) {
- env.OpenFile("hello.txt")
env.Await(
- OnceMet(
- env.DoneWithOpen(),
- NoShowMessage(),
- ),
+ NoOutstandingWork(),
)
})
})
@@ -603,7 +599,14 @@
fmt.Println("")
}
`
- WithOptions(InGOPATH()).Run(t, collision, func(t *testing.T, env *Env) {
+ WithOptions(
+ InGOPATH(),
+ EditorConfig{
+ Env: map[string]string{
+ "GO111MODULE": "off",
+ },
+ },
+ ).Run(t, collision, func(t *testing.T, env *Env) {
env.OpenFile("x/x.go")
env.Await(
env.DiagnosticAtRegexpWithMessage("x/x.go", `^`, "found packages main (main.go) and x (x.go)"),
@@ -898,24 +901,6 @@
})
}
-// Reproduces golang/go#40825.
-func TestEmptyGOPATHXTest_40825(t *testing.T) {
- const files = `
--- x.go --
-package x
--- x_test.go --
-`
-
- WithOptions(InGOPATH()).Run(t, files, func(t *testing.T, env *Env) {
- env.OpenFile("x_test.go")
- env.EditBuffer("x_test.go", fake.NewEdit(0, 0, 0, 0, "pack"))
- env.Await(
- env.DoneWithChange(),
- NoShowMessage(),
- )
- })
-}
-
func TestIgnoredFiles(t *testing.T) {
const ws = `
-- go.mod --
@@ -1491,6 +1476,11 @@
WithOptions(
ProxyFiles(proxy),
InGOPATH(),
+ EditorConfig{
+ Env: map[string]string{
+ "GO111MODULE": "off",
+ },
+ },
).Run(t, contents, func(t *testing.T, env *Env) {
// Simulate typing character by character.
env.OpenFile("foo/foo_test.go")
@@ -1863,6 +1853,29 @@
})
}
+func TestInitialization(t *testing.T) {
+ const files = `
+-- go.mod --
+module mod.com
+
+go 1.16
+-- main.go --
+package main
+`
+ Run(t, files, func(t *testing.T, env *Env) {
+ env.OpenFile("go.mod")
+ env.Await(env.DoneWithOpen())
+ env.RegexpReplace("go.mod", "module", "modul")
+ env.SaveBufferWithoutActions("go.mod")
+ env.Await(
+ OnceMet(
+ env.DoneWithSave(),
+ NoLogMatching(protocol.Error, "initial workspace load failed"),
+ ),
+ )
+ })
+}
+
// Tests golang/go#45075, a panic in fillreturns breaks diagnostics.
func TestFillReturnsPanic(t *testing.T) {
// At tip, the panic no longer reproduces.
diff --git a/gopls/internal/regtest/env.go b/gopls/internal/regtest/env.go
index 322799d..3c41066 100644
--- a/gopls/internal/regtest/env.go
+++ b/gopls/internal/regtest/env.go
@@ -282,8 +282,9 @@
e.mu.Unlock()
return
case Unmeetable:
+ failure := fmt.Sprintf("unmeetable expectations:\n%s\nstate:\n%v", summary, e.state)
e.mu.Unlock()
- e.T.Fatalf("unmeetable expectations:\n%s\nstate:\n%v", summary, e.state)
+ e.T.Fatal(failure)
}
cond := &condition{
expectations: expectations,
diff --git a/gopls/internal/regtest/expectation.go b/gopls/internal/regtest/expectation.go
index 02e00dd..f86bcb6 100644
--- a/gopls/internal/regtest/expectation.go
+++ b/gopls/internal/regtest/expectation.go
@@ -582,8 +582,9 @@
return DiagnosticExpectation{path: name, message: msg, present: false}
}
-// GoSum asserts that a "go.sum is out of sync" diagnostic for the given module
-// (as formatted in a go.mod file, e.g. "example.com v1.0.0") is present.
+// GoSumDiagnostic asserts that a "go.sum is out of sync" diagnostic for the
+// given module (as formatted in a go.mod file, e.g. "example.com v1.0.0") is
+// present.
func (e *Env) GoSumDiagnostic(name, module string) Expectation {
e.T.Helper()
// In 1.16, go.sum diagnostics should appear on the relevant module. Earlier
diff --git a/gopls/internal/regtest/misc/fix_test.go b/gopls/internal/regtest/misc/fix_test.go
index 395bfd8..9225a83 100644
--- a/gopls/internal/regtest/misc/fix_test.go
+++ b/gopls/internal/regtest/misc/fix_test.go
@@ -81,6 +81,25 @@
env.DiagnosticAtRegexpWithMessage("main.go", `return`, "wrong number of return values"),
ReadDiagnostics("main.go", &d),
))
+ codeActions := env.CodeAction("main.go", d.Diagnostics)
+ if len(codeActions) != 2 {
+ t.Fatalf("expected 2 code actions, got %v", len(codeActions))
+ }
+ var foundQuickFix, foundFixAll bool
+ for _, a := range codeActions {
+ if a.Kind == protocol.QuickFix {
+ foundQuickFix = true
+ }
+ if a.Kind == protocol.SourceFixAll {
+ foundFixAll = true
+ }
+ }
+ if !foundQuickFix {
+ t.Fatalf("expected quickfix code action, got none")
+ }
+ if !foundFixAll {
+ t.Fatalf("expected fixall code action, got none")
+ }
env.ApplyQuickFixes("main.go", d.Diagnostics)
env.Await(EmptyDiagnostics("main.go"))
})
diff --git a/gopls/internal/regtest/misc/highlight_test.go b/gopls/internal/regtest/misc/highlight_test.go
new file mode 100644
index 0000000..56e6116
--- /dev/null
+++ b/gopls/internal/regtest/misc/highlight_test.go
@@ -0,0 +1,151 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package misc
+
+import (
+ "sort"
+ "testing"
+
+ . "golang.org/x/tools/gopls/internal/regtest"
+ "golang.org/x/tools/internal/lsp/fake"
+ "golang.org/x/tools/internal/lsp/protocol"
+)
+
+func TestWorkspacePackageHighlight(t *testing.T) {
+ const mod = `
+-- go.mod --
+module mod.com
+
+go 1.12
+-- main.go --
+package main
+
+func main() {
+ var A string = "A"
+ x := "x-" + A
+ println(A, x)
+}`
+
+ Run(t, mod, func(t *testing.T, env *Env) {
+ const file = "main.go"
+ env.OpenFile(file)
+ _, pos := env.GoToDefinition(file, env.RegexpSearch(file, `var (A) string`))
+
+ checkHighlights(env, file, pos, 3)
+ })
+}
+
+func TestStdPackageHighlight_Issue43511(t *testing.T) {
+ const mod = `
+-- go.mod --
+module mod.com
+
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Printf()
+}`
+
+ Run(t, mod, func(t *testing.T, env *Env) {
+ env.OpenFile("main.go")
+ file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `fmt\.(Printf)`))
+ pos := env.RegexpSearch(file, `func Printf\((format) string`)
+
+ checkHighlights(env, file, pos, 2)
+ })
+}
+
+func TestThirdPartyPackageHighlight_Issue43511(t *testing.T) {
+ const proxy = `
+-- example.com@v1.2.3/go.mod --
+module example.com
+
+go 1.12
+-- example.com@v1.2.3/global/global.go --
+package global
+
+const A = 1
+
+func foo() {
+ _ = A
+}
+
+func bar() int {
+ return A + A
+}
+-- example.com@v1.2.3/local/local.go --
+package local
+
+func foo() int {
+ const b = 2
+
+ return b * b * (b+1) + b
+}`
+
+ const mod = `
+-- go.mod --
+module mod.com
+
+go 1.12
+
+require example.com v1.2.3
+-- go.sum --
+example.com v1.2.3 h1:WFzrgiQJwEDJNLDUOV1f9qlasQkvzXf2UNLaNIqbWsI=
+example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
+-- main.go --
+package main
+
+import (
+ _ "example.com/global"
+ _ "example.com/local"
+)
+
+func main() {}`
+
+ WithOptions(
+ ProxyFiles(proxy),
+ ).Run(t, mod, func(t *testing.T, env *Env) {
+ env.OpenFile("main.go")
+
+ file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"example.com/global"`))
+ pos := env.RegexpSearch(file, `const (A)`)
+ checkHighlights(env, file, pos, 4)
+
+ file, _ = env.GoToDefinition("main.go", env.RegexpSearch("main.go", `"example.com/local"`))
+ pos = env.RegexpSearch(file, `const (b)`)
+ checkHighlights(env, file, pos, 5)
+ })
+}
+
+func checkHighlights(env *Env, file string, pos fake.Pos, highlightCount int) {
+ t := env.T
+ t.Helper()
+
+ highlights := env.DocumentHighlight(file, pos)
+ if len(highlights) != highlightCount {
+ t.Fatalf("expected %v highlight(s), got %v", highlightCount, len(highlights))
+ }
+
+ references := env.References(file, pos)
+ if len(highlights) != len(references) {
+ t.Fatalf("number of highlights and references is expected to be equal: %v != %v", len(highlights), len(references))
+ }
+
+ sort.Slice(highlights, func(i, j int) bool {
+ return protocol.CompareRange(highlights[i].Range, highlights[j].Range) < 0
+ })
+ sort.Slice(references, func(i, j int) bool {
+ return protocol.CompareRange(references[i].Range, references[j].Range) < 0
+ })
+ for i := range highlights {
+ if highlights[i].Range != references[i].Range {
+ t.Errorf("highlight and reference ranges are expected to be equal: %v != %v", highlights[i].Range, references[i].Range)
+ }
+ }
+}
diff --git a/gopls/internal/regtest/misc/imports_test.go b/gopls/internal/regtest/misc/imports_test.go
index 9a95208..2a666c4 100644
--- a/gopls/internal/regtest/misc/imports_test.go
+++ b/gopls/internal/regtest/misc/imports_test.go
@@ -74,7 +74,7 @@
Run(t, "", func(t *testing.T, env *Env) {
env.CreateBuffer("main.go", vim1)
env.OrganizeImports("main.go")
- actions := env.CodeAction("main.go")
+ actions := env.CodeAction("main.go", nil)
if len(actions) > 0 {
got := env.Editor.BufferText("main.go")
t.Errorf("unexpected actions %#v", actions)
@@ -107,7 +107,7 @@
Run(t, "", func(t *testing.T, env *Env) {
env.CreateBuffer("main.go", vim2)
env.OrganizeImports("main.go")
- actions := env.CodeAction("main.go")
+ actions := env.CodeAction("main.go", nil)
if len(actions) > 0 {
t.Errorf("unexpected actions %#v", actions)
}
diff --git a/gopls/internal/regtest/runner.go b/gopls/internal/regtest/runner.go
index a029b9c..cfa64af 100644
--- a/gopls/internal/regtest/runner.go
+++ b/gopls/internal/regtest/runner.go
@@ -70,19 +70,21 @@
}
type runConfig struct {
- editor fake.EditorConfig
- sandbox fake.SandboxConfig
- modes Mode
- timeout time.Duration
- debugAddr string
- skipLogs bool
- skipHooks bool
+ editor fake.EditorConfig
+ sandbox fake.SandboxConfig
+ modes Mode
+ timeout time.Duration
+ debugAddr string
+ skipLogs bool
+ skipHooks bool
+ optionsHook func(*source.Options)
}
func (r *Runner) defaultConfig() *runConfig {
return &runConfig{
- modes: r.DefaultModes,
- timeout: r.Timeout,
+ modes: r.DefaultModes,
+ timeout: r.Timeout,
+ optionsHook: hooks.Options,
}
}
@@ -118,6 +120,19 @@
})
}
+// Options configures the various server and user options.
+func Options(hook func(*source.Options)) RunOption {
+ return optionSetter(func(opts *runConfig) {
+ old := opts.optionsHook
+ opts.optionsHook = func(o *source.Options) {
+ if old != nil {
+ old(o)
+ }
+ hook(o)
+ }
+ })
+}
+
func SendPID() RunOption {
return optionSetter(func(opts *runConfig) {
opts.editor.SendPID = true
@@ -216,7 +231,7 @@
tests := []struct {
name string
mode Mode
- getServer func(context.Context, *testing.T) jsonrpc2.StreamServer
+ getServer func(context.Context, *testing.T, func(*source.Options)) jsonrpc2.StreamServer
}{
{"singleton", Singleton, singletonServer},
{"forwarded", Forwarded, r.forwardedServer},
@@ -268,7 +283,7 @@
// better solution to ensure that all Go processes started by gopls have
// exited before we clean up.
r.AddCloser(sandbox)
- ss := tc.getServer(ctx, t)
+ ss := tc.getServer(ctx, t, config.optionsHook)
framer := jsonrpc2.NewRawStream
ls := &loggingFramer{}
if !config.skipLogs {
@@ -367,38 +382,38 @@
fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs for %q\n", testname)
}
-func singletonServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
- return lsprpc.NewStreamServer(cache.New(ctx, hooks.Options), false)
+func singletonServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
+ return lsprpc.NewStreamServer(cache.New(optsHook), false)
}
-func experimentalWorkspaceModule(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
+func experimentalWorkspaceModule(_ context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
options := func(o *source.Options) {
- hooks.Options(o)
+ optsHook(o)
o.ExperimentalWorkspaceModule = true
}
- return lsprpc.NewStreamServer(cache.New(ctx, options), false)
+ return lsprpc.NewStreamServer(cache.New(options), false)
}
-func (r *Runner) forwardedServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
- ts := r.getTestServer()
+func (r *Runner) forwardedServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
+ ts := r.getTestServer(optsHook)
return lsprpc.NewForwarder("tcp", ts.Addr)
}
// getTestServer gets the shared test server instance to connect to, or creates
// one if it doesn't exist.
-func (r *Runner) getTestServer() *servertest.TCPServer {
+func (r *Runner) getTestServer(optsHook func(*source.Options)) *servertest.TCPServer {
r.mu.Lock()
defer r.mu.Unlock()
if r.ts == nil {
ctx := context.Background()
ctx = debug.WithInstance(ctx, "", "off")
- ss := lsprpc.NewStreamServer(cache.New(ctx, hooks.Options), false)
+ ss := lsprpc.NewStreamServer(cache.New(optsHook), false)
r.ts = servertest.NewTCPServer(ctx, ss, nil)
}
return r.ts
}
-func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T) jsonrpc2.StreamServer {
+func (r *Runner) separateProcessServer(ctx context.Context, t *testing.T, optsHook func(*source.Options)) jsonrpc2.StreamServer {
// TODO(rfindley): can we use the autostart behavior here, instead of
// pre-starting the remote?
socket := r.getRemoteSocket(t)
diff --git a/gopls/internal/regtest/watch/watch_test.go b/gopls/internal/regtest/watch/watch_test.go
index ae43bea..9cc9d0a 100644
--- a/gopls/internal/regtest/watch/watch_test.go
+++ b/gopls/internal/regtest/watch/watch_test.go
@@ -617,6 +617,11 @@
`
WithOptions(
InGOPATH(),
+ EditorConfig{
+ Env: map[string]string{
+ "GO111MODULE": "auto",
+ },
+ },
Modes(Experimental), // module is in a subdirectory
).Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("foo/main.go")
@@ -624,12 +629,6 @@
if err := env.Sandbox.RunGoCommand(env.Ctx, "foo", "mod", []string{"init", "mod.com"}); err != nil {
t.Fatal(err)
}
- env.Await(
- OnceMet(
- env.DoneWithChangeWatchedFiles(),
- env.DiagnosticAtRegexp("foo/main.go", `"blah"`),
- ),
- )
env.RegexpReplace("foo/main.go", `"blah"`, `"mod.com/blah"`)
env.Await(
EmptyDiagnostics("foo/main.go"),
@@ -661,6 +660,11 @@
`
WithOptions(
InGOPATH(),
+ EditorConfig{
+ Env: map[string]string{
+ "GO111MODULE": "auto",
+ },
+ },
).Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("foo/main.go")
env.RemoveWorkspaceFile("foo/go.mod")
diff --git a/gopls/internal/regtest/workspace/workspace_test.go b/gopls/internal/regtest/workspace/workspace_test.go
index f0128d8..21e33b6 100644
--- a/gopls/internal/regtest/workspace/workspace_test.go
+++ b/gopls/internal/regtest/workspace/workspace_test.go
@@ -5,15 +5,16 @@
package workspace
import (
+ "encoding/json"
"fmt"
"io/ioutil"
- "os"
"path/filepath"
"strings"
"testing"
. "golang.org/x/tools/gopls/internal/regtest"
+ "golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/testenv"
@@ -647,10 +648,19 @@
Modes(Experimental),
SendPID(),
).Run(t, multiModule, func(t *testing.T, env *Env) {
- pid := os.Getpid()
+ params := &protocol.ExecuteCommandParams{
+ Command: command.WorkspaceMetadata.ID(),
+ Arguments: []json.RawMessage{json.RawMessage("{}")},
+ }
+ var result command.WorkspaceMetadataResult
+ env.ExecuteCommand(params, &result)
+
+ if n := len(result.Workspaces); n != 1 {
+ env.T.Fatalf("got %d workspaces, want 1", n)
+ }
// Don't factor this out of Server.addFolders. vscode-go expects this
// directory.
- modPath := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.workspace", pid), "go.mod")
+ modPath := filepath.Join(result.Workspaces[0].ModuleDir, "go.mod")
gotb, err := ioutil.ReadFile(modPath)
if err != nil {
t.Fatalf("reading expected workspace modfile: %v", err)
diff --git a/gopls/internal/regtest/wrappers.go b/gopls/internal/regtest/wrappers.go
index c77de5b..2038849 100644
--- a/gopls/internal/regtest/wrappers.go
+++ b/gopls/internal/regtest/wrappers.go
@@ -5,6 +5,7 @@
package regtest
import (
+ "encoding/json"
"io"
"path"
"testing"
@@ -229,6 +230,15 @@
return links
}
+func (e *Env) DocumentHighlight(name string, pos fake.Pos) []protocol.DocumentHighlight {
+ e.T.Helper()
+ highlights, err := e.Editor.DocumentHighlight(e.Ctx, name, pos)
+ if err != nil {
+ e.T.Fatal(err)
+ }
+ return highlights
+}
+
func checkIsFatal(t *testing.T, err error) {
t.Helper()
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrClosedPipe) {
@@ -312,6 +322,7 @@
// ExecuteCodeLensCommand executes the command for the code lens matching the
// given command name.
func (e *Env) ExecuteCodeLensCommand(path string, cmd command.Command) {
+ e.T.Helper()
lenses := e.CodeLens(path)
var lens protocol.CodeLens
var found bool
@@ -324,10 +335,32 @@
if !found {
e.T.Fatalf("found no command with the ID %s", cmd.ID())
}
- if _, err := e.Editor.ExecuteCommand(e.Ctx, &protocol.ExecuteCommandParams{
+ e.ExecuteCommand(&protocol.ExecuteCommandParams{
Command: lens.Command.Command,
Arguments: lens.Command.Arguments,
- }); err != nil {
+ }, nil)
+}
+
+func (e *Env) ExecuteCommand(params *protocol.ExecuteCommandParams, result interface{}) {
+ e.T.Helper()
+ response, err := e.Editor.ExecuteCommand(e.Ctx, params)
+ if err != nil {
+ e.T.Fatal(err)
+ }
+ if result == nil {
+ return
+ }
+ // Hack: The result of an executeCommand request will be unmarshaled into
+ // maps. Re-marshal and unmarshal into the type we expect.
+ //
+ // This could be improved by generating a jsonrpc2 command client from the
+ // command.Interface, but that should only be done if we're consolidating
+ // this part of the tsprotocol generation.
+ data, err := json.Marshal(response)
+ if err != nil {
+ e.T.Fatal(err)
+ }
+ if err := json.Unmarshal(data, result); err != nil {
e.T.Fatal(err)
}
}
@@ -364,9 +397,9 @@
// CodeAction calls testDocument/codeAction for the given path, and calls
// t.Fatal if there are errors.
-func (e *Env) CodeAction(path string) []protocol.CodeAction {
+func (e *Env) CodeAction(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction {
e.T.Helper()
- actions, err := e.Editor.CodeAction(e.Ctx, path, nil)
+ actions, err := e.Editor.CodeAction(e.Ctx, path, nil, diagnostics)
if err != nil {
e.T.Fatal(err)
}
diff --git a/internal/event/label/label.go b/internal/event/label/label.go
index b55c12e..0f526e1 100644
--- a/internal/event/label/label.go
+++ b/internal/event/label/label.go
@@ -96,6 +96,8 @@
// access should be done with the From method of the key.
func (t Label) Unpack64() uint64 { return t.packed }
+type stringptr unsafe.Pointer
+
// OfString creates a new label from a key and a string.
// This method is for implementing new key types, label creation should
// normally be done with the Of method of the key.
@@ -104,7 +106,7 @@
return Label{
key: k,
packed: uint64(hdr.Len),
- untyped: unsafe.Pointer(hdr.Data),
+ untyped: stringptr(hdr.Data),
}
}
@@ -115,9 +117,9 @@
func (t Label) UnpackString() string {
var v string
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
- hdr.Data = uintptr(t.untyped.(unsafe.Pointer))
+ hdr.Data = uintptr(t.untyped.(stringptr))
hdr.Len = int(t.packed)
- return *(*string)(unsafe.Pointer(hdr))
+ return v
}
// Valid returns true if the Label is a valid one (it has a key).
diff --git a/internal/event/label/label_test.go b/internal/event/label/label_test.go
index ea34ff7..a2b5818 100644
--- a/internal/event/label/label_test.go
+++ b/internal/event/label/label_test.go
@@ -7,7 +7,9 @@
import (
"bytes"
"fmt"
+ "runtime"
"testing"
+ "unsafe"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
@@ -267,3 +269,17 @@
}
return buf.String()
}
+
+func TestAttemptedStringCorruption(t *testing.T) {
+ defer func() {
+ r := recover()
+ if _, ok := r.(*runtime.TypeAssertionError); !ok {
+ t.Fatalf("wanted to recover TypeAssertionError, got %T", r)
+ }
+ }()
+
+ var x uint64 = 12390
+ p := unsafe.Pointer(&x)
+ l := label.OfValue(AKey, p)
+ _ = l.UnpackString()
+}
diff --git a/internal/jsonrpc2_v2/conn.go b/internal/jsonrpc2_v2/conn.go
new file mode 100644
index 0000000..6d92c0c
--- /dev/null
+++ b/internal/jsonrpc2_v2/conn.go
@@ -0,0 +1,486 @@
+// Copyright 2018 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 jsonrpc2
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "sync/atomic"
+
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/event/label"
+ "golang.org/x/tools/internal/lsp/debug/tag"
+ errors "golang.org/x/xerrors"
+)
+
+// Binder builds a connection configuration.
+// This may be used in servers to generate a new configuration per connection.
+// ConnectionOptions itself implements Binder returning itself unmodified, to
+// allow for the simple cases where no per connection information is needed.
+type Binder interface {
+ // Bind is invoked when creating a new connection.
+ // The connection is not ready to use when Bind is called.
+ Bind(context.Context, *Connection) (ConnectionOptions, error)
+}
+
+// ConnectionOptions holds the options for new connections.
+type ConnectionOptions struct {
+ // Framer allows control over the message framing and encoding.
+ // If nil, HeaderFramer will be used.
+ Framer Framer
+ // Preempter allows registration of a pre-queue message handler.
+ // If nil, no messages will be preempted.
+ Preempter Preempter
+ // Handler is used as the queued message handler for inbound messages.
+ // If nil, all responses will be ErrNotHandled.
+ Handler Handler
+}
+
+// Connection manages the jsonrpc2 protocol, connecting responses back to their
+// calls.
+// Connection is bidirectional; it does not have a designated server or client
+// end.
+type Connection struct {
+ seq int64 // must only be accessed using atomic operations
+ closer io.Closer
+ writerBox chan Writer
+ outgoingBox chan map[ID]chan<- *Response
+ incomingBox chan map[ID]*incoming
+ async async
+}
+
+type AsyncCall struct {
+ id ID
+ response chan *Response // the channel a response will be delivered on
+ resultBox chan asyncResult
+ endSpan func() // close the tracing span when all processing for the message is complete
+}
+
+type asyncResult struct {
+ result []byte
+ err error
+}
+
+// incoming is used to track an incoming request as it is being handled
+type incoming struct {
+ request *Request // the request being processed
+ baseCtx context.Context // a base context for the message processing
+ done func() // a function called when all processing for the message is complete
+ handleCtx context.Context // the context for handling the message, child of baseCtx
+ cancel func() // a function that cancels the handling context
+}
+
+// Bind returns the options unmodified.
+func (o ConnectionOptions) Bind(context.Context, *Connection) (ConnectionOptions, error) {
+ return o, nil
+}
+
+// newConnection creates a new connection and runs it.
+// This is used by the Dial and Serve functions to build the actual connection.
+func newConnection(ctx context.Context, rwc io.ReadWriteCloser, binder Binder) (*Connection, error) {
+ c := &Connection{
+ closer: rwc,
+ writerBox: make(chan Writer, 1),
+ outgoingBox: make(chan map[ID]chan<- *Response, 1),
+ incomingBox: make(chan map[ID]*incoming, 1),
+ }
+
+ options, err := binder.Bind(ctx, c)
+ if err != nil {
+ return nil, err
+ }
+ if options.Framer == nil {
+ options.Framer = HeaderFramer()
+ }
+ if options.Preempter == nil {
+ options.Preempter = defaultHandler{}
+ }
+ if options.Handler == nil {
+ options.Handler = defaultHandler{}
+ }
+ c.outgoingBox <- make(map[ID]chan<- *Response)
+ c.incomingBox <- make(map[ID]*incoming)
+ c.async.init()
+ // the goroutines started here will continue until the underlying stream is closed
+ reader := options.Framer.Reader(rwc)
+ readToQueue := make(chan *incoming)
+ queueToDeliver := make(chan *incoming)
+ go c.readIncoming(ctx, reader, readToQueue)
+ go c.manageQueue(ctx, options.Preempter, readToQueue, queueToDeliver)
+ go c.deliverMessages(ctx, options.Handler, queueToDeliver)
+ // releaseing the writer must be the last thing we do in case any requests
+ // are blocked waiting for the connection to be ready
+ c.writerBox <- options.Framer.Writer(rwc)
+ return c, nil
+}
+
+// Notify invokes the target method but does not wait for a response.
+// The params will be marshaled to JSON before sending over the wire, and will
+// be handed to the method invoked.
+func (c *Connection) Notify(ctx context.Context, method string, params interface{}) error {
+ notify, err := NewNotification(method, params)
+ if err != nil {
+ return errors.Errorf("marshaling notify parameters: %v", err)
+ }
+ ctx, done := event.Start(ctx, method,
+ tag.Method.Of(method),
+ tag.RPCDirection.Of(tag.Outbound),
+ )
+ event.Metric(ctx, tag.Started.Of(1))
+ err = c.write(ctx, notify)
+ switch {
+ case err != nil:
+ event.Label(ctx, tag.StatusCode.Of("ERROR"))
+ default:
+ event.Label(ctx, tag.StatusCode.Of("OK"))
+ }
+ done()
+ return err
+}
+
+// Call invokes the target method and returns an object that can be used to await the response.
+// The params will be marshaled to JSON before sending over the wire, and will
+// be handed to the method invoked.
+// You do not have to wait for the response, it can just be ignored if not needed.
+// If sending the call failed, the response will be ready and have the error in it.
+func (c *Connection) Call(ctx context.Context, method string, params interface{}) *AsyncCall {
+ result := &AsyncCall{
+ id: Int64ID(atomic.AddInt64(&c.seq, 1)),
+ resultBox: make(chan asyncResult, 1),
+ }
+ // generate a new request identifier
+ call, err := NewCall(result.id, method, params)
+ if err != nil {
+ //set the result to failed
+ result.resultBox <- asyncResult{err: errors.Errorf("marshaling call parameters: %w", err)}
+ return result
+ }
+ ctx, endSpan := event.Start(ctx, method,
+ tag.Method.Of(method),
+ tag.RPCDirection.Of(tag.Outbound),
+ tag.RPCID.Of(fmt.Sprintf("%q", result.id)),
+ )
+ result.endSpan = endSpan
+ event.Metric(ctx, tag.Started.Of(1))
+ // We have to add ourselves to the pending map before we send, otherwise we
+ // are racing the response.
+ // rchan is buffered in case the response arrives without a listener.
+ result.response = make(chan *Response, 1)
+ pending := <-c.outgoingBox
+ pending[result.id] = result.response
+ c.outgoingBox <- pending
+ // now we are ready to send
+ if err := c.write(ctx, call); err != nil {
+ // sending failed, we will never get a response, so deliver a fake one
+ r, _ := NewResponse(result.id, nil, err)
+ c.incomingResponse(r)
+ }
+ return result
+}
+
+// ID used for this call.
+// This can be used to cancel the call if needed.
+func (a *AsyncCall) ID() ID { return a.id }
+
+// IsReady can be used to check if the result is already prepared.
+// This is guaranteed to return true on a result for which Await has already
+// returned, or a call that failed to send in the first place.
+func (a *AsyncCall) IsReady() bool {
+ select {
+ case r := <-a.resultBox:
+ a.resultBox <- r
+ return true
+ default:
+ return false
+ }
+}
+
+// Await the results of a Call.
+// The response will be unmarshaled from JSON into the result.
+func (a *AsyncCall) Await(ctx context.Context, result interface{}) error {
+ defer a.endSpan()
+ var r asyncResult
+ select {
+ case response := <-a.response:
+ // response just arrived, prepare the result
+ switch {
+ case response.Error != nil:
+ r.err = response.Error
+ event.Label(ctx, tag.StatusCode.Of("ERROR"))
+ default:
+ r.result = response.Result
+ event.Label(ctx, tag.StatusCode.Of("OK"))
+ }
+ case r = <-a.resultBox:
+ // result already available
+ case <-ctx.Done():
+ event.Label(ctx, tag.StatusCode.Of("CANCELLED"))
+ return ctx.Err()
+ }
+ // refill the box for the next caller
+ a.resultBox <- r
+ // and unpack the result
+ if r.err != nil {
+ return r.err
+ }
+ if result == nil || len(r.result) == 0 {
+ return nil
+ }
+ return json.Unmarshal(r.result, result)
+}
+
+// Respond deliverers a response to an incoming Call.
+// It is an error to not call this exactly once for any message for which a
+// handler has previously returned ErrAsyncResponse. It is also an error to
+// call this for any other message.
+func (c *Connection) Respond(id ID, result interface{}, rerr error) error {
+ pending := <-c.incomingBox
+ defer func() { c.incomingBox <- pending }()
+ entry, found := pending[id]
+ if !found {
+ return nil
+ }
+ delete(pending, id)
+ return c.respond(entry, result, rerr)
+}
+
+// Cancel is used to cancel an inbound message by ID, it does not cancel
+// outgoing messages.
+// This is only used inside a message handler that is layering a
+// cancellation protocol on top of JSON RPC 2.
+// It will not complain if the ID is not a currently active message, and it will
+// not cause any messages that have not arrived yet with that ID to be
+// cancelled.
+func (c *Connection) Cancel(id ID) {
+ pending := <-c.incomingBox
+ defer func() { c.incomingBox <- pending }()
+ if entry, found := pending[id]; found && entry.cancel != nil {
+ entry.cancel()
+ entry.cancel = nil
+ }
+}
+
+// Wait blocks until the connection is fully closed, but does not close it.
+func (c *Connection) Wait() error {
+ return c.async.wait()
+}
+
+// Close can be used to close the underlying stream, and then wait for the connection to
+// fully shut down.
+// This does not cancel in flight requests, but waits for them to gracefully complete.
+func (c *Connection) Close() error {
+ // close the underlying stream
+ if err := c.closer.Close(); err != nil && !isClosingError(err) {
+ return err
+ }
+ // and then wait for it to cause the connection to close
+ if err := c.Wait(); err != nil && !isClosingError(err) {
+ return err
+ }
+ return nil
+}
+
+// readIncoming collects inbound messages from the reader and delivers them, either responding
+// to outgoing calls or feeding requests to the queue.
+func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue chan<- *incoming) {
+ defer close(toQueue)
+ for {
+ // get the next message
+ // no lock is needed, this is the only reader
+ msg, n, err := reader.Read(ctx)
+ if err != nil {
+ // The stream failed, we cannot continue
+ c.async.setError(err)
+ return
+ }
+ switch msg := msg.(type) {
+ case *Request:
+ entry := &incoming{
+ request: msg,
+ }
+ // add a span to the context for this request
+ labels := append(make([]label.Label, 0, 3), // make space for the id if present
+ tag.Method.Of(msg.Method),
+ tag.RPCDirection.Of(tag.Inbound),
+ )
+ if msg.IsCall() {
+ labels = append(labels, tag.RPCID.Of(fmt.Sprintf("%q", msg.ID)))
+ }
+ entry.baseCtx, entry.done = event.Start(ctx, msg.Method, labels...)
+ event.Metric(entry.baseCtx,
+ tag.Started.Of(1),
+ tag.ReceivedBytes.Of(n))
+ // in theory notifications cannot be cancelled, but we build them a cancel context anyway
+ entry.handleCtx, entry.cancel = context.WithCancel(entry.baseCtx)
+ // if the request is a call, add it to the incoming map so it can be
+ // cancelled by id
+ if msg.IsCall() {
+ pending := <-c.incomingBox
+ c.incomingBox <- pending
+ pending[msg.ID] = entry
+ }
+ // send the message to the incoming queue
+ toQueue <- entry
+ case *Response:
+ // If method is not set, this should be a response, in which case we must
+ // have an id to send the response back to the caller.
+ c.incomingResponse(msg)
+ }
+ }
+}
+
+func (c *Connection) incomingResponse(msg *Response) {
+ pending := <-c.outgoingBox
+ response, ok := pending[msg.ID]
+ if ok {
+ delete(pending, msg.ID)
+ }
+ c.outgoingBox <- pending
+ if response != nil {
+ response <- msg
+ }
+}
+
+// manageQueue reads incoming requests, attempts to proccess them with the preempter, or queue them
+// up for normal handling.
+func (c *Connection) manageQueue(ctx context.Context, preempter Preempter, fromRead <-chan *incoming, toDeliver chan<- *incoming) {
+ defer close(toDeliver)
+ q := []*incoming{}
+ ok := true
+ for {
+ var nextReq *incoming
+ if len(q) == 0 {
+ // no messages in the queue
+ // if we were closing, then we are done
+ if !ok {
+ return
+ }
+ // not closing, but nothing in the queue, so just block waiting for a read
+ nextReq, ok = <-fromRead
+ } else {
+ // we have a non empty queue, so pick whichever of reading or delivering
+ // that we can make progress on
+ select {
+ case nextReq, ok = <-fromRead:
+ case toDeliver <- q[0]:
+ //TODO: this causes a lot of shuffling, should we use a growing ring buffer? compaction?
+ q = q[1:]
+ }
+ }
+ if nextReq != nil {
+ // TODO: should we allow to limit the queue size?
+ var result interface{}
+ rerr := nextReq.handleCtx.Err()
+ if rerr == nil {
+ // only preempt if not already cancelled
+ result, rerr = preempter.Preempt(nextReq.handleCtx, nextReq.request)
+ }
+ switch {
+ case rerr == ErrNotHandled:
+ // message not handled, add it to the queue for the main handler
+ q = append(q, nextReq)
+ case rerr == ErrAsyncResponse:
+ // message handled but the response will come later
+ default:
+ // anything else means the message is fully handled
+ c.reply(nextReq, result, rerr)
+ }
+ }
+ }
+}
+
+func (c *Connection) deliverMessages(ctx context.Context, handler Handler, fromQueue <-chan *incoming) {
+ defer c.async.done()
+ for entry := range fromQueue {
+ // cancel any messages in the queue that we have a pending cancel for
+ var result interface{}
+ rerr := entry.handleCtx.Err()
+ if rerr == nil {
+ // only deliver if not already cancelled
+ result, rerr = handler.Handle(entry.handleCtx, entry.request)
+ }
+ switch {
+ case rerr == ErrNotHandled:
+ // message not handled, report it back to the caller as an error
+ c.reply(entry, nil, errors.Errorf("%w: %q", ErrMethodNotFound, entry.request.Method))
+ case rerr == ErrAsyncResponse:
+ // message handled but the response will come later
+ default:
+ c.reply(entry, result, rerr)
+ }
+ }
+}
+
+// reply is used to reply to an incoming request that has just been handled
+func (c *Connection) reply(entry *incoming, result interface{}, rerr error) {
+ if entry.request.IsCall() {
+ // we have a call finishing, remove it from the incoming map
+ pending := <-c.incomingBox
+ defer func() { c.incomingBox <- pending }()
+ delete(pending, entry.request.ID)
+ }
+ if err := c.respond(entry, result, rerr); err != nil {
+ // no way to propagate this error
+ //TODO: should we do more than just log it?
+ event.Error(entry.baseCtx, "jsonrpc2 message delivery failed", err)
+ }
+}
+
+// respond sends a response.
+// This is the code shared between reply and SendResponse.
+func (c *Connection) respond(entry *incoming, result interface{}, rerr error) error {
+ var err error
+ if entry.request.IsCall() {
+ // send the response
+ if result == nil && rerr == nil {
+ // call with no response, send an error anyway
+ rerr = errors.Errorf("%w: %q produced no response", ErrInternal, entry.request.Method)
+ }
+ var response *Response
+ response, err = NewResponse(entry.request.ID, result, rerr)
+ if err == nil {
+ // we write the response with the base context, in case the message was cancelled
+ err = c.write(entry.baseCtx, response)
+ }
+ } else {
+ switch {
+ case rerr != nil:
+ // notification failed
+ err = errors.Errorf("%w: %q notification failed: %v", ErrInternal, entry.request.Method, rerr)
+ rerr = nil
+ case result != nil:
+ //notification produced a response, which is an error
+ err = errors.Errorf("%w: %q produced unwanted response", ErrInternal, entry.request.Method)
+ default:
+ // normal notification finish
+ }
+ }
+ switch {
+ case rerr != nil || err != nil:
+ event.Label(entry.baseCtx, tag.StatusCode.Of("ERROR"))
+ default:
+ event.Label(entry.baseCtx, tag.StatusCode.Of("OK"))
+ }
+ // and just to be clean, invoke and clear the cancel if needed
+ if entry.cancel != nil {
+ entry.cancel()
+ entry.cancel = nil
+ }
+ // mark the entire request processing as done
+ entry.done()
+ return err
+}
+
+// write is used by all things that write outgoing messages, including replies.
+// it makes sure that writes are atomic
+func (c *Connection) write(ctx context.Context, msg Message) error {
+ writer := <-c.writerBox
+ defer func() { c.writerBox <- writer }()
+ n, err := writer.Write(ctx, msg)
+ event.Metric(ctx, tag.SentBytes.Of(n))
+ return err
+}
diff --git a/internal/jsonrpc2_v2/frame.go b/internal/jsonrpc2_v2/frame.go
new file mode 100644
index 0000000..634717c
--- /dev/null
+++ b/internal/jsonrpc2_v2/frame.go
@@ -0,0 +1,179 @@
+// Copyright 2018 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 jsonrpc2
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+
+ errors "golang.org/x/xerrors"
+)
+
+// Reader abstracts the transport mechanics from the JSON RPC protocol.
+// A Conn reads messages from the reader it was provided on construction,
+// and assumes that each call to Read fully transfers a single message,
+// or returns an error.
+// A reader is not safe for concurrent use, it is expected it will be used by
+// a single Conn in a safe manner.
+type Reader interface {
+ // Read gets the next message from the stream.
+ Read(context.Context) (Message, int64, error)
+}
+
+// Writer abstracts the transport mechanics from the JSON RPC protocol.
+// A Conn writes messages using the writer it was provided on construction,
+// and assumes that each call to Write fully transfers a single message,
+// or returns an error.
+// A writer is not safe for concurrent use, it is expected it will be used by
+// a single Conn in a safe manner.
+type Writer interface {
+ // Write sends a message to the stream.
+ Write(context.Context, Message) (int64, error)
+}
+
+// Framer wraps low level byte readers and writers into jsonrpc2 message
+// readers and writers.
+// It is responsible for the framing and encoding of messages into wire form.
+type Framer interface {
+ // Reader wraps a byte reader into a message reader.
+ Reader(rw io.Reader) Reader
+ // Writer wraps a byte writer into a message writer.
+ Writer(rw io.Writer) Writer
+}
+
+// RawFramer returns a new Framer.
+// The messages are sent with no wrapping, and rely on json decode consistency
+// to determine message boundaries.
+func RawFramer() Framer { return rawFramer{} }
+
+type rawFramer struct{}
+type rawReader struct{ in *json.Decoder }
+type rawWriter struct{ out io.Writer }
+
+func (rawFramer) Reader(rw io.Reader) Reader {
+ return &rawReader{in: json.NewDecoder(rw)}
+}
+
+func (rawFramer) Writer(rw io.Writer) Writer {
+ return &rawWriter{out: rw}
+}
+
+func (r *rawReader) Read(ctx context.Context) (Message, int64, error) {
+ select {
+ case <-ctx.Done():
+ return nil, 0, ctx.Err()
+ default:
+ }
+ var raw json.RawMessage
+ if err := r.in.Decode(&raw); err != nil {
+ return nil, 0, err
+ }
+ msg, err := DecodeMessage(raw)
+ return msg, int64(len(raw)), err
+}
+
+func (w *rawWriter) Write(ctx context.Context, msg Message) (int64, error) {
+ select {
+ case <-ctx.Done():
+ return 0, ctx.Err()
+ default:
+ }
+ data, err := EncodeMessage(msg)
+ if err != nil {
+ return 0, errors.Errorf("marshaling message: %v", err)
+ }
+ n, err := w.out.Write(data)
+ return int64(n), err
+}
+
+// HeaderFramer returns a new Framer.
+// The messages are sent with HTTP content length and MIME type headers.
+// This is the format used by LSP and others.
+func HeaderFramer() Framer { return headerFramer{} }
+
+type headerFramer struct{}
+type headerReader struct{ in *bufio.Reader }
+type headerWriter struct{ out io.Writer }
+
+func (headerFramer) Reader(rw io.Reader) Reader {
+ return &headerReader{in: bufio.NewReader(rw)}
+}
+
+func (headerFramer) Writer(rw io.Writer) Writer {
+ return &headerWriter{out: rw}
+}
+
+func (r *headerReader) Read(ctx context.Context) (Message, int64, error) {
+ select {
+ case <-ctx.Done():
+ return nil, 0, ctx.Err()
+ default:
+ }
+ var total, length int64
+ // read the header, stop on the first empty line
+ for {
+ line, err := r.in.ReadString('\n')
+ total += int64(len(line))
+ if err != nil {
+ return nil, total, errors.Errorf("failed reading header line: %w", err)
+ }
+ line = strings.TrimSpace(line)
+ // check we have a header line
+ if line == "" {
+ break
+ }
+ colon := strings.IndexRune(line, ':')
+ if colon < 0 {
+ return nil, total, errors.Errorf("invalid header line %q", line)
+ }
+ name, value := line[:colon], strings.TrimSpace(line[colon+1:])
+ switch name {
+ case "Content-Length":
+ if length, err = strconv.ParseInt(value, 10, 32); err != nil {
+ return nil, total, errors.Errorf("failed parsing Content-Length: %v", value)
+ }
+ if length <= 0 {
+ return nil, total, errors.Errorf("invalid Content-Length: %v", length)
+ }
+ default:
+ // ignoring unknown headers
+ }
+ }
+ if length == 0 {
+ return nil, total, errors.Errorf("missing Content-Length header")
+ }
+ data := make([]byte, length)
+ n, err := io.ReadFull(r.in, data)
+ total += int64(n)
+ if err != nil {
+ return nil, total, err
+ }
+ msg, err := DecodeMessage(data)
+ return msg, total, err
+}
+
+func (w *headerWriter) Write(ctx context.Context, msg Message) (int64, error) {
+ select {
+ case <-ctx.Done():
+ return 0, ctx.Err()
+ default:
+ }
+ data, err := EncodeMessage(msg)
+ if err != nil {
+ return 0, errors.Errorf("marshaling message: %v", err)
+ }
+ n, err := fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data))
+ total := int64(n)
+ if err == nil {
+ n, err = w.out.Write(data)
+ total += int64(n)
+ }
+ return total, err
+}
diff --git a/internal/jsonrpc2_v2/jsonrpc2.go b/internal/jsonrpc2_v2/jsonrpc2.go
new file mode 100644
index 0000000..1279ba3
--- /dev/null
+++ b/internal/jsonrpc2_v2/jsonrpc2.go
@@ -0,0 +1,93 @@
+// Copyright 2018 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 jsonrpc2 is a minimal implementation of the JSON RPC 2 spec.
+// https://www.jsonrpc.org/specification
+// It is intended to be compatible with other implementations at the wire level.
+package jsonrpc2
+
+import (
+ "context"
+ "errors"
+)
+
+var (
+ // ErrIdleTimeout is returned when serving timed out waiting for new connections.
+ ErrIdleTimeout = errors.New("timed out waiting for new connections")
+ // ErrNotHandled is returned from a handler to indicate it did not handle the
+ // message.
+ ErrNotHandled = errors.New("JSON RPC not handled")
+ // ErrAsyncResponse is returned from a handler to indicate it will generate a
+ // response asynchronously.
+ ErrAsyncResponse = errors.New("JSON RPC asynchronous response")
+)
+
+// Preempter handles messages on a connection before they are queued to the main
+// handler.
+// Primarily this is used for cancel handlers or notifications for which out of
+// order processing is not an issue.
+type Preempter interface {
+ // Preempt is invoked for each incoming request before it is queued.
+ // If the request is a call, it must return a value or an error for the reply.
+ // Preempt should not block or start any new messages on the connection.
+ Preempt(ctx context.Context, req *Request) (interface{}, error)
+}
+
+// Handler handles messages on a connection.
+type Handler interface {
+ // Handle is invoked for each incoming request.
+ // If the request is a call, it must return a value or an error for the reply.
+ Handle(ctx context.Context, req *Request) (interface{}, error)
+}
+
+type defaultHandler struct{}
+
+func (defaultHandler) Preempt(context.Context, *Request) (interface{}, error) {
+ return nil, ErrNotHandled
+}
+
+func (defaultHandler) Handle(context.Context, *Request) (interface{}, error) {
+ return nil, ErrNotHandled
+}
+
+// async is a small helper for things with an asynchronous result that you can
+// wait for.
+type async struct {
+ ready chan struct{}
+ errBox chan error
+}
+
+func (a *async) init() {
+ a.ready = make(chan struct{})
+ a.errBox = make(chan error, 1)
+ a.errBox <- nil
+}
+
+func (a *async) done() {
+ close(a.ready)
+}
+
+func (a *async) isDone() bool {
+ select {
+ case <-a.ready:
+ return true
+ default:
+ return false
+ }
+}
+
+func (a *async) wait() error {
+ <-a.ready
+ err := <-a.errBox
+ a.errBox <- err
+ return err
+}
+
+func (a *async) setError(err error) {
+ storedErr := <-a.errBox
+ if storedErr == nil {
+ storedErr = err
+ }
+ a.errBox <- storedErr
+}
diff --git a/internal/jsonrpc2_v2/jsonrpc2_test.go b/internal/jsonrpc2_v2/jsonrpc2_test.go
new file mode 100644
index 0000000..6d057b4
--- /dev/null
+++ b/internal/jsonrpc2_v2/jsonrpc2_test.go
@@ -0,0 +1,389 @@
+// Copyright 2018 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 jsonrpc2_test
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "path"
+ "reflect"
+ "testing"
+ "time"
+
+ "golang.org/x/tools/internal/event/export/eventtest"
+ jsonrpc2 "golang.org/x/tools/internal/jsonrpc2_v2"
+ "golang.org/x/tools/internal/stack/stacktest"
+ errors "golang.org/x/xerrors"
+)
+
+var callTests = []invoker{
+ call{"no_args", nil, true},
+ call{"one_string", "fish", "got:fish"},
+ call{"one_number", 10, "got:10"},
+ call{"join", []string{"a", "b", "c"}, "a/b/c"},
+ sequence{"notify", []invoker{
+ notify{"set", 3},
+ notify{"add", 5},
+ call{"get", nil, 8},
+ }},
+ sequence{"preempt", []invoker{
+ async{"a", "wait", "a"},
+ notify{"unblock", "a"},
+ collect{"a", true, false},
+ }},
+ sequence{"basic cancel", []invoker{
+ async{"b", "wait", "b"},
+ cancel{"b"},
+ collect{"b", nil, true},
+ }},
+ sequence{"queue", []invoker{
+ async{"a", "wait", "a"},
+ notify{"set", 1},
+ notify{"add", 2},
+ notify{"add", 3},
+ notify{"add", 4},
+ call{"peek", nil, 0}, // accumulator will not have any adds yet
+ notify{"unblock", "a"},
+ collect{"a", true, false},
+ call{"get", nil, 10}, // accumulator now has all the adds
+ }},
+ sequence{"fork", []invoker{
+ async{"a", "fork", "a"},
+ notify{"set", 1},
+ notify{"add", 2},
+ notify{"add", 3},
+ notify{"add", 4},
+ call{"get", nil, 10}, // fork will not have blocked the adds
+ notify{"unblock", "a"},
+ collect{"a", true, false},
+ }},
+}
+
+type binder struct {
+ framer jsonrpc2.Framer
+ runTest func(*handler)
+}
+
+type handler struct {
+ conn *jsonrpc2.Connection
+ accumulator int
+ waitersBox chan map[string]chan struct{}
+ calls map[string]*jsonrpc2.AsyncCall
+}
+
+type invoker interface {
+ Name() string
+ Invoke(t *testing.T, ctx context.Context, h *handler)
+}
+
+type notify struct {
+ method string
+ params interface{}
+}
+
+type call struct {
+ method string
+ params interface{}
+ expect interface{}
+}
+
+type async struct {
+ name string
+ method string
+ params interface{}
+}
+
+type collect struct {
+ name string
+ expect interface{}
+ fails bool
+}
+
+type cancel struct {
+ name string
+}
+
+type sequence struct {
+ name string
+ tests []invoker
+}
+
+type echo call
+
+type cancelParams struct{ ID int64 }
+
+func TestConnectionRaw(t *testing.T) {
+ testConnection(t, jsonrpc2.RawFramer())
+}
+
+func TestConnectionHeader(t *testing.T) {
+ testConnection(t, jsonrpc2.HeaderFramer())
+}
+
+func testConnection(t *testing.T, framer jsonrpc2.Framer) {
+ stacktest.NoLeak(t)
+ ctx := eventtest.NewContext(context.Background(), t)
+ listener, err := jsonrpc2.NetPipe(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server, err := jsonrpc2.Serve(ctx, listener, binder{framer, nil})
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ listener.Close()
+ server.Wait()
+ }()
+
+ for _, test := range callTests {
+ t.Run(test.Name(), func(t *testing.T) {
+ client, err := jsonrpc2.Dial(ctx,
+ listener.Dialer(), binder{framer, func(h *handler) {
+ defer h.conn.Close()
+ ctx := eventtest.NewContext(ctx, t)
+ test.Invoke(t, ctx, h)
+ if call, ok := test.(*call); ok {
+ // also run all simple call tests in echo mode
+ (*echo)(call).Invoke(t, ctx, h)
+ }
+ }})
+ if err != nil {
+ t.Fatal(err)
+ }
+ client.Wait()
+ })
+ }
+}
+
+func (test notify) Name() string { return test.method }
+func (test notify) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ if err := h.conn.Notify(ctx, test.method, test.params); err != nil {
+ t.Fatalf("%v:Notify failed: %v", test.method, err)
+ }
+}
+
+func (test call) Name() string { return test.method }
+func (test call) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ results := newResults(test.expect)
+ if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, results); err != nil {
+ t.Fatalf("%v:Call failed: %v", test.method, err)
+ }
+ verifyResults(t, test.method, results, test.expect)
+}
+
+func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ results := newResults(test.expect)
+ if err := h.conn.Call(ctx, "echo", []interface{}{test.method, test.params}).Await(ctx, results); err != nil {
+ t.Fatalf("%v:Echo failed: %v", test.method, err)
+ }
+ verifyResults(t, test.method, results, test.expect)
+}
+
+func (test async) Name() string { return test.name }
+func (test async) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ h.calls[test.name] = h.conn.Call(ctx, test.method, test.params)
+}
+
+func (test collect) Name() string { return test.name }
+func (test collect) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ o := h.calls[test.name]
+ results := newResults(test.expect)
+ err := o.Await(ctx, results)
+ switch {
+ case test.fails && err == nil:
+ t.Fatalf("%v:Collect was supposed to fail", test.name)
+ case !test.fails && err != nil:
+ t.Fatalf("%v:Collect failed: %v", test.name, err)
+ }
+ verifyResults(t, test.name, results, test.expect)
+}
+
+func (test cancel) Name() string { return test.name }
+func (test cancel) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ o := h.calls[test.name]
+ if err := h.conn.Notify(ctx, "cancel", &cancelParams{o.ID().Raw().(int64)}); err != nil {
+ t.Fatalf("%v:Collect failed: %v", test.name, err)
+ }
+}
+
+func (test sequence) Name() string { return test.name }
+func (test sequence) Invoke(t *testing.T, ctx context.Context, h *handler) {
+ for _, child := range test.tests {
+ child.Invoke(t, ctx, h)
+ }
+}
+
+// newResults makes a new empty copy of the expected type to put the results into
+func newResults(expect interface{}) interface{} {
+ switch e := expect.(type) {
+ case []interface{}:
+ var r []interface{}
+ for _, v := range e {
+ r = append(r, reflect.New(reflect.TypeOf(v)).Interface())
+ }
+ return r
+ case nil:
+ return nil
+ default:
+ return reflect.New(reflect.TypeOf(expect)).Interface()
+ }
+}
+
+// verifyResults compares the results to the expected values
+func verifyResults(t *testing.T, method string, results interface{}, expect interface{}) {
+ if expect == nil {
+ if results != nil {
+ t.Errorf("%v:Got results %+v where none expeted", method, expect)
+ }
+ return
+ }
+ val := reflect.Indirect(reflect.ValueOf(results)).Interface()
+ if !reflect.DeepEqual(val, expect) {
+ t.Errorf("%v:Results are incorrect, got %+v expect %+v", method, val, expect)
+ }
+}
+
+func (b binder) Bind(ctx context.Context, conn *jsonrpc2.Connection) (jsonrpc2.ConnectionOptions, error) {
+ h := &handler{
+ conn: conn,
+ waitersBox: make(chan map[string]chan struct{}, 1),
+ calls: make(map[string]*jsonrpc2.AsyncCall),
+ }
+ h.waitersBox <- make(map[string]chan struct{})
+ if b.runTest != nil {
+ go b.runTest(h)
+ }
+ return jsonrpc2.ConnectionOptions{
+ Framer: b.framer,
+ Preempter: h,
+ Handler: h,
+ }, nil
+}
+
+func (h *handler) waiter(name string) chan struct{} {
+ waiters := <-h.waitersBox
+ defer func() { h.waitersBox <- waiters }()
+ waiter, found := waiters[name]
+ if !found {
+ waiter = make(chan struct{})
+ waiters[name] = waiter
+ }
+ return waiter
+}
+
+func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+ switch req.Method {
+ case "unblock":
+ var name string
+ if err := json.Unmarshal(req.Params, &name); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ close(h.waiter(name))
+ return nil, nil
+ case "peek":
+ if len(req.Params) > 0 {
+ return nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)
+ }
+ return h.accumulator, nil
+ case "cancel":
+ var params cancelParams
+ if err := json.Unmarshal(req.Params, ¶ms); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ h.conn.Cancel(jsonrpc2.Int64ID(params.ID))
+ return nil, nil
+ default:
+ return nil, jsonrpc2.ErrNotHandled
+ }
+}
+
+func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+ switch req.Method {
+ case "no_args":
+ if len(req.Params) > 0 {
+ return nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)
+ }
+ return true, nil
+ case "one_string":
+ var v string
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ return "got:" + v, nil
+ case "one_number":
+ var v int
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ return fmt.Sprintf("got:%d", v), nil
+ case "set":
+ var v int
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ h.accumulator = v
+ return nil, nil
+ case "add":
+ var v int
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ h.accumulator += v
+ return nil, nil
+ case "get":
+ if len(req.Params) > 0 {
+ return nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)
+ }
+ return h.accumulator, nil
+ case "join":
+ var v []string
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ return path.Join(v...), nil
+ case "echo":
+ var v []interface{}
+ if err := json.Unmarshal(req.Params, &v); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ var result interface{}
+ err := h.conn.Call(ctx, v[0].(string), v[1]).Await(ctx, &result)
+ return result, err
+ case "wait":
+ var name string
+ if err := json.Unmarshal(req.Params, &name); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ select {
+ case <-h.waiter(name):
+ return true, nil
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-time.After(time.Second):
+ return nil, errors.Errorf("wait for %q timed out", name)
+ }
+ case "fork":
+ var name string
+ if err := json.Unmarshal(req.Params, &name); err != nil {
+ return nil, errors.Errorf("%w: %s", jsonrpc2.ErrParse, err)
+ }
+ waitFor := h.waiter(name)
+ go func() {
+ select {
+ case <-waitFor:
+ h.conn.Respond(req.ID, true, nil)
+ case <-ctx.Done():
+ h.conn.Respond(req.ID, nil, ctx.Err())
+ case <-time.After(time.Second):
+ h.conn.Respond(req.ID, nil, errors.Errorf("wait for %q timed out", name))
+ }
+ }()
+ return nil, jsonrpc2.ErrAsyncResponse
+ default:
+ return nil, jsonrpc2.ErrNotHandled
+ }
+}
diff --git a/internal/jsonrpc2_v2/messages.go b/internal/jsonrpc2_v2/messages.go
new file mode 100644
index 0000000..652ac81
--- /dev/null
+++ b/internal/jsonrpc2_v2/messages.go
@@ -0,0 +1,181 @@
+// Copyright 2018 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 jsonrpc2
+
+import (
+ "encoding/json"
+
+ errors "golang.org/x/xerrors"
+)
+
+// ID is a Request identifier.
+type ID struct {
+ value interface{}
+}
+
+// Message is the interface to all jsonrpc2 message types.
+// They share no common functionality, but are a closed set of concrete types
+// that are allowed to implement this interface. The message types are *Request
+// and *Response.
+type Message interface {
+ // marshal builds the wire form from the API form.
+ // It is private, which makes the set of Message implementations closed.
+ marshal(to *wireCombined)
+}
+
+// Request is a Message sent to a peer to request behavior.
+// If it has an ID it is a call, otherwise it is a notification.
+type Request struct {
+ // ID of this request, used to tie the Response back to the request.
+ // This will be nil for notifications.
+ ID ID
+ // Method is a string containing the method name to invoke.
+ Method string
+ // Params is either a struct or an array with the parameters of the method.
+ Params json.RawMessage
+}
+
+// Response is a Message used as a reply to a call Request.
+// It will have the same ID as the call it is a response to.
+type Response struct {
+ // result is the content of the response.
+ Result json.RawMessage
+ // err is set only if the call failed.
+ Error error
+ // id of the request this is a response to.
+ ID ID
+}
+
+// StringID creates a new string request identifier.
+func StringID(s string) ID { return ID{value: s} }
+
+// Int64ID creates a new integer request identifier.
+func Int64ID(i int64) ID { return ID{value: i} }
+
+// IsValid returns true if the ID is a valid identifier.
+// The default value for ID will return false.
+func (id ID) IsValid() bool { return id.value != nil }
+
+// Raw returns the underlying value of the ID.
+func (id ID) Raw() interface{} { return id.value }
+
+// NewNotification constructs a new Notification message for the supplied
+// method and parameters.
+func NewNotification(method string, params interface{}) (*Request, error) {
+ p, merr := marshalToRaw(params)
+ return &Request{Method: method, Params: p}, merr
+}
+
+// NewCall constructs a new Call message for the supplied ID, method and
+// parameters.
+func NewCall(id ID, method string, params interface{}) (*Request, error) {
+ p, merr := marshalToRaw(params)
+ return &Request{ID: id, Method: method, Params: p}, merr
+}
+
+func (msg *Request) IsCall() bool { return msg.ID.IsValid() }
+
+func (msg *Request) marshal(to *wireCombined) {
+ to.ID = msg.ID.value
+ to.Method = msg.Method
+ to.Params = msg.Params
+}
+
+// NewResponse constructs a new Response message that is a reply to the
+// supplied. If err is set result may be ignored.
+func NewResponse(id ID, result interface{}, rerr error) (*Response, error) {
+ r, merr := marshalToRaw(result)
+ return &Response{ID: id, Result: r, Error: rerr}, merr
+}
+
+func (msg *Response) marshal(to *wireCombined) {
+ to.ID = msg.ID.value
+ to.Error = toWireError(msg.Error)
+ to.Result = msg.Result
+}
+
+func toWireError(err error) *wireError {
+ if err == nil {
+ // no error, the response is complete
+ return nil
+ }
+ if err, ok := err.(*wireError); ok {
+ // already a wire error, just use it
+ return err
+ }
+ result := &wireError{Message: err.Error()}
+ var wrapped *wireError
+ if errors.As(err, &wrapped) {
+ // if we wrapped a wire error, keep the code from the wrapped error
+ // but the message from the outer error
+ result.Code = wrapped.Code
+ }
+ return result
+}
+
+func EncodeMessage(msg Message) ([]byte, error) {
+ wire := wireCombined{VersionTag: wireVersion}
+ msg.marshal(&wire)
+ data, err := json.Marshal(&wire)
+ if err != nil {
+ return data, errors.Errorf("marshaling jsonrpc message: %w", err)
+ }
+ return data, nil
+}
+
+func DecodeMessage(data []byte) (Message, error) {
+ msg := wireCombined{}
+ if err := json.Unmarshal(data, &msg); err != nil {
+ return nil, errors.Errorf("unmarshaling jsonrpc message: %w", err)
+ }
+ if msg.VersionTag != wireVersion {
+ return nil, errors.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion)
+ }
+ id := ID{}
+ switch v := msg.ID.(type) {
+ case nil:
+ case float64:
+ // coerce the id type to int64 if it is float64, the spec does not allow fractional parts
+ id = Int64ID(int64(v))
+ case int64:
+ id = Int64ID(v)
+ case string:
+ id = StringID(v)
+ default:
+ return nil, errors.Errorf("invalid message id type <%T>%v", v, v)
+ }
+ if msg.Method != "" {
+ // has a method, must be a call
+ return &Request{
+ Method: msg.Method,
+ ID: id,
+ Params: msg.Params,
+ }, nil
+ }
+ // no method, should be a response
+ if !id.IsValid() {
+ return nil, ErrInvalidRequest
+ }
+ resp := &Response{
+ ID: id,
+ Result: msg.Result,
+ }
+ // we have to check if msg.Error is nil to avoid a typed error
+ if msg.Error != nil {
+ resp.Error = msg.Error
+ }
+ return resp, nil
+}
+
+func marshalToRaw(obj interface{}) (json.RawMessage, error) {
+ if obj == nil {
+ return nil, nil
+ }
+ data, err := json.Marshal(obj)
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(data), nil
+}
diff --git a/internal/jsonrpc2_v2/net.go b/internal/jsonrpc2_v2/net.go
new file mode 100644
index 0000000..c8cfaab
--- /dev/null
+++ b/internal/jsonrpc2_v2/net.go
@@ -0,0 +1,129 @@
+// Copyright 2018 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 jsonrpc2
+
+import (
+ "context"
+ "io"
+ "net"
+ "os"
+ "time"
+)
+
+// This file contains implementations of the transport primitives that use the standard network
+// package.
+
+// NetListenOptions is the optional arguments to the NetListen function.
+type NetListenOptions struct {
+ NetListenConfig net.ListenConfig
+ NetDialer net.Dialer
+}
+
+// NetListener returns a new Listener that listents on a socket using the net package.
+func NetListener(ctx context.Context, network, address string, options NetListenOptions) (Listener, error) {
+ ln, err := options.NetListenConfig.Listen(ctx, network, address)
+ if err != nil {
+ return nil, err
+ }
+ return &netListener{net: ln}, nil
+}
+
+// netListener is the implementation of Listener for connections made using the net package.
+type netListener struct {
+ net net.Listener
+}
+
+// Accept blocks waiting for an incoming connection to the listener.
+func (l *netListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
+ return l.net.Accept()
+}
+
+// Close will cause the listener to stop listening. It will not close any connections that have
+// already been accepted.
+func (l *netListener) Close() error {
+ addr := l.net.Addr()
+ err := l.net.Close()
+ if addr.Network() == "unix" {
+ rerr := os.Remove(addr.String())
+ if rerr != nil && err == nil {
+ err = rerr
+ }
+ }
+ return err
+}
+
+// Dialer returns a dialer that can be used to connect to the listener.
+func (l *netListener) Dialer() Dialer {
+ return NetDialer(l.net.Addr().Network(), l.net.Addr().String(), net.Dialer{
+ Timeout: 5 * time.Second,
+ })
+}
+
+// NetDialer returns a Dialer using the supplied standard network dialer.
+func NetDialer(network, address string, nd net.Dialer) Dialer {
+ return &netDialer{
+ network: network,
+ address: address,
+ dialer: nd,
+ }
+}
+
+type netDialer struct {
+ network string
+ address string
+ dialer net.Dialer
+}
+
+func (n *netDialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
+ return n.dialer.DialContext(ctx, n.network, n.address)
+}
+
+// NetPipe returns a new Listener that listens using net.Pipe.
+// It is only possibly to connect to it using the Dialier returned by the
+// Dialer method, each call to that method will generate a new pipe the other
+// side of which will be returnd from the Accept call.
+func NetPipe(ctx context.Context) (Listener, error) {
+ return &netPiper{
+ done: make(chan struct{}),
+ dialed: make(chan io.ReadWriteCloser),
+ }, nil
+}
+
+// netPiper is the implementation of Listener build on top of net.Pipes.
+type netPiper struct {
+ done chan struct{}
+ dialed chan io.ReadWriteCloser
+}
+
+// Accept blocks waiting for an incoming connection to the listener.
+func (l *netPiper) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
+ // block until we have a listener, or are closed or cancelled
+ select {
+ case rwc := <-l.dialed:
+ return rwc, nil
+ case <-l.done:
+ return nil, io.EOF
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ }
+}
+
+// Close will cause the listener to stop listening. It will not close any connections that have
+// already been accepted.
+func (l *netPiper) Close() error {
+ // unblock any accept calls that are pending
+ close(l.done)
+ return nil
+}
+
+func (l *netPiper) Dialer() Dialer {
+ return l
+}
+
+func (l *netPiper) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
+ client, server := net.Pipe()
+ l.dialed <- server
+ return client, nil
+}
diff --git a/internal/jsonrpc2_v2/serve.go b/internal/jsonrpc2_v2/serve.go
new file mode 100644
index 0000000..78259b1
--- /dev/null
+++ b/internal/jsonrpc2_v2/serve.go
@@ -0,0 +1,265 @@
+// Copyright 2020 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 jsonrpc2
+
+import (
+ "context"
+ "io"
+ "sync"
+ "time"
+
+ errors "golang.org/x/xerrors"
+)
+
+// Listener is implemented by protocols to accept new inbound connections.
+type Listener interface {
+ // Accept an inbound connection to a server.
+ // It must block until an inbound connection is made, or the listener is
+ // shut down.
+ Accept(context.Context) (io.ReadWriteCloser, error)
+
+ // Close is used to ask a listener to stop accepting new connections.
+ Close() error
+
+ // Dialer returns a dialer that can be used to connect to this listener
+ // locally.
+ // If a listener does not implement this it will return a nil.
+ Dialer() Dialer
+}
+
+// Dialer is used by clients to dial a server.
+type Dialer interface {
+ // Dial returns a new communication byte stream to a listening server.
+ Dial(ctx context.Context) (io.ReadWriteCloser, error)
+}
+
+// Server is a running server that is accepting incoming connections.
+type Server struct {
+ listener Listener
+ binder Binder
+ async async
+}
+
+// Dial uses the dialer to make a new connection, wraps the returned
+// reader and writer using the framer to make a stream, and then builds
+// a connection on top of that stream using the binder.
+func Dial(ctx context.Context, dialer Dialer, binder Binder) (*Connection, error) {
+ // dial a server
+ rwc, err := dialer.Dial(ctx)
+ if err != nil {
+ return nil, err
+ }
+ return newConnection(ctx, rwc, binder)
+}
+
+// Serve starts a new server listening for incoming connections and returns
+// it.
+// This returns a fully running and connected server, it does not block on
+// the listener.
+// You can call Wait to block on the server, or Shutdown to get the sever to
+// terminate gracefully.
+// To notice incoming connections, use an intercepting Binder.
+func Serve(ctx context.Context, listener Listener, binder Binder) (*Server, error) {
+ server := &Server{
+ listener: listener,
+ binder: binder,
+ }
+ server.async.init()
+ go server.run(ctx)
+ return server, nil
+}
+
+// Wait returns only when the server has shut down.
+func (s *Server) Wait() error {
+ return s.async.wait()
+}
+
+// run accepts incoming connections from the listener,
+// If IdleTimeout is non-zero, run exits after there are no clients for this
+// duration, otherwise it exits only on error.
+func (s *Server) run(ctx context.Context) {
+ defer s.async.done()
+ var activeConns []*Connection
+ for {
+ // we never close the accepted connection, we rely on the other end
+ // closing or the socket closing itself naturally
+ rwc, err := s.listener.Accept(ctx)
+ if err != nil {
+ if !isClosingError(err) {
+ s.async.setError(err)
+ }
+ // we are done generating new connections for good
+ break
+ }
+
+ // see if any connections were closed while we were waiting
+ activeConns = onlyActive(activeConns)
+
+ // a new inbound connection,
+ conn, err := newConnection(ctx, rwc, s.binder)
+ if err != nil {
+ if !isClosingError(err) {
+ s.async.setError(err)
+ }
+ continue
+ }
+ activeConns = append(activeConns, conn)
+ }
+
+ // wait for all active conns to finish
+ for _, c := range activeConns {
+ c.Wait()
+ }
+}
+
+func onlyActive(conns []*Connection) []*Connection {
+ i := 0
+ for _, c := range conns {
+ if !c.async.isDone() {
+ conns[i] = c
+ i++
+ }
+ }
+ // trim the slice down
+ return conns[:i]
+}
+
+// isClosingError reports if the error occurs normally during the process of
+// closing a network connection. It uses imperfect heuristics that err on the
+// side of false negatives, and should not be used for anything critical.
+func isClosingError(err error) bool {
+ if err == nil {
+ return false
+ }
+ // fully unwrap the error, so the following tests work
+ for wrapped := err; wrapped != nil; wrapped = errors.Unwrap(err) {
+ err = wrapped
+ }
+
+ // was it based on an EOF error?
+ if err == io.EOF {
+ return true
+ }
+
+ // Per https://github.com/golang/go/issues/4373, this error string should not
+ // change. This is not ideal, but since the worst that could happen here is
+ // some superfluous logging, it is acceptable.
+ if err.Error() == "use of closed network connection" {
+ return true
+ }
+
+ return false
+}
+
+// NewIdleListener wraps a listener with an idle timeout.
+// When there are no active connections for at least the timeout duration a
+// call to accept will fail with ErrIdleTimeout.
+func NewIdleListener(timeout time.Duration, wrap Listener) Listener {
+ l := &idleListener{
+ timeout: timeout,
+ wrapped: wrap,
+ newConns: make(chan *idleCloser),
+ closed: make(chan struct{}),
+ wasTimeout: make(chan struct{}),
+ }
+ go l.run()
+ return l
+}
+
+type idleListener struct {
+ wrapped Listener
+ timeout time.Duration
+ newConns chan *idleCloser
+ closed chan struct{}
+ wasTimeout chan struct{}
+ closeOnce sync.Once
+}
+
+type idleCloser struct {
+ wrapped io.ReadWriteCloser
+ closed chan struct{}
+ closeOnce sync.Once
+}
+
+func (c *idleCloser) Read(p []byte) (int, error) {
+ n, err := c.wrapped.Read(p)
+ if err != nil && isClosingError(err) {
+ c.closeOnce.Do(func() { close(c.closed) })
+ }
+ return n, err
+}
+
+func (c *idleCloser) Write(p []byte) (int, error) {
+ // we do not close on write failure, we rely on the wrapped writer to do that
+ // if it is appropriate, which we will detect in the next read.
+ return c.wrapped.Write(p)
+}
+
+func (c *idleCloser) Close() error {
+ // we rely on closing the wrapped stream to signal to the next read that we
+ // are closed, rather than triggering the closed signal directly
+ return c.wrapped.Close()
+}
+
+func (l *idleListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
+ rwc, err := l.wrapped.Accept(ctx)
+ if err != nil {
+ if isClosingError(err) {
+ // underlying listener was closed
+ l.closeOnce.Do(func() { close(l.closed) })
+ // was it closed because of the idle timeout?
+ select {
+ case <-l.wasTimeout:
+ err = ErrIdleTimeout
+ default:
+ }
+ }
+ return nil, err
+ }
+ conn := &idleCloser{
+ wrapped: rwc,
+ closed: make(chan struct{}),
+ }
+ l.newConns <- conn
+ return conn, err
+}
+
+func (l *idleListener) Close() error {
+ defer l.closeOnce.Do(func() { close(l.closed) })
+ return l.wrapped.Close()
+}
+
+func (l *idleListener) Dialer() Dialer {
+ return l.wrapped.Dialer()
+}
+
+func (l *idleListener) run() {
+ var conns []*idleCloser
+ for {
+ var firstClosed chan struct{} // left at nil if there are no active conns
+ var timeout <-chan time.Time // left at nil if there are active conns
+ if len(conns) > 0 {
+ firstClosed = conns[0].closed
+ } else {
+ timeout = time.After(l.timeout)
+ }
+ select {
+ case <-l.closed:
+ // the main listener closed, no need to keep going
+ return
+ case conn := <-l.newConns:
+ // a new conn arrived, add it to the list
+ conns = append(conns, conn)
+ case <-timeout:
+ // we timed out, only happens when there are no active conns
+ // close the underlying listener, and allow the normal closing process to happen
+ close(l.wasTimeout)
+ l.wrapped.Close()
+ case <-firstClosed:
+ // a conn closed, remove it from the active list
+ conns = conns[:copy(conns, conns[1:])]
+ }
+ }
+}
diff --git a/internal/jsonrpc2_v2/serve_test.go b/internal/jsonrpc2_v2/serve_test.go
new file mode 100644
index 0000000..22f18cd
--- /dev/null
+++ b/internal/jsonrpc2_v2/serve_test.go
@@ -0,0 +1,140 @@
+// Copyright 2020 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 jsonrpc2_test
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ jsonrpc2 "golang.org/x/tools/internal/jsonrpc2_v2"
+ "golang.org/x/tools/internal/stack/stacktest"
+)
+
+func TestIdleTimeout(t *testing.T) {
+ stacktest.NoLeak(t)
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ listener, err := jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ listener = jsonrpc2.NewIdleListener(100*time.Millisecond, listener)
+ defer listener.Close()
+ server, err := jsonrpc2.Serve(ctx, listener, jsonrpc2.ConnectionOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ connect := func() *jsonrpc2.Connection {
+ client, err := jsonrpc2.Dial(ctx,
+ listener.Dialer(),
+ jsonrpc2.ConnectionOptions{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ return client
+ }
+ // Exercise some connection/disconnection patterns, and then assert that when
+ // our timer fires, the server exits.
+ conn1 := connect()
+ conn2 := connect()
+ if err := conn1.Close(); err != nil {
+ t.Fatalf("conn1.Close failed with error: %v", err)
+ }
+ if err := conn2.Close(); err != nil {
+ t.Fatalf("conn2.Close failed with error: %v", err)
+ }
+ conn3 := connect()
+ if err := conn3.Close(); err != nil {
+ t.Fatalf("conn3.Close failed with error: %v", err)
+ }
+
+ serverError := server.Wait()
+
+ if !errors.Is(serverError, jsonrpc2.ErrIdleTimeout) {
+ t.Errorf("run() returned error %v, want %v", serverError, jsonrpc2.ErrIdleTimeout)
+ }
+}
+
+type msg struct {
+ Msg string
+}
+
+type fakeHandler struct{}
+
+func (fakeHandler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
+ switch req.Method {
+ case "ping":
+ return &msg{"pong"}, nil
+ default:
+ return nil, jsonrpc2.ErrNotHandled
+ }
+}
+
+func TestServe(t *testing.T) {
+ stacktest.NoLeak(t)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ tests := []struct {
+ name string
+ factory func(context.Context) (jsonrpc2.Listener, error)
+ }{
+ {"tcp", func(ctx context.Context) (jsonrpc2.Listener, error) {
+ return jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{})
+ }},
+ {"pipe", func(ctx context.Context) (jsonrpc2.Listener, error) {
+ return jsonrpc2.NetPipe(ctx)
+ }},
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ fake, err := test.factory(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ conn, shutdown, err := newFake(ctx, fake)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer shutdown(ctx)
+ var got msg
+ if err := conn.Call(ctx, "ping", &msg{"ting"}).Await(ctx, &got); err != nil {
+ t.Fatal(err)
+ }
+ if want := "pong"; got.Msg != want {
+ t.Errorf("conn.Call(...): returned %q, want %q", got, want)
+ }
+ })
+ }
+}
+
+func newFake(ctx context.Context, l jsonrpc2.Listener) (*jsonrpc2.Connection, func(context.Context), error) {
+ l = jsonrpc2.NewIdleListener(100*time.Millisecond, l)
+ server, err := jsonrpc2.Serve(ctx, l, jsonrpc2.ConnectionOptions{
+ Handler: fakeHandler{},
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
+ client, err := jsonrpc2.Dial(ctx,
+ l.Dialer(),
+ jsonrpc2.ConnectionOptions{
+ Handler: fakeHandler{},
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+ return client, func(ctx context.Context) {
+ l.Close()
+ client.Close()
+ server.Wait()
+ }, nil
+}
diff --git a/internal/jsonrpc2_v2/wire.go b/internal/jsonrpc2_v2/wire.go
new file mode 100644
index 0000000..97b1ae8
--- /dev/null
+++ b/internal/jsonrpc2_v2/wire.go
@@ -0,0 +1,74 @@
+// Copyright 2018 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 jsonrpc2
+
+import (
+ "encoding/json"
+)
+
+// This file contains the go forms of the wire specification.
+// see http://www.jsonrpc.org/specification for details
+
+var (
+ // ErrUnknown should be used for all non coded errors.
+ ErrUnknown = NewError(-32001, "JSON RPC unknown error")
+ // ErrParse is used when invalid JSON was received by the server.
+ ErrParse = NewError(-32700, "JSON RPC parse error")
+ // ErrInvalidRequest is used when the JSON sent is not a valid Request object.
+ ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request")
+ // ErrMethodNotFound should be returned by the handler when the method does
+ // not exist / is not available.
+ ErrMethodNotFound = NewError(-32601, "JSON RPC method not found")
+ // ErrInvalidParams should be returned by the handler when method
+ // parameter(s) were invalid.
+ ErrInvalidParams = NewError(-32602, "JSON RPC invalid params")
+ // ErrInternal indicates a failure to process a call correctly
+ ErrInternal = NewError(-32603, "JSON RPC internal error")
+
+ // The following errors are not part of the json specification, but
+ // compliant extensions specific to this implimentation.
+
+ // ErrServerOverloaded is returned when a message was refused due to a
+ // server being temporarily unable to accept any new messages.
+ ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded")
+)
+
+const wireVersion = "2.0"
+
+// wireCombined has all the fields of both Request and Response.
+// We can decode this and then work out which it is.
+type wireCombined struct {
+ VersionTag string `json:"jsonrpc"`
+ ID interface{} `json:"id,omitempty"`
+ Method string `json:"method,omitempty"`
+ Params json.RawMessage `json:"params,omitempty"`
+ Result json.RawMessage `json:"result,omitempty"`
+ Error *wireError `json:"error,omitempty"`
+}
+
+// wireError represents a structured error in a Response.
+type wireError struct {
+ // Code is an error code indicating the type of failure.
+ Code int64 `json:"code"`
+ // Message is a short description of the error.
+ Message string `json:"message"`
+ // Data is optional structured data containing additional information about the error.
+ Data json.RawMessage `json:"data,omitempty"`
+}
+
+// NewError returns an error that will encode on the wire correctly.
+// The standard codes are made available from this package, this function should
+// only be used to build errors for application specific codes as allowed by the
+// specification.
+func NewError(code int64, message string) error {
+ return &wireError{
+ Code: code,
+ Message: message,
+ }
+}
+
+func (err *wireError) Error() string {
+ return err.Message
+}
diff --git a/internal/jsonrpc2_v2/wire_test.go b/internal/jsonrpc2_v2/wire_test.go
new file mode 100644
index 0000000..e933737
--- /dev/null
+++ b/internal/jsonrpc2_v2/wire_test.go
@@ -0,0 +1,118 @@
+// Copyright 2020 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 jsonrpc2_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "reflect"
+ "testing"
+
+ jsonrpc2 "golang.org/x/tools/internal/jsonrpc2_v2"
+)
+
+func TestWireMessage(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ msg jsonrpc2.Message
+ encoded []byte
+ }{{
+ name: "notification",
+ msg: newNotification("alive", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","method":"alive"}`),
+ }, {
+ name: "call",
+ msg: newCall("msg1", "ping", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","id":"msg1","method":"ping"}`),
+ }, {
+ name: "response",
+ msg: newResponse("msg2", "pong", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","id":"msg2","result":"pong"}`),
+ }, {
+ name: "numerical id",
+ msg: newCall(1, "poke", nil),
+ encoded: []byte(`{"jsonrpc":"2.0","id":1,"method":"poke"}`),
+ }, {
+ // originally reported in #39719, this checks that result is not present if
+ // it is an error response
+ name: "computing fix edits",
+ msg: newResponse(3, nil, jsonrpc2.NewError(0, "computing fix edits")),
+ encoded: []byte(`{
+ "jsonrpc":"2.0",
+ "id":3,
+ "error":{
+ "code":0,
+ "message":"computing fix edits"
+ }
+ }`),
+ }} {
+ b, err := jsonrpc2.EncodeMessage(test.msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ checkJSON(t, b, test.encoded)
+ msg, err := jsonrpc2.DecodeMessage(test.encoded)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(msg, test.msg) {
+ t.Errorf("decoded message does not match\nGot:\n%+#v\nWant:\n%+#v", msg, test.msg)
+ }
+ }
+}
+
+func newNotification(method string, params interface{}) jsonrpc2.Message {
+ msg, err := jsonrpc2.NewNotification(method, params)
+ if err != nil {
+ panic(err)
+ }
+ return msg
+}
+
+func newID(id interface{}) jsonrpc2.ID {
+ switch v := id.(type) {
+ case nil:
+ return jsonrpc2.ID{}
+ case string:
+ return jsonrpc2.StringID(v)
+ case int:
+ return jsonrpc2.Int64ID(int64(v))
+ case int64:
+ return jsonrpc2.Int64ID(v)
+ default:
+ panic("invalid ID type")
+ }
+}
+
+func newCall(id interface{}, method string, params interface{}) jsonrpc2.Message {
+ msg, err := jsonrpc2.NewCall(newID(id), method, params)
+ if err != nil {
+ panic(err)
+ }
+ return msg
+}
+
+func newResponse(id interface{}, result interface{}, rerr error) jsonrpc2.Message {
+ msg, err := jsonrpc2.NewResponse(newID(id), result, rerr)
+ if err != nil {
+ panic(err)
+ }
+ return msg
+}
+
+func checkJSON(t *testing.T, got, want []byte) {
+ // compare the compact form, to allow for formatting differences
+ g := &bytes.Buffer{}
+ if err := json.Compact(g, []byte(got)); err != nil {
+ t.Fatal(err)
+ }
+ w := &bytes.Buffer{}
+ if err := json.Compact(w, []byte(want)); err != nil {
+ t.Fatal(err)
+ }
+ if g.String() != w.String() {
+ t.Errorf("encoded message does not match\nGot:\n%s\nWant:\n%s", g, w)
+ }
+}
diff --git a/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go
index 19675af..e3d7443 100644
--- a/internal/lsp/cache/analysis.go
+++ b/internal/lsp/cache/analysis.go
@@ -351,7 +351,7 @@
}
for _, diag := range diagnostics {
- srcDiags, err := analysisDiagnosticDiagnostics(ctx, snapshot, pkg, analyzer, diag)
+ srcDiags, err := analysisDiagnosticDiagnostics(snapshot, pkg, analyzer, diag)
if err != nil {
event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(pkg.ID()))
continue
@@ -423,6 +423,7 @@
clone := *diag
clone.SuggestedFixes = eaDiag.SuggestedFixes
clone.Tags = eaDiag.Tags
+ clone.Analyzer = eaDiag.Analyzer
diag = &clone
}
}
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index fa1b530..7221874 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -29,7 +29,7 @@
"golang.org/x/tools/internal/span"
)
-func New(ctx context.Context, options func(*source.Options)) *Cache {
+func New(options func(*source.Options)) *Cache {
index := atomic.AddInt64(&cacheIndex, 1)
c := &Cache{
id: strconv.FormatInt(index, 10),
@@ -270,7 +270,7 @@
return 0
}
var count int64
- ast.Inspect(f, func(n ast.Node) bool {
+ ast.Inspect(f, func(_ ast.Node) bool {
count += 32 // nodes are pretty small.
return true
})
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 9df626b..65c3371 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -55,6 +55,26 @@
}
}
+func (ph *packageHandle) imports(ctx context.Context, s source.Snapshot) (result []string) {
+ for _, pgh := range ph.goFiles {
+ f, err := s.ParseGo(ctx, pgh.file, source.ParseHeader)
+ if err != nil {
+ continue
+ }
+ seen := map[string]struct{}{}
+ for _, impSpec := range f.File.Imports {
+ imp := strings.Trim(impSpec.Path.Value, `"`)
+ if _, ok := seen[imp]; !ok {
+ seen[imp] = struct{}{}
+ result = append(result, imp)
+ }
+ }
+ }
+
+ sort.Strings(result)
+ return result
+}
+
// packageData contains the data produced by type-checking a package.
type packageData struct {
pkg *pkg
@@ -162,7 +182,7 @@
depKeys = append(depKeys, depHandle.key)
}
experimentalKey := s.View().Options().ExperimentalPackageCacheKey
- ph.key = checkPackageKey(ctx, ph.m.id, compiledGoFiles, m.config, depKeys, mode, experimentalKey)
+ ph.key = checkPackageKey(ph.m.id, compiledGoFiles, m.config, depKeys, mode, experimentalKey)
return ph, deps, nil
}
@@ -174,7 +194,7 @@
}
}
-func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey {
+func checkPackageKey(id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey {
b := bytes.NewBuffer(nil)
b.WriteString(string(id))
if !experimentalKey {
@@ -381,7 +401,7 @@
// Try to attach error messages to the file as much as possible.
var found bool
for _, e := range m.errors {
- srcDiags, err := goPackagesErrorDiagnostics(ctx, snapshot, pkg, e)
+ srcDiags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
if err != nil {
continue
}
@@ -443,7 +463,7 @@
if len(m.errors) != 0 {
pkg.hasListOrParseErrors = true
for _, e := range m.errors {
- diags, err := goPackagesErrorDiagnostics(ctx, snapshot, pkg, e)
+ diags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(pkg.ID()))
continue
@@ -468,7 +488,7 @@
if len(parseErrors) != 0 {
pkg.hasListOrParseErrors = true
for _, e := range parseErrors {
- diags, err := parseErrorDiagnostics(ctx, snapshot, pkg, e)
+ diags, err := parseErrorDiagnostics(snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(pkg.ID()))
continue
@@ -486,7 +506,7 @@
for _, e := range expandErrors(typeErrors, snapshot.View().Options().RelatedInformationSupported) {
pkg.hasTypeErrors = true
- diags, err := typeErrorDiagnostics(ctx, snapshot, pkg, e)
+ diags, err := typeErrorDiagnostics(snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(pkg.ID()))
continue
@@ -764,7 +784,7 @@
if i == -1 {
return true
}
- if pkgPath == "command-line-arguments" {
+ if isCommandLineArguments(string(pkgPath)) {
return true
}
return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
index f18d5ed..9d109de 100644
--- a/internal/lsp/cache/errors.go
+++ b/internal/lsp/cache/errors.go
@@ -5,7 +5,6 @@
package cache
import (
- "context"
"fmt"
"go/scanner"
"go/token"
@@ -25,9 +24,9 @@
errors "golang.org/x/xerrors"
)
-func goPackagesErrorDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, e packages.Error) ([]*source.Diagnostic, error) {
- if msg, spn, ok := parseGoListImportCycleError(ctx, snapshot, e, pkg); ok {
- rng, err := spanToRange(snapshot, pkg, spn)
+func goPackagesErrorDiagnostics(snapshot *snapshot, pkg *pkg, e packages.Error) ([]*source.Diagnostic, error) {
+ if msg, spn, ok := parseGoListImportCycleError(snapshot, e, pkg); ok {
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
@@ -44,7 +43,7 @@
if e.Pos == "" {
spn = parseGoListError(e.Msg, pkg.m.config.Dir)
// We may not have been able to parse a valid span. Apply the errors to all files.
- if _, err := spanToRange(snapshot, pkg, spn); err != nil {
+ if _, err := spanToRange(pkg, spn); err != nil {
var diags []*source.Diagnostic
for _, cgf := range pkg.compiledGoFiles {
diags = append(diags, &source.Diagnostic{
@@ -60,7 +59,7 @@
spn = span.ParseInDir(e.Pos, pkg.m.config.Dir)
}
- rng, err := spanToRange(snapshot, pkg, spn)
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
@@ -73,7 +72,7 @@
}}, nil
}
-func parseErrorDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, errList scanner.ErrorList) ([]*source.Diagnostic, error) {
+func parseErrorDiagnostics(snapshot *snapshot, pkg *pkg, errList scanner.ErrorList) ([]*source.Diagnostic, error) {
// The first parser error is likely the root cause of the problem.
if errList.Len() <= 0 {
return nil, errors.Errorf("no errors in %v", errList)
@@ -88,7 +87,7 @@
if err != nil {
return nil, err
}
- rng, err := spanToRange(snapshot, pkg, spn)
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
@@ -103,12 +102,12 @@
var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
-func typeErrorDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
+func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary)
if err != nil {
return nil, err
}
- rng, err := spanToRange(snapshot, pkg, spn)
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
@@ -129,7 +128,7 @@
if err != nil {
return nil, err
}
- rng, err := spanToRange(snapshot, pkg, secondarySpan)
+ rng, err := spanToRange(pkg, secondarySpan)
if err != nil {
return nil, err
}
@@ -163,10 +162,10 @@
if err != nil {
return nil, err
}
- return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)}, nil
+ return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil
}
-func analysisDiagnosticDiagnostics(ctx context.Context, snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
+func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
var srcAnalyzer *source.Analyzer
// Find the analyzer that generated this diagnostic.
for _, sa := range source.EnabledAnalyzers(snapshot) {
@@ -180,11 +179,15 @@
if err != nil {
return nil, err
}
- rng, err := spanToRange(snapshot, pkg, spn)
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
- fixes, err := suggestedAnalysisFixes(snapshot, pkg, e)
+ kinds := srcAnalyzer.ActionKind
+ if len(srcAnalyzer.ActionKind) == 0 {
+ kinds = append(kinds, protocol.QuickFix)
+ }
+ fixes, err := suggestedAnalysisFixes(snapshot, pkg, e, kinds)
if err != nil {
return nil, err
}
@@ -197,9 +200,11 @@
if err != nil {
return nil, err
}
- fixes = append(fixes, source.SuggestedFixFromCommand(cmd))
+ for _, kind := range kinds {
+ fixes = append(fixes, source.SuggestedFixFromCommand(cmd, kind))
+ }
}
- related, err := relatedInformation(snapshot, pkg, e)
+ related, err := relatedInformation(pkg, snapshot.FileSet(), e)
if err != nil {
return nil, err
}
@@ -242,7 +247,7 @@
return fmt.Sprintf("https://%s/golang.org/x/tools/internal/typesinternal#%s", target, code.String())
}
-func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
+func suggestedAnalysisFixes(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic, kinds []protocol.CodeActionKind) ([]source.SuggestedFix, error) {
var fixes []source.SuggestedFix
for _, fix := range diag.SuggestedFixes {
edits := make(map[span.URI][]protocol.TextEdit)
@@ -251,7 +256,7 @@
if err != nil {
return nil, err
}
- rng, err := spanToRange(snapshot, pkg, spn)
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
@@ -260,22 +265,26 @@
NewText: string(e.NewText),
})
}
- fixes = append(fixes, source.SuggestedFix{
- Title: fix.Message,
- Edits: edits,
- })
+ for _, kind := range kinds {
+ fixes = append(fixes, source.SuggestedFix{
+ Title: fix.Message,
+ Edits: edits,
+ ActionKind: kind,
+ })
+ }
+
}
return fixes, nil
}
-func relatedInformation(snapshot *snapshot, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
+func relatedInformation(pkg *pkg, fset *token.FileSet, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
var out []source.RelatedInformation
for _, related := range diag.Related {
- spn, err := span.NewRange(snapshot.view.session.cache.fset, related.Pos, related.End).Span()
+ spn, err := span.NewRange(fset, related.Pos, related.End).Span()
if err != nil {
return nil, err
}
- rng, err := spanToRange(snapshot, pkg, spn)
+ rng, err := spanToRange(pkg, spn)
if err != nil {
return nil, err
}
@@ -315,7 +324,7 @@
// spanToRange converts a span.Span to a protocol.Range,
// assuming that the span belongs to the package whose diagnostics are being computed.
-func spanToRange(snapshot *snapshot, pkg *pkg, spn span.Span) (protocol.Range, error) {
+func spanToRange(pkg *pkg, spn span.Span) (protocol.Range, error) {
pgf, err := pkg.File(spn.URI())
if err != nil {
return protocol.Range{}, err
@@ -340,7 +349,7 @@
return span.ParseInDir(input[:msgIndex], wd)
}
-func parseGoListImportCycleError(ctx context.Context, snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
+func parseGoListImportCycleError(snapshot *snapshot, e packages.Error, pkg *pkg) (string, span.Span, bool) {
re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`)
matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg))
if len(matches) < 3 {
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index 2e1917a..a915d05 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -358,7 +358,7 @@
Source: source.ListError,
Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
+ SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
}, nil
case strings.Contains(goCmdError, "updates to go.sum needed"), strings.Contains(goCmdError, "missing go.sum entry"):
@@ -385,8 +385,8 @@
Source: source.ListError,
Message: msg,
SuggestedFixes: []source.SuggestedFix{
- source.SuggestedFixFromCommand(tidyCmd),
- source.SuggestedFixFromCommand(updateCmd),
+ source.SuggestedFixFromCommand(tidyCmd, protocol.QuickFix),
+ source.SuggestedFixFromCommand(updateCmd, protocol.QuickFix),
},
}, nil
case strings.Contains(goCmdError, "disabled by GOPROXY=off") && innermost != nil:
@@ -405,7 +405,7 @@
Severity: protocol.SeverityError,
Message: fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version),
Source: source.ListError,
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
+ SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
}, nil
default:
return &source.Diagnostic{
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
index 6891a39..7c92746 100644
--- a/internal/lsp/cache/mod_tidy.go
+++ b/internal/lsp/cache/mod_tidy.go
@@ -77,11 +77,11 @@
Diagnostics: criticalErr.DiagList,
}, nil
}
- workspacePkgs, err := s.WorkspacePackages(ctx)
+ workspacePkgs, err := s.workspacePackageHandles(ctx)
if err != nil {
return nil, err
}
- importHash, err := hashImports(ctx, workspacePkgs)
+ importHash, err := s.hashImports(ctx, workspacePkgs)
if err != nil {
return nil, err
}
@@ -167,18 +167,16 @@
return rangeFromPositions(pmf.Mapper, pmf.File.Module.Syntax.Start, pmf.File.Module.Syntax.End)
}
-func hashImports(ctx context.Context, wsPackages []source.Package) (string, error) {
- results := make(map[string]bool)
+func (s *snapshot) hashImports(ctx context.Context, wsPackages []*packageHandle) (string, error) {
+ seen := map[string]struct{}{}
var imports []string
- for _, pkg := range wsPackages {
- for _, path := range pkg.Imports() {
- imp := path.PkgPath()
- if _, ok := results[imp]; !ok {
- results[imp] = true
+ for _, ph := range wsPackages {
+ for _, imp := range ph.imports(ctx, s) {
+ if _, ok := seen[imp]; !ok {
imports = append(imports, imp)
+ seen[imp] = struct{}{}
}
}
- imports = append(imports, pkg.MissingDependencies()...)
}
sort.Strings(imports)
hashed := strings.Join(imports, ",")
@@ -188,7 +186,7 @@
// modTidyDiagnostics computes the differences between the original and tidied
// go.mod files to produce diagnostic and suggested fixes. Some diagnostics
// may appear on the Go files that import packages from missing modules.
-func modTidyDiagnostics(ctx context.Context, snapshot source.Snapshot, pm *source.ParsedModule, ideal *modfile.File, workspacePkgs []source.Package) (diagnostics []*source.Diagnostic, err error) {
+func modTidyDiagnostics(ctx context.Context, snapshot source.Snapshot, pm *source.ParsedModule, ideal *modfile.File, workspacePkgs []*packageHandle) (diagnostics []*source.Diagnostic, err error) {
// First, determine which modules are unused and which are missing from the
// original go.mod file.
var (
@@ -224,7 +222,7 @@
// statements in the Go files in which the dependencies are used.
missingModuleFixes := map[*modfile.Require][]source.SuggestedFix{}
for _, req := range missing {
- srcDiag, err := missingModuleDiagnostic(snapshot, pm, req)
+ srcDiag, err := missingModuleDiagnostic(pm, req)
if err != nil {
return nil, err
}
@@ -233,17 +231,13 @@
}
// Add diagnostics for missing modules anywhere they are imported in the
// workspace.
- for _, pkg := range workspacePkgs {
+ for _, ph := range workspacePkgs {
missingImports := map[string]*modfile.Require{}
- var importedPkgs []string
// If -mod=readonly is not set we may have successfully imported
// packages from missing modules. Otherwise they'll be in
// MissingDependencies. Combine both.
- for _, imp := range pkg.Imports() {
- importedPkgs = append(importedPkgs, imp.PkgPath())
- }
- importedPkgs = append(importedPkgs, pkg.MissingDependencies()...)
+ importedPkgs := ph.imports(ctx, snapshot)
for _, imp := range importedPkgs {
if req, ok := missing[imp]; ok {
@@ -274,7 +268,11 @@
if len(missingImports) == 0 {
continue
}
- for _, pgf := range pkg.CompiledGoFiles() {
+ for _, pgh := range ph.compiledGoFiles {
+ pgf, err := snapshot.ParseGo(ctx, pgh.file, source.ParseHeader)
+ if err != nil {
+ continue
+ }
file, m := pgf.File, pgf.Mapper
if file == nil || m == nil {
continue
@@ -311,7 +309,7 @@
// Finally, add errors for any unused dependencies.
onlyDiagnostic := len(diagnostics) == 0 && len(unused) == 1
for _, req := range unused {
- srcErr, err := unusedDiagnostic(pm.Mapper, req, onlyDiagnostic, snapshot.View().Options().ComputeEdits)
+ srcErr, err := unusedDiagnostic(pm.Mapper, req, onlyDiagnostic)
if err != nil {
return nil, err
}
@@ -321,7 +319,7 @@
}
// unusedDiagnostic returns a source.Diagnostic for an unused require.
-func unusedDiagnostic(m *protocol.ColumnMapper, req *modfile.Require, onlyDiagnostic bool, computeEdits diff.ComputeEdits) (*source.Diagnostic, error) {
+func unusedDiagnostic(m *protocol.ColumnMapper, req *modfile.Require, onlyDiagnostic bool) (*source.Diagnostic, error) {
rng, err := rangeFromPositions(m, req.Syntax.Start, req.Syntax.End)
if err != nil {
return nil, err
@@ -341,7 +339,7 @@
Severity: protocol.SeverityWarning,
Source: source.ModTidyError,
Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path),
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
+ SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
}, nil
}
@@ -383,11 +381,12 @@
Edits: map[span.URI][]protocol.TextEdit{
m.URI: edits,
},
+ ActionKind: protocol.QuickFix,
}},
}, nil
}
-func missingModuleDiagnostic(snapshot source.Snapshot, pm *source.ParsedModule, req *modfile.Require) (*source.Diagnostic, error) {
+func missingModuleDiagnostic(pm *source.ParsedModule, req *modfile.Require) (*source.Diagnostic, error) {
var rng protocol.Range
// Default to the start of the file if there is no module declaration.
if pm.File != nil && pm.File.Module != nil && pm.File.Module.Syntax != nil {
@@ -413,7 +412,7 @@
Severity: protocol.SeverityError,
Source: source.ModTidyError,
Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
+ SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
}, nil
}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 928249e..ba155c7 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -7,7 +7,6 @@
import (
"context"
"fmt"
- "os"
"strconv"
"sync"
"sync/atomic"
@@ -30,7 +29,7 @@
viewMu sync.Mutex
views []*View
- viewMap map[span.URI]*View
+ viewMap map[span.URI]*View // map of URI->best view
overlayMu sync.Mutex
overlays map[span.URI]*overlay
@@ -244,15 +243,13 @@
snapshot.initialize(initCtx, true)
if v.tempWorkspace != "" {
var err error
- if err = os.Mkdir(v.tempWorkspace.Filename(), 0700); err == nil {
- var wsdir span.URI
- wsdir, err = snapshot.getWorkspaceDir(initCtx)
- if err == nil {
- err = copyWorkspace(v.tempWorkspace, wsdir)
- }
+ var wsdir span.URI
+ wsdir, err = snapshot.getWorkspaceDir(initCtx)
+ if err == nil {
+ err = copyWorkspace(v.tempWorkspace, wsdir)
}
if err != nil {
- event.Error(initCtx, "creating workspace dir", err)
+ event.Error(ctx, "copying workspace dir", err)
}
}
release()
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index b80d518..28d0449 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -200,14 +200,10 @@
verboseOutput := s.view.options.VerboseOutput
s.view.optionsMu.Unlock()
- // Forcibly disable GOPACKAGESDRIVER. It's incompatible with the
- // packagesinternal APIs we use, and we really only support the go command
- // anyway.
- env := append(append([]string{}, inv.Env...), "GOPACKAGESDRIVER=off")
cfg := &packages.Config{
Context: ctx,
Dir: inv.WorkingDir,
- Env: env,
+ Env: inv.Env,
BuildFlags: inv.BuildFlags,
Mode: packages.NeedName |
packages.NeedFiles |
@@ -761,12 +757,13 @@
}
func (s *snapshot) WorkspacePackages(ctx context.Context) ([]source.Package, error) {
- if err := s.awaitLoaded(ctx); err != nil {
+ phs, err := s.workspacePackageHandles(ctx)
+ if err != nil {
return nil, err
}
var pkgs []source.Package
- for _, pkgID := range s.workspacePackageIDs() {
- pkg, err := s.checkedPackage(ctx, pkgID, s.workspaceParseMode(pkgID))
+ for _, ph := range phs {
+ pkg, err := ph.check(ctx, s)
if err != nil {
return nil, err
}
@@ -775,6 +772,21 @@
return pkgs, nil
}
+func (s *snapshot) workspacePackageHandles(ctx context.Context) ([]*packageHandle, error) {
+ if err := s.awaitLoaded(ctx); err != nil {
+ return nil, err
+ }
+ var phs []*packageHandle
+ for _, pkgID := range s.workspacePackageIDs() {
+ ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID))
+ if err != nil {
+ return nil, err
+ }
+ phs = append(phs, ph)
+ }
+ return phs, nil
+}
+
func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
if err := s.awaitLoaded(ctx); err != nil {
return nil, err
@@ -1085,7 +1097,7 @@
func containsCommandLineArguments(pkgs []source.Package) bool {
for _, pkg := range pkgs {
- if strings.Contains(pkg.ID(), "command-line-arguments") {
+ if isCommandLineArguments(pkg.ID()) {
return true
}
}
@@ -1096,6 +1108,16 @@
// Do not return results until the snapshot's view has been initialized.
s.AwaitInitialized(ctx)
+ // TODO(rstambler): Should we be more careful about returning the
+ // initialization error? Is it possible for the initialization error to be
+ // corrected without a successful reinitialization?
+ s.mu.Lock()
+ initializedErr := s.initializedErr
+ s.mu.Unlock()
+ if initializedErr != nil {
+ return initializedErr
+ }
+
if ctx.Err() != nil {
return &source.CriticalError{MainError: ctx.Err()}
}
@@ -1114,10 +1136,7 @@
DiagList: diags,
}
}
- // TODO(rstambler): Should we be more careful about returning the
- // initialization error? Is it possible for the initialization error to be
- // corrected without a successful reinitialization?
- return s.initializedErr
+ return nil
}
func (s *snapshot) AwaitInitialized(ctx context.Context) {
@@ -1173,11 +1192,27 @@
// that exist only in overlays. As a workaround, we search all of the files
// available in the snapshot and reload their metadata individually using a
// file= query if the metadata is unavailable.
- scopes := s.orphanedFileScopes()
+ files := s.orphanedFiles()
+
+ // Files without a valid package declaration can't be loaded. Don't try.
+ var scopes []interface{}
+ for _, file := range files {
+ pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
+ if err != nil {
+ continue
+ }
+ if !pgf.File.Package.IsValid() {
+ continue
+ }
+ scopes = append(scopes, fileURI(file.URI()))
+ }
+
if len(scopes) == 0 {
return nil
}
+ // The regtests match this exact log message, keep them in sync.
+ event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes))
err := s.load(ctx, false, scopes...)
// If we failed to load some files, i.e. they have no metadata,
@@ -1203,11 +1238,11 @@
return nil
}
-func (s *snapshot) orphanedFileScopes() []interface{} {
+func (s *snapshot) orphanedFiles() []source.VersionedFileHandle {
s.mu.Lock()
defer s.mu.Unlock()
- scopeSet := make(map[span.URI]struct{})
+ var files []source.VersionedFileHandle
for uri, fh := range s.files {
// Don't try to reload metadata for go.mod files.
if fh.Kind() != source.Go {
@@ -1228,14 +1263,10 @@
continue
}
if s.getMetadataForURILocked(uri) == nil {
- scopeSet[uri] = struct{}{}
+ files = append(files, fh)
}
}
- var scopes []interface{}
- for uri := range scopeSet {
- scopes = append(scopes, fileURI(uri))
- }
- return scopes
+ return files
}
func contains(views []*View, view *View) bool {
@@ -1485,14 +1516,17 @@
}
// Copy the URI to package ID mappings, skipping only those URIs whose
// metadata will be reloaded in future calls to load.
-copyIDs:
for k, ids := range s.ids {
+ var newIDs []packageID
for _, id := range ids {
if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok {
- continue copyIDs
+ continue
}
+ newIDs = append(newIDs, id)
}
- result.ids[k] = ids
+ if len(newIDs) != 0 {
+ result.ids[k] = newIDs
+ }
}
// Copy the set of initially loaded packages.
for id, pkgPath := range s.workspacePackages {
@@ -1767,7 +1801,7 @@
// BuildGoplsMod generates a go.mod file for all modules in the workspace. It
// bypasses any existing gopls.mod.
func BuildGoplsMod(ctx context.Context, root span.URI, s source.Snapshot) (*modfile.File, error) {
- allModules, err := findModules(ctx, root, pathExcludedByFilterFunc(s.View().Options()), 0)
+ allModules, err := findModules(root, pathExcludedByFilterFunc(s.View().Options()), 0)
if err != nil {
return nil, err
}
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 64a9e7e..af00c39 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -236,6 +236,10 @@
return v.folder
}
+func (v *View) TempWorkspace() span.URI {
+ return v.tempWorkspace
+}
+
func (v *View) Options() *source.Options {
v.optionsMu.Lock()
defer v.optionsMu.Unlock()
@@ -456,11 +460,6 @@
go v.snapshot.generation.Destroy()
v.snapshotMu.Unlock()
v.importsState.destroy()
- if v.tempWorkspace != "" {
- if err := os.RemoveAll(v.tempWorkspace.Filename()); err != nil {
- event.Error(ctx, "removing temp workspace", err)
- }
- }
}
func (v *View) Session() *Session {
@@ -528,24 +527,30 @@
return
}
s.initializeOnce.Do(func() {
- defer func() {
- s.initializeOnce = nil
- if firstAttempt {
- close(s.view.initialWorkspaceLoad)
- }
- }()
+ s.loadWorkspace(ctx, firstAttempt)
+ })
+}
- // If we have multiple modules, we need to load them by paths.
- var scopes []interface{}
- var modDiagnostics []*source.Diagnostic
- addError := func(uri span.URI, err error) {
- modDiagnostics = append(modDiagnostics, &source.Diagnostic{
- URI: uri,
- Severity: protocol.SeverityError,
- Source: source.ListError,
- Message: err.Error(),
- })
+func (s *snapshot) loadWorkspace(ctx context.Context, firstAttempt bool) {
+ defer func() {
+ s.initializeOnce = nil
+ if firstAttempt {
+ close(s.view.initialWorkspaceLoad)
}
+ }()
+
+ // If we have multiple modules, we need to load them by paths.
+ var scopes []interface{}
+ var modDiagnostics []*source.Diagnostic
+ addError := func(uri span.URI, err error) {
+ modDiagnostics = append(modDiagnostics, &source.Diagnostic{
+ URI: uri,
+ Severity: protocol.SeverityError,
+ Source: source.ListError,
+ Message: err.Error(),
+ })
+ }
+ if len(s.workspace.getActiveModFiles()) > 0 {
for modURI := range s.workspace.getActiveModFiles() {
fh, err := s.GetFile(ctx, modURI)
if err != nil {
@@ -564,31 +569,41 @@
path := parsed.File.Module.Mod.Path
scopes = append(scopes, moduleLoadScope(path))
}
- if len(scopes) == 0 {
- scopes = append(scopes, viewLoadScope("LOAD_VIEW"))
+ } else {
+ scopes = append(scopes, viewLoadScope("LOAD_VIEW"))
+ }
+ var err error
+ if len(scopes) > 0 {
+ err = s.load(ctx, firstAttempt, append(scopes, packagePath("builtin"))...)
+ }
+ if ctx.Err() != nil {
+ return
+ }
+
+ var criticalErr *source.CriticalError
+ if err != nil {
+ event.Error(ctx, "initial workspace load failed", err)
+ extractedDiags, _ := s.extractGoCommandErrors(ctx, err.Error())
+ criticalErr = &source.CriticalError{
+ MainError: err,
+ DiagList: append(modDiagnostics, extractedDiags...),
}
- err := s.load(ctx, firstAttempt, append(scopes, packagePath("builtin"))...)
- if ctx.Err() != nil {
- return
+ } else if len(modDiagnostics) == 1 {
+ criticalErr = &source.CriticalError{
+ MainError: fmt.Errorf(modDiagnostics[0].Message),
+ DiagList: modDiagnostics,
}
- if err != nil {
- event.Error(ctx, "initial workspace load failed", err)
- extractedDiags, _ := s.extractGoCommandErrors(ctx, err.Error())
- s.initializedErr = &source.CriticalError{
- MainError: err,
- DiagList: append(modDiagnostics, extractedDiags...),
- }
- } else if len(modDiagnostics) != 0 {
- s.initializedErr = &source.CriticalError{
- MainError: fmt.Errorf("error loading module names"),
- DiagList: modDiagnostics,
- }
- } else {
- // Clear out the initialization error, in case it had been set
- // previously.
- s.initializedErr = nil
+ } else if len(modDiagnostics) > 1 {
+ criticalErr = &source.CriticalError{
+ MainError: fmt.Errorf("error loading module names"),
+ DiagList: modDiagnostics,
}
- })
+ }
+
+ // Lock the snapshot when setting the initialized error.
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.initializedErr = criticalErr
}
// invalidateContent invalidates the content of a Go file,
@@ -614,7 +629,9 @@
v.snapshot, workspaceChanged = oldSnapshot.clone(ctx, v.baseCtx, changes, forceReloadMetadata)
if workspaceChanged && v.tempWorkspace != "" {
snap := v.snapshot
+ release := snap.generation.Acquire(ctx)
go func() {
+ defer release()
wsdir, err := snap.getWorkspaceDir(ctx)
if err != nil {
event.Error(ctx, "getting workspace dir", err)
@@ -745,7 +762,7 @@
}
// ...else we should check if there's exactly one nested module.
- all, err := findModules(ctx, folder, excludePath, 2)
+ all, err := findModules(folder, excludePath, 2)
if err == errExhausted {
// Fall-back behavior: if we don't find any modules after searching 10000
// files, assume there are none.
diff --git a/internal/lsp/cache/workspace.go b/internal/lsp/cache/workspace.go
index 84db093..6b62d29 100644
--- a/internal/lsp/cache/workspace.go
+++ b/internal/lsp/cache/workspace.go
@@ -108,7 +108,7 @@
}
// Otherwise, in all other modes, search for all of the go.mod files in the
// workspace.
- knownModFiles, err := findModules(ctx, root, excludePath, 0)
+ knownModFiles, err := findModules(root, excludePath, 0)
if err != nil {
return nil, err
}
@@ -305,7 +305,7 @@
result.moduleSource = fileSystemWorkspace
// The parsed gopls.mod is no longer valid.
result.mod = nil
- knownModFiles, err := findModules(ctx, w.root, w.excludePath, 0)
+ knownModFiles, err := findModules(w.root, w.excludePath, 0)
if err != nil {
result.knownModFiles = nil
result.activeModFiles = nil
@@ -463,7 +463,7 @@
// searching stops once modLimit modules have been found.
//
// TODO(rfindley): consider overlays.
-func findModules(ctx context.Context, root span.URI, excludePath func(string) bool, modLimit int) (map[span.URI]struct{}, error) {
+func findModules(root span.URI, excludePath func(string) bool, modLimit int) (map[span.URI]struct{}, error) {
// Walk the view's folder to find all modules in the view.
modFiles := make(map[span.URI]struct{})
searched := 0
diff --git a/internal/lsp/cmd/capabilities_test.go b/internal/lsp/cmd/capabilities_test.go
index acd5450..70db8d7 100644
--- a/internal/lsp/cmd/capabilities_test.go
+++ b/internal/lsp/cmd/capabilities_test.go
@@ -43,7 +43,7 @@
params.Capabilities.Workspace.Configuration = true
// Send an initialize request to the server.
- c.Server = lsp.NewServer(cache.New(ctx, app.options).NewSession(ctx), c.Client)
+ c.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), c.Client)
result, err := c.Server.Initialize(ctx, params)
if err != nil {
t.Fatal(err)
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 228a713..41c2bce 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -208,7 +208,7 @@
switch {
case app.Remote == "":
connection := newConnection(app)
- connection.Server = lsp.NewServer(cache.New(ctx, app.options).NewSession(ctx), connection.Client)
+ connection.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), connection.Client)
ctx = protocol.WithClient(ctx, connection.Client)
return connection, connection.initialize(ctx, app.options)
case strings.HasPrefix(app.Remote, "internal@"):
diff --git a/internal/lsp/cmd/serve.go b/internal/lsp/cmd/serve.go
index a3bfd91..c45790d 100644
--- a/internal/lsp/cmd/serve.go
+++ b/internal/lsp/cmd/serve.go
@@ -86,7 +86,7 @@
lsprpc.RemoteLogfile(s.RemoteLogfile),
)
} else {
- ss = lsprpc.NewStreamServer(cache.New(ctx, s.app.options), isDaemon)
+ ss = lsprpc.NewStreamServer(cache.New(s.app.options), isDaemon)
}
var network, addr string
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
index b8abe6d..869d4f5 100644
--- a/internal/lsp/cmd/test/cmdtest.go
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -49,7 +49,7 @@
func NewTestServer(ctx context.Context, options func(*source.Options)) *servertest.TCPServer {
ctx = debug.WithInstance(ctx, "", "")
- cache := cache.New(ctx, options)
+ cache := cache.New(options)
ss := lsprpc.NewStreamServer(cache, false)
return servertest.NewTCPServer(ctx, ss, nil)
}
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index da6cf97..7a389b5 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -147,11 +147,14 @@
if len(d.SuggestedFixes) == 0 {
continue
}
- kind := protocol.QuickFix
- if d.Analyzer != nil && d.Analyzer.ActionKind != "" {
- kind = d.Analyzer.ActionKind
+ var isFix bool
+ for _, fix := range d.SuggestedFixes {
+ if fix.ActionKind == protocol.QuickFix || fix.ActionKind == protocol.SourceFixAll {
+ isFix = true
+ break
+ }
}
- if kind == protocol.QuickFix || kind == protocol.SourceFixAll {
+ if isFix {
fixDiags = append(fixDiags, d)
} else {
nonFixDiags = append(nonFixDiags, d)
@@ -356,25 +359,13 @@
func codeActionsForDiagnostic(ctx context.Context, snapshot source.Snapshot, sd *source.Diagnostic, pd *protocol.Diagnostic) ([]protocol.CodeAction, error) {
var actions []protocol.CodeAction
for _, fix := range sd.SuggestedFixes {
- action := protocol.CodeAction{
- Title: fix.Title,
- Kind: protocol.QuickFix,
- Edit: protocol.WorkspaceEdit{},
- Command: fix.Command,
- }
- if pd != nil {
- action.Diagnostics = []protocol.Diagnostic{*pd}
- }
- if sd.Analyzer != nil && sd.Analyzer.ActionKind != "" {
- action.Kind = sd.Analyzer.ActionKind
- }
-
+ var changes []protocol.TextDocumentEdit
for uri, edits := range fix.Edits {
fh, err := snapshot.GetVersionedFile(ctx, uri)
if err != nil {
return nil, err
}
- action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, protocol.TextDocumentEdit{
+ changes = append(changes, protocol.TextDocumentEdit{
TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
Version: fh.Version(),
TextDocumentIdentifier: protocol.TextDocumentIdentifier{
@@ -384,6 +375,17 @@
Edits: edits,
})
}
+ action := protocol.CodeAction{
+ Title: fix.Title,
+ Kind: fix.ActionKind,
+ Edit: protocol.WorkspaceEdit{
+ DocumentChanges: changes,
+ },
+ Command: fix.Command,
+ }
+ if pd != nil {
+ action.Diagnostics = []protocol.Diagnostic{*pd}
+ }
actions = append(actions, action)
}
return actions, nil
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 1960270..24fd719 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -683,3 +683,14 @@
})
return result, err
}
+
+func (c *commandHandler) WorkspaceMetadata(ctx context.Context) (command.WorkspaceMetadataResult, error) {
+ var result command.WorkspaceMetadataResult
+ for _, view := range c.s.session.Views() {
+ result.Workspaces = append(result.Workspaces, command.Workspace{
+ Name: view.Name(),
+ ModuleDir: view.TempWorkspace().Filename(),
+ })
+ }
+ return result, nil
+}
diff --git a/internal/lsp/command/command_gen.go b/internal/lsp/command/command_gen.go
index 1871c9d..9cdcb41 100644
--- a/internal/lsp/command/command_gen.go
+++ b/internal/lsp/command/command_gen.go
@@ -37,6 +37,7 @@
UpdateGoSum Command = "update_go_sum"
UpgradeDependency Command = "upgrade_dependency"
Vendor Command = "vendor"
+ WorkspaceMetadata Command = "workspace_metadata"
)
var Commands = []Command{
@@ -58,6 +59,7 @@
UpdateGoSum,
UpgradeDependency,
Vendor,
+ WorkspaceMetadata,
}
func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) {
@@ -172,6 +174,8 @@
return nil, err
}
return nil, s.Vendor(ctx, a0)
+ case "gopls.workspace_metadata":
+ return s.WorkspaceMetadata(ctx)
}
return nil, fmt.Errorf("unsupported command %q", params.Command)
}
@@ -391,3 +395,15 @@
Arguments: args,
}, nil
}
+
+func NewWorkspaceMetadataCommand(title string) (protocol.Command, error) {
+ args, err := MarshalArgs()
+ if err != nil {
+ return protocol.Command{}, err
+ }
+ return protocol.Command{
+ Title: title,
+ Command: "gopls.workspace_metadata",
+ Arguments: args,
+ }, nil
+}
diff --git a/internal/lsp/command/interface.go b/internal/lsp/command/interface.go
index bd5e4c2..1712327 100644
--- a/internal/lsp/command/interface.go
+++ b/internal/lsp/command/interface.go
@@ -118,6 +118,8 @@
ListKnownPackages(context.Context, URIArg) (ListKnownPackagesResult, error)
AddImport(context.Context, AddImportArgs) (AddImportResult, error)
+
+ WorkspaceMetadata(context.Context) (WorkspaceMetadataResult, error)
}
type RunTestsArgs struct {
@@ -206,3 +208,15 @@
type ListKnownPackagesResult struct {
Packages []string
}
+
+type WorkspaceMetadataArgs struct {
+}
+
+type WorkspaceMetadataResult struct {
+ Workspaces []Workspace
+}
+
+type Workspace struct {
+ Name string
+ ModuleDir string
+}
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index 22a377e..d496a40 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -20,9 +20,8 @@
opts.Matcher = source.CaseInsensitive
opts.CompleteUnimported = false
opts.InsertTextFormat = protocol.SnippetTextFormat
- if !strings.Contains(string(src.URI()), "literal") {
- opts.LiteralCompletions = false
- }
+ opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
+ opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
})
got = tests.FilterBuiltins(src, got)
want := expected(t, test, items)
@@ -101,6 +100,7 @@
opts.Matcher = source.Fuzzy
opts.CompleteUnimported = false
opts.LiteralCompletions = true
+ opts.ExperimentalPostfixCompletions = true
})
want := expected(t, test, items)
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
diff --git a/internal/lsp/debug/info.go b/internal/lsp/debug/info.go
index 92114cc..7013396 100644
--- a/internal/lsp/debug/info.go
+++ b/internal/lsp/debug/info.go
@@ -26,7 +26,7 @@
)
// Version is a manually-updated mechanism for tracking versions.
-const Version = "v0.6.9"
+const Version = "v0.6.10"
// ServerVersion is the format used by gopls to report its version to the
// client. This format is structured so that the client can parse it easily.
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 6b75feb..f93e525 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -285,15 +285,25 @@
if err != nil {
event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
}
- for id, diags := range gcReports {
- fh := snapshot.FindFile(id.URI)
- // Don't publish gc details for unsaved buffers, since the underlying
- // logic operates on the file on disk.
- if fh == nil || !fh.Saved() {
- continue
+ s.gcOptimizationDetailsMu.Lock()
+ _, enableGCDetails := s.gcOptimizationDetails[pkg.ID()]
+
+ // NOTE(golang/go#44826): hold the gcOptimizationDetails lock, and re-check
+ // whether gc optimization details are enabled, while storing gc_details
+ // results. This ensures that the toggling of GC details and clearing of
+ // diagnostics does not race with storing the results here.
+ if enableGCDetails {
+ for id, diags := range gcReports {
+ fh := snapshot.FindFile(id.URI)
+ // Don't publish gc details for unsaved buffers, since the underlying
+ // logic operates on the file on disk.
+ if fh == nil || !fh.Saved() {
+ continue
+ }
+ s.storeDiagnostics(snapshot, id.URI, gcDetailsSource, diags)
}
- s.storeDiagnostics(snapshot, id.URI, gcDetailsSource, diags)
}
+ s.gcOptimizationDetailsMu.Unlock()
}
}
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
index 725c596..f10ea44 100644
--- a/internal/lsp/fake/editor.go
+++ b/internal/lsp/fake/editor.go
@@ -275,6 +275,8 @@
params.ProcessID = int32(os.Getpid())
}
+ params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
+
// This is a bit of a hack, since the fake editor doesn't actually support
// watching changed files that match a specific glob pattern. However, the
// editor does send didChangeWatchedFiles notifications, so set this to
@@ -1039,7 +1041,7 @@
}
// CodeAction executes a codeAction request on the server.
-func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range) ([]protocol.CodeAction, error) {
+func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
if e.Server == nil {
return nil, nil
}
@@ -1051,6 +1053,9 @@
}
params := &protocol.CodeActionParams{
TextDocument: e.textDocumentIdentifier(path),
+ Context: protocol.CodeActionContext{
+ Diagnostics: diagnostics,
+ },
}
if rng != nil {
params.Range = *rng
@@ -1089,3 +1094,17 @@
params.TextDocument.URI = e.sandbox.Workdir.URI(path)
return e.Server.DocumentLink(ctx, params)
}
+
+func (e *Editor) DocumentHighlight(ctx context.Context, path string, pos Pos) ([]protocol.DocumentHighlight, error) {
+ if e.Server == nil {
+ return nil, nil
+ }
+ if err := e.checkBufferPosition(path, pos); err != nil {
+ return nil, err
+ }
+ params := &protocol.DocumentHighlightParams{}
+ params.TextDocument.URI = e.sandbox.Workdir.URI(path)
+ params.Position = pos.ToProtocolPosition()
+
+ return e.Server.DocumentHighlight(ctx, params)
+}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index c87107f..6603e47 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -33,7 +33,20 @@
s.state = serverInitializing
s.stateMu.Unlock()
- s.clientPID = int(params.ProcessID)
+ // For uniqueness, use the gopls PID rather than params.ProcessID (the client
+ // pid). Some clients might start multiple gopls servers, though they
+ // probably shouldn't.
+ pid := os.Getpid()
+ s.tempDir = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.%s", pid, s.session.ID()))
+ err := os.Mkdir(s.tempDir, 0700)
+ if err != nil {
+ // MkdirTemp could fail due to permissions issues. This is a problem with
+ // the user's environment, but should not block gopls otherwise behaving.
+ // All usage of s.tempDir should be predicated on having a non-empty
+ // s.tempDir.
+ event.Error(ctx, "creating temp dir", err)
+ s.tempDir = ""
+ }
s.progress.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress
options := s.session.Options()
@@ -197,7 +210,6 @@
}()
}
// Only one view gets to have a workspace.
- assignedWorkspace := false
var allFoldersWg sync.WaitGroup
for _, folder := range folders {
uri := span.URIFromURI(folder.URI)
@@ -206,22 +218,7 @@
continue
}
work := s.progress.start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
- var workspaceURI span.URI = ""
- if !assignedWorkspace && s.clientPID != 0 {
- // For quick-and-dirty testing, set the temp workspace file to
- // $TMPDIR/gopls-<client PID>.workspace.
- //
- // This has a couple limitations:
- // + If there are multiple workspace roots, only the first one gets
- // written to this dir (and the client has no way to know precisely
- // which one).
- // + If a single client PID spawns multiple gopls sessions, they will
- // clobber eachother's temp workspace.
- wsdir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.workspace", s.clientPID))
- workspaceURI = span.URIFromPath(wsdir)
- assignedWorkspace = true
- }
- snapshot, release, err := s.addView(ctx, folder.Name, uri, workspaceURI)
+ snapshot, release, err := s.addView(ctx, folder.Name, uri)
if err != nil {
viewErrors[uri] = err
work.end(fmt.Sprintf("Error loading packages: %s", err))
@@ -471,6 +468,11 @@
// drop all the active views
s.session.Shutdown(ctx)
s.state = serverShutDown
+ if s.tempDir != "" {
+ if err := os.RemoveAll(s.tempDir); err != nil {
+ event.Error(ctx, "removing temp dir", err)
+ }
+ }
}
return nil
}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 2b71029..621e42a 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -46,7 +46,7 @@
func testLSP(t *testing.T, datum *tests.Data) {
ctx := tests.Context(t)
- cache := cache.New(ctx, nil)
+ cache := cache.New(nil)
session := cache.NewSession(ctx)
options := source.DefaultOptions().Clone()
tests.DefaultOptions(options)
diff --git a/internal/lsp/lsprpc/lsprpc_test.go b/internal/lsp/lsprpc/lsprpc_test.go
index 1b546e7..538569b 100644
--- a/internal/lsp/lsprpc/lsprpc_test.go
+++ b/internal/lsp/lsprpc/lsprpc_test.go
@@ -57,7 +57,7 @@
client := fakeClient{logs: make(chan string, 10)}
ctx = debug.WithInstance(ctx, "", "")
- ss := NewStreamServer(cache.New(ctx, nil), false)
+ ss := NewStreamServer(cache.New(nil), false)
ss.serverForTest = server
ts := servertest.NewPipeServer(ctx, ss, nil)
defer checkClose(t, ts.Close)
@@ -114,7 +114,7 @@
func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) {
t.Helper()
serveCtx := debug.WithInstance(ctx, "", "")
- ss := NewStreamServer(cache.New(serveCtx, nil), false)
+ ss := NewStreamServer(cache.New(nil), false)
ss.serverForTest = s
tsDirect := servertest.NewTCPServer(serveCtx, ss, nil)
@@ -217,7 +217,7 @@
clientCtx := debug.WithInstance(baseCtx, "", "")
serverCtx := debug.WithInstance(baseCtx, "", "")
- cache := cache.New(serverCtx, nil)
+ cache := cache.New(nil)
ss := NewStreamServer(cache, false)
tsBackend := servertest.NewTCPServer(serverCtx, ss, nil)
diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go
index 1598ed5..d7310c7 100644
--- a/internal/lsp/mod/code_lens.go
+++ b/internal/lsp/mod/code_lens.go
@@ -50,7 +50,7 @@
upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{
URI: uri,
AddRequire: false,
- GoCmdArgs: []string{"-u", "all"},
+ GoCmdArgs: []string{"-d", "-u", "-t", "./..."},
})
if err != nil {
return nil, err
@@ -58,7 +58,7 @@
upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{
URI: uri,
AddRequire: false,
- GoCmdArgs: requires,
+ GoCmdArgs: append([]string{"-d"}, requires...),
})
if err != nil {
return nil, err
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
index 0aa8caf..6495aeb 100644
--- a/internal/lsp/mod/diagnostics.go
+++ b/internal/lsp/mod/diagnostics.go
@@ -81,7 +81,7 @@
Severity: protocol.SeverityInformation,
Source: source.UpgradeNotification,
Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path),
- SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
+ SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)},
})
}
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index 6ce8926..3298910 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -26,7 +26,7 @@
testenv.NeedsGo1Point(t, 14)
ctx := tests.Context(t)
- cache := cache.New(ctx, nil)
+ cache := cache.New(nil)
session := cache.NewSession(ctx)
options := source.DefaultOptions().Clone()
tests.DefaultOptions(options)
diff --git a/internal/lsp/protocol/typescript/README.md b/internal/lsp/protocol/typescript/README.md
index 456cc85..bf6dc8d 100644
--- a/internal/lsp/protocol/typescript/README.md
+++ b/internal/lsp/protocol/typescript/README.md
@@ -16,7 +16,7 @@
Code is generated and normalized by
-`tsc code.ts && node code.js && gofmt -w ts*.go`
+`tsc && node code.js && gofmt -w ts*.go`
(`code.ts` imports `util.ts`.) This generates 3 files in the current directory, `tsprotocol.go`
containing type definitions, and `tsserver.go`, `tsclient.go` containing API stubs.
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
index bc6c643..dc5d266 100644
--- a/internal/lsp/protocol/typescript/code.ts
+++ b/internal/lsp/protocol/typescript/code.ts
@@ -23,15 +23,13 @@
import { constName, getComments, goName, loc, strKind } from './util';
var program: ts.Program;
-// eslint-disable-next-line no-unused-vars
-var checker: ts.TypeChecker;
function parse() {
// this won't complain if some fnames don't exist
program = ts.createProgram(
u.fnames,
{ target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS });
- checker = program.getTypeChecker(); // finish type checking and assignment
+ program.getTypeChecker(); // finish type checking and assignment
}
// ----- collecting information for RPCs
@@ -56,7 +54,7 @@
if (!ts.isModuleBlock(node.body)) {
throw new Error(
- `expected ModuleBody got ${strKind(node.body)} at ${loc(node)}`);
+ `expected ModuleBlock got ${strKind(node.body)} at ${loc(node)}`);
}
let x: ts.ModuleBlock = node.body;
// The story is to expect const method = 'textDocument/implementation'
@@ -103,7 +101,7 @@
ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]]);
break;
default:
- throw new Error(`${nn.typeArguments.length} at ${loc(nn)}`);
+ throw new Error(`${nn.typeArguments?.length} at ${loc(nn)}`);
}
}
}
@@ -426,7 +424,7 @@
// helper function to find underlying types
// eslint-disable-next-line no-unused-vars
-function underlying(n: ts.Node, f: (n: ts.Node) => void) {
+function underlying(n: ts.Node | undefined, f: (n: ts.Node) => void) {
if (!n) return;
const ff = function (n: ts.Node) {
underlying(n, f);
@@ -543,7 +541,9 @@
}
throw new Error(`546 sameType? ${strKind(a)} ${strKind(b)}`);
}
-
+type CreateMutable<Type> = {
+ -readonly [Property in keyof Type]: Type[Property];
+};
type propMap = Map<string, ts.PropertySignature>;
function propMapSet(pm: propMap, name: string, v: ts.PropertySignature) {
if (!pm.get(name)) {
@@ -557,16 +557,16 @@
return;
}
if (ts.isTypeReferenceNode(a) && ts.isTypeLiteralNode(b)) {
- const x = mergeTypeRefLit(name, a, b);
- const fake: Object = v;
+ const x = mergeTypeRefLit(a, b);
+ const fake: CreateMutable<ts.PropertySignature> = v;
fake['type'] = x;
check(fake as ts.PropertySignature, '565');
pm.set(name, fake as ts.PropertySignature);
return;
}
if (ts.isTypeLiteralNode(a) && ts.isTypeLiteralNode(b)) {
- const x = mergeTypeLitLit(name, a, b);
- const fake: Object = v;
+ const x = mergeTypeLitLit(a, b);
+ const fake: CreateMutable<ts.PropertySignature> = v;
fake['type'] = x;
check(fake as ts.PropertySignature, '578');
pm.set(name, fake as ts.PropertySignature);
@@ -575,7 +575,8 @@
console.log(`577 ${pm.get(name).getText()}\n${v.getText()}`);
throw new Error(`578 should merge ${strKind(a)} and ${strKind(b)} for ${name}`);
}
-function addToProperties(pm: propMap, tn: ts.TypeNode, prefix = '') {
+function addToProperties(pm: propMap, tn: ts.TypeNode | undefined, prefix = '') {
+ if (!tn) return;
if (ts.isTypeReferenceNode(tn)) {
const d = seenTypes.get(goName(tn.typeName.getText()));
if (tn.typeName.getText() === 'T') return;
@@ -602,9 +603,9 @@
});
}
}
-function deepProperties(d: Data): propMap {
+function deepProperties(d: Data): propMap | undefined {
let properties: propMap = new Map<string, ts.PropertySignature>();
- if (!d.alias || !ts.isIntersectionTypeNode(d.alias)) return;
+ if (!d.alias || !ts.isIntersectionTypeNode(d.alias)) return undefined;
d.alias.types.forEach((ts) => addToProperties(properties, ts));
return properties;
}
@@ -625,19 +626,19 @@
d.properties = ts.factory.createNodeArray(v);
}
-function mergeTypeLitLit(name: string, a: ts.TypeLiteralNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
+function mergeTypeLitLit(a: ts.TypeLiteralNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
const v = new Map<string, ts.TypeElement>(); // avoid duplicates
a.members.forEach((te) => v.set(te.name.getText(), te));
b.members.forEach((te) => v.set(te.name.getText(), te));
const x: ts.TypeElement[] = [];
v.forEach((te) => x.push(te));
- const fake: Object = a;
- fake['members'] = x;
+ const fake: CreateMutable<ts.TypeLiteralNode> = a;
+ fake['members'] = ts.factory.createNodeArray(x);
check(fake as ts.TypeLiteralNode, '643');
return fake as ts.TypeLiteralNode;
}
-function mergeTypeRefLit(name: string, a: ts.TypeReferenceNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
+function mergeTypeRefLit(a: ts.TypeReferenceNode, b: ts.TypeLiteralNode): ts.TypeLiteralNode {
const d = seenTypes.get(goName(a.typeName.getText()));
if (!d) throw new Error(`644 name ${a.typeName.getText()} not found`);
const typ = d.me;
@@ -649,10 +650,10 @@
v.forEach((te) => x.push(te));
const w = ts.factory.createNodeArray(x);
- const fk: Object = b;
+ const fk: CreateMutable<ts.TypeLiteralNode> = b;
fk['members'] = w;
- fk['members']['pos'] = b.members.pos;
- fk['members']['end'] = b.members.end;
+ (fk['members'] as { pos: number })['pos'] = b.members.pos;
+ (fk['members'] as { end: number })['end'] = b.members.end;
check(fk as ts.TypeLiteralNode, '662');
return fk as ts.TypeLiteralNode;
}
@@ -801,7 +802,8 @@
}
// return a go type and maybe an assocated javascript tag
-function goType(n: ts.TypeNode, nm: string): string {
+function goType(n: ts.TypeNode | undefined, nm: string): string {
+ if (!n) throw new Error(`goType undefined for ${nm}`);
if (n.getText() == 'T') return 'interface{}'; // should check it's generic
if (ts.isTypeReferenceNode(n)) {
switch (n.getText()) {
@@ -1305,8 +1307,8 @@
side.fd = fs.openSync(side.outputFile, 'w');
}
const f = function (s: string) {
- fs.writeSync(side.fd, s);
- fs.writeSync(side.fd, '\n');
+ fs.writeSync(side.fd!, s);
+ fs.writeSync(side.fd!, '\n');
};
f(u.computeHeader(false));
f(`
diff --git a/internal/lsp/protocol/typescript/tsconfig.json b/internal/lsp/protocol/typescript/tsconfig.json
new file mode 100644
index 0000000..14cfe0c
--- /dev/null
+++ b/internal/lsp/protocol/typescript/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "isolatedModules": true,
+ "moduleResolution": "node",
+ "lib":["ES2020"],
+ "sourceMap": true, // sourceMap or inlineSourceMap? and see inlineSources
+ "target": "ES5",
+
+ "noFallthroughCasesInSwitch": false, // there is one legitimate on
+ "noImplicitReturns": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": false,
+ "noEmitOnError": true,
+
+ // "extendedDiagnostics": true, // for occasional amusement
+
+ // "strict": true, // too many undefineds in types, etc
+ "alwaysStrict": true,
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "strictBindCallApply": true,
+ "strictFunctionTypes": true,
+ "strictNullChecks": false, // doesn't like arrray access, among other things.
+ //"strictPropertyInitialization": true, // needs strictNullChecks
+ },
+ "files": ["./code.ts", "./util.ts"]
+}
diff --git a/internal/lsp/protocol/typescript/util.ts b/internal/lsp/protocol/typescript/util.ts
index 09c8aae..a4f5d7e 100644
--- a/internal/lsp/protocol/typescript/util.ts
+++ b/internal/lsp/protocol/typescript/util.ts
@@ -58,7 +58,7 @@
// Produce a header for Go output files
export function computeHeader(pkgDoc: boolean): string {
let lastMod = 0;
- let lastDate: Date;
+ let lastDate = new Date();
for (const f of fnames) {
const st = fs.statSync(f);
if (st.mtimeMs > lastMod) {
@@ -90,6 +90,7 @@
export function goName(s: string): string {
let ans = s;
if (s.charAt(0) == '_') {
+ // in the end, none of these are emitted.
ans = 'Inner' + s.substring(1);
}
else { ans = s.substring(0, 1).toUpperCase() + s.substring(1); }
@@ -100,8 +101,7 @@
// Generate JSON tag for a struct field
export function JSON(n: ts.PropertySignature): string {
- const json = `\`json:"${n.name.getText()}${
- n.questionToken != undefined ? ',omitempty' : ''}"\``;
+ const json = `\`json:"${n.name.getText()}${n.questionToken !== undefined ? ',omitempty' : ''}"\``;
return json;
}
@@ -149,18 +149,19 @@
}
pra('\n');
for (const key of Object.keys(seenThings).sort()) {
- pra(`${key}: ${seenThings[key]} \n`);
+ pra(`${key}: ${seenThings.get(key)} \n`);
}
}
// Used in printing the AST
let seenThings = new Map<string, number>();
function seenAdd(x: string) {
- seenThings[x] = (seenThings[x] === undefined ? 1 : seenThings[x] + 1);
+ const u = seenThings.get(x);
+ seenThings.set(x, u === undefined ? 1 : u + 1);
}
// eslint-disable-next-line no-unused-vars
-function describe(node: ts.Node, pr: (s: string) => any) {
+function describe(node: ts.Node, pr: (_: string) => any) {
if (node === undefined) {
return;
}
@@ -191,7 +192,8 @@
// For debugging, say where an AST node is in a file
-export function loc(node: ts.Node): string {
+export function loc(node: ts.Node | undefined): string {
+ if (!node) throw new Error('loc called with undefined (cannot happen!)');
const sf = node.getSourceFile();
const start = node.getStart();
const x = sf.getLineAndCharacterOfPosition(start);
@@ -200,9 +202,9 @@
let fn = sf.fileName;
const n = fn.search(/-node./);
fn = fn.substring(n + 6);
- return `${fn} ${x.line + 1}: ${x.character + 1} (${y.line + 1}: ${
- y.character + 1})`;
+ return `${fn} ${x.line + 1}: ${x.character + 1} (${y.line + 1}: ${y.character + 1})`;
}
+
// --- various string stuff
// return a string of the kinds of the immediate descendants
@@ -217,15 +219,14 @@
// What kind of AST node is it? This would just be typescript's
// SyntaxKind[n.kind] except that the default names for some nodes
// are misleading
-export function strKind(n: ts.Node): string {
+export function strKind(n: ts.Node | undefined): string {
if (n == null || n == undefined) {
return 'null';
}
- return kindToStr(n.kind);
+ return kindToStr(n.kind);
}
-export function kindToStr(k: ts.SyntaxKind): string {
- if (k === undefined) return 'unDefined';
+function kindToStr(k: ts.SyntaxKind): string {
const x = ts.SyntaxKind[k];
// some of these have two names
switch (x) {
diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go
index 7f168bc..fbca581 100644
--- a/internal/lsp/semantic.go
+++ b/internal/lsp/semantic.go
@@ -394,11 +394,15 @@
// nothing to map it to
case *types.Nil:
// nil is a predeclared identifier
- e.token(x.Pos(), len("nil"), tokVariable, []string{"readonly"})
+ e.token(x.Pos(), len("nil"), tokVariable, []string{"readonly", "defaultLibrary"})
case *types.PkgName:
e.token(x.Pos(), len(x.Name), tokNamespace, nil)
case *types.TypeName:
- e.token(x.Pos(), len(x.String()), tokType, nil)
+ var mods []string
+ if _, ok := y.Type().(*types.Basic); ok {
+ mods = []string{"defaultLibrary"}
+ }
+ e.token(x.Pos(), len(x.String()), tokType, mods)
case *types.Var:
e.token(x.Pos(), len(x.Name), tokVariable, nil)
default:
@@ -447,6 +451,15 @@
case *ast.InterfaceType:
return tokMember, mods
case *ast.TypeSpec:
+ // GenDecl/Typespec/FuncType/FieldList/Field/Ident
+ // (type A func(b uint64)) (err error)
+ // b and err should not be tokType, but tokVaraible
+ // and in GenDecl/TpeSpec/StructType/FieldList/Field/Ident
+ // (type A struct{b uint64})
+ fldm := e.stack[len(e.stack)-2]
+ if _, ok := fldm.(*ast.Field); ok {
+ return tokVariable, mods
+ }
return tokType, mods
}
}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 793ee5d..dd6d6e2 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -64,13 +64,13 @@
stateMu sync.Mutex
state serverState
-
- session source.Session
- clientPID int
-
// notifications generated before serverInitialized
notifications []*protocol.ShowMessageParams
+ session source.Session
+
+ tempDir string
+
// changedFiles tracks files for which there has been a textDocument/didChange.
changedFilesMu sync.Mutex
changedFiles map[span.URI]struct{}
diff --git a/internal/lsp/snippet/snippet_builder.go b/internal/lsp/snippet/snippet_builder.go
index d518980..f7fc5b4 100644
--- a/internal/lsp/snippet/snippet_builder.go
+++ b/internal/lsp/snippet/snippet_builder.go
@@ -46,6 +46,10 @@
b.sb.WriteString(rawSnip)
}
+func (b *Builder) Write(data []byte) (int, error) {
+ return b.sb.Write(data)
+}
+
// WritePlaceholder writes a tab stop and placeholder value to the Builder.
// The callback style allows for creating nested placeholders. To write an
// empty tab stop, provide a nil callback.
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 8f72405..a106c61 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -222,6 +222,19 @@
Hierarchy: "ui.completion",
},
{
+ Name: "experimentalPostfixCompletions",
+ Type: "bool",
+ Doc: "experimentalPostfixCompletions enables artifical method snippets\nsuch as \"someSlice.sort!\".\n",
+ EnumKeys: EnumKeys{
+ ValueType: "",
+ Keys: nil,
+ },
+ EnumValues: nil,
+ Default: "false",
+ Status: "experimental",
+ Hierarchy: "ui.completion",
+ },
+ {
Name: "importShortcut",
Type: "enum",
Doc: "importShortcut specifies whether import statements should link to\ndocumentation or go to definitions.\n",
@@ -790,6 +803,12 @@
Doc: "Runs `go mod vendor` for a module.",
ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
},
+ {
+ Command: "gopls.workspace_metadata",
+ Title: "",
+ Doc: "",
+ ArgDoc: "",
+ },
},
Lenses: []*LensJSON{
{
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index de6e53f..8864081 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -96,6 +96,7 @@
placeholders bool
literal bool
snippets bool
+ postfix bool
matcher source.Matcher
budget time.Duration
}
@@ -521,6 +522,7 @@
literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
budget: opts.CompletionBudget,
snippets: opts.InsertTextFormat == protocol.SnippetTextFormat,
+ postfix: opts.ExperimentalPostfixCompletions,
},
// default to a matcher that always matches
matcher: prefixMatcher(""),
@@ -1104,6 +1106,9 @@
for _, cand := range candidates {
c.deepState.enqueue(cand)
}
+
+ c.addPostfixSnippetCandidates(ctx, sel)
+
return nil
}
diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go
index ec67060..6d8299c 100644
--- a/internal/lsp/source/completion/format.go
+++ b/internal/lsp/source/completion/format.go
@@ -158,7 +158,7 @@
if prefix != "" {
// If we are in a selector, add an edit to place prefix before selector.
if sel := enclosingSelector(c.path, c.pos); sel != nil {
- edits, err := prependEdit(c.snapshot.FileSet(), c.mapper, sel, prefix)
+ edits, err := c.editText(sel.Pos(), sel.Pos(), prefix)
if err != nil {
return CompletionItem{}, err
}
@@ -229,7 +229,7 @@
return item, nil
}
- hover, err := source.HoverInfo(ctx, pkg, obj, decl)
+ hover, err := source.HoverInfo(ctx, c.snapshot, pkg, obj, decl)
if err != nil {
event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
return item, nil
diff --git a/internal/lsp/source/completion/literal.go b/internal/lsp/source/completion/literal.go
index 2c75310..0938e02 100644
--- a/internal/lsp/source/completion/literal.go
+++ b/internal/lsp/source/completion/literal.go
@@ -7,14 +7,11 @@
import (
"context"
"fmt"
- "go/ast"
- "go/token"
"go/types"
"strings"
"unicode"
"golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/lsp/source"
@@ -111,7 +108,7 @@
// If we are in a selector we must place the "&" before the selector.
// For example, "foo.B<>" must complete to "&foo.Bar{}", not
// "foo.&Bar{}".
- edits, err := prependEdit(c.snapshot.FileSet(), c.mapper, sel, "&")
+ edits, err := c.editText(sel.Pos(), sel.Pos(), "&")
if err != nil {
event.Error(ctx, "error making edit for literal pointer completion", err)
return
@@ -168,20 +165,6 @@
}
}
-// prependEdit produces text edits that preprend the specified prefix
-// to the specified node.
-func prependEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node, prefix string) ([]protocol.TextEdit, error) {
- rng := source.NewMappedRange(fset, m, node.Pos(), node.Pos())
- spn, err := rng.Span()
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(m, []diff.TextEdit{{
- Span: spn,
- NewText: prefix,
- }})
-}
-
// literalCandidateScore is the base score for literal candidates.
// Literal candidates match the expected type so they should be high
// scoring, but we want them ranked below lexical objects of the
diff --git a/internal/lsp/source/completion/postfix_snippets.go b/internal/lsp/source/completion/postfix_snippets.go
new file mode 100644
index 0000000..2c3c6e9
--- /dev/null
+++ b/internal/lsp/source/completion/postfix_snippets.go
@@ -0,0 +1,436 @@
+// Copyright 2020 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 completion
+
+import (
+ "context"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "log"
+ "reflect"
+ "strings"
+ "sync"
+ "text/template"
+
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/imports"
+ "golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/lsp/snippet"
+ "golang.org/x/tools/internal/lsp/source"
+ errors "golang.org/x/xerrors"
+)
+
+// Postfix snippets are artificial methods that allow the user to
+// compose common operations in an "argument oriented" fashion. For
+// example, instead of "sort.Slice(someSlice, ...)" a user can expand
+// "someSlice.sort!".
+
+// postfixTmpl represents a postfix snippet completion candidate.
+type postfixTmpl struct {
+ // label is the completion candidate's label presented to the user.
+ label string
+
+ // details is passed along to the client as the candidate's details.
+ details string
+
+ // body is the template text. See postfixTmplArgs for details on the
+ // facilities available to the template.
+ body string
+
+ tmpl *template.Template
+}
+
+// postfixTmplArgs are the template execution arguments available to
+// the postfix snippet templates.
+type postfixTmplArgs struct {
+ // StmtOK is true if it is valid to replace the selector with a
+ // statement. For example:
+ //
+ // func foo() {
+ // bar.sort! // statement okay
+ //
+ // someMethod(bar.sort!) // statement not okay
+ // }
+ StmtOK bool
+
+ // X is the textual SelectorExpr.X. For example, when completing
+ // "foo.bar.print!", "X" is "foo.bar".
+ X string
+
+ // Obj is the types.Object of SelectorExpr.X, if any.
+ Obj types.Object
+
+ // Type is the type of "foo.bar" in "foo.bar.print!".
+ Type types.Type
+
+ scope *types.Scope
+ snip snippet.Builder
+ importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error)
+ edits []protocol.TextEdit
+ qf types.Qualifier
+ varNames map[string]bool
+}
+
+var postfixTmpls = []postfixTmpl{{
+ label: "sort",
+ details: "sort.Slice()",
+ body: `{{if and (eq .Kind "slice") .StmtOK -}}
+{{.Import "sort"}}.Slice({{.X}}, func({{.VarName nil "i"}}, {{.VarName nil "j"}} int) bool {
+ {{.Cursor}}
+})
+{{- end}}`,
+}, {
+ label: "last",
+ details: "s[len(s)-1]",
+ body: `{{if and (eq .Kind "slice") .Obj -}}
+{{.X}}[len({{.X}})-1]
+{{- end}}`,
+}, {
+ label: "reverse",
+ details: "reverse slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK -}}
+{{$i := .VarName nil "i"}}{{$j := .VarName nil "j" -}}
+for {{$i}}, {{$j}} := 0, len({{.X}})-1; {{$i}} < {{$j}}; {{$i}}, {{$j}} = {{$i}}+1, {{$j}}-1 {
+ {{.X}}[{{$i}}], {{.X}}[{{$j}}] = {{.X}}[{{$j}}], {{.X}}[{{$i}}]
+}
+{{end}}`,
+}, {
+ label: "range",
+ details: "range over slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK -}}
+for {{.VarName nil "i"}}, {{.VarName .ElemType "v"}} := range {{.X}} {
+ {{.Cursor}}
+}
+{{- end}}`,
+}, {
+ label: "append",
+ details: "append and re-assign slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
+{{.X}} = append({{.X}}, {{.Cursor}})
+{{- end}}`,
+}, {
+ label: "append",
+ details: "append to slice",
+ body: `{{if and (eq .Kind "slice") (not .StmtOK) -}}
+append({{.X}}, {{.Cursor}})
+{{- end}}`,
+}, {
+ label: "copy",
+ details: "duplicate slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
+{{$v := (.VarName nil (printf "%sCopy" .X))}}{{$v}} := make([]{{.TypeName .ElemType}}, len({{.X}}))
+copy({{$v}}, {{.X}})
+{{end}}`,
+}, {
+ label: "range",
+ details: "range over map",
+ body: `{{if and (eq .Kind "map") .StmtOK -}}
+for {{.VarName .KeyType "k"}}, {{.VarName .ElemType "v"}} := range {{.X}} {
+ {{.Cursor}}
+}
+{{- end}}`,
+}, {
+ label: "clear",
+ details: "clear map contents",
+ body: `{{if and (eq .Kind "map") .StmtOK -}}
+{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
+ delete({{.X}}, {{$k}})
+}
+{{end}}`,
+}, {
+ label: "keys",
+ details: "create slice of keys",
+ body: `{{if and (eq .Kind "map") .StmtOK -}}
+{{$keysVar := (.VarName nil "keys")}}{{$keysVar}} := make([]{{.TypeName .KeyType}}, 0, len({{.X}}))
+{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
+ {{$keysVar}} = append({{$keysVar}}, {{$k}})
+}
+{{end}}`,
+}, {
+ label: "var",
+ details: "assign to variables",
+ body: `{{if and (eq .Kind "tuple") .StmtOK -}}
+{{$a := .}}{{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{$a.VarName $v.Type $v.Name}}{{end}} := {{.X}}
+{{- end}}`,
+}, {
+ label: "var",
+ details: "assign to variable",
+ body: `{{if and (ne .Kind "tuple") .StmtOK -}}
+{{.VarName .Type ""}} := {{.X}}
+{{- end}}`,
+}, {
+ label: "print",
+ details: "print to stdout",
+ body: `{{if and (ne .Kind "tuple") .StmtOK -}}
+{{.Import "fmt"}}.Printf("{{.EscapeQuotes .X}}: %v\n", {{.X}})
+{{- end}}`,
+}, {
+ label: "print",
+ details: "print to stdout",
+ body: `{{if and (eq .Kind "tuple") .StmtOK -}}
+{{.Import "fmt"}}.Println({{.X}})
+{{- end}}`,
+}}
+
+// Cursor indicates where the client's cursor should end up after the
+// snippet is done.
+func (a *postfixTmplArgs) Cursor() string {
+ a.snip.WriteFinalTabstop()
+ return ""
+}
+
+// Import makes sure the package corresponding to path is imported,
+// returning the identifier to use to refer to the package.
+func (a *postfixTmplArgs) Import(path string) (string, error) {
+ name, edits, err := a.importIfNeeded(path, a.scope)
+ if err != nil {
+ return "", errors.Errorf("couldn't import %q: %w", path, err)
+ }
+ a.edits = append(a.edits, edits...)
+ return name, nil
+}
+
+func (a *postfixTmplArgs) EscapeQuotes(v string) string {
+ return strings.ReplaceAll(v, `"`, `\\"`)
+}
+
+// ElemType returns the Elem() type of xType, if applicable.
+func (a *postfixTmplArgs) ElemType() types.Type {
+ if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil {
+ return e.Elem()
+ }
+ return nil
+}
+
+// Kind returns the underlying kind of type, e.g. "slice", "struct",
+// etc.
+func (a *postfixTmplArgs) Kind() string {
+ t := reflect.TypeOf(a.Type.Underlying())
+ return strings.ToLower(strings.TrimPrefix(t.String(), "*types."))
+}
+
+// KeyType returns the type of X's key. KeyType panics if X is not a
+// map.
+func (a *postfixTmplArgs) KeyType() types.Type {
+ return a.Type.Underlying().(*types.Map).Key()
+}
+
+// Tuple returns the tuple result vars if X is a call expression.
+func (a *postfixTmplArgs) Tuple() []*types.Var {
+ tuple, _ := a.Type.(*types.Tuple)
+ if tuple == nil {
+ return nil
+ }
+
+ typs := make([]*types.Var, 0, tuple.Len())
+ for i := 0; i < tuple.Len(); i++ {
+ typs = append(typs, tuple.At(i))
+ }
+ return typs
+}
+
+// TypeName returns the textual representation of type t.
+func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
+ if t == nil || t == types.Typ[types.Invalid] {
+ return "", fmt.Errorf("invalid type: %v", t)
+ }
+ return types.TypeString(t, a.qf), nil
+}
+
+// VarName returns a suitable variable name for the type t. If t
+// implements the error interface, "err" is used. If t is not a named
+// type then nonNamedDefault is used. Otherwise a name is made by
+// abbreviating the type name. If the resultant name is already in
+// scope, an integer is appended to make a unique name.
+func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string {
+ if t == nil {
+ t = types.Typ[types.Invalid]
+ }
+
+ var name string
+ if types.Implements(t, errorIntf) {
+ name = "err"
+ } else if _, isNamed := source.Deref(t).(*types.Named); !isNamed {
+ name = nonNamedDefault
+ }
+
+ if name == "" {
+ name = types.TypeString(t, func(p *types.Package) string {
+ return ""
+ })
+ name = abbreviateTypeName(name)
+ }
+
+ if dot := strings.LastIndex(name, "."); dot > -1 {
+ name = name[dot+1:]
+ }
+
+ uniqueName := name
+ for i := 2; ; i++ {
+ if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] {
+ break
+ }
+ uniqueName = fmt.Sprintf("%s%d", name, i)
+ }
+
+ a.varNames[uniqueName] = true
+
+ return uniqueName
+}
+
+func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) {
+ if !c.opts.postfix {
+ return
+ }
+
+ initPostfixRules()
+
+ if sel == nil || sel.Sel == nil {
+ return
+ }
+
+ selType := c.pkg.GetTypesInfo().TypeOf(sel.X)
+ if selType == nil {
+ return
+ }
+
+ // Skip empty tuples since there is no value to operate on.
+ if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil {
+ return
+ }
+
+ // Only replace sel with a statement if sel is already a statement.
+ var stmtOK bool
+ for i, n := range c.path {
+ if n == sel && i < len(c.path)-1 {
+ _, stmtOK = c.path[i+1].(*ast.ExprStmt)
+ break
+ }
+ }
+
+ scope := c.pkg.GetTypes().Scope().Innermost(c.pos)
+ if scope == nil {
+ return
+ }
+
+ // afterDot is the position after selector dot, e.g. "|" in
+ // "foo.|print".
+ afterDot := sel.Sel.Pos()
+
+ // We must detect dangling selectors such as:
+ //
+ // foo.<>
+ // bar
+ //
+ // and adjust afterDot so that we don't mistakenly delete the
+ // newline thinking "bar" is part of our selector.
+ tokFile := c.snapshot.FileSet().File(c.pos)
+ if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) {
+ if tokFile.Line(c.pos) != startLine {
+ return
+ }
+ afterDot = c.pos
+ }
+
+ for _, rule := range postfixTmpls {
+ // When completing foo.print<>, "print" is naturally overwritten,
+ // but we need to also remove "foo." so the snippet has a clean
+ // slate.
+ edits, err := c.editText(sel.Pos(), afterDot, "")
+ if err != nil {
+ event.Error(ctx, "error calculating postfix edits", err)
+ return
+ }
+
+ tmplArgs := postfixTmplArgs{
+ X: source.FormatNode(c.snapshot.FileSet(), sel.X),
+ StmtOK: stmtOK,
+ Obj: exprObj(c.pkg.GetTypesInfo(), sel.X),
+ Type: selType,
+ qf: c.qf,
+ importIfNeeded: c.importIfNeeded,
+ scope: scope,
+ varNames: make(map[string]bool),
+ }
+
+ // Feed the template straight into the snippet builder. This
+ // allows templates to build snippets as they are executed.
+ err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs)
+ if err != nil {
+ event.Error(ctx, "error executing postfix template", err)
+ continue
+ }
+
+ if strings.TrimSpace(tmplArgs.snip.String()) == "" {
+ continue
+ }
+
+ score := c.matcher.Score(rule.label)
+ if score <= 0 {
+ continue
+ }
+
+ c.items = append(c.items, CompletionItem{
+ Label: rule.label + "!",
+ Detail: rule.details,
+ Score: float64(score) * 0.01,
+ Kind: protocol.SnippetCompletion,
+ snippet: &tmplArgs.snip,
+ AdditionalTextEdits: append(edits, tmplArgs.edits...),
+ })
+ }
+}
+
+var postfixRulesOnce sync.Once
+
+func initPostfixRules() {
+ postfixRulesOnce.Do(func() {
+ var idx int
+ for _, rule := range postfixTmpls {
+ var err error
+ rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body)
+ if err != nil {
+ log.Panicf("error parsing postfix snippet template: %v", err)
+ }
+ postfixTmpls[idx] = rule
+ idx++
+ }
+ postfixTmpls = postfixTmpls[:idx]
+ })
+}
+
+// importIfNeeded returns the package identifier and any necessary
+// edits to import package pkgPath.
+func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) {
+ defaultName := imports.ImportPathToAssumedName(pkgPath)
+
+ // Check if file already imports pkgPath.
+ for _, s := range c.file.Imports {
+ if source.ImportPath(s) == pkgPath {
+ if s.Name == nil {
+ return defaultName, nil, nil
+ }
+ if s.Name.Name != "_" {
+ return s.Name.Name, nil, nil
+ }
+ }
+ }
+
+ // Give up if the package's name is already in use by another object.
+ if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil {
+ return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath)
+ }
+
+ edits, err := c.importEdits(&importInfo{
+ importPath: pkgPath,
+ })
+ if err != nil {
+ return "", nil, err
+ }
+
+ return defaultName, edits, nil
+}
diff --git a/internal/lsp/source/completion/statements.go b/internal/lsp/source/completion/statements.go
index 62d3cf0..3280bb5 100644
--- a/internal/lsp/source/completion/statements.go
+++ b/internal/lsp/source/completion/statements.go
@@ -18,7 +18,7 @@
// addStatementCandidates adds full statement completion candidates
// appropriate for the current context.
func (c *completer) addStatementCandidates() {
- c.addErrCheckAndReturn()
+ c.addErrCheck()
c.addAssignAppend()
}
@@ -162,27 +162,36 @@
return bestItem
}
-// addErrCheckAndReturn offers a completion candidate of the form:
+// addErrCheck offers a completion candidate of the form:
//
// if err != nil {
// return nil, err
// }
//
+// In the case of test functions, it offers a completion candidate of the form:
+//
+// if err != nil {
+// t.Fatal(err)
+// }
+//
// The position must be in a function that returns an error, and the
// statement preceding the position must be an assignment where the
-// final LHS object is an error. addErrCheckAndReturn will synthesize
+// final LHS object is an error. addErrCheck will synthesize
// zero values as necessary to make the return statement valid.
-func (c *completer) addErrCheckAndReturn() {
+func (c *completer) addErrCheck() {
if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
return
}
var (
- errorType = types.Universe.Lookup("error").Type()
- result = c.enclosingFunc.sig.Results()
+ errorType = types.Universe.Lookup("error").Type()
+ result = c.enclosingFunc.sig.Results()
+ testVar = getTestVar(c.enclosingFunc, c.pkg)
+ isTest = testVar != ""
+ doesNotReturnErr = result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType)
)
- // Make sure our enclosing function returns an error.
- if result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType) {
+ // Make sure our enclosing function is a Test func or returns an error.
+ if !isTest && doesNotReturnErr {
return
}
@@ -205,15 +214,17 @@
}
var (
- // errText is e.g. "err" in "foo, err := bar()".
- errText = source.FormatNode(c.snapshot.FileSet(), lastAssignee)
+ // errVar is e.g. "err" in "foo, err := bar()".
+ errVar = source.FormatNode(c.snapshot.FileSet(), lastAssignee)
// Whether we need to include the "if" keyword in our candidate.
needsIf = true
)
- // "_" isn't a real object.
- if errText == "_" {
+ // If the returned error from the previous statement is "_", it is not a real object.
+ // If we don't have an error, and the function signature takes a testing.TB that is either ignored
+ // or an "_", then we also can't call t.Fatal(err).
+ if errVar == "_" {
return
}
@@ -240,7 +251,7 @@
// if er<>
// Make sure they are typing the error's name.
- if c.matcher.Score(errText) <= 0 {
+ if c.matcher.Score(errVar) <= 0 {
return
}
@@ -277,20 +288,26 @@
if needsIf {
snip.WriteText("if ")
}
- snip.WriteText(fmt.Sprintf("%s != nil {\n\treturn ", errText))
+ snip.WriteText(fmt.Sprintf("%s != nil {\n\t", errVar))
- for i := 0; i < result.Len()-1; i++ {
- snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
- snip.WriteText(", ")
+ var label string
+ if isTest {
+ snip.WriteText(fmt.Sprintf("%s.Fatal(%s)", testVar, errVar))
+ label = fmt.Sprintf("%[1]s != nil { %[2]s.Fatal(%[1]s) }", errVar, testVar)
+ } else {
+ snip.WriteText("return ")
+ for i := 0; i < result.Len()-1; i++ {
+ snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
+ snip.WriteText(", ")
+ }
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ b.WriteText(errVar)
+ })
+ label = fmt.Sprintf("%[1]s != nil { return %[1]s }", errVar)
}
- snip.WritePlaceholder(func(b *snippet.Builder) {
- b.WriteText(errText)
- })
-
snip.WriteText("\n}")
- label := fmt.Sprintf("%[1]s != nil { return %[1]s }", errText)
if needsIf {
label = "if " + label
}
@@ -303,3 +320,41 @@
snippet: &snip,
})
}
+
+// getTestVar checks the function signature's input parameters and returns
+// the name of the first parameter that implements "testing.TB". For example,
+// func someFunc(t *testing.T) returns the string "t", func someFunc(b *testing.B)
+// returns "b" etc. An empty string indicates that the function signature
+// does not take a testing.TB parameter or does so but is ignored such
+// as func someFunc(*testing.T).
+func getTestVar(enclosingFunc *funcInfo, pkg source.Package) string {
+ if enclosingFunc == nil || enclosingFunc.sig == nil {
+ return ""
+ }
+
+ sig := enclosingFunc.sig
+ for i := 0; i < sig.Params().Len(); i++ {
+ param := sig.Params().At(i)
+ if param.Name() == "_" {
+ continue
+ }
+ testingPkg, err := pkg.GetImport("testing")
+ if err != nil {
+ continue
+ }
+ tbObj := testingPkg.GetTypes().Scope().Lookup("TB")
+ if tbObj == nil {
+ continue
+ }
+ iface, ok := tbObj.Type().Underlying().(*types.Interface)
+ if !ok {
+ continue
+ }
+ if !types.Implements(param.Type(), iface) {
+ continue
+ }
+ return param.Name()
+ }
+
+ return ""
+}
diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go
index 6733906..505c7e2 100644
--- a/internal/lsp/source/completion/util.go
+++ b/internal/lsp/source/completion/util.go
@@ -9,6 +9,8 @@
"go/token"
"go/types"
+ "golang.org/x/tools/internal/lsp/diff"
+ "golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
@@ -310,3 +312,15 @@
b, _ := t.Underlying().(*types.Basic)
return b != nil && b.Info()&k > 0
}
+
+func (c *completer) editText(from, to token.Pos, newText string) ([]protocol.TextEdit, error) {
+ rng := source.NewMappedRange(c.snapshot.FileSet(), c.mapper, from, to)
+ spn, err := rng.Span()
+ if err != nil {
+ return nil, err
+ }
+ return source.ToProtocolEdits(c.mapper, []diff.TextEdit{{
+ Span: spn,
+ NewText: newText,
+ }})
+}
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 1948381..58154ca 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -12,9 +12,10 @@
)
type SuggestedFix struct {
- Title string
- Edits map[span.URI][]protocol.TextEdit
- Command *protocol.Command
+ Title string
+ Edits map[span.URI][]protocol.TextEdit
+ Command *protocol.Command
+ ActionKind protocol.CodeActionKind
}
type RelatedInformation struct {
diff --git a/internal/lsp/source/extract.go b/internal/lsp/source/extract.go
index e7faaff..854dc90 100644
--- a/internal/lsp/source/extract.go
+++ b/internal/lsp/source/extract.go
@@ -276,6 +276,10 @@
if _, ok := seenVars[v.obj]; ok {
continue
}
+ if v.obj.Name() == "_" {
+ // The blank identifier is always a local variable
+ continue
+ }
typ := analysisinternal.TypeExpr(fset, file, pkg, v.obj.Type())
if typ == nil {
return nil, fmt.Errorf("nil AST expression for type: %v", v.obj.Name())
diff --git a/internal/lsp/source/fix.go b/internal/lsp/source/fix.go
index 3918355..6a01239 100644
--- a/internal/lsp/source/fix.go
+++ b/internal/lsp/source/fix.go
@@ -42,10 +42,11 @@
ExtractFunction: extractFunction,
}
-func SuggestedFixFromCommand(cmd protocol.Command) SuggestedFix {
+func SuggestedFixFromCommand(cmd protocol.Command, kind protocol.CodeActionKind) SuggestedFix {
return SuggestedFix{
- Title: cmd.Title,
- Command: &cmd,
+ Title: cmd.Title,
+ Command: &cmd,
+ ActionKind: kind,
}
}
diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go
index 3af3f49..7cdb484 100644
--- a/internal/lsp/source/highlight.go
+++ b/internal/lsp/source/highlight.go
@@ -22,10 +22,18 @@
ctx, done := event.Start(ctx, "source.Highlight")
defer done()
- pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
+ // Don't use GetParsedFile because it uses TypecheckWorkspace, and we
+ // always want fully parsed files for highlight, regardless of whether
+ // the file belongs to a workspace package.
+ pkg, err := snapshot.PackageForFile(ctx, fh.URI(), TypecheckFull, WidestPackage)
+ if err != nil {
+ return nil, errors.Errorf("getting package for Highlight: %w", err)
+ }
+ pgf, err := pkg.File(fh.URI())
if err != nil {
return nil, errors.Errorf("getting file for Highlight: %w", err)
}
+
spn, err := pgf.Mapper.PointSpan(pos)
if err != nil {
return nil, err
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 8f23d1d..e27216e 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -12,12 +12,14 @@
"go/constant"
"go/doc"
"go/format"
+ "go/token"
"go/types"
"strings"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
@@ -93,7 +95,7 @@
defer done()
fset := i.Snapshot.FileSet()
- h, err := HoverInfo(ctx, i.pkg, i.Declaration.obj, i.Declaration.node)
+ h, err := HoverInfo(ctx, i.Snapshot, i.pkg, i.Declaration.obj, i.Declaration.node)
if err != nil {
return nil, err
}
@@ -252,7 +254,7 @@
// HoverInfo returns a HoverInformation struct for an ast node and its type
// object.
-func HoverInfo(ctx context.Context, pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
+func HoverInfo(ctx context.Context, s Snapshot, pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
var info *HoverInformation
switch node := node.(type) {
@@ -304,6 +306,35 @@
info = &HoverInformation{source: obj, comment: node.Doc}
case *types.Builtin:
info = &HoverInformation{source: node.Type, comment: node.Doc}
+ case *types.Var:
+ // Object is a function param or the field of an anonymous struct
+ // declared with ':='. Skip the first one because only fields
+ // can have docs.
+ if isFunctionParam(obj, node) {
+ break
+ }
+
+ f := s.FileSet().File(obj.Pos())
+ if f == nil {
+ break
+ }
+
+ pgf, err := pkg.File(span.URIFromPath(f.Name()))
+ if err != nil {
+ return nil, err
+ }
+ posToField, err := s.PosToField(ctx, pgf)
+ if err != nil {
+ return nil, err
+ }
+
+ if field := posToField[obj.Pos()]; field != nil {
+ comment := field.Doc
+ if comment.Text() == "" {
+ comment = field.Comment
+ }
+ info = &HoverInformation{source: obj, comment: comment}
+ }
}
}
@@ -319,6 +350,24 @@
return info, nil
}
+// isFunctionParam returns true if the passed object is either an incoming
+// or an outgoing function param
+func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool {
+ for _, f := range node.Type.Params.List {
+ if f.Pos() == obj.Pos() {
+ return true
+ }
+ }
+ if node.Type.Results != nil {
+ for _, f := range node.Type.Results.List {
+ if f.Pos() == obj.Pos() {
+ return true
+ }
+ }
+ }
+ return false
+}
+
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*HoverInformation, error) {
if _, ok := typ.(*types.Named); ok {
switch typ.Underlying().(type) {
@@ -370,6 +419,11 @@
fieldList = t.Methods
}
case *ast.ValueSpec:
+ // Try to extract the field list of an anonymous struct
+ if fieldList = extractFieldList(spec.Type); fieldList != nil {
+ break
+ }
+
comment := spec.Doc
if comment == nil {
comment = decl.Doc
@@ -379,20 +433,55 @@
}
return &HoverInformation{source: obj, comment: comment}
}
- // If we have a struct or interface declaration,
- // we need to match the object to the corresponding field or method.
+
if fieldList != nil {
- for i := 0; i < len(fieldList.List); i++ {
- field := fieldList.List[i]
- if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
- if field.Doc.Text() != "" {
- return &HoverInformation{source: obj, comment: field.Doc}
- }
- return &HoverInformation{source: obj, comment: field.Comment}
+ comment := findFieldComment(obj.Pos(), fieldList)
+ return &HoverInformation{source: obj, comment: comment}
+ }
+ return &HoverInformation{source: obj, comment: decl.Doc}
+}
+
+// extractFieldList recursively tries to extract a field list.
+// If it is not found, nil is returned.
+func extractFieldList(specType ast.Expr) *ast.FieldList {
+ switch t := specType.(type) {
+ case *ast.StructType:
+ return t.Fields
+ case *ast.InterfaceType:
+ return t.Methods
+ case *ast.ArrayType:
+ return extractFieldList(t.Elt)
+ case *ast.MapType:
+ // Map value has a greater chance to be a struct
+ if fields := extractFieldList(t.Value); fields != nil {
+ return fields
+ }
+ return extractFieldList(t.Key)
+ case *ast.ChanType:
+ return extractFieldList(t.Value)
+ }
+ return nil
+}
+
+// findFieldComment visits all fields in depth-first order and returns
+// the comment of a field with passed position. If no comment is found,
+// nil is returned.
+func findFieldComment(pos token.Pos, fieldList *ast.FieldList) *ast.CommentGroup {
+ for _, field := range fieldList.List {
+ if field.Pos() == pos {
+ if field.Doc.Text() != "" {
+ return field.Doc
+ }
+ return field.Comment
+ }
+
+ if nestedFieldList := extractFieldList(field.Type); nestedFieldList != nil {
+ if c := findFieldComment(pos, nestedFieldList); c != nil {
+ return c
}
}
}
- return &HoverInformation{source: obj, comment: decl.Doc}
+ return nil
}
func FormatHover(h *HoverInformation, options *Options) (string, error) {
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index e648893..7c5e62c 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -323,6 +323,12 @@
return typ.Obj()
case *types.Pointer:
return typeToObject(typ.Elem())
+ case *types.Array:
+ return typeToObject(typ.Elem())
+ case *types.Slice:
+ return typeToObject(typ.Elem())
+ case *types.Chan:
+ return typeToObject(typ.Elem())
default:
return nil
}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 0f8dda1..826faa6 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -129,8 +129,9 @@
SymbolStyle: DynamicSymbols,
},
CompletionOptions: CompletionOptions{
- Matcher: Fuzzy,
- CompletionBudget: 100 * time.Millisecond,
+ Matcher: Fuzzy,
+ CompletionBudget: 100 * time.Millisecond,
+ ExperimentalPostfixCompletions: false,
},
Codelenses: map[string]bool{
string(command.Generate): true,
@@ -293,6 +294,10 @@
// Matcher sets the algorithm that is used when calculating completion
// candidates.
Matcher Matcher `status:"advanced"`
+
+ // ExperimentalPostfixCompletions enables artifical method snippets
+ // such as "someSlice.sort!".
+ ExperimentalPostfixCompletions bool `status:"experimental"`
}
type DocumentationOptions struct {
@@ -681,6 +686,7 @@
// should be enabled in enableAllExperimentMaps.
func (o *Options) enableAllExperiments() {
o.SemanticTokens = true
+ o.ExperimentalPostfixCompletions = true
}
func (o *Options) enableAllExperimentMaps() {
@@ -852,6 +858,9 @@
case "expandWorkspaceToModule":
result.setBool(&o.ExpandWorkspaceToModule)
+ case "experimentalPostfixCompletions":
+ result.setBool(&o.ExperimentalPostfixCompletions)
+
case "experimentalWorkspaceModule":
result.setBool(&o.ExperimentalWorkspaceModule)
@@ -1080,7 +1089,7 @@
return map[string]*Analyzer{
fillreturns.Analyzer.Name: {
Analyzer: fillreturns.Analyzer,
- ActionKind: protocol.SourceFixAll,
+ ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
Enabled: true,
},
nonewvars.Analyzer.Name: {
@@ -1105,7 +1114,7 @@
Analyzer: fillstruct.Analyzer,
Fix: FillStruct,
Enabled: true,
- ActionKind: protocol.RefactorRewrite,
+ ActionKind: []protocol.CodeActionKind{protocol.RefactorRewrite},
},
}
}
@@ -1150,9 +1159,21 @@
unusedwrite.Analyzer.Name: {Analyzer: unusedwrite.Analyzer, Enabled: false},
// gofmt -s suite:
- simplifycompositelit.Analyzer.Name: {Analyzer: simplifycompositelit.Analyzer, Enabled: true, ActionKind: protocol.SourceFixAll},
- simplifyrange.Analyzer.Name: {Analyzer: simplifyrange.Analyzer, Enabled: true, ActionKind: protocol.SourceFixAll},
- simplifyslice.Analyzer.Name: {Analyzer: simplifyslice.Analyzer, Enabled: true, ActionKind: protocol.SourceFixAll},
+ simplifycompositelit.Analyzer.Name: {
+ Analyzer: simplifycompositelit.Analyzer,
+ Enabled: true,
+ ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
+ },
+ simplifyrange.Analyzer.Name: {
+ Analyzer: simplifyrange.Analyzer,
+ Enabled: true,
+ ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
+ },
+ simplifyslice.Analyzer.Name: {
+ Analyzer: simplifyslice.Analyzer,
+ Enabled: true,
+ ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
+ },
}
}
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index 03ac4e8..90cd554 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -106,7 +106,7 @@
node: node,
}
decl.MappedRange = append(decl.MappedRange, rng)
- d, err := HoverInfo(ctx, pkg, decl.obj, decl.node)
+ d, err := HoverInfo(ctx, snapshot, pkg, decl.obj, decl.node)
if err != nil {
return nil, 0, err
}
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 8064b0d..23df0c5 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -47,7 +47,7 @@
func testSource(t *testing.T, datum *tests.Data) {
ctx := tests.Context(t)
- cache := cache.New(ctx, nil)
+ cache := cache.New(nil)
session := cache.NewSession(ctx)
options := source.DefaultOptions().Clone()
tests.DefaultOptions(options)
@@ -177,9 +177,8 @@
opts.DeepCompletion = false
opts.CompleteUnimported = false
opts.InsertTextFormat = protocol.SnippetTextFormat
- if !strings.Contains(string(src.URI()), "literal") {
- opts.LiteralCompletions = false
- }
+ opts.LiteralCompletions = strings.Contains(string(src.URI()), "literal")
+ opts.ExperimentalPostfixCompletions = strings.Contains(string(src.URI()), "postfix")
})
got = tests.FilterBuiltins(src, got)
if diff := tests.DiffCompletionItems(want, got); diff != "" {
@@ -278,6 +277,7 @@
_, got := r.callCompletion(t, src, func(opts *source.Options) {
opts.DeepCompletion = true
opts.Matcher = source.Fuzzy
+ opts.ExperimentalPostfixCompletions = true
})
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
t.Errorf("%s: %s", src, msg)
@@ -931,6 +931,7 @@
// These are pure LSP features, no source level functionality to be tested.
func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {}
+
func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) {
}
func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {}
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index c1ba082..690a781 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -350,6 +350,9 @@
return ""
}
if name, ok := imports[p]; ok {
+ if name == "." {
+ return ""
+ }
return name
}
return p.Name()
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 9bc5eca..412866c 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -215,6 +215,10 @@
// Folder returns the folder with which this view was created.
Folder() span.URI
+ // TempWorkspace returns the folder this view uses for its temporary
+ // workspace module.
+ TempWorkspace() span.URI
+
// Shutdown closes this view, and detaches it from its session.
Shutdown(ctx context.Context)
@@ -291,8 +295,13 @@
// of the client.
// A session may have many active views at any given time.
type Session interface {
- // NewView creates a new View, returning it and its first snapshot.
- NewView(ctx context.Context, name string, folder, tempWorkspaceDir span.URI, options *Options) (View, Snapshot, func(), error)
+ // ID returns the unique identifier for this session on this server.
+ ID() string
+ // NewView creates a new View, returning it and its first snapshot. If a
+ // non-empty tempWorkspace directory is provided, the View will record a copy
+ // of its gopls workspace module in that directory, so that client tooling
+ // can execute in the same main module.
+ NewView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *Options) (View, Snapshot, func(), error)
// Cache returns the cache that created this session, for debugging only.
Cache() interface{}
@@ -522,7 +531,7 @@
// ActionKind is the kind of code action this analyzer produces. If
// unspecified the type defaults to quickfix.
- ActionKind protocol.CodeActionKind
+ ActionKind []protocol.CodeActionKind
}
func (a Analyzer) IsEnabled(view View) bool {
diff --git a/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go b/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go
new file mode 100644
index 0000000..9713b91
--- /dev/null
+++ b/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go
@@ -0,0 +1,13 @@
+package extract
+
+import "fmt"
+
+func main() {
+ x := []rune{} //@mark(exSt9, "x")
+ s := "HELLO"
+ for _, c := range s {
+ x = append(x, c)
+ } //@mark(exEn9, "}")
+ //@extractfunc(exSt9, exEn9)
+ fmt.Printf("%x\n", x)
+}
diff --git a/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden b/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden
new file mode 100644
index 0000000..8604745
--- /dev/null
+++ b/internal/lsp/testdata/extract/extract_function/extract_issue_44813.go.golden
@@ -0,0 +1,20 @@
+-- functionextraction_extract_issue_44813_6_2 --
+package extract
+
+import "fmt"
+
+func main() {
+ x := fn0() //@mark(exEn9, "}")
+ //@extractfunc(exSt9, exEn9)
+ fmt.Printf("%x\n", x)
+}
+
+func fn0() []rune {
+ x := []rune{}
+ s := "HELLO"
+ for _, c := range s {
+ x = append(x, c)
+ }
+ return x
+}
+
diff --git a/internal/lsp/testdata/godef/a/h.go b/internal/lsp/testdata/godef/a/h.go
new file mode 100644
index 0000000..efe7d4e
--- /dev/null
+++ b/internal/lsp/testdata/godef/a/h.go
@@ -0,0 +1,147 @@
+package a
+
+func _() {
+ type s struct {
+ nested struct {
+ // nested number
+ number int64 //@mark(nestedNumber, "number")
+ }
+ nested2 []struct {
+ // nested string
+ str string //@mark(nestedString, "str")
+ }
+ x struct {
+ x struct {
+ x struct {
+ x struct {
+ x struct {
+ // nested map
+ m map[string]float64 //@mark(nestedMap, "m")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ var t s
+ _ = t.nested.number //@hover("number", nestedNumber)
+ _ = t.nested2[0].str //@hover("str", nestedString)
+ _ = t.x.x.x.x.x.m //@hover("m", nestedMap)
+}
+
+func _() {
+ var s struct {
+ // a field
+ a int //@mark(structA, "a")
+ // b nested struct
+ b struct { //@mark(structB, "b")
+ // c field of nested struct
+ c int //@mark(structC, "c")
+ }
+ }
+ _ = s.a //@hover("a", structA)
+ _ = s.b //@hover("b", structB)
+ _ = s.b.c //@hover("c", structC)
+
+ var arr []struct {
+ // d field
+ d int //@mark(arrD, "d")
+ // e nested struct
+ e struct { //@mark(arrE, "e")
+ // f field of nested struct
+ f int //@mark(arrF, "f")
+ }
+ }
+ _ = arr[0].d //@hover("d", arrD)
+ _ = arr[0].e //@hover("e", arrE)
+ _ = arr[0].e.f //@hover("f", arrF)
+
+ var complex []struct {
+ c <-chan map[string][]struct {
+ // h field
+ h int //@mark(complexH, "h")
+ // i nested struct
+ i struct { //@mark(complexI, "i")
+ // j field of nested struct
+ j int //@mark(complexJ, "j")
+ }
+ }
+ }
+ _ = (<-complex[0].c)["0"][0].h //@hover("h", complexH)
+ _ = (<-complex[0].c)["0"][0].i //@hover("i", complexI)
+ _ = (<-complex[0].c)["0"][0].i.j //@hover("j", complexJ)
+
+ var mapWithStructKey map[struct {
+ // X key field
+ x []string //@mark(mapStructKeyX, "x")
+ }]int
+ for k := range mapWithStructKey {
+ _ = k.x //@hover("x", mapStructKeyX)
+ }
+
+ var mapWithStructKeyAndValue map[struct {
+ // Y key field
+ y string //@mark(mapStructKeyY, "y")
+ }]struct {
+ // X value field
+ x string //@mark(mapStructValueX, "x")
+ }
+ for k, v := range mapWithStructKeyAndValue {
+ // TODO: we don't show docs for y field because both map key and value
+ // are structs. And in this case, we parse only map value
+ _ = k.y //@hover("y", mapStructKeyY)
+ _ = v.x //@hover("x", mapStructValueX)
+ }
+
+ var i []map[string]interface {
+ // open method comment
+ open() error //@mark(openMethod, "open")
+ }
+ i[0]["1"].open() //@hover("open", openMethod)
+}
+
+func _() {
+ test := struct {
+ // test description
+ desc string //@mark(testDescription, "desc")
+ }{}
+ _ = test.desc //@hover("desc", testDescription)
+
+ for _, tt := range []struct {
+ // test input
+ in map[string][]struct { //@mark(testInput, "in")
+ // test key
+ key string //@mark(testInputKey, "key")
+ // test value
+ value interface{} //@mark(testInputValue, "value")
+ }
+ result struct {
+ v <-chan struct {
+ // expected test value
+ value int //@mark(testResultValue, "value")
+ }
+ }
+ }{} {
+ _ = tt.in //@hover("in", testInput)
+ _ = tt.in["0"][0].key //@hover("key", testInputKey)
+ _ = tt.in["0"][0].value //@hover("value", testInputValue)
+
+ _ = (<-tt.result.v).value //@hover("value", testResultValue)
+ }
+}
+
+func _() {
+ getPoints := func() []struct {
+ // X coord
+ x int //@mark(returnX, "x")
+ // Y coord
+ y int //@mark(returnY, "y")
+ } {
+ return nil
+ }
+
+ r := getPoints()
+ r[0].x //@hover("x", returnX)
+ r[0].y //@hover("y", returnY)
+}
diff --git a/internal/lsp/testdata/godef/a/h.go.golden b/internal/lsp/testdata/godef/a/h.go.golden
new file mode 100644
index 0000000..71f78e1
--- /dev/null
+++ b/internal/lsp/testdata/godef/a/h.go.golden
@@ -0,0 +1,136 @@
+-- nestedNumber-hover --
+```go
+field number int64
+```
+
+nested number
+-- nestedString-hover --
+```go
+field str string
+```
+
+nested string
+-- nestedMap-hover --
+```go
+field m map[string]float64
+```
+
+nested map
+-- structA-hover --
+```go
+field a int
+```
+
+a field
+-- structB-hover --
+```go
+field b struct{c int}
+```
+
+b nested struct
+-- structC-hover --
+```go
+field c int
+```
+
+c field of nested struct
+-- arrD-hover --
+```go
+field d int
+```
+
+d field
+-- arrE-hover --
+```go
+field e struct{f int}
+```
+
+e nested struct
+-- arrF-hover --
+```go
+field f int
+```
+
+f field of nested struct
+-- complexH-hover --
+```go
+field h int
+```
+
+h field
+-- complexI-hover --
+```go
+field i struct{j int}
+```
+
+i nested struct
+-- complexJ-hover --
+```go
+field j int
+```
+
+j field of nested struct
+-- mapStructKeyX-hover --
+```go
+field x []string
+```
+
+X key field
+-- mapStructKeyY-hover --
+```go
+field y string
+```
+-- mapStructValueX-hover --
+```go
+field x string
+```
+
+X value field
+-- openMethod-hover --
+```go
+func (interface).open() error
+```
+
+open method comment
+-- testDescription-hover --
+```go
+field desc string
+```
+
+test description
+-- testInput-hover --
+```go
+field in map[string][]struct{key string; value interface{}}
+```
+
+test input
+-- testInputKey-hover --
+```go
+field key string
+```
+
+test key
+-- testInputValue-hover --
+```go
+field value interface{}
+```
+
+test value
+-- testResultValue-hover --
+```go
+field value int
+```
+
+expected test value
+-- returnX-hover --
+```go
+field x int
+```
+
+X coord
+-- returnY-hover --
+```go
+field y int
+```
+
+Y coord
\ No newline at end of file
diff --git a/internal/lsp/testdata/godef/b/h.go b/internal/lsp/testdata/godef/b/h.go
new file mode 100644
index 0000000..c2776a0
--- /dev/null
+++ b/internal/lsp/testdata/godef/b/h.go
@@ -0,0 +1,10 @@
+package b
+
+import . "golang.org/x/tools/internal/lsp/godef/a"
+
+func _() {
+ // variable of type a.A
+ var _ A //@mark(AVariable, "_"),hover("_", AVariable)
+
+ AStuff() //@hover("AStuff", AStuff)
+}
diff --git a/internal/lsp/testdata/godef/b/h.go.golden b/internal/lsp/testdata/godef/b/h.go.golden
new file mode 100644
index 0000000..85f0404
--- /dev/null
+++ b/internal/lsp/testdata/godef/b/h.go.golden
@@ -0,0 +1,12 @@
+-- AVariable-hover --
+```go
+var _ A
+```
+
+variable of type a\.A
+-- AStuff-hover --
+```go
+func AStuff()
+```
+
+[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
\ No newline at end of file
diff --git a/internal/lsp/testdata/semantic/a.go.golden b/internal/lsp/testdata/semantic/a.go.golden
index 5bb276c..b09c20d 100644
--- a/internal/lsp/testdata/semantic/a.go.golden
+++ b/internal/lsp/testdata/semantic/a.go.golden
@@ -11,12 +11,12 @@
/*⇒3,keyword,[]*/var (
/*⇒1,variable,[definition]*/a = /*⇒3,namespace,[]*/fmt./*⇒5,function,[]*/Print
- /*⇒1,variable,[definition]*/b []/*⇒6,type,[]*/string = []/*⇒6,type,[]*/string{/*⇒5,string,[]*/"foo"}
- /*⇒2,variable,[definition]*/c1 /*⇒4,keyword,[]*/chan /*⇒3,type,[]*/int
- /*⇒2,variable,[definition]*/c2 <-/*⇒4,keyword,[]*/chan /*⇒3,type,[]*/int
- /*⇒2,variable,[definition]*/c3 = /*⇒4,function,[defaultLibrary]*/make([]/*⇒4,keyword,[]*/chan<- /*⇒3,type,[]*/int)
+ /*⇒1,variable,[definition]*/b []/*⇒6,type,[defaultLibrary]*/string = []/*⇒6,type,[defaultLibrary]*/string{/*⇒5,string,[]*/"foo"}
+ /*⇒2,variable,[definition]*/c1 /*⇒4,keyword,[]*/chan /*⇒3,type,[defaultLibrary]*/int
+ /*⇒2,variable,[definition]*/c2 <-/*⇒4,keyword,[]*/chan /*⇒3,type,[defaultLibrary]*/int
+ /*⇒2,variable,[definition]*/c3 = /*⇒4,function,[defaultLibrary]*/make([]/*⇒4,keyword,[]*/chan<- /*⇒3,type,[defaultLibrary]*/int)
/*⇒1,variable,[definition]*/b = /*⇒1,type,[]*/A{/*⇒1,variable,[]*/X: /*⇒2,number,[]*/23}
- /*⇒1,variable,[definition]*/m /*⇒3,keyword,[]*/map[/*⇒4,type,[]*/bool][/*⇒1,number,[]*/3]/*⇒1,operator,[]*/*/*⇒7,type,[]*/float64
+ /*⇒1,variable,[definition]*/m /*⇒3,keyword,[]*/map[/*⇒4,type,[defaultLibrary]*/bool][/*⇒1,number,[]*/3]/*⇒1,operator,[]*/*/*⇒7,type,[defaultLibrary]*/float64
)
/*⇒5,keyword,[]*/const (
@@ -27,17 +27,17 @@
)
/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/A /*⇒6,keyword,[]*/struct {
- /*⇒1,type,[definition]*/X /*⇒3,type,[]*/int /*⇒6,comment,[]*/`foof`
+ /*⇒1,variable,[definition]*/X /*⇒3,type,[defaultLibrary]*/int /*⇒6,comment,[]*/`foof`
}
/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/B /*⇒9,keyword,[]*/interface {
/*⇒1,type,[]*/A
- /*⇒3,member,[definition]*/sad(/*⇒3,type,[]*/int) /*⇒4,type,[]*/bool
+ /*⇒3,member,[definition]*/sad(/*⇒3,type,[defaultLibrary]*/int) /*⇒4,type,[defaultLibrary]*/bool
}
-/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/F /*⇒3,type,[]*/int
+/*⇒4,keyword,[]*/type /*⇒1,type,[definition]*/F /*⇒3,type,[defaultLibrary]*/int
-/*⇒4,keyword,[]*/func (/*⇒1,variable,[]*/a /*⇒1,operator,[]*/*/*⇒1,type,[]*/A) /*⇒1,member,[definition]*/f() /*⇒4,type,[]*/bool {
- /*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/z /*⇒6,type,[]*/string
+/*⇒4,keyword,[]*/func (/*⇒1,variable,[]*/a /*⇒1,operator,[]*/*/*⇒1,type,[]*/A) /*⇒1,member,[definition]*/f() /*⇒4,type,[defaultLibrary]*/bool {
+ /*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/z /*⇒6,type,[defaultLibrary]*/string
/*⇒1,variable,[definition]*/x /*⇒2,operator,[]*/:= /*⇒5,string,[]*/"foo"
/*⇒1,variable,[]*/a(/*⇒1,variable,[definition]*/x)
/*⇒1,variable,[definition]*/y /*⇒2,operator,[]*/:= /*⇒5,string,[]*/"bar" /*⇒1,operator,[]*/+ /*⇒1,variable,[]*/x
@@ -50,7 +50,7 @@
/*⇒7,keyword,[]*/default:
}
/*⇒3,keyword,[]*/for /*⇒1,variable,[definition]*/k, /*⇒1,variable,[definition]*/v := /*⇒5,keyword,[]*/range /*⇒1,variable,[]*/m {
- /*⇒6,keyword,[]*/return (/*⇒1,operator,[]*/!/*⇒1,variable,[]*/k) /*⇒2,operator,[]*/&& /*⇒1,variable,[]*/v[/*⇒1,number,[]*/0] /*⇒2,operator,[]*/== /*⇒3,variable,[readonly]*/nil
+ /*⇒6,keyword,[]*/return (/*⇒1,operator,[]*/!/*⇒1,variable,[]*/k) /*⇒2,operator,[]*/&& /*⇒1,variable,[]*/v[/*⇒1,number,[]*/0] /*⇒2,operator,[]*/== /*⇒3,variable,[readonly defaultLibrary]*/nil
}
/*⇒2,variable,[]*/c2 /*⇒2,operator,[]*/<- /*⇒1,type,[]*/A./*⇒1,variable,[definition]*/X
/*⇒1,variable,[definition]*/w /*⇒2,operator,[]*/:= /*⇒1,variable,[]*/b[/*⇒1,number,[]*/4:]
diff --git a/internal/lsp/testdata/semantic/b.go.golden b/internal/lsp/testdata/semantic/b.go.golden
index b56bb11..906a624 100644
--- a/internal/lsp/testdata/semantic/b.go.golden
+++ b/internal/lsp/testdata/semantic/b.go.golden
@@ -6,7 +6,7 @@
/*⇒4,keyword,[]*/func /*⇒6,function,[definition]*/weirâ°€d() {
/*⇒5,keyword,[]*/const (
- /*⇒4,variable,[definition readonly]*/snil = /*⇒3,variable,[readonly]*/nil
+ /*⇒4,variable,[definition readonly]*/snil = /*⇒3,variable,[readonly defaultLibrary]*/nil
/*⇒3,variable,[definition readonly]*/nil = /*⇒4,variable,[readonly]*/true
/*⇒4,variable,[definition readonly]*/true = /*⇒5,variable,[readonly]*/false
/*⇒5,variable,[definition readonly]*/false = /*⇒4,variable,[readonly]*/snil
diff --git a/internal/lsp/testdata/snippets/postfix.go b/internal/lsp/testdata/snippets/postfix.go
new file mode 100644
index 0000000..29b4192
--- /dev/null
+++ b/internal/lsp/testdata/snippets/postfix.go
@@ -0,0 +1,27 @@
+package snippets
+
+// These tests check that postfix completions do and do not show up in
+// certain cases. Tests for the postfix completion contents are under
+// regtest.
+
+func _() {
+ /* append! */ //@item(postfixAppend, "append!", "append and re-assign slice", "snippet")
+ var foo []int
+ foo.append //@rank(" //", postfixAppend)
+
+ []int{}.append //@complete(" //")
+
+ []int{}.last //@complete(" //")
+
+ /* copy! */ //@item(postfixCopy, "copy!", "duplicate slice", "snippet")
+
+ foo.copy //@rank(" //", postfixCopy)
+
+ var s struct{ i []int }
+ s.i.copy //@rank(" //", postfixCopy)
+
+ var _ []int = s.i.copy //@complete(" //")
+
+ var blah func() []int
+ blah().append //@complete(" //")
+}
diff --git a/internal/lsp/testdata/statements/if_err_check_test.go b/internal/lsp/testdata/statements/if_err_check_test.go
new file mode 100644
index 0000000..6de5878
--- /dev/null
+++ b/internal/lsp/testdata/statements/if_err_check_test.go
@@ -0,0 +1,20 @@
+package statements
+
+import (
+ "os"
+ "testing"
+)
+
+func TestErr(t *testing.T) {
+ /* if err != nil { t.Fatal(err) } */ //@item(stmtOneIfErrTFatal, "if err != nil { t.Fatal(err) }", "", "")
+
+ _, err := os.Open("foo")
+ //@snippet("", stmtOneIfErrTFatal, "", "if err != nil {\n\tt.Fatal(err)\n\\}")
+}
+
+func BenchmarkErr(b *testing.B) {
+ /* if err != nil { b.Fatal(err) } */ //@item(stmtOneIfErrBFatal, "if err != nil { b.Fatal(err) }", "", "")
+
+ _, err := os.Open("foo")
+ //@snippet("", stmtOneIfErrBFatal, "", "if err != nil {\n\tb.Fatal(err)\n\\}")
+}
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index e0b6366..7b9b6ed 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -1,12 +1,12 @@
-- summary --
CallHierarchyCount = 2
CodeLensCount = 5
-CompletionsCount = 258
-CompletionSnippetCount = 92
+CompletionsCount = 262
+CompletionSnippetCount = 94
UnimportedCompletionsCount = 5
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8
-RankedCompletionsCount = 159
+RankedCompletionsCount = 162
CaseSensitiveCompletionsCount = 4
DiagnosticsCount = 37
FoldingRangesCount = 2
@@ -14,9 +14,9 @@
ImportCount = 8
SemanticTokenCount = 3
SuggestedFixCount = 40
-FunctionExtractionCount = 12
-DefinitionsCount = 65
-TypeDefinitionsCount = 2
+FunctionExtractionCount = 13
+DefinitionsCount = 90
+TypeDefinitionsCount = 10
HighlightsCount = 69
ReferencesCount = 25
RenamesCount = 33
diff --git a/internal/lsp/testdata/typdef/typdef.go b/internal/lsp/testdata/typdef/typdef.go
new file mode 100644
index 0000000..87d5197
--- /dev/null
+++ b/internal/lsp/testdata/typdef/typdef.go
@@ -0,0 +1,38 @@
+package typdef
+
+type Struct struct { //@item(Struct, "Struct", "struct{...}", "struct")
+ Field string
+}
+
+type Int int //@item(Int, "Int", "int", "type")
+
+func _() {
+ var (
+ value Struct
+ point *Struct
+ )
+ _ = value //@typdef("value", Struct)
+ _ = point //@typdef("point", Struct)
+
+ var (
+ array [3]Struct
+ slice []Struct
+ ch chan Struct
+ complex [3]chan *[5][]Int
+ )
+ _ = array //@typdef("array", Struct)
+ _ = slice //@typdef("slice", Struct)
+ _ = ch //@typdef("ch", Struct)
+ _ = complex //@typdef("complex", Int)
+
+ var s struct {
+ x struct {
+ xx struct {
+ field1 []Struct
+ field2 []Int
+ }
+ }
+ }
+ s.x.xx.field1 //@typdef("field1", Struct)
+ s.x.xx.field2 //@typdef("field2", Int)
+}
diff --git a/internal/lsp/tests/README.md b/internal/lsp/tests/README.md
index d8ba10f..2c18675 100644
--- a/internal/lsp/tests/README.md
+++ b/internal/lsp/tests/README.md
@@ -52,7 +52,7 @@
```bash
cd /path/to/tools
-go test ./internal/lsp -v -run TestLSP/Modules/SuggestedFix/bar_11_21
+go test ./internal/lsp/... -v -run TestLSP/Modules/SuggestedFix/bar_11_21
```
## Resetting marker tests
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
index 2dce905..093adc7 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -6,7 +6,12 @@
import (
"context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync/atomic"
+ "golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
@@ -26,7 +31,9 @@
return s.addFolders(ctx, event.Added)
}
-func (s *Server) addView(ctx context.Context, name string, uri, tempWorkspace span.URI) (source.Snapshot, func(), error) {
+var wsIndex int64
+
+func (s *Server) addView(ctx context.Context, name string, uri span.URI) (source.Snapshot, func(), error) {
s.stateMu.Lock()
state := s.state
s.stateMu.Unlock()
@@ -37,6 +44,18 @@
if err := s.fetchConfig(ctx, name, uri, options); err != nil {
return nil, func() {}, err
}
+ // Try to assign a persistent temp directory for tracking this view's
+ // temporary workspace.
+ var tempWorkspace span.URI
+ if s.tempDir != "" {
+ index := atomic.AddInt64(&wsIndex, 1)
+ wsDir := filepath.Join(s.tempDir, fmt.Sprintf("workspace.%d", index))
+ if err := os.Mkdir(wsDir, 0700); err == nil {
+ tempWorkspace = span.URIFromPath(wsDir)
+ } else {
+ event.Error(ctx, "making workspace dir", err)
+ }
+ }
_, snapshot, release, err := s.session.NewView(ctx, name, uri, tempWorkspace, options)
return snapshot, release, err
}
diff --git a/present/caption.go b/present/caption.go
index 0fa2656..64a5268 100644
--- a/present/caption.go
+++ b/present/caption.go
@@ -18,7 +18,7 @@
func (c Caption) PresentCmd() string { return c.Cmd }
func (c Caption) TemplateName() string { return "caption" }
-func parseCaption(_ *Context, _ string, _ int, text string) (Elem, error) {
- text = strings.TrimSpace(strings.TrimPrefix(text, ".caption"))
- return Caption{text, text}, nil
+func parseCaption(_ *Context, _ string, _ int, cmd string) (Elem, error) {
+ text := strings.TrimSpace(strings.TrimPrefix(cmd, ".caption"))
+ return Caption{cmd, text}, nil
}
diff --git a/present/parse.go b/present/parse.go
index 8340559..4294ea5 100644
--- a/present/parse.go
+++ b/present/parse.go
@@ -525,6 +525,7 @@
// Command breaks text block.
// Section heading breaks text block in markdown.
if text[0] == '.' || isSpeakerNote(text) {
+ lines.back()
break
}
if strings.HasPrefix(text, `\.`) { // Backslash escapes initial period.
diff --git a/present/testdata/media.p b/present/testdata/media.p
index f11970b..ffddf9f 100644
--- a/present/testdata/media.p
+++ b/present/testdata/media.p
@@ -2,6 +2,7 @@
*
+The Gopher
.image gopher.jpg _ 100
.caption A gopher.
@@ -14,6 +15,7 @@
---
<h1>Media</h1>
<section>
+<p>The Gopher</p>
<img src="gopher.jpg" width="100" alt="">
<figcaption>A gopher.</figcaption>
<iframe src="https://golang.org/"></iframe>