Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lsp: multi-byte paths in URIs not decoded correctly #2781

Closed
q107580018 opened this issue Mar 21, 2020 · 16 comments · Fixed by #2784
Closed

lsp: multi-byte paths in URIs not decoded correctly #2781

q107580018 opened this issue Mar 21, 2020 · 16 comments · Fixed by #2784
Labels

Comments

@q107580018
Copy link

Does Godef not support Chinese file paths?
when i use godef in chinese paths, It not useful。
Is there any other way to solve it?

Configuration (MUST fill this out):

vim-go version:

v1.22

vimrc you used to reproduce:

vimrc

Vim version (first three lines from :version):

v8.2

Go version (go version):

1.14

Go environment

go env Output:
GOBIN="/Users/qiuwen/SynologyDrive/go/bin"
GOCACHE="/Users/qiuwen/Library/Caches/go-build"
GOENV="/Users/qiuwen/Library/Application Support/go/env"
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOPATH="/Users/qiuwen/SynologyDrive/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.14/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.14/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"

gopls version

gopls version Output:

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 21, 2020

What is the value of your g:go_def_mode?

What error do you see?

@q107580018
Copy link
Author

i am not set g:go_def_mode, it maight be default.
the error shows : "vim-go:no views in the session"

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

You might try setting adding let g:go_def_mode='godef' to your vimrc.

However, vim-go should work with chinese characters in file names. Can you please add let g:go_debug=['lsp'] to your vimrc and provide the LSP output here so we can see what's being sent to gopls and how it's responding?

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

Also, what version of gopls are you using?

@q107580018
Copy link
Author

oh! It solved.Thanks!!!!i don't know how to see the version of gopls , i might be latest.
debug:

===== sent =====
Content-Length: 36483

{"method":"textDocument/didChange","jsonrpc":"2.0","params":{"contentChanges":[{"text":"// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.\n// Use of this source code is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage gin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"mime/multipart\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gin-contrib/sse\"\n\t\"github.com/gin-gonic/gin/binding\"\n\t\"github.com/gin-gonic/gin/render\"\n)\n\n// Content-Type MIME of the most common data formats.\nconst (\n\tMIMEJSON              = binding.MIMEJSON\n\tMIMEHTML              = binding.MIMEHTML\n\tMIMEXML               = binding.MIMEXML\n\tMIMEXML2              = binding.MIMEXML2\n\tMIMEPlain             = binding.MIMEPlain\n\tMIMEPOSTForm          = binding.MIMEPOSTForm\n\tMIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm\n\tMIMEYAML              = binding.MIMEYAML\n\tBodyBytesKey          = \"_gin-gonic/gin/bodybyteskey\"\n)\n\nconst abortIndex int8 = math.MaxInt8 / 2\n\n// Context is the most important part of gin. It allows us to pass variables between middleware,\n// manage the flow, validate the JSON of a request and render a JSON response for example.\ntype Context struct {\n\twritermem responseWriter\n\tRequest   *http.Request\n\tWriter    ResponseWriter\n\n\tParams   Params\n\thandlers HandlersChain\n\tindex    int8\n\tfullPath string\n\n\tengine *Engine\n\n\t// This mutex protect Keys map\n\tKeysMutex *sync.RWMutex\n\n\t// Keys is a key/value pair exclusively for the context of each request.\n\tKeys map[string]interface{}\n\n\t// Errors is a list of errors attached to all the handlers/middlewares who used this context.\n\tErrors errorMsgs\n\n\t// Accepted defines a list of manually accepted formats for content negotiation.\n\tAccepted []string\n\n\t// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()\n\tqueryCache url.Values\n\n\t// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,\n\t// or PUT body parameters.\n\tformCache url.Values\n}\n\n/************************************/\n/********** CONTEXT CREATION ********/\n/************************************/\n\nfunc (c *Context) reset() {\n\tc.Writer = &c.writermem\n\tc.Params = c.Params[0:0]\n\tc.handlers = nil\n\tc.index = -1\n\tc.KeysMutex = &sync.RWMutex{}\n\tc.fullPath = \"\"\n\tc.Keys = nil\n\tc.Errors = c.Errors[0:0]\n\tc.Accepted = nil\n\tc.queryCache = nil\n\tc.formCache = nil\n}\n\n// Copy returns a copy of the current context that can be safely used outside the request's scope.\n// This has to be used when the context has to be passed to a goroutine.\nfunc (c *Context) Copy() *Context {\n\tvar cp = *c\n\tcp.writermem.ResponseWriter = nil\n\tcp.Writer = &cp.writermem\n\tcp.index = abortIndex\n\tcp.handlers = nil\n\tcp.Keys = map[string]interface{}{}\n\tfor k, v := range c.Keys {\n\t\tcp.Keys[k] = v\n\t}\n\tparamCopy := make([]Param, len(cp.Params))\n\tcopy(paramCopy, cp.Params)\n\tcp.Params = paramCopy\n\treturn &cp\n}\n\n// HandlerName returns the main handler's name. For example if the handler is \"handleGetUsers()\",\n// this function will return \"main.handleGetUsers\".\nfunc (c *Context) HandlerName() string {\n\treturn nameOfFunction(c.handlers.Last())\n}\n\n// HandlerNames returns a list of all registered handlers for this context in descending order,\n// following the semantics of HandlerName()\nfunc (c *Context) HandlerNames() []string {\n\thn := make([]string, 0, len(c.handlers))\n\tfor _, val := range c.handlers {\n\t\thn = append(hn, nameOfFunction(val))\n\t}\n\treturn hn\n}\n\n// Handler returns the main handler.\nfunc (c *Context) Handler() HandlerFunc {\n\treturn c.handlers.Last()\n}\n\n// FullPath returns a matched route full path. For not found routes\n// returns an empty string.\n//     router.GET(\"/user/:id\", func(c *gin.Context) {\n//         c.FullPath() == \"/user/:id\" // true\n//     })\nfunc (c *Context) FullPath() string {\n\treturn c.fullPath\n}\n\n/************************************/\n/*********** FLOW CONTROL ***********/\n/************************************/\n\n// Next should be used only inside middleware.\n// It executes the pending handlers in the chain inside the calling handler.\n// See example in GitHub.\nfunc (c *Context) Next() {\n\tc.index++\n\tfor c.index < int8(len(c.handlers)) {\n\t\tc.handlers[c.index](c)\n\t\tc.index++\n\t}\n}\n\n// IsAborted returns true if the current context was aborted.\nfunc (c *Context) IsAborted() bool {\n\treturn c.index >= abortIndex\n}\n\n// Abort prevents pending handlers from being called. Note that this will not stop the current handler.\n// Let's say you have an authorization middleware that validates that the current request is authorized.\n// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers\n// for this request are not called.\nfunc (c *Context) Abort() {\n\tc.index = abortIndex\n}\n\n// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.\n// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401).\nfunc (c *Context) AbortWithStatus(code int) {\n\tc.Status(code)\n\tc.Writer.WriteHeaderNow()\n\tc.Abort()\n}\n\n// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.\n// This method stops the chain, writes the status code and return a JSON body.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {\n\tc.Abort()\n\tc.JSON(code, jsonObj)\n}\n\n// AbortWithError calls `AbortWithStatus()` and `Error()` internally.\n// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`.\n// See Context.Error() for more details.\nfunc (c *Context) AbortWithError(code int, err error) *Error {\n\tc.AbortWithStatus(code)\n\treturn c.Error(err)\n}\n\n/************************************/\n/********* ERROR MANAGEMENT *********/\n/************************************/\n\n// Error attaches an error to the current context. The error is pushed to a list of errors.\n// It's a good idea to call Error for each error that occurred during the resolution of a request.\n// A middleware can be used to collect all the errors and push them to a database together,\n// print a log, or append it in the HTTP response.\n// Error will panic if err is nil.\nfunc (c *Context) Error(err error) *Error {\n\tif err == nil {\n\t\tpanic(\"err is nil\")\n\t}\n\n\tparsedError, ok := err.(*Error)\n\tif !ok {\n\t\tparsedError = &Error{\n\t\t\tErr:  err,\n\t\t\tType: ErrorTypePrivate,\n\t\t}\n\t}\n\n\tc.Errors = append(c.Errors, parsedError)\n\treturn parsedError\n}\n\n/************************************/\n/******** METADATA MANAGEMENT********/\n/************************************/\n\n// Set is used to store a new key/value pair exclusively for this context.\n// It also lazy initializes  c.Keys if it was not used previously.\nfunc (c *Context) Set(key string, value interface{}) {\n\tc.KeysMutex.Lock()\n\tif c.Keys == nil {\n\t\tc.Keys = make(map[string]interface{})\n\t}\n\n\tc.Keys[key] = value\n\tc.KeysMutex.Unlock()\n}\n\n// Get returns the value for the given key, ie: (value, true).\n// If the value does not exists it returns (nil, false)\nfunc (c *Context) Get(key string) (value interface{}, exists bool) {\n\tc.KeysMutex.RLock()\n\tvalue, exists = c.Keys[key]\n\tc.KeysMutex.RUnlock()\n\treturn\n}\n\n// MustGet returns the value for the given key if it exists, otherwise it panics.\nfunc (c *Context) MustGet(key string) interface{} {\n\tif value, exists := c.Get(key); exists {\n\t\treturn value\n\t}\n\tpanic(\"Key \\\"\" + key + \"\\\" does not exist\")\n}\n\n// GetString returns the value associated with the key as a string.\nfunc (c *Context) GetString(key string) (s string) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\ts, _ = val.(string)\n\t}\n\treturn\n}\n\n// GetBool returns the value associated with the key as a boolean.\nfunc (c *Context) GetBool(key string) (b bool) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tb, _ = val.(bool)\n\t}\n\treturn\n}\n\n// GetInt returns the value associated with the key as an integer.\nfunc (c *Context) GetInt(key string) (i int) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\ti, _ = val.(int)\n\t}\n\treturn\n}\n\n// GetInt64 returns the value associated with the key as an integer.\nfunc (c *Context) GetInt64(key string) (i64 int64) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\ti64, _ = val.(int64)\n\t}\n\treturn\n}\n\n// GetFloat64 returns the value associated with the key as a float64.\nfunc (c *Context) GetFloat64(key string) (f64 float64) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tf64, _ = val.(float64)\n\t}\n\treturn\n}\n\n// GetTime returns the value associated with the key as time.\nfunc (c *Context) GetTime(key string) (t time.Time) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tt, _ = val.(time.Time)\n\t}\n\treturn\n}\n\n// GetDuration returns the value associated with the key as a duration.\nfunc (c *Context) GetDuration(key string) (d time.Duration) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\td, _ = val.(time.Duration)\n\t}\n\treturn\n}\n\n// GetStringSlice returns the value associated with the key as a slice of strings.\nfunc (c *Context) GetStringSlice(key string) (ss []string) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tss, _ = val.([]string)\n\t}\n\treturn\n}\n\n// GetStringMap returns the value associated with the key as a map of interfaces.\nfunc (c *Context) GetStringMap(key string) (sm map[string]interface{}) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tsm, _ = val.(map[string]interface{})\n\t}\n\treturn\n}\n\n// GetStringMapString returns the value associated with the key as a map of strings.\nfunc (c *Context) GetStringMapString(key string) (sms map[string]string) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tsms, _ = val.(map[string]string)\n\t}\n\treturn\n}\n\n// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.\nfunc (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) {\n\tif val, ok := c.Get(key); ok && val != nil {\n\t\tsmss, _ = val.(map[string][]string)\n\t}\n\treturn\n}\n\n/************************************/\n/************ INPUT DATA ************/\n/************************************/\n\n// Param returns the value of the URL param.\n// It is a shortcut for c.Params.ByName(key)\n//     router.GET(\"/user/:id\", func(c *gin.Context) {\n//         // a GET request to /user/john\n//         id := c.Param(\"id\") // id == \"john\"\n//     })\nfunc (c *Context) Param(key string) string {\n\treturn c.Params.ByName(key)\n}\n\n// Query returns the keyed url query value if it exists,\n// otherwise it returns an empty string `(\"\")`.\n// It is shortcut for `c.Request.URL.Query().Get(key)`\n//     GET /path?id=1234&name=Manu&value=\n// \t   c.Query(\"id\") == \"1234\"\n// \t   c.Query(\"name\") == \"Manu\"\n// \t   c.Query(\"value\") == \"\"\n// \t   c.Query(\"wtf\") == \"\"\nfunc (c *Context) Query(key string) string {\n\tvalue, _ := c.GetQuery(key)\n\treturn value\n}\n\n// DefaultQuery returns the keyed url query value if it exists,\n// otherwise it returns the specified defaultValue string.\n// See: Query() and GetQuery() for further information.\n//     GET /?name=Manu&lastname=\n//     c.DefaultQuery(\"name\", \"unknown\") == \"Manu\"\n//     c.DefaultQuery(\"id\", \"none\") == \"none\"\n//     c.DefaultQuery(\"lastname\", \"none\") == \"\"\nfunc (c *Context) DefaultQuery(key, defaultValue string) string {\n\tif value, ok := c.GetQuery(key); ok {\n\t\treturn value\n\t}\n\treturn defaultValue\n}\n\n// GetQuery is like Query(), it returns the keyed url query value\n// if it exists `(value, true)` (even when the value is an empty string),\n// otherwise it returns `(\"\", false)`.\n// It is shortcut for `c.Request.URL.Query().Get(key)`\n//     GET /?name=Manu&lastname=\n//     (\"Manu\", true) == c.GetQuery(\"name\")\n//     (\"\", false) == c.GetQuery(\"id\")\n//     (\"\", true) == c.GetQuery(\"lastname\")\nfunc (c *Context) GetQuery(key string) (string, bool) {\n\tif values, ok := c.GetQueryArray(key); ok {\n\t\treturn values[0], ok\n\t}\n\treturn \"\", false\n}\n\n// QueryArray returns a slice of strings for a given query key.\n// The length of the slice depends on the number of params with the given key.\nfunc (c *Context) QueryArray(key string) []string {\n\tvalues, _ := c.GetQueryArray(key)\n\treturn values\n}\n\nfunc (c *Context) getQueryCache() {\n\tif c.queryCache == nil {\n\t\tc.queryCache = c.Request.URL.Query()\n\t}\n}\n\n// GetQueryArray returns a slice of strings for a given query key, plus\n// a boolean value whether at least one value exists for the given key.\nfunc (c *Context) GetQueryArray(key string) ([]string, bool) {\n\tc.getQueryCache()\n\tif values, ok := c.queryCache[key]; ok && len(values) > 0 {\n\t\treturn values, true\n\t}\n\treturn []string{}, false\n}\n\n// QueryMap returns a map for a given query key.\nfunc (c *Context) QueryMap(key string) map[string]string {\n\tdicts, _ := c.GetQueryMap(key)\n\treturn dicts\n}\n\n// GetQueryMap returns a map for a given query key, plus a boolean value\n// whether at least one value exists for the given key.\nfunc (c *Context) GetQueryMap(key string) (map[string]string, bool) {\n\tc.getQueryCache()\n\treturn c.get(c.queryCache, key)\n}\n\n// PostForm returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns an empty string `(\"\")`.\nfunc (c *Context) PostForm(key string) string {\n\tvalue, _ := c.GetPostForm(key)\n\treturn value\n}\n\n// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form\n// when it exists, otherwise it returns the specified defaultValue string.\n// See: PostForm() and GetPostForm() for further information.\nfunc (c *Context) DefaultPostForm(key, defaultValue string) string {\n\tif value, ok := c.GetPostForm(key); ok {\n\t\treturn value\n\t}\n\treturn defaultValue\n}\n\n// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded\n// form or multipart form when it exists `(value, true)` (even when the value is an empty string),\n// otherwise it returns (\"\", false).\n// For example, during a PATCH request to update the user's email:\n//     [email protected]  -->  (\"[email protected]\", true) := GetPostForm(\"email\") // set email to \"[email protected]\"\n// \t   email=                  -->  (\"\", true) := GetPostForm(\"email\") // set email to \"\"\n//                             -->  (\"\", false) := GetPostForm(\"email\") // do nothing with email\nfunc (c *Context) GetPostForm(key string) (string, bool) {\n\tif values, ok := c.GetPostFormArray(key); ok {\n\t\treturn values[0], ok\n\t}\n\treturn \"\", false\n}\n\n// PostFormArray returns a slice of strings for a given form key.\n// The length of the slice depends on the number of params with the given key.\nfunc (c *Context) PostFormArray(key string) []string {\n\tvalues, _ := c.GetPostFormArray(key)\n\treturn values\n}\n\nfunc (c *Context) getFormCache() {\n\tif c.formCache == nil {\n\t\tc.formCache = make(url.Values)\n\t\treq := c.Request\n\t\tif err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {\n\t\t\tif err != http.ErrNotMultipart {\n\t\t\t\tdebugPrint(\"error on parse multipart form array: %v\", err)\n\t\t\t}\n\t\t}\n\t\tc.formCache = req.PostForm\n\t}\n}\n\n// GetPostFormArray returns a slice of strings for a given form key, plus\n// a boolean value whether at least one value exists for the given key.\nfunc (c *Context) GetPostFormArray(key string) ([]string, bool) {\n\tc.getFormCache()\n\tif values := c.formCache[key]; len(values) > 0 {\n\t\treturn values, true\n\t}\n\treturn []string{}, false\n}\n\n// PostFormMap returns a map for a given form key.\nfunc (c *Context) PostFormMap(key string) map[string]string {\n\tdicts, _ := c.GetPostFormMap(key)\n\treturn dicts\n}\n\n// GetPostFormMap returns a map for a given form key, plus a boolean value\n// whether at least one value exists for the given key.\nfunc (c *Context) GetPostFormMap(key string) (map[string]string, bool) {\n\tc.getFormCache()\n\treturn c.get(c.formCache, key)\n}\n\n// get is an internal method and returns a map which satisfy conditions.\nfunc (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {\n\tdicts := make(map[string]string)\n\texist := false\n\tfor k, v := range m {\n\t\tif i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {\n\t\t\tif j := strings.IndexByte(k[i+1:], ']'); j >= 1 {\n\t\t\t\texist = true\n\t\t\t\tdicts[k[i+1:][:j]] = v[0]\n\t\t\t}\n\t\t}\n\t}\n\treturn dicts, exist\n}\n\n// FormFile returns the first file for the provided form key.\nfunc (c *Context) FormFile(name string) (*multipart.FileHeader, error) {\n\tif c.Request.MultipartForm == nil {\n\t\tif err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tf, fh, err := c.Request.FormFile(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tf.Close()\n\treturn fh, err\n}\n\n// MultipartForm is the parsed multipart form, including file uploads.\nfunc (c *Context) MultipartForm() (*multipart.Form, error) {\n\terr := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)\n\treturn c.Request.MultipartForm, err\n}\n\n// SaveUploadedFile uploads the form file to specific dst.\nfunc (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {\n\tsrc, err := file.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer src.Close()\n\n\tout, err := os.Create(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t_, err = io.Copy(out, src)\n\treturn err\n}\n\n// Bind checks the Content-Type to select a binding engine automatically,\n// Depending the \"Content-Type\" header different bindings are used:\n//     \"application/json\" --> JSON binding\n//     \"application/xml\"  --> XML binding\n// otherwise --> returns an error.\n// It parses the request's body as JSON if Content-Type == \"application/json\" using JSON or XML as a JSON input.\n// It decodes the json payload into the struct specified as a pointer.\n// It writes a 400 error and sets Content-Type header \"text/plain\" in the response if input is not valid.\nfunc (c *Context) Bind(obj interface{}) error {\n\tb := binding.Default(c.Request.Method, c.ContentType())\n\treturn c.MustBindWith(obj, b)\n}\n\n// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).\nfunc (c *Context) BindJSON(obj interface{}) error {\n\treturn c.MustBindWith(obj, binding.JSON)\n}\n\n// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).\nfunc (c *Context) BindXML(obj interface{}) error {\n\treturn c.MustBindWith(obj, binding.XML)\n}\n\n// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).\nfunc (c *Context) BindQuery(obj interface{}) error {\n\treturn c.MustBindWith(obj, binding.Query)\n}\n\n// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).\nfunc (c *Context) BindYAML(obj interface{}) error {\n\treturn c.MustBindWith(obj, binding.YAML)\n}\n\n// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).\nfunc (c *Context) BindHeader(obj interface{}) error {\n\treturn c.MustBindWith(obj, binding.Header)\n}\n\n// BindUri binds the passed struct pointer using binding.Uri.\n// It will abort the request with HTTP 400 if any error occurs.\nfunc (c *Context) BindUri(obj interface{}) error {\n\tif err := c.ShouldBindUri(obj); err != nil {\n\t\tc.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// MustBindWith binds the passed struct pointer using the specified binding engine.\n// It will abort the request with HTTP 400 if any error occurs.\n// See the binding package.\nfunc (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {\n\tif err := c.ShouldBindWith(obj, b); err != nil {\n\t\tc.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ShouldBind checks the Content-Type to select a binding engine automatically,\n// Depending the \"Content-Type\" header different bindings are used:\n//     \"application/json\" --> JSON binding\n//     \"application/xml\"  --> XML binding\n// otherwise --> returns an error\n// It parses the request's body as JSON if Content-Type == \"application/json\" using JSON or XML as a JSON input.\n// It decodes the json payload into the struct specified as a pointer.\n// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.\nfunc (c *Context) ShouldBind(obj interface{}) error {\n\tb := binding.Default(c.Request.Method, c.ContentType())\n\treturn c.ShouldBindWith(obj, b)\n}\n\n// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).\nfunc (c *Context) ShouldBindJSON(obj interface{}) error {\n\treturn c.ShouldBindWith(obj, binding.JSON)\n}\n\n// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).\nfunc (c *Context) ShouldBindXML(obj interface{}) error {\n\treturn c.ShouldBindWith(obj, binding.XML)\n}\n\n// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).\nfunc (c *Context) ShouldBindQuery(obj interface{}) error {\n\treturn c.ShouldBindWith(obj, binding.Query)\n}\n\n// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).\nfunc (c *Context) ShouldBindYAML(obj interface{}) error {\n\treturn c.ShouldBindWith(obj, binding.YAML)\n}\n\n// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).\nfunc (c *Context) ShouldBindHeader(obj interface{}) error {\n\treturn c.ShouldBindWith(obj, binding.Header)\n}\n\n// ShouldBindUri binds the passed struct pointer using the specified binding engine.\nfunc (c *Context) ShouldBindUri(obj interface{}) error {\n\tm := make(map[string][]string)\n\tfor _, v := range c.Params {\n\t\tm[v.Key] = []string{v.Value}\n\t}\n\treturn binding.Uri.BindUri(m, obj)\n}\n\n// ShouldBindWith binds the passed struct pointer using the specified binding engine.\n// See the binding package.\nfunc (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {\n\treturn b.Bind(c.Request, obj)\n}\n\n// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request\n// body into the context, and reuse when it is called again.\n//\n// NOTE: This method reads the body before binding. So you should use\n// ShouldBindWith for better performance if you need to call only once.\nfunc (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {\n\tvar body []byte\n\tif cb, ok := c.Get(BodyBytesKey); ok {\n\t\tif cbb, ok := cb.([]byte); ok {\n\t\t\tbody = cbb\n\t\t}\n\t}\n\tif body == nil {\n\t\tbody, err = ioutil.ReadAll(c.Request.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.Set(BodyBytesKey, body)\n\t}\n\treturn bb.BindBody(body, obj)\n}\n\n// ClientIP implements a best effort algorithm to return the real client IP, it parses\n// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.\n// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.\nfunc (c *Context) ClientIP() string {\n\tif c.engine.ForwardedByClientIP {\n\t\tclientIP := c.requestHeader(\"X-Forwarded-For\")\n\t\tclientIP = strings.TrimSpace(strings.Split(clientIP, \",\")[0])\n\t\tif clientIP == \"\" {\n\t\t\tclientIP = strings.TrimSpace(c.requestHeader(\"X-Real-Ip\"))\n\t\t}\n\t\tif clientIP != \"\" {\n\t\t\treturn clientIP\n\t\t}\n\t}\n\n\tif c.engine.AppEngine {\n\t\tif addr := c.requestHeader(\"X-Appengine-Remote-Addr\"); addr != \"\" {\n\t\t\treturn addr\n\t\t}\n\t}\n\n\tif ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {\n\t\treturn ip\n\t}\n\n\treturn \"\"\n}\n\n// ContentType returns the Content-Type header of the request.\nfunc (c *Context) ContentType() string {\n\treturn filterFlags(c.requestHeader(\"Content-Type\"))\n}\n\n// IsWebsocket returns true if the request headers indicate that a websocket\n// handshake is being initiated by the client.\nfunc (c *Context) IsWebsocket() bool {\n\tif strings.Contains(strings.ToLower(c.requestHeader(\"Connection\")), \"upgrade\") &&\n\t\tstrings.EqualFold(c.requestHeader(\"Upgrade\"), \"websocket\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (c *Context) requestHeader(key string) string {\n\treturn c.Request.Header.Get(key)\n}\n\n/************************************/\n/******** RESPONSE RENDERING ********/\n/************************************/\n\n// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.\nfunc bodyAllowedForStatus(status int) bool {\n\tswitch {\n\tcase status >= 100 && status <= 199:\n\t\treturn false\n\tcase status == http.StatusNoContent:\n\t\treturn false\n\tcase status == http.StatusNotModified:\n\t\treturn false\n\t}\n\treturn true\n}\n\n// Status sets the HTTP response code.\nfunc (c *Context) Status(code int) {\n\tc.Writer.WriteHeader(code)\n}\n\n// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).\n// It writes a header in the response.\n// If value == \"\", this method removes the header `c.Writer.Header().Del(key)`\nfunc (c *Context) Header(key, value string) {\n\tif value == \"\" {\n\t\tc.Writer.Header().Del(key)\n\t\treturn\n\t}\n\tc.Writer.Header().Set(key, value)\n}\n\n// GetHeader returns value from request headers.\nfunc (c *Context) GetHeader(key string) string {\n\treturn c.requestHeader(key)\n}\n\n// GetRawData return stream data.\nfunc (c *Context) GetRawData() ([]byte, error) {\n\treturn ioutil.ReadAll(c.Request.Body)\n}\n\n// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.\n// The provided cookie must have a valid Name. Invalid cookies may be\n// silently dropped.\nfunc (c *Context) SetCookie(name, value string, maxAge int, path, domain string, sameSite http.SameSite, secure, httpOnly bool) {\n\tif path == \"\" {\n\t\tpath = \"/\"\n\t}\n\thttp.SetCookie(c.Writer, &http.Cookie{\n\t\tName:     name,\n\t\tValue:    url.QueryEscape(value),\n\t\tMaxAge:   maxAge,\n\t\tPath:     path,\n\t\tDomain:   domain,\n\t\tSameSite: sameSite,\n\t\tSecure:   secure,\n\t\tHttpOnly: httpOnly,\n\t})\n}\n\n// Cookie returns the named cookie provided in the request or\n// ErrNoCookie if not found. And return the named cookie is unescaped.\n// If multiple cookies match the given name, only one cookie will\n// be returned.\nfunc (c *Context) Cookie(name string) (string, error) {\n\tcookie, err := c.Request.Cookie(name)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tval, _ := url.QueryUnescape(cookie.Value)\n\treturn val, nil\n}\n\n// Render writes the response headers and calls render.Render to render data.\nfunc (c *Context) Render(code int, r render.Render) {\n\tc.Status(code)\n\n\tif !bodyAllowedForStatus(code) {\n\t\tr.WriteContentType(c.Writer)\n\t\tc.Writer.WriteHeaderNow()\n\t\treturn\n\t}\n\n\tif err := r.Render(c.Writer); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// HTML renders the HTTP template specified by its file name.\n// It also updates the HTTP code and sets the Content-Type as \"text/html\".\n// See http://golang.org/doc/articles/wiki/\nfunc (c *Context) HTML(code int, name string, obj interface{}) {\n\tinstance := c.engine.HTMLRender.Instance(name, obj)\n\tc.Render(code, instance)\n}\n\n// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.\n// It also sets the Content-Type as \"application/json\".\n// WARNING: we recommend to use this only for development purposes since printing pretty JSON is\n// more CPU and bandwidth consuming. Use Context.JSON() instead.\nfunc (c *Context) IndentedJSON(code int, obj interface{}) {\n\tc.Render(code, render.IndentedJSON{Data: obj})\n}\n\n// SecureJSON serializes the given struct as Secure JSON into the response body.\n// Default prepends \"while(1),\" to response body if the given struct is array values.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) SecureJSON(code int, obj interface{}) {\n\tc.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})\n}\n\n// JSONP serializes the given struct as JSON into the response body.\n// It add padding to response body to request data from a server residing in a different domain than the client.\n// It also sets the Content-Type as \"application/javascript\".\nfunc (c *Context) JSONP(code int, obj interface{}) {\n\tcallback := c.DefaultQuery(\"callback\", \"\")\n\tif callback == \"\" {\n\t\tc.Render(code, render.JSON{Data: obj})\n\t\treturn\n\t}\n\tc.Render(code, render.JsonpJSON{Callback: callback, Data: obj})\n}\n\n// JSON serializes the given struct as JSON into the response body.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) JSON(code int, obj interface{}) {\n\tc.Render(code, render.JSON{Data: obj})\n}\n\n// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.\n// It also sets the Content-Type as \"application/json\".\nfunc (c *Context) AsciiJSON(code int, obj interface{}) {\n\tc.Render(code, render.AsciiJSON{Data: obj})\n}\n\n// PureJSON serializes the given struct as JSON into the response body.\n// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.\nfunc (c *Context) PureJSON(code int, obj interface{}) {\n\tc.Render(code, render.PureJSON{Data: obj})\n}\n\n// XML serializes the given struct as XML into the response body.\n// It also sets the Content-Type as \"application/xml\".\nfunc (c *Context) XML(code int, obj interface{}) {\n\tc.Render(code, render.XML{Data: obj})\n}\n\n// YAML serializes the given struct as YAML into the response body.\nfunc (c *Context) YAML(code int, obj interface{}) {\n\tc.Render(code, render.YAML{Data: obj})\n}\n\n// ProtoBuf serializes the given struct as ProtoBuf into the response body.\nfunc (c *Context) ProtoBuf(code int, obj interface{}) {\n\tc.Render(code, render.ProtoBuf{Data: obj})\n}\n\n// String writes the given string into the response body.\nfunc (c *Context) String(code int, format string, values ...interface{}) {\n\tc.Render(code, render.String{Format: format, Data: values})\n}\n\n// Redirect returns a HTTP redirect to the specific location.\nfunc (c *Context) Redirect(code int, location string) {\n\tc.Render(-1, render.Redirect{\n\t\tCode:     code,\n\t\tLocation: location,\n\t\tRequest:  c.Request,\n\t})\n}\n\n// Data writes some data into the body stream and updates the HTTP code.\nfunc (c *Context) Data(code int, contentType string, data []byte) {\n\tc.Render(code, render.Data{\n\t\tContentType: contentType,\n\t\tData:        data,\n\t})\n}\n\n// DataFromReader writes the specified reader into the body stream and updates the HTTP code.\nfunc (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {\n\tc.Render(code, render.Reader{\n\t\tHeaders:       extraHeaders,\n\t\tContentType:   contentType,\n\t\tContentLength: contentLength,\n\t\tReader:        reader,\n\t})\n}\n\n// File writes the specified file into the body stream in a efficient way.\nfunc (c *Context) File(filepath string) {\n\thttp.ServeFile(c.Writer, c.Request, filepath)\n}\n\n// FileFromFS writes the specified file from http.FileSytem into the body stream in an efficient way.\nfunc (c *Context) FileFromFS(filepath string, fs http.FileSystem) {\n\tdefer func(old string) {\n\t\tc.Request.URL.Path = old\n\t}(c.Request.URL.Path)\n\n\tc.Request.URL.Path = filepath\n\n\thttp.FileServer(fs).ServeHTTP(c.Writer, c.Request)\n}\n\n// FileAttachment writes the specified file into the body stream in an efficient way\n// On the client side, the file will typically be downloaded with the given filename\nfunc (c *Context) FileAttachment(filepath, filename string) {\n\tc.Writer.Header().Set(\"content-disposition\", fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", filename))\n\thttp.ServeFile(c.Writer, c.Request, filepath)\n}\n\n// SSEvent writes a Server-Sent Event into the body stream.\nfunc (c *Context) SSEvent(name string, message interface{}) {\n\tc.Render(-1, sse.Event{\n\t\tEvent: name,\n\t\tData:  message,\n\t})\n}\n\n// Stream sends a streaming response and returns a boolean\n// indicates \"Is client disconnected in middle of stream\"\nfunc (c *Context) Stream(step func(w io.Writer) bool) bool {\n\tw := c.Writer\n\tclientGone := w.CloseNotify()\n\tfor {\n\t\tselect {\n\t\tcase <-clientGone:\n\t\t\treturn true\n\t\tdefault:\n\t\t\tkeepOpen := step(w)\n\t\t\tw.Flush()\n\t\t\tif !keepOpen {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n}\n\n/************************************/\n/******** CONTENT NEGOTIATION *******/\n/************************************/\n\n// Negotiate contains all negotiations data.\ntype Negotiate struct {\n\tOffered  []string\n\tHTMLName string\n\tHTMLData interface{}\n\tJSONData interface{}\n\tXMLData  interface{}\n\tYAMLData interface{}\n\tData     interface{}\n}\n\n// Negotiate calls different Render according acceptable Accept format.\nfunc (c *Context) Negotiate(code int, config Negotiate) {\n\tswitch c.NegotiateFormat(config.Offered...) {\n\tcase binding.MIMEJSON:\n\t\tdata := chooseData(config.JSONData, config.Data)\n\t\tc.JSON(code, data)\n\n\tcase binding.MIMEHTML:\n\t\tdata := chooseData(config.HTMLData, config.Data)\n\t\tc.HTML(code, config.HTMLName, data)\n\n\tcase binding.MIMEXML:\n\t\tdata := chooseData(config.XMLData, config.Data)\n\t\tc.XML(code, data)\n\n\tcase binding.MIMEYAML:\n\t\tdata := chooseData(config.YAMLData, config.Data)\n\t\tc.YAML(code, data)\n\n\tdefault:\n\t\tc.AbortWithError(http.StatusNotAcceptable, errors.New(\"the accepted formats are not offered by the server\")) // nolint: errcheck\n\t}\n}\n\n// NegotiateFormat returns an acceptable Accept format.\nfunc (c *Context) NegotiateFormat(offered ...string) string {\n\tassert1(len(offered) > 0, \"you must provide at least one offer\")\n\n\tif c.Accepted == nil {\n\t\tc.Accepted = parseAccept(c.requestHeader(\"Accept\"))\n\t}\n\tif len(c.Accepted) == 0 {\n\t\treturn offered[0]\n\t}\n\tfor _, accepted := range c.Accepted {\n\t\tfor _, offer := range offered {\n\t\t\t// According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers,\n\t\t\t// therefore we can just iterate over the string without casting it into []rune\n\t\t\ti := 0\n\t\t\tfor ; i < len(accepted); i++ {\n\t\t\t\tif accepted[i] == '*' || offer[i] == '*' {\n\t\t\t\t\treturn offer\n\t\t\t\t}\n\t\t\t\tif accepted[i] != offer[i] {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif i == len(accepted) {\n\t\t\t\treturn offer\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// SetAccepted sets Accept header data.\nfunc (c *Context) SetAccepted(formats ...string) {\n\tc.Accepted = formats\n}\n\n/************************************/\n/***** GOLANG.ORG/X/NET/CONTEXT *****/\n/************************************/\n\n// Deadline always returns that there is no deadline (ok==false),\n// maybe you want to use Request.Context().Deadline() instead.\nfunc (c *Context) Deadline() (deadline time.Time, ok bool) {\n\treturn\n}\n\n// Done always returns nil (chan which will wait forever),\n// if you want to abort your work when the connection was closed\n// you should use Request.Context().Done() instead.\nfunc (c *Context) Done() <-chan struct{} {\n\treturn nil\n}\n\n// Err always returns nil, maybe you want to use Request.Context().Err() instead.\nfunc (c *Context) Err() error {\n\treturn nil\n}\n\n// Value returns the value associated with this context for key, or nil\n// if no value is associated with key. Successive calls to Value with\n// the same key returns the same result.\nfunc (c *Context) Value(key interface{}) interface{} {\n\tif key == 0 {\n\t\treturn c.Request\n\t}\n\tif keyAsString, ok := key.(string); ok {\n\t\tval, _ := c.Get(keyAsString)\n\t\treturn val\n\t}\n\treturn nil\n}\n"}],"textDocument":{"uri":"file:///Users/qiuwen/SynologyDrive/go/src/github.com/gin-gonic/gin/context.go","version":5}}}
===== sent =====
Content-Length: 212

{"method":"textDocument/definition","jsonrpc":"2.0","id":9,"params":{"textDocument":{"uri":"file:///Users/qiuwen/SynologyDrive/go/src/github.com/gin-gonic/gin/context.go"},"position":{"character":18,"line":868}}}
===== stderr =====
2020/03/22 12:21:57 : no views in the session
===== received =====
Content-Length: 91

{"jsonrpc":"2.0","error":{"code":0,"message":"no views in the session","data":null},"id":9}
===== received =====
Content-Length: 124

{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":1,"message":"2020/03/22 12:21:57 : no views in the session"}}

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

Are you saying that adding let g:go_def_mode='godef' solved it? If so, that's a workaround, but not a full solution.

I don't see any chinese characters in the file paths in the debug output and it looks incomplete, too; I'd expect to see several other messages. 🤔

You can run gopls version to see the version of gopls that you're running.

It looks like I'm going to need a clear and simple replication path to be able to help you figure out why gopls is saying no views in session. Can you provide a simple file and expected location for the file to help me duplicate this?

@q107580018
Copy link
Author

my gopls version is v0.3.4
I found that whether it is GoDef or GoInfo, as long as you use gopls, the file path containing Chinese will be invalid. Just change go_def_mode to "godef" and go_info_mode to "guru", and it will have effect.

@q107580018
Copy link
Author

my file path is "~/SynologyDrive/go/src/github.com/q107580018/studyGo/p7/11_路由和路由组/main.go"

@q107580018
Copy link
Author

q107580018 commented Mar 22, 2020

you can also see my github
github

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

Jumping to definitions may work by using a different value for g:go_def_mode, but if there are problems with gopls on your machine, then you're likely to run into other problems than jumping to defnitions.

I am not able to duplicate the problem you're describing. I cloned your repository into a clean GOPATH, opened $GOPATH/src/github.com/q107580018/studyGo/p7/11_路由和路由组/main.go, and I'm able to use completion and jump to definitions with g:go_def_mode to set gopls. I'm using gopls v0.3.4, too.

The go env output in your original description seems to be truncated. Can you verify what your GO111MODULE environment variable is set to?

You might try cleaning your cache by running go clean -cache and go clean -modcache at the command line and then try jumping to some definitions again.

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

If you're still having problems after cleaning your caches, will you please provide some specific examples (e.g. a specific location within your repository from which you'd like to jump, but it's failing)?

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

Looking at the lsp debug output you provided, it looks like you're trying to jump from within gin-tonic:

{"method":"textDocument/definition","jsonrpc":"2.0","id":9,"params":{"textDocument":{"uri":"file:///Users/qiuwen/SynologyDrive/go/src/github.com/gin-gonic/gin/context.go"},"position":{"character":18,"line":868}}}

Can you confirm? Which version of gin-gonic are you using?

That is not within your repository, though, so I'm not sure why the chinese characters in the filepath would come into play. Can you clarify?

@q107580018
Copy link
Author

I did a comparison experiment.
when I use "vim ~/SynologyDrive/go/src/github.com/q107580018/studyGo/p3/04regexp/main.go","godef" and "goinfo" are worked. if I rename the chinese path。"vim ~/SynologyDrive/go/src/github.com/q107580018/studyGo/p3/04正则表达式/main.go", It not worked.

@q107580018
Copy link
Author

I try running go clean -cache and go clean -modcache, but no useful

@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

Ok. I might see a problem in vim-go's conversion of URIs to multi-byte characters. I'm constructing some tests and will put some attention to this soon; the problem I've found is the source of the problem with jumping to definitions that you're encountering.

edit: typos

@bhcleek bhcleek added the bug label Mar 22, 2020
@bhcleek bhcleek changed the title Does Godef not support Chinese file paths? lsp: multi-byte paths in URIs not decoded correctly Mar 22, 2020
bhcleek added a commit to bhcleek/vim-go that referenced this issue Mar 22, 2020
@bhcleek
Copy link
Collaborator

bhcleek commented Mar 22, 2020

@q107580018 Thank you for bringing this to my attention. The fix should be on master now; you should be able to jump to definitions from and to files that contain multi-byte characters using the default g:go_def_mode value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants