From eea40102637a99ac5ed206172ca3fcf439df70b1 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 6 Nov 2020 21:19:12 +0100 Subject: [PATCH 01/61] Use github.com/arduino/go-properties-orderedmap for handling properties --- go.mod | 1 + go.sum | 11 ++++++++++ handler/builder.go | 29 ++++++++++++++------------- handler/properties.go | 41 -------------------------------------- handler/properties_test.go | 33 ------------------------------ 5 files changed, 27 insertions(+), 88 deletions(-) delete mode 100644 handler/properties.go delete mode 100644 handler/properties_test.go diff --git a/go.mod b/go.mod index ad27295..20a9e06 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/bcmi-labs/arduino-language-server go 1.12 require ( + github.com/arduino/go-properties-orderedmap v1.4.0 github.com/gorilla/websocket v1.4.0 // indirect github.com/pkg/errors v0.8.1 github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1 diff --git a/go.sum b/go.sum index 386ebdb..5b03502 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,19 @@ +github.com/arduino/go-paths-helper v1.0.1 h1:utYXLM2RfFlc9qp/MJTIYp3t6ux/xM6mWjeEb/WLK4Q= +github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= +github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1 h1:O1d7nVzpGmP5pGAZBSlp9TSpjNwwI0xThxhPd9oVJuU= github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1/go.mod h1:tpps84QRlOVVLYk5QpKYX8Tr289D1v/UTWDLqeguiqM= github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a h1:jTZwOlrDhmk4Ez2vhWh7kA0eKUahp1lCO2uyM4fi/Qk= github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a/go.mod h1:eESpbCslcLDs8j2D7IEdGVgul7xuk9odqDTaor30IUU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/handler/builder.go b/handler/builder.go index 170bbd2..88a5402 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" ) @@ -142,7 +143,7 @@ func generateCompileFlags(tempDir, inoPath, sourcePath, fqbn string) (string, er err = logCommandErr(globalCliPath, output, err, errMsgFilter(tempDir)) return "", err } - properties, err := readProperties(bytes.NewReader(output)) + buildProps, err := properties.LoadFromBytes(output) if err != nil { return "", errors.Wrap(err, "Error while reading build properties.") } @@ -154,7 +155,7 @@ func generateCompileFlags(tempDir, inoPath, sourcePath, fqbn string) (string, er defer outFile.Close() printer := Printer{Writer: bufio.NewWriter(outFile)} - printCompileFlags(properties, &printer, fqbn) + printCompileFlags(buildProps, &printer, fqbn) printLibraryPaths(sourcePath, &printer) printer.Flush() return flagsPath, printer.Err @@ -214,40 +215,40 @@ func copyIno2Cpp(inoCode string, cppPath string) (cppCode []byte, err error) { return } -func printCompileFlags(properties map[string]string, printer *Printer, fqbn string) { +func printCompileFlags(buildProps *properties.Map, printer *Printer, fqbn string) { if strings.Contains(fqbn, ":avr:") { printer.Println("--target=avr") } else if strings.Contains(fqbn, ":sam:") { printer.Println("--target=arm-none-eabi") } - cppFlags := expandProperty(properties, "compiler.cpp.flags") + cppFlags := buildProps.ExpandPropsInString(buildProps.Get("compiler.cpp.flags")) printer.Println(splitFlags(cppFlags)) - mcu := expandProperty(properties, "build.mcu") + mcu := buildProps.ExpandPropsInString(buildProps.Get("build.mcu")) if strings.Contains(fqbn, ":avr:") { printer.Println("-mmcu=", mcu) } else if strings.Contains(fqbn, ":sam:") { printer.Println("-mcpu=", mcu) } - fcpu := expandProperty(properties, "build.f_cpu") + fcpu := buildProps.ExpandPropsInString(buildProps.Get("build.f_cpu")) printer.Println("-DF_CPU=", fcpu) - ideVersion := expandProperty(properties, "runtime.ide.version") + ideVersion := buildProps.ExpandPropsInString(buildProps.Get("runtime.ide.version")) printer.Println("-DARDUINO=", ideVersion) - board := expandProperty(properties, "build.board") + board := buildProps.ExpandPropsInString(buildProps.Get("build.board")) printer.Println("-DARDUINO_", board) - arch := expandProperty(properties, "build.arch") + arch := buildProps.ExpandPropsInString(buildProps.Get("build.arch")) printer.Println("-DARDUINO_ARCH_", arch) if strings.Contains(fqbn, ":sam:") { - libSamFlags := expandProperty(properties, "compiler.libsam.c.flags") + libSamFlags := buildProps.ExpandPropsInString(buildProps.Get("compiler.libsam.c.flags")) printer.Println(splitFlags(libSamFlags)) } - extraFlags := expandProperty(properties, "build.extra_flags") + extraFlags := buildProps.ExpandPropsInString(buildProps.Get("build.extra_flags")) printer.Println(splitFlags(extraFlags)) - corePath := expandProperty(properties, "build.core.path") + corePath := buildProps.ExpandPropsInString(buildProps.Get("build.core.path")) printer.Println("-I", corePath) - variantPath := expandProperty(properties, "build.variant.path") + variantPath := buildProps.ExpandPropsInString(buildProps.Get("build.variant.path")) printer.Println("-I", variantPath) if strings.Contains(fqbn, ":avr:") { - avrgccPath := expandProperty(properties, "runtime.tools.avr-gcc.path") + avrgccPath := buildProps.ExpandPropsInString(buildProps.Get("runtime.tools.avr-gcc.path")) printer.Println("-I", filepath.Join(avrgccPath, "avr", "include")) } diff --git a/handler/properties.go b/handler/properties.go deleted file mode 100644 index b30de0b..0000000 --- a/handler/properties.go +++ /dev/null @@ -1,41 +0,0 @@ -package handler - -import ( - "bufio" - "io" - "strings" -) - -func readProperties(propsFile io.Reader) (map[string]string, error) { - properties := make(map[string]string) - scanner := bufio.NewScanner(propsFile) - for scanner.Scan() { - line := scanner.Text() - equalIndex := strings.Index(line, "=") - if equalIndex >= 0 { - key := strings.TrimSpace(line[:equalIndex]) - if len(key) > 0 { - value := strings.TrimSpace(line[equalIndex+1:]) - properties[key] = value - } - } - } - return properties, scanner.Err() -} - -func expandProperty(properties map[string]string, name string) string { - value := properties[name] - varStart := strings.Index(value, "{") - for varStart >= 0 { - varEnd := strings.Index(value[varStart:], "}") - if varEnd >= 0 { - referencedName := value[varStart+1 : varStart+varEnd] - expanded := expandProperty(properties, referencedName) - value = value[:varStart] + expanded + value[varStart+varEnd+1:] - varStart = strings.Index(value, "{") - } else { - varStart = -1 - } - } - return value -} diff --git a/handler/properties_test.go b/handler/properties_test.go deleted file mode 100644 index 845ddba..0000000 --- a/handler/properties_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package handler - -import ( - "reflect" - "strings" - "testing" -) - -func TestReadProperties(t *testing.T) { - properties, err := readProperties(strings.NewReader("foo=Hello\n bar = World \nbaz=!")) - if err != nil { - t.Error(err) - } - if !reflect.DeepEqual(properties, map[string]string{ - "foo": "Hello", - "bar": "World", - "baz": "!", - }) { - t.Error(properties) - } -} - -func TestExpandProperty(t *testing.T) { - properties := map[string]string{ - "foo": "Hello {bar} {baz}", - "bar": "{baz} World", - "baz": "!", - } - foo := expandProperty(properties, "foo") - if foo != "Hello ! World !" { - t.Error(foo) - } -} From e9ee2675760195c97ffd697c4277d7b22cc2c0c7 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 6 Nov 2020 21:22:18 +0100 Subject: [PATCH 02/61] When logging command output add also the command line args --- handler/builder.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/handler/builder.go b/handler/builder.go index 88a5402..e182f9a 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -140,7 +140,7 @@ func generateCompileFlags(tempDir, inoPath, sourcePath, fqbn string) (string, er propertiesCmd := exec.Command(globalCliPath, cliArgs...) output, err := propertiesCmd.Output() if err != nil { - err = logCommandErr(globalCliPath, output, err, errMsgFilter(tempDir)) + err = logCommandErr(propertiesCmd, output, err, errMsgFilter(tempDir)) return "", err } buildProps, err := properties.LoadFromBytes(output) @@ -171,7 +171,7 @@ func generateTargetFile(tempDir, inoPath, cppPath, fqbn string) (cppCode []byte, preprocessCmd := exec.Command(globalCliPath, cliArgs...) cppCode, err = preprocessCmd.Output() if err != nil { - err = logCommandErr(globalCliPath, cppCode, err, errMsgFilter(tempDir)) + err = logCommandErr(preprocessCmd, cppCode, err, errMsgFilter(tempDir)) return } @@ -331,9 +331,9 @@ func splitFlags(flags string) string { return string(result) } -func logCommandErr(command string, stdout []byte, err error, filter func(string) string) error { +func logCommandErr(command *exec.Cmd, stdout []byte, err error, filter func(string) string) error { message := "" - log.Println("Command error:", command, err) + log.Println("Command error:", command.Args, err) if len(stdout) > 0 { stdoutStr := string(stdout) log.Println("------------------------------BEGIN STDOUT\n", stdoutStr, "------------------------------END STDOUT") From 3ec5d2ae9dd7ebb8d7cf4d4bf2b24b9f6923cc33 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 8 Nov 2020 11:22:55 +0100 Subject: [PATCH 03/61] updated go modules --- go.mod | 7 +++---- go.sum | 7 +++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 20a9e06..562a57e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,7 @@ go 1.12 require ( github.com/arduino/go-properties-orderedmap v1.4.0 - github.com/gorilla/websocket v1.4.0 // indirect - github.com/pkg/errors v0.8.1 - github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1 - github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a + github.com/pkg/errors v0.9.1 + github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d + github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 ) diff --git a/go.sum b/go.sum index 5b03502..ee7e7db 100644 --- a/go.sum +++ b/go.sum @@ -6,14 +6,21 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1 h1:O1d7nVzpGmP5pGAZBSlp9TSpjNwwI0xThxhPd9oVJuU= github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1/go.mod h1:tpps84QRlOVVLYk5QpKYX8Tr289D1v/UTWDLqeguiqM= +github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d h1:afLbh+ltiygTOB37ymZVwKlJwWZn+86syPTbrrOAydY= +github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d/go.mod h1:SULmZY7YNBsvNiQbrb/BEDdEJ84TGnfyUQxaHt8t8rY= github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a h1:jTZwOlrDhmk4Ez2vhWh7kA0eKUahp1lCO2uyM4fi/Qk= github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a/go.mod h1:eESpbCslcLDs8j2D7IEdGVgul7xuk9odqDTaor30IUU= +github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4= +github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= From a8d0ddc9687407c60539a40c92d97979fb717e51 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 8 Nov 2020 11:24:43 +0100 Subject: [PATCH 04/61] Some refactoring wrt logging and debugging - clangd launcher has been moved into main module, removing an unneded indirection. - streams management functions now resides in their own module. - file logging has been downgraded from an object to a function --- handler/handler.go | 36 +++-------- handler/streamlog.go | 147 ------------------------------------------- main.go | 50 +++++++++++---- streams/dumper.go | 45 +++++++++++++ streams/streams.go | 33 ++++++++++ 5 files changed, 123 insertions(+), 188 deletions(-) delete mode 100644 handler/streamlog.go create mode 100644 streams/dumper.go create mode 100644 streams/streams.go diff --git a/handler/handler.go b/handler/handler.go index 4141da6..46d255b 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "log" "os" "regexp" @@ -33,19 +32,19 @@ func Setup(cliPath string, _enableLogging bool, _asyncProcessing bool) { type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io.ReadCloser) // NewInoHandler creates and configures an InoHandler. -func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *StreamLogger, startClangd CLangdStarter, board Board) *InoHandler { +func NewInoHandler(stdio io.ReadWriteCloser, clangdStdio io.ReadWriteCloser, board Board) *InoHandler { handler := &InoHandler{ - clangdProc: ClangdProc{ - Start: startClangd, - Logs: logStreams, - }, data: make(map[lsp.DocumentURI]*FileData), config: BoardConfig{ SelectedBoard: board, }, } - handler.startClangd() - stdStream := jsonrpc2.NewBufferedStream(logStreams.AttachStdInOut(stdin, stdout), jsonrpc2.VSCodeObjectCodec{}) + + clangdStream := jsonrpc2.NewBufferedStream(clangdStdio, jsonrpc2.VSCodeObjectCodec{}) + clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) + handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) + + stdStream := jsonrpc2.NewBufferedStream(stdio, jsonrpc2.VSCodeObjectCodec{}) var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.FromStdio) if asyncProcessing { stdHandler = AsyncHandler{ @@ -64,18 +63,11 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *Strea type InoHandler struct { StdioConn *jsonrpc2.Conn ClangdConn *jsonrpc2.Conn - clangdProc ClangdProc data map[lsp.DocumentURI]*FileData config BoardConfig synchronizer Synchronizer } -// ClangdProc contains the process input / output streams for clangd. -type ClangdProc struct { - Start func() (io.WriteCloser, io.ReadCloser, io.ReadCloser) - Logs *StreamLogger -} - // FileData gathers information on a .ino source file. type FileData struct { sourceText string @@ -86,20 +78,6 @@ type FileData struct { version int } -// StartClangd starts the clangd process and connects its input / output streams. -func (handler *InoHandler) startClangd() { - clangdWrite, clangdRead, clangdErr := handler.clangdProc.Start() - if enableLogging { - go io.Copy(handler.clangdProc.Logs.ClangdErr, clangdErr) - } else { - go io.Copy(ioutil.Discard, clangdErr) - } - srw := handler.clangdProc.Logs.AttachClangdInOut(clangdRead, clangdWrite) - clangdStream := jsonrpc2.NewBufferedStream(srw, jsonrpc2.VSCodeObjectCodec{}) - clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) - handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) -} - // StopClangd closes the connection to the clangd process. func (handler *InoHandler) StopClangd() { handler.ClangdConn.Close() diff --git a/handler/streamlog.go b/handler/streamlog.go deleted file mode 100644 index 7df9e99..0000000 --- a/handler/streamlog.go +++ /dev/null @@ -1,147 +0,0 @@ -package handler - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" -) - -// StreamLogger maintains log files for all streams involved in the language server -type StreamLogger struct { - Default io.WriteCloser - Stdin io.WriteCloser - Stdout io.WriteCloser - ClangdIn io.WriteCloser - ClangdOut io.WriteCloser - ClangdErr io.WriteCloser -} - -// Close closes all logging streams -func (s *StreamLogger) Close() (err error) { - var errs []string - for _, c := range []io.Closer{s.Default, s.Stdin, s.Stdout, s.ClangdIn, s.ClangdOut, s.ClangdErr} { - if c == nil { - continue - } - - err = c.Close() - if err != nil { - errs = append(errs, err.Error()) - } - } - if len(errs) != 0 { - return fmt.Errorf(strings.Join(errs, ", ")) - } - - return nil -} - -// AttachStdInOut attaches the stdin, stdout logger to the in/out channels -func (s *StreamLogger) AttachStdInOut(in io.ReadCloser, out io.WriteCloser) io.ReadWriteCloser { - return &streamDuplex{ - io.TeeReader(in, s.Stdin), - in, - io.MultiWriter(out, s.Stdout), - out, - } -} - -// AttachClangdInOut attaches the clangd in, out logger to the in/out channels -func (s *StreamLogger) AttachClangdInOut(in io.ReadCloser, out io.WriteCloser) io.ReadWriteCloser { - return &streamDuplex{ - io.TeeReader(in, s.ClangdIn), - in, - io.MultiWriter(out, s.ClangdOut), - out, - } -} - -type streamDuplex struct { - in io.Reader - inc io.Closer - out io.Writer - outc io.Closer -} - -func (sd *streamDuplex) Read(p []byte) (int, error) { - return sd.in.Read(p) -} - -func (sd *streamDuplex) Write(p []byte) (int, error) { - return sd.out.Write(p) -} - -func (sd *streamDuplex) Close() error { - ierr := sd.inc.Close() - oerr := sd.outc.Close() - - if ierr != nil { - return ierr - } - if oerr != nil { - return oerr - } - return nil -} - -// NewStreamLogger creates files for all stream logs. Returns an error if opening a single stream fails. -func NewStreamLogger(basepath string) (res *StreamLogger, err error) { - res = &StreamLogger{} - - res.Default, err = os.OpenFile(filepath.Join(basepath, "inols.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - res.Close() - return - } - res.Stdin, err = os.OpenFile(filepath.Join(basepath, "inols-stdin.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - res.Close() - return - } - res.Stdout, err = os.OpenFile(filepath.Join(basepath, "inols-stdout.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - res.Close() - return - } - res.ClangdIn, err = os.OpenFile(filepath.Join(basepath, "inols-clangd-in.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - res.Close() - return - } - res.ClangdOut, err = os.OpenFile(filepath.Join(basepath, "inols-clangd-out.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - res.Close() - return - } - res.ClangdErr, err = os.OpenFile(filepath.Join(basepath, "inols-clangd-err.log"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - res.Close() - return - } - - return -} - -// NewNoopLogger creates a logger that does nothing -func NewNoopLogger() (res *StreamLogger) { - noop := noopCloser{ioutil.Discard} - return &StreamLogger{ - Default: noop, - Stdin: noop, - Stdout: noop, - ClangdIn: noop, - ClangdOut: noop, - ClangdErr: noop, - } -} - -type noopCloser struct { - io.Writer -} - -func (noopCloser) Close() error { - return nil -} diff --git a/main.go b/main.go index 9980174..a97dbf1 100644 --- a/main.go +++ b/main.go @@ -7,8 +7,10 @@ import ( "log" "os" "os/exec" + "path/filepath" "github.com/bcmi-labs/arduino-language-server/handler" + "github.com/bcmi-labs/arduino-language-server/streams" ) var clangdPath string @@ -29,29 +31,53 @@ func main() { flag.StringVar(&loggingBasePath, "logpath", ".", "Location where to write logging files to when logging is enabled") flag.Parse() - // var stdinLog, stdoutLog, clangdinLog, clangdoutLog, clangderrLog io.Writer - var logStreams *handler.StreamLogger if enableLogging { - var err error - logStreams, err = handler.NewStreamLogger(loggingBasePath) - if err != nil { - log.Fatal(err) - } - defer logStreams.Close() - - log.SetOutput(logStreams.Default) + logfile := openLogFile("inols-err.log") + defer logfile.Close() + log.SetOutput(logfile) } else { - logStreams = handler.NewNoopLogger() log.SetOutput(os.Stderr) } handler.Setup(cliPath, enableLogging, true) initialBoard := handler.Board{Fqbn: initialFqbn, Name: initialBoardName} - inoHandler := handler.NewInoHandler(os.Stdin, os.Stdout, logStreams, startClangd, initialBoard) + + clangdStdout, clangdStdin, clangdStderr := startClangd() + clangdStdio := streams.NewReadWriteCloser(clangdStdin, clangdStdout) + if enableLogging { + logfile := openLogFile("inols-clangd.log") + defer logfile.Close() + clangdStdio = streams.LogReadWriteCloserToFile(clangdStdio, logfile) + + errLogfile := openLogFile("inols-clangd-err.log") + defer errLogfile.Close() + go io.Copy(errLogfile, clangdStderr) + } + + stdio := streams.NewReadWriteCloser(os.Stdin, os.Stdout) + if enableLogging { + logfile := openLogFile("inols.log") + defer logfile.Close() + stdio = streams.LogReadWriteCloserToFile(stdio, logfile) + } + + inoHandler := handler.NewInoHandler(stdio, clangdStdio, initialBoard) defer inoHandler.StopClangd() <-inoHandler.StdioConn.DisconnectNotify() } +func openLogFile(name string) *os.File { + path := filepath.Join(loggingBasePath, name) + logfile, err := os.Create(path) + if err != nil { + log.Fatalf("Error opening log file: %s", err) + } else { + abs, _ := filepath.Abs(path) + log.Println("logging to " + abs) + } + return logfile +} + func startClangd() (clangdIn io.WriteCloser, clangdOut io.ReadCloser, clangdErr io.ReadCloser) { if enableLogging { log.Println("Starting clangd process:", clangdPath) diff --git a/streams/dumper.go b/streams/dumper.go new file mode 100644 index 0000000..1cc8359 --- /dev/null +++ b/streams/dumper.go @@ -0,0 +1,45 @@ +package streams + +import ( + "fmt" + "io" + "os" +) + +// LogReadWriteCloserToFile return a proxy for the given upstream io.ReadWriteCloser +// that forward and logs all read/write/close operations on the given file. +func LogReadWriteCloserToFile(upstream io.ReadWriteCloser, file *os.File) io.ReadWriteCloser { + return &dumper{upstream, file} +} + +type dumper struct { + upstream io.ReadWriteCloser + logfile *os.File +} + +func (d *dumper) Read(buff []byte) (int, error) { + n, err := d.upstream.Read(buff) + if err != nil { + d.logfile.Write([]byte(fmt.Sprintf("<<< Read Error: %s\n", err))) + } else { + d.logfile.Write([]byte(fmt.Sprintf("<<< Read %d bytes:\n%s\n", n, buff[:n]))) + } + return n, err +} + +func (d *dumper) Write(buff []byte) (int, error) { + n, err := d.upstream.Write(buff) + if err != nil { + _, _ = d.logfile.Write([]byte(fmt.Sprintf(">>> Write Error: %s\n", err))) + } else { + _, _ = d.logfile.Write([]byte(fmt.Sprintf(">>> Wrote %d bytes:\n%s\n", n, buff[:n]))) + } + return n, err +} + +func (d *dumper) Close() error { + err := d.upstream.Close() + _, _ = d.logfile.Write([]byte(fmt.Sprintf("--- Stream closed, err=%s\n", err))) + _ = d.logfile.Close() + return err +} diff --git a/streams/streams.go b/streams/streams.go new file mode 100644 index 0000000..10d313c --- /dev/null +++ b/streams/streams.go @@ -0,0 +1,33 @@ +package streams + +import "io" + +// NewReadWriteCloser create an io.ReadWriteCloser from given io.ReadCloser and io.WriteCloser. +func NewReadWriteCloser(in io.ReadCloser, out io.WriteCloser) io.ReadWriteCloser { + return &combinedReadWriteCloser{in, out} +} + +type combinedReadWriteCloser struct { + reader io.ReadCloser + writer io.WriteCloser +} + +func (sd *combinedReadWriteCloser) Read(p []byte) (int, error) { + return sd.reader.Read(p) +} + +func (sd *combinedReadWriteCloser) Write(p []byte) (int, error) { + return sd.writer.Write(p) +} + +func (sd *combinedReadWriteCloser) Close() error { + ierr := sd.reader.Close() + oerr := sd.writer.Close() + if ierr != nil { + return ierr + } + if oerr != nil { + return oerr + } + return nil +} From 12a1bf64e9d2ba5234fa891e4dbef302ae457647 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 9 Nov 2020 19:36:44 +0100 Subject: [PATCH 05/61] Create a source mapper object and factored textutils subroutine --- handler/handler.go | 95 +++----- handler/sourcemap.go | 204 ------------------ handler/sourcemapper/ino.go | 157 ++++++++++++++ handler/sourcemapper/ino_test.go | 137 ++++++++++++ handler/textutils/textutils.go | 97 +++++++++ .../textutils_test.go} | 70 +----- 6 files changed, 427 insertions(+), 333 deletions(-) delete mode 100644 handler/sourcemap.go create mode 100644 handler/sourcemapper/ino.go create mode 100644 handler/sourcemapper/ino_test.go create mode 100644 handler/textutils/textutils.go rename handler/{sourcemap_test.go => textutils/textutils_test.go} (69%) diff --git a/handler/handler.go b/handler/handler.go index 46d255b..77732d6 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -12,6 +12,8 @@ import ( "strings" "time" + "github.com/bcmi-labs/arduino-language-server/handler/sourcemapper" + "github.com/bcmi-labs/arduino-language-server/handler/textutils" "github.com/pkg/errors" lsp "github.com/sourcegraph/go-lsp" "github.com/sourcegraph/jsonrpc2" @@ -70,12 +72,11 @@ type InoHandler struct { // FileData gathers information on a .ino source file. type FileData struct { - sourceText string - sourceURI lsp.DocumentURI - targetURI lsp.DocumentURI - sourceLineMap map[int]int - targetLineMap map[int]int - version int + sourceText string + sourceURI lsp.DocumentURI + targetURI lsp.DocumentURI + sourceMap *sourcemapper.InoMapper + version int } // StopClangd closes the connection to the clangd process. @@ -240,13 +241,11 @@ func (handler *InoHandler) createFileData(ctx context.Context, sourceURI lsp.Doc } targetURI := pathToURI(targetPath) - sourceLineMap, targetLineMap := createSourceMaps(bytes.NewReader(targetBytes)) data := &FileData{ sourceText, sourceURI, targetURI, - sourceLineMap, - targetLineMap, + sourcemapper.CreateInoMapper(bytes.NewReader(targetBytes)), version, } handler.data[sourceURI] = data @@ -262,7 +261,7 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c if rang == nil { newSourceText = change.Text } else { - newSourceText, err = applyTextChange(data.sourceText, *rang, change.Text) + newSourceText, err = textutils.ApplyTextChange(data.sourceText, *rang, change.Text) if err != nil { return err } @@ -277,35 +276,28 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c } } else { // Fallback: try to apply a multi-line update - targetStartLine := data.targetLineMap[rang.Start.Line] - targetEndLine := data.targetLineMap[rang.End.Line] data.sourceText = newSourceText - updateSourceMaps(data.sourceLineMap, data.targetLineMap, rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text) - rang.Start.Line = targetStartLine - rang.End.Line = targetEndLine + data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text) + *rang = data.sourceMap.InoToCppRange(*rang) return nil } } - sourceLineMap, targetLineMap := createSourceMaps(bytes.NewReader(targetBytes)) data.sourceText = newSourceText - data.sourceLineMap = sourceLineMap - data.targetLineMap = targetLineMap + data.sourceMap = sourcemapper.CreateInoMapper(bytes.NewReader(targetBytes)) change.Text = string(targetBytes) change.Range = nil change.RangeLength = 0 } else { // Apply an update to a single line both to the source and the target text - targetLine := data.targetLineMap[rang.Start.Line] - data.sourceText, err = applyTextChange(data.sourceText, *rang, change.Text) + data.sourceText, err = textutils.ApplyTextChange(data.sourceText, *rang, change.Text) if err != nil { return err } - updateSourceMaps(data.sourceLineMap, data.targetLineMap, 0, rang.Start.Line, change.Text) + data.sourceMap.Update(0, rang.Start.Line, change.Text) - rang.Start.Line = targetLine - rang.End.Line = targetLine + *rang = data.sourceMap.InoToCppRange(*rang) } return nil } @@ -391,7 +383,7 @@ func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Contex func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - targetLine := data.targetLineMap[params.Position.Line] + targetLine := data.sourceMap.InoToCppLine(params.Position.Line) params.Position.Line = targetLine return nil } @@ -401,12 +393,10 @@ func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDoc func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Range.Start.Line = data.targetLineMap[params.Range.Start.Line] - params.Range.End.Line = data.targetLineMap[params.Range.End.Line] + params.Range = data.sourceMap.InoToCppRange(params.Range) for index := range params.Context.Diagnostics { r := ¶ms.Context.Diagnostics[index].Range - r.Start.Line = data.targetLineMap[r.Start.Line] - r.End.Line = data.targetLineMap[r.End.Line] + *r = data.sourceMap.InoToCppRange(*r) } return nil } @@ -416,8 +406,7 @@ func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.DocumentRangeFormattingParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Range.Start.Line = data.targetLineMap[params.Range.Start.Line] - params.Range.End.Line = data.targetLineMap[params.Range.End.Line] + params.Range = data.sourceMap.InoToCppRange(params.Range) return nil } return unknownURI(params.TextDocument.URI) @@ -426,7 +415,7 @@ func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.Docu func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.DocumentOnTypeFormattingParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Position.Line = data.targetLineMap[params.Position.Line] + params.Position.Line = data.sourceMap.InoToCppLine(params.Position.Line) return nil } return unknownURI(params.TextDocument.URI) @@ -435,7 +424,7 @@ func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.Doc func (handler *InoHandler) ino2cppRenameParams(params *lsp.RenameParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Position.Line = data.targetLineMap[params.Position.Line] + params.Position.Line = data.sourceMap.InoToCppLine(params.Position.Line) return nil } return unknownURI(params.TextDocument.URI) @@ -467,13 +456,9 @@ func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { newValue := make([]lsp.TextEdit, len(edit)) for index := range edit { - r := edit[index].Range newValue[index] = lsp.TextEdit{ NewText: edit[index].NewText, - Range: lsp.Range{ - Start: lsp.Position{Line: data.targetLineMap[r.Start.Line], Character: r.Start.Character}, - End: lsp.Position{Line: data.targetLineMap[r.End.Line], Character: r.End.Character}, - }, + Range: data.sourceMap.InoToCppRange(edit[index].Range), } } newEdit.Changes[string(data.targetURI)] = newValue @@ -577,9 +562,7 @@ func (handler *InoHandler) cpp2inoCompletionList(list *lsp.CompletionList, uri l for _, item := range list.Items { if !strings.HasPrefix(item.InsertText, "_") { if item.TextEdit != nil { - r := &item.TextEdit.Range - r.Start.Line = data.sourceLineMap[r.Start.Line] - r.End.Line = data.sourceLineMap[r.End.Line] + item.TextEdit.Range = data.sourceMap.CppToInoRange(item.TextEdit.Range) } newItems = append(newItems, item) } @@ -592,9 +575,7 @@ func (handler *InoHandler) cpp2inoCodeAction(codeAction *CodeAction, uri lsp.Doc codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) if data, ok := handler.data[uri]; ok { for index := range codeAction.Diagnostics { - r := &codeAction.Diagnostics[index].Range - r.Start.Line = data.sourceLineMap[r.Start.Line] - r.End.Line = data.sourceLineMap[r.End.Line] + codeAction.Diagnostics[index].Range = data.sourceMap.CppToInoRange(codeAction.Diagnostics[index].Range) } } } @@ -614,13 +595,9 @@ func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { newValue := make([]lsp.TextEdit, len(edit)) for index := range edit { - r := edit[index].Range newValue[index] = lsp.TextEdit{ NewText: edit[index].NewText, - Range: lsp.Range{ - Start: lsp.Position{Line: data.sourceLineMap[r.Start.Line], Character: r.Start.Character}, - End: lsp.Position{Line: data.sourceLineMap[r.End.Line], Character: r.End.Character}, - }, + Range: data.sourceMap.CppToInoRange(edit[index].Range), } } newEdit.Changes[string(data.sourceURI)] = newValue @@ -635,8 +612,7 @@ func (handler *InoHandler) cpp2inoHover(hover *Hover, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { r := hover.Range if r != nil { - r.Start.Line = data.sourceLineMap[r.Start.Line] - r.End.Line = data.sourceLineMap[r.End.Line] + *r = data.sourceMap.CppToInoRange(*r) } } } @@ -644,22 +620,19 @@ func (handler *InoHandler) cpp2inoHover(hover *Hover, uri lsp.DocumentURI) { func (handler *InoHandler) cpp2inoLocation(location *lsp.Location) { if data, ok := handler.data[location.URI]; ok { location.URI = data.sourceURI - location.Range.Start.Line = data.sourceLineMap[location.Range.Start.Line] - location.Range.End.Line = data.sourceLineMap[location.Range.End.Line] + location.Range = data.sourceMap.CppToInoRange(location.Range) } } func (handler *InoHandler) cpp2inoDocumentHighlight(highlight *lsp.DocumentHighlight, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { - highlight.Range.Start.Line = data.sourceLineMap[highlight.Range.Start.Line] - highlight.Range.End.Line = data.sourceLineMap[highlight.Range.End.Line] + highlight.Range = data.sourceMap.CppToInoRange(highlight.Range) } } func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { - edit.Range.Start.Line = data.sourceLineMap[edit.Range.Start.Line] - edit.Range.End.Line = data.sourceLineMap[edit.Range.End.Line] + edit.Range = data.sourceMap.CppToInoRange(edit.Range) } } @@ -672,8 +645,7 @@ func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, symbolIdx := make(map[string]*DocumentSymbol) for i := 0; i < len(origSymbols); i++ { symbol := &origSymbols[i] - symbol.Range.Start.Line = data.sourceLineMap[symbol.Range.Start.Line] - symbol.Range.End.Line = data.sourceLineMap[symbol.Range.End.Line] + symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) duplicate := false other, duplicate := symbolIdx[symbol.Name] @@ -686,8 +658,7 @@ func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, } } - symbol.SelectionRange.Start.Line = data.sourceLineMap[symbol.SelectionRange.Start.Line] - symbol.SelectionRange.End.Line = data.sourceLineMap[symbol.SelectionRange.End.Line] + symbol.SelectionRange = data.sourceMap.CppToInoRange(symbol.SelectionRange) symbol.Children = handler.cpp2inoDocumentSymbols(symbol.Children, uri) symbolIdx[symbol.Name] = symbol } @@ -776,9 +747,9 @@ func (handler *InoHandler) cpp2inoPublishDiagnosticsParams(params *lsp.PublishDi newDiagnostics := make([]lsp.Diagnostic, 0, len(params.Diagnostics)) for index := range params.Diagnostics { r := ¶ms.Diagnostics[index].Range - if startLine, ok := data.sourceLineMap[r.Start.Line]; ok { + if startLine, ok := data.sourceMap.CppToInoLineOk(r.Start.Line); ok { r.Start.Line = startLine - r.End.Line = data.sourceLineMap[r.End.Line] + r.End.Line = data.sourceMap.CppToInoLine(r.End.Line) newDiagnostics = append(newDiagnostics, params.Diagnostics[index]) } } diff --git a/handler/sourcemap.go b/handler/sourcemap.go deleted file mode 100644 index 41840a4..0000000 --- a/handler/sourcemap.go +++ /dev/null @@ -1,204 +0,0 @@ -package handler - -import ( - "bufio" - "fmt" - "io" - "strconv" - "strings" - - lsp "github.com/sourcegraph/go-lsp" -) - -func createSourceMaps(targetFile io.Reader) (sourceLineMap, targetLineMap map[int]int) { - sourceLine := -1 - targetLine := 0 - sourceLineMap = make(map[int]int) - targetLineMap = make(map[int]int) - scanner := bufio.NewScanner(targetFile) - for scanner.Scan() { - lineStr := scanner.Text() - if strings.HasPrefix(lineStr, "#line") { - nrEnd := strings.Index(lineStr[6:], " ") - var l int - var err error - if nrEnd > 0 { - l, err = strconv.Atoi(lineStr[6 : nrEnd+6]) - } else { - l, err = strconv.Atoi(lineStr[6:]) - } - if err == nil && l > 0 { - sourceLine = l - 1 - } - } else if sourceLine >= 0 { - sourceLineMap[targetLine] = sourceLine - targetLineMap[sourceLine] = targetLine - sourceLine++ - } - targetLine++ - } - sourceLineMap[targetLine] = sourceLine - targetLineMap[sourceLine] = targetLine - return -} - -func updateSourceMaps(sourceLineMap, targetLineMap map[int]int, deletedLines, insertLine int, insertText string) { - for i := 1; i <= deletedLines; i++ { - sourceLine := insertLine + 1 - targetLine := targetLineMap[sourceLine] - - // Shift up all following lines by one and put them into a new map - newMappings := make(map[int]int) - maxSourceLine, maxTargetLine := 0, 0 - for t, s := range sourceLineMap { - if t > targetLine && s > sourceLine { - newMappings[t-1] = s - 1 - } else if s > sourceLine { - newMappings[t] = s - 1 - } else if t > targetLine { - newMappings[t-1] = s - } - if s > maxSourceLine { - maxSourceLine = s - } - if t > maxTargetLine { - maxTargetLine = t - } - } - - // Remove mappings for the deleted line - delete(sourceLineMap, maxTargetLine) - delete(targetLineMap, maxSourceLine) - - // Copy the mappings from the intermediate map - copyMappings(sourceLineMap, targetLineMap, newMappings) - } - - addedLines := strings.Count(insertText, "\n") - if addedLines > 0 { - targetLine := targetLineMap[insertLine] - - // Shift down all following lines and put them into a new map - newMappings := make(map[int]int) - for t, s := range sourceLineMap { - if t > targetLine && s > insertLine { - newMappings[t+addedLines] = s + addedLines - } else if s > insertLine { - newMappings[t] = s + addedLines - } else if t > targetLine { - newMappings[t+addedLines] = s - } - } - - // Add mappings for the added lines - for i := 1; i <= addedLines; i++ { - sourceLineMap[targetLine+i] = insertLine + i - targetLineMap[insertLine+i] = targetLine + i - } - - // Copy the mappings from the intermediate map - copyMappings(sourceLineMap, targetLineMap, newMappings) - } -} - -func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) { - for t, s := range newMappings { - sourceLineMap[t] = s - targetLineMap[s] = t - } - for t, s := range newMappings { - // In case multiple target lines are present for a source line, use the last one - if t > targetLineMap[s] { - targetLineMap[s] = t - } - } -} - -// OutOfRangeError returned if one attempts to access text out of its range -type OutOfRangeError struct { - Type string - Max int - Req int -} - -func (oor OutOfRangeError) Error() string { - return fmt.Sprintf("%s access out of range: max=%d requested=%d", oor.Type, oor.Max, oor.Req) -} - -func applyTextChange(text string, rang lsp.Range, insertText string) (res string, err error) { - start, err := getOffset(text, rang.Start) - if err != nil { - return "", err - } - end, err := getOffset(text, rang.End) - if err != nil { - return "", err - } - - return text[:start] + insertText + text[end:], nil -} - -// getOffset computes the offset in the text expressed by the lsp.Position. -// Returns OutOfRangeError if the position is out of range. -func getOffset(text string, pos lsp.Position) (int, error) { - // Find line - lineOffset, err := getLineOffset(text, pos.Line) - if err != nil { - return -1, err - } - character := pos.Character - if character == 0 { - return lineOffset, nil - } - - // Find the character and return its offset within the text - var count = len(text[lineOffset:]) - for offset, c := range text[lineOffset:] { - if character == offset { - // We've found the character - return lineOffset + offset, nil - } - if c == '\n' { - // We've reached the end of line. LSP spec says we should default back to the line length. - // See https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#position - if character > offset { - return lineOffset + offset, nil - } - count = offset - break - } - } - if character > 0 { - // We've reached the end of the last line. Default to the text length (see above). - return len(text), nil - } - - // We haven't found the character in the text (character index was negative) - return -1, OutOfRangeError{"Character", count, character} -} - -// getLineOffset finds the offset/position of the beginning of a line within the text. -// For example: -// text := "foo\nfoobar\nbaz" -// getLineOffset(text, 0) == 0 -// getLineOffset(text, 1) == 4 -// getLineOffset(text, 2) == 11 -func getLineOffset(text string, line int) (int, error) { - if line == 0 { - return 0, nil - } - - // Find the line and return its offset within the text - var count int - for offset, c := range text { - if c == '\n' { - count++ - if count == line { - return offset + 1, nil - } - } - } - - // We haven't found the line in the text - return -1, OutOfRangeError{"Line", count, line} -} diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go new file mode 100644 index 0000000..8294a9b --- /dev/null +++ b/handler/sourcemapper/ino.go @@ -0,0 +1,157 @@ +package sourcemapper + +import ( + "bufio" + "io" + "strconv" + "strings" + + "github.com/sourcegraph/go-lsp" +) + +// InoMapper is a mapping between the .ino sketch and the preprocessed .cpp file +type InoMapper struct { + toCpp map[int]int + toIno map[int]int +} + +// InoToCppLine converts a source (.ino) line into a target (.cpp) line +func (s *InoMapper) InoToCppLine(sourceLine int) int { + return s.toCpp[sourceLine] +} + +// InoToCppRange converts a source (.ino) lsp.Range into a target (.cpp) lsp.Range +func (s *InoMapper) InoToCppRange(r lsp.Range) lsp.Range { + r.Start.Line = s.InoToCppLine(r.Start.Line) + r.End.Line = s.InoToCppLine(r.End.Line) + return r +} + +// CppToInoLine converts a target (.cpp) line into a source (.ino) line +func (s *InoMapper) CppToInoLine(targetLine int) int { + return s.toIno[targetLine] +} + +// CppToInoRange converts a target (.cpp) lsp.Range into a source (.ino) lsp.Range +func (s *InoMapper) CppToInoRange(r lsp.Range) lsp.Range { + r.Start.Line = s.CppToInoLine(r.Start.Line) + r.End.Line = s.CppToInoLine(r.End.Line) + return r +} + +// CppToInoLineOk converts a target (.cpp) line into a source (.ino) line and +// returns true if the conversion is successful +func (s *InoMapper) CppToInoLineOk(targetLine int) (int, bool) { + res, ok := s.toIno[targetLine] + return res, ok +} + +// CreateInoMapper create a InoMapper from the given target file +func CreateInoMapper(targetFile io.Reader) *InoMapper { + sourceLine := -1 + targetLine := 0 + sourceLineMap := make(map[int]int) + targetLineMap := make(map[int]int) + scanner := bufio.NewScanner(targetFile) + for scanner.Scan() { + lineStr := scanner.Text() + if strings.HasPrefix(lineStr, "#line") { + nrEnd := strings.Index(lineStr[6:], " ") + var l int + var err error + if nrEnd > 0 { + l, err = strconv.Atoi(lineStr[6 : nrEnd+6]) + } else { + l, err = strconv.Atoi(lineStr[6:]) + } + if err == nil && l > 0 { + sourceLine = l - 1 + } + } else if sourceLine >= 0 { + sourceLineMap[targetLine] = sourceLine + targetLineMap[sourceLine] = targetLine + sourceLine++ + } + targetLine++ + } + sourceLineMap[targetLine] = sourceLine + targetLineMap[sourceLine] = targetLine + return &InoMapper{ + toIno: sourceLineMap, + toCpp: targetLineMap, + } +} + +// Update performs an update to the SourceMap considering the deleted lines, the +// insertion line and the inserted text +func (s *InoMapper) Update(deletedLines, insertLine int, insertText string) { + for i := 1; i <= deletedLines; i++ { + sourceLine := insertLine + 1 + targetLine := s.toCpp[sourceLine] + + // Shift up all following lines by one and put them into a new map + newMappings := make(map[int]int) + maxSourceLine, maxTargetLine := 0, 0 + for t, s := range s.toIno { + if t > targetLine && s > sourceLine { + newMappings[t-1] = s - 1 + } else if s > sourceLine { + newMappings[t] = s - 1 + } else if t > targetLine { + newMappings[t-1] = s + } + if s > maxSourceLine { + maxSourceLine = s + } + if t > maxTargetLine { + maxTargetLine = t + } + } + + // Remove mappings for the deleted line + delete(s.toIno, maxTargetLine) + delete(s.toCpp, maxSourceLine) + + // Copy the mappings from the intermediate map + copyMappings(s.toIno, s.toCpp, newMappings) + } + + addedLines := strings.Count(insertText, "\n") + if addedLines > 0 { + targetLine := s.toCpp[insertLine] + + // Shift down all following lines and put them into a new map + newMappings := make(map[int]int) + for t, s := range s.toIno { + if t > targetLine && s > insertLine { + newMappings[t+addedLines] = s + addedLines + } else if s > insertLine { + newMappings[t] = s + addedLines + } else if t > targetLine { + newMappings[t+addedLines] = s + } + } + + // Add mappings for the added lines + for i := 1; i <= addedLines; i++ { + s.toIno[targetLine+i] = insertLine + i + s.toCpp[insertLine+i] = targetLine + i + } + + // Copy the mappings from the intermediate map + copyMappings(s.toIno, s.toCpp, newMappings) + } +} + +func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) { + for t, s := range newMappings { + sourceLineMap[t] = s + targetLineMap[s] = t + } + for t, s := range newMappings { + // In case multiple target lines are present for a source line, use the last one + if t > targetLineMap[s] { + targetLineMap[s] = t + } + } +} diff --git a/handler/sourcemapper/ino_test.go b/handler/sourcemapper/ino_test.go new file mode 100644 index 0000000..0fdede0 --- /dev/null +++ b/handler/sourcemapper/ino_test.go @@ -0,0 +1,137 @@ +package sourcemapper + +import ( + "reflect" + "strings" + "testing" +) + +func TestCreateSourceMaps(t *testing.T) { + input := `#include +#line 1 "sketch_july2a.ino" +#line 1 "sketch_july2a.ino" + +#line 2 "sketch_july2a.ino" +void setup(); +#line 7 "sketch_july2a.ino" +void loop(); +#line 2 "sketch_july2a.ino" +void setup() { + // put your setup code here, to run once: + +} + +void loop() { + // put your main code here, to run repeatedly: + +} +` + sourceMap := CreateInoMapper(strings.NewReader(input)) + if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ + 3: 0, + 5: 1, + 7: 6, + 9: 1, + 10: 2, + 11: 3, + 12: 4, + 13: 5, + 14: 6, + 15: 7, + 16: 8, + 17: 9, + 18: 10, + }) { + t.Error(sourceMap.toIno) + } + if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ + 0: 3, + 1: 9, + 2: 10, + 3: 11, + 4: 12, + 5: 13, + 6: 14, + 7: 15, + 8: 16, + 9: 17, + 10: 18}, + ) { + t.Error(sourceMap.toCpp) + } +} + +func TestUpdateSourceMaps1(t *testing.T) { + sourceMap := &InoMapper{ + toCpp: map[int]int{ + 0: 1, + 1: 2, + 2: 0, + 3: 5, + 4: 3, + 5: 4, + }, + toIno: make(map[int]int), + } + for s, t := range sourceMap.toCpp { + sourceMap.toIno[t] = s + } + sourceMap.Update(0, 1, "foo\nbar\nbaz") + if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ + 0: 1, + 1: 2, + 2: 3, + 3: 4, + 4: 0, + 5: 7, + 6: 5, + 7: 6}, + ) { + t.Error(sourceMap.toCpp) + } + if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ + 0: 4, + 1: 0, + 2: 1, + 3: 2, + 4: 3, + 5: 6, + 6: 7, + 7: 5}, + ) { + t.Error(sourceMap.toIno) + } +} + +func TestUpdateSourceMaps2(t *testing.T) { + sourceMap := &InoMapper{ + toCpp: map[int]int{ + 0: 1, + 1: 2, + 2: 0, + 3: 5, + 4: 3, + 5: 4}, + toIno: make(map[int]int), + } + for s, t := range sourceMap.toCpp { + sourceMap.toIno[t] = s + } + sourceMap.Update(2, 1, "foo") + if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ + 0: 0, + 1: 1, + 2: 2, + 3: 3}, + ) { + t.Error(sourceMap.toCpp) + } + if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ + 0: 0, + 1: 1, + 2: 2, + 3: 3}, + ) { + t.Error(sourceMap.toIno) + } +} diff --git a/handler/textutils/textutils.go b/handler/textutils/textutils.go new file mode 100644 index 0000000..d3a24b4 --- /dev/null +++ b/handler/textutils/textutils.go @@ -0,0 +1,97 @@ +package textutils + +import ( + "fmt" + + "github.com/sourcegraph/go-lsp" +) + +// ApplyTextChange replaces startingText substring specified by replaceRange with insertText +func ApplyTextChange(startingText string, replaceRange lsp.Range, insertText string) (res string, err error) { + start, err := getOffset(startingText, replaceRange.Start) + if err != nil { + return "", err + } + end, err := getOffset(startingText, replaceRange.End) + if err != nil { + return "", err + } + + return startingText[:start] + insertText + startingText[end:], nil +} + +// getOffset computes the offset in the text expressed by the lsp.Position. +// Returns OutOfRangeError if the position is out of range. +func getOffset(text string, pos lsp.Position) (int, error) { + // Find line + lineOffset, err := getLineOffset(text, pos.Line) + if err != nil { + return -1, err + } + character := pos.Character + if character == 0 { + return lineOffset, nil + } + + // Find the character and return its offset within the text + var count = len(text[lineOffset:]) + for offset, c := range text[lineOffset:] { + if character == offset { + // We've found the character + return lineOffset + offset, nil + } + if c == '\n' { + // We've reached the end of line. LSP spec says we should default back to the line length. + // See https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#position + if character > offset { + return lineOffset + offset, nil + } + count = offset + break + } + } + if character > 0 { + // We've reached the end of the last line. Default to the text length (see above). + return len(text), nil + } + + // We haven't found the character in the text (character index was negative) + return -1, OutOfRangeError{"Character", count, character} +} + +// getLineOffset finds the offset/position of the beginning of a line within the text. +// For example: +// text := "foo\nfoobar\nbaz" +// getLineOffset(text, 0) == 0 +// getLineOffset(text, 1) == 4 +// getLineOffset(text, 2) == 11 +func getLineOffset(text string, line int) (int, error) { + if line == 0 { + return 0, nil + } + + // Find the line and return its offset within the text + var count int + for offset, c := range text { + if c == '\n' { + count++ + if count == line { + return offset + 1, nil + } + } + } + + // We haven't found the line in the text + return -1, OutOfRangeError{"Line", count, line} +} + +// OutOfRangeError returned if one attempts to access text out of its range +type OutOfRangeError struct { + Type string + Max int + Req int +} + +func (oor OutOfRangeError) Error() string { + return fmt.Sprintf("%s access out of range: max=%d requested=%d", oor.Type, oor.Max, oor.Req) +} diff --git a/handler/sourcemap_test.go b/handler/textutils/textutils_test.go similarity index 69% rename from handler/sourcemap_test.go rename to handler/textutils/textutils_test.go index d9fe22a..032acda 100644 --- a/handler/sourcemap_test.go +++ b/handler/textutils/textutils_test.go @@ -1,76 +1,12 @@ -package handler +package textutils import ( - "reflect" "strings" "testing" - lsp "github.com/sourcegraph/go-lsp" + "github.com/sourcegraph/go-lsp" ) -func TestCreateSourceMaps(t *testing.T) { - input := `#include -#line 1 "sketch_july2a.ino" -#line 1 "sketch_july2a.ino" - -#line 2 "sketch_july2a.ino" -void setup(); -#line 7 "sketch_july2a.ino" -void loop(); -#line 2 "sketch_july2a.ino" -void setup() { - // put your setup code here, to run once: - -} - -void loop() { - // put your main code here, to run repeatedly: - -} -` - sourceLineMap, targetLineMap := createSourceMaps(strings.NewReader(input)) - if !reflect.DeepEqual(sourceLineMap, map[int]int{ - 3: 0, 5: 1, 7: 6, 9: 1, 10: 2, 11: 3, 12: 4, 13: 5, 14: 6, 15: 7, 16: 8, 17: 9, 18: 10, - }) { - t.Error(sourceLineMap) - } - if !reflect.DeepEqual(targetLineMap, map[int]int{ - 0: 3, 1: 9, 2: 10, 3: 11, 4: 12, 5: 13, 6: 14, 7: 15, 8: 16, 9: 17, 10: 18, - }) { - t.Error(targetLineMap) - } -} - -func TestUpdateSourceMaps1(t *testing.T) { - targetLineMap := map[int]int{0: 1, 1: 2, 2: 0, 3: 5, 4: 3, 5: 4} - sourceLineMap := make(map[int]int) - for s, t := range targetLineMap { - sourceLineMap[t] = s - } - updateSourceMaps(sourceLineMap, targetLineMap, 0, 1, "foo\nbar\nbaz") - if !reflect.DeepEqual(targetLineMap, map[int]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 0, 5: 7, 6: 5, 7: 6}) { - t.Error(targetLineMap) - } - if !reflect.DeepEqual(sourceLineMap, map[int]int{0: 4, 1: 0, 2: 1, 3: 2, 4: 3, 5: 6, 6: 7, 7: 5}) { - t.Error(sourceLineMap) - } -} - -func TestUpdateSourceMaps2(t *testing.T) { - targetLineMap := map[int]int{0: 1, 1: 2, 2: 0, 3: 5, 4: 3, 5: 4} - sourceLineMap := make(map[int]int) - for s, t := range targetLineMap { - sourceLineMap[t] = s - } - updateSourceMaps(sourceLineMap, targetLineMap, 2, 1, "foo") - if !reflect.DeepEqual(targetLineMap, map[int]int{0: 0, 1: 1, 2: 2, 3: 3}) { - t.Error(targetLineMap) - } - if !reflect.DeepEqual(sourceLineMap, map[int]int{0: 0, 1: 1, 2: 2, 3: 3}) { - t.Error(sourceLineMap) - } -} - func TestApplyTextChange(t *testing.T) { tests := []struct { InitialText string @@ -159,7 +95,7 @@ func TestApplyTextChange(t *testing.T) { expectation := strings.ReplaceAll(test.Expectation, "\n", "\\n") t.Logf("applyTextChange(\"%s\", %v, \"%s\") == \"%s\"", initial, test.Range, insertion, expectation) - act, err := applyTextChange(test.InitialText, test.Range, test.Insertion) + act, err := ApplyTextChange(test.InitialText, test.Range, test.Insertion) if act != test.Expectation { t.Errorf("applyTextChange(\"%s\", %v, \"%s\") != \"%s\", got \"%s\"", initial, test.Range, insertion, expectation, strings.ReplaceAll(act, "\n", "\\n")) } From a03fc2034e8ab58a44f4e44614b3ef48c7f29f6e Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 9 Nov 2020 19:55:44 +0100 Subject: [PATCH 06/61] Output stackstrace in log in case of crash --- main.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index a97dbf1..7fd478c 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime/debug" "github.com/bcmi-labs/arduino-language-server/handler" "github.com/bcmi-labs/arduino-language-server/streams" @@ -33,7 +34,13 @@ func main() { if enableLogging { logfile := openLogFile("inols-err.log") - defer logfile.Close() + defer func() { + // In case of panic output the stack trace in the log file before exiting + if r := recover(); r != nil { + log.Println(string(debug.Stack())) + } + logfile.Close() + }() log.SetOutput(logfile) } else { log.SetOutput(os.Stderr) From 09d329dbf1cb15142e0f2424304884e3ab9b3235 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 19 Nov 2020 12:50:18 +0100 Subject: [PATCH 07/61] Consume stderr if no log file is specified --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 7fd478c..2e6cebe 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,8 @@ func main() { errLogfile := openLogFile("inols-clangd-err.log") defer errLogfile.Close() go io.Copy(errLogfile, clangdStderr) + } else { + go io.Copy(os.Stderr, clangdStderr) } stdio := streams.NewReadWriteCloser(os.Stdin, os.Stdout) From 9b0a39b5f78f204744598f587698537b2027600e Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 19 Nov 2020 16:02:28 +0100 Subject: [PATCH 08/61] Converted method-switch into type-switch --- handler/handler.go | 119 ++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 72 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 77732d6..7399333 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -151,75 +151,60 @@ func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method s defer handler.synchronizer.DataMux.RUnlock() } - switch method { - case "textDocument/didOpen": - p := params.(*lsp.DidOpenTextDocumentParams) + switch p := params.(type) { + case *lsp.DidOpenTextDocumentParams: // "textDocument/didOpen": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentItem(ctx, &p.TextDocument) - case "textDocument/didChange": - p := params.(*lsp.DidChangeTextDocumentParams) + case *lsp.DidChangeTextDocumentParams: // "textDocument/didChange": uri = p.TextDocument.URI err = handler.ino2cppDidChangeTextDocumentParams(ctx, p) - case "textDocument/didSave": - p := params.(*lsp.DidSaveTextDocumentParams) + case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) - case "textDocument/didClose": - p := params.(*lsp.DidCloseTextDocumentParams) + case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) handler.deleteFileData(uri) - case "textDocument/completion": - p := params.(*lsp.CompletionParams) + case *lsp.CompletionParams: // "textDocument/completion": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) - case "textDocument/codeAction": - p := params.(*lsp.CodeActionParams) + case *lsp.CodeActionParams: // "textDocument/codeAction": uri = p.TextDocument.URI err = handler.ino2cppCodeActionParams(p) - case "textDocument/signatureHelp": - fallthrough - case "textDocument/hover": - fallthrough - case "textDocument/definition": - fallthrough - case "textDocument/typeDefinition": - fallthrough - case "textDocument/implementation": - fallthrough - case "textDocument/documentHighlight": - p := params.(*lsp.TextDocumentPositionParams) + // case "textDocument/signatureHelp": + // fallthrough + // case "textDocument/hover": + // fallthrough + // case "textDocument/definition": + // fallthrough + // case "textDocument/typeDefinition": + // fallthrough + // case "textDocument/implementation": + // fallthrough + case *lsp.TextDocumentPositionParams: // "textDocument/documentHighlight": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(p) - case "textDocument/references": - p := params.(*lsp.ReferenceParams) + case *lsp.ReferenceParams: // "textDocument/references": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) - case "textDocument/formatting": - p := params.(*lsp.DocumentFormattingParams) + case *lsp.DocumentFormattingParams: // "textDocument/formatting": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) - case "textDocument/rangeFormatting": - p := params.(*lsp.DocumentRangeFormattingParams) + case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting": uri = p.TextDocument.URI err = handler.ino2cppDocumentRangeFormattingParams(p) - case "textDocument/onTypeFormatting": - p := params.(*lsp.DocumentOnTypeFormattingParams) + case *lsp.DocumentOnTypeFormattingParams: // "textDocument/onTypeFormatting": uri = p.TextDocument.URI err = handler.ino2cppDocumentOnTypeFormattingParams(p) - case "textDocument/documentSymbol": - p := params.(*lsp.DocumentSymbolParams) + case *lsp.DocumentSymbolParams: // "textDocument/documentSymbol": uri = p.TextDocument.URI err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) - case "textDocument/rename": - p := params.(*lsp.RenameParams) + case *lsp.RenameParams: // "textDocument/rename": uri = p.TextDocument.URI err = handler.ino2cppRenameParams(p) - case "workspace/didChangeWatchedFiles": - p := params.(*lsp.DidChangeWatchedFilesParams) + case *lsp.DidChangeWatchedFilesParams: // "workspace/didChangeWatchedFiles": err = handler.ino2cppDidChangeWatchedFilesParams(p) - case "workspace/executeCommand": - p := params.(*lsp.ExecuteCommandParams) + case *lsp.ExecuteCommandParams: // "workspace/executeCommand": err = handler.ino2cppExecuteCommand(p) } return @@ -473,12 +458,10 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document handler.synchronizer.DataMux.RLock() defer handler.synchronizer.DataMux.RUnlock() - switch method { - case "textDocument/completion": - r := result.(*lsp.CompletionList) + switch r := result.(type) { + case *lsp.CompletionList: // "textDocument/completion": handler.cpp2inoCompletionList(r, uri) - case "textDocument/codeAction": - r := result.(*[]*commandOrCodeAction) + case *[]*commandOrCodeAction: // "textDocument/codeAction": for index := range *r { command := (*r)[index].Command if command != nil { @@ -489,41 +472,35 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document handler.cpp2inoCodeAction(codeAction, uri) } } - case "textDocument/hover": - r := result.(*Hover) + case *Hover: // "textDocument/hover": if len(r.Contents.Value) == 0 { return nil } handler.cpp2inoHover(r, uri) - case "textDocument/definition": - fallthrough - case "textDocument/typeDefinition": - fallthrough - case "textDocument/implementation": - fallthrough - case "textDocument/references": - r := result.(*[]lsp.Location) + // case "textDocument/definition": + // fallthrough + // case "textDocument/typeDefinition": + // fallthrough + // case "textDocument/implementation": + // fallthrough + case *[]lsp.Location: // "textDocument/references": for index := range *r { handler.cpp2inoLocation(&(*r)[index]) } - case "textDocument/documentHighlight": - r := result.(*[]lsp.DocumentHighlight) + case *[]lsp.DocumentHighlight: // "textDocument/documentHighlight": for index := range *r { handler.cpp2inoDocumentHighlight(&(*r)[index], uri) } - case "textDocument/formatting": - fallthrough - case "textDocument/rangeFormatting": - fallthrough - case "textDocument/onTypeFormatting": - r := result.(*[]lsp.TextEdit) + // case "textDocument/formatting": + // fallthrough + // case "textDocument/rangeFormatting": + // fallthrough + case *[]lsp.TextEdit: // "textDocument/onTypeFormatting": for index := range *r { handler.cpp2inoTextEdit(&(*r)[index], uri) } - case "textDocument/documentSymbol": - r, ok := result.(*[]*documentSymbolOrSymbolInformation) - - if !ok || len(*r) == 0 { + case *[]*documentSymbolOrSymbolInformation: // "textDocument/documentSymbol": + if len(*r) == 0 { return result } @@ -544,11 +521,9 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document } return handler.cpp2inoSymbolInformation(symbols) } - case "textDocument/rename": - r := result.(*lsp.WorkspaceEdit) + case *lsp.WorkspaceEdit: // "textDocument/rename": return handler.cpp2inoWorkspaceEdit(r) - case "workspace/symbol": - r := result.(*[]lsp.SymbolInformation) + case *[]lsp.SymbolInformation: // "workspace/symbol": for index := range *r { handler.cpp2inoLocation(&(*r)[index].Location) } From ee6b06eaa1d73b8bd52f9dd112ff2907f91a69f6 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 19 Nov 2020 17:30:29 +0100 Subject: [PATCH 09/61] InoMapper now maps all .ino files in a project --- go.mod | 6 + go.sum | 27 ++- handler/handler.go | 59 +++--- handler/sourcemapper/ino.go | 240 ++++++++++++++--------- handler/sourcemapper/ino_test.go | 322 +++++++++++++++++++++---------- 5 files changed, 417 insertions(+), 237 deletions(-) diff --git a/go.mod b/go.mod index 562a57e..6550e0e 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,14 @@ module github.com/bcmi-labs/arduino-language-server go 1.12 require ( + github.com/arduino/go-paths-helper v1.3.2 github.com/arduino/go-properties-orderedmap v1.4.0 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.9.1 github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 + github.com/stretchr/testify v1.6.1 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index ee7e7db..1346ef7 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,37 @@ github.com/arduino/go-paths-helper v1.0.1 h1:utYXLM2RfFlc9qp/MJTIYp3t6ux/xM6mWjeEb/WLK4Q= github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.3.2 h1:5U9TSKQODiwSVgTxskC0PNl0l0Vf40GUlp99Zy2SK8w= +github.com/arduino/go-paths-helper v1.3.2/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1 h1:O1d7nVzpGmP5pGAZBSlp9TSpjNwwI0xThxhPd9oVJuU= -github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1/go.mod h1:tpps84QRlOVVLYk5QpKYX8Tr289D1v/UTWDLqeguiqM= github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d h1:afLbh+ltiygTOB37ymZVwKlJwWZn+86syPTbrrOAydY= github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d/go.mod h1:SULmZY7YNBsvNiQbrb/BEDdEJ84TGnfyUQxaHt8t8rY= -github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a h1:jTZwOlrDhmk4Ez2vhWh7kA0eKUahp1lCO2uyM4fi/Qk= -github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a/go.mod h1:eESpbCslcLDs8j2D7IEdGVgul7xuk9odqDTaor30IUU= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler/handler.go b/handler/handler.go index 7399333..6c80e76 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -210,30 +210,24 @@ func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method s return } -func (handler *InoHandler) createFileData(ctx context.Context, sourceURI lsp.DocumentURI, sourceText string, version int) (*FileData, []byte, error) { - sourcePath := uriToPath(sourceURI) +func (handler *InoHandler) createFileData(ctx context.Context, inoSourceURI lsp.DocumentURI, sourceText string, version int) (*FileData, []byte, error) { + log.Println("InoHandler: createFileData(", inoSourceURI, sourceText, version) + sourcePath := uriToPath(inoSourceURI) targetPath, targetBytes, err := generateCpp([]byte(sourceText), sourcePath, handler.config.SelectedBoard.Fqbn) if err != nil { err = handler.handleError(ctx, err) - if len(targetPath) == 0 { - return nil, nil, err - } - // Fallback: use the source text unchanged - targetBytes, err = copyIno2Cpp(sourceText, targetPath) - if err != nil { - return nil, nil, err - } + return nil, nil, err } targetURI := pathToURI(targetPath) data := &FileData{ sourceText, - sourceURI, + inoSourceURI, targetURI, sourcemapper.CreateInoMapper(bytes.NewReader(targetBytes)), version, } - handler.data[sourceURI] = data + handler.data[inoSourceURI] = data handler.data[targetURI] = data return data, targetBytes, nil } @@ -263,7 +257,7 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c // Fallback: try to apply a multi-line update data.sourceText = newSourceText data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text) - *rang = data.sourceMap.InoToCppRange(*rang) + *rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang) return nil } } @@ -282,7 +276,7 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c } data.sourceMap.Update(0, rang.Start.Line, change.Text) - *rang = data.sourceMap.InoToCppRange(*rang) + *rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang) } return nil } @@ -368,7 +362,7 @@ func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Contex func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - targetLine := data.sourceMap.InoToCppLine(params.Position.Line) + targetLine := data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) params.Position.Line = targetLine return nil } @@ -378,10 +372,10 @@ func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDoc func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Range = data.sourceMap.InoToCppRange(params.Range) + params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) for index := range params.Context.Diagnostics { r := ¶ms.Context.Diagnostics[index].Range - *r = data.sourceMap.InoToCppRange(*r) + *r = data.sourceMap.InoToCppLSPRange(data.sourceURI, *r) } return nil } @@ -391,7 +385,7 @@ func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.DocumentRangeFormattingParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Range = data.sourceMap.InoToCppRange(params.Range) + params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) return nil } return unknownURI(params.TextDocument.URI) @@ -400,7 +394,7 @@ func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.Docu func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.DocumentOnTypeFormattingParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Position.Line = data.sourceMap.InoToCppLine(params.Position.Line) + params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) return nil } return unknownURI(params.TextDocument.URI) @@ -409,7 +403,7 @@ func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.Doc func (handler *InoHandler) ino2cppRenameParams(params *lsp.RenameParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Position.Line = data.sourceMap.InoToCppLine(params.Position.Line) + params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) return nil } return unknownURI(params.TextDocument.URI) @@ -443,7 +437,7 @@ func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls for index := range edit { newValue[index] = lsp.TextEdit{ NewText: edit[index].NewText, - Range: data.sourceMap.InoToCppRange(edit[index].Range), + Range: data.sourceMap.InoToCppLSPRange(data.sourceURI, edit[index].Range), } } newEdit.Changes[string(data.targetURI)] = newValue @@ -537,7 +531,7 @@ func (handler *InoHandler) cpp2inoCompletionList(list *lsp.CompletionList, uri l for _, item := range list.Items { if !strings.HasPrefix(item.InsertText, "_") { if item.TextEdit != nil { - item.TextEdit.Range = data.sourceMap.CppToInoRange(item.TextEdit.Range) + _, item.TextEdit.Range = data.sourceMap.CppToInoRange(item.TextEdit.Range) } newItems = append(newItems, item) } @@ -550,7 +544,7 @@ func (handler *InoHandler) cpp2inoCodeAction(codeAction *CodeAction, uri lsp.Doc codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) if data, ok := handler.data[uri]; ok { for index := range codeAction.Diagnostics { - codeAction.Diagnostics[index].Range = data.sourceMap.CppToInoRange(codeAction.Diagnostics[index].Range) + _, codeAction.Diagnostics[index].Range = data.sourceMap.CppToInoRange(codeAction.Diagnostics[index].Range) } } } @@ -570,9 +564,10 @@ func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { newValue := make([]lsp.TextEdit, len(edit)) for index := range edit { + _, newRange := data.sourceMap.CppToInoRange(edit[index].Range) newValue[index] = lsp.TextEdit{ NewText: edit[index].NewText, - Range: data.sourceMap.CppToInoRange(edit[index].Range), + Range: newRange, } } newEdit.Changes[string(data.sourceURI)] = newValue @@ -587,7 +582,7 @@ func (handler *InoHandler) cpp2inoHover(hover *Hover, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { r := hover.Range if r != nil { - *r = data.sourceMap.CppToInoRange(*r) + _, *r = data.sourceMap.CppToInoRange(*r) } } } @@ -595,19 +590,19 @@ func (handler *InoHandler) cpp2inoHover(hover *Hover, uri lsp.DocumentURI) { func (handler *InoHandler) cpp2inoLocation(location *lsp.Location) { if data, ok := handler.data[location.URI]; ok { location.URI = data.sourceURI - location.Range = data.sourceMap.CppToInoRange(location.Range) + _, location.Range = data.sourceMap.CppToInoRange(location.Range) } } func (handler *InoHandler) cpp2inoDocumentHighlight(highlight *lsp.DocumentHighlight, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { - highlight.Range = data.sourceMap.CppToInoRange(highlight.Range) + _, highlight.Range = data.sourceMap.CppToInoRange(highlight.Range) } } func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { - edit.Range = data.sourceMap.CppToInoRange(edit.Range) + _, edit.Range = data.sourceMap.CppToInoRange(edit.Range) } } @@ -620,7 +615,7 @@ func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, symbolIdx := make(map[string]*DocumentSymbol) for i := 0; i < len(origSymbols); i++ { symbol := &origSymbols[i] - symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) + _, symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) duplicate := false other, duplicate := symbolIdx[symbol.Name] @@ -633,7 +628,7 @@ func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, } } - symbol.SelectionRange = data.sourceMap.CppToInoRange(symbol.SelectionRange) + _, symbol.SelectionRange = data.sourceMap.CppToInoRange(symbol.SelectionRange) symbol.Children = handler.cpp2inoDocumentSymbols(symbol.Children, uri) symbolIdx[symbol.Name] = symbol } @@ -722,9 +717,9 @@ func (handler *InoHandler) cpp2inoPublishDiagnosticsParams(params *lsp.PublishDi newDiagnostics := make([]lsp.Diagnostic, 0, len(params.Diagnostics)) for index := range params.Diagnostics { r := ¶ms.Diagnostics[index].Range - if startLine, ok := data.sourceMap.CppToInoLineOk(r.Start.Line); ok { + if _, startLine, ok := data.sourceMap.CppToInoLineOk(r.Start.Line); ok { r.Start.Line = startLine - r.End.Line = data.sourceMap.CppToInoLine(r.End.Line) + _, r.End.Line = data.sourceMap.CppToInoLine(r.End.Line) newDiagnostics = append(newDiagnostics, params.Diagnostics[index]) } } diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 8294a9b..78bda14 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -3,6 +3,10 @@ package sourcemapper import ( "bufio" "io" + "net/url" + "path/filepath" + "regexp" + "runtime" "strconv" "strings" @@ -11,136 +15,163 @@ import ( // InoMapper is a mapping between the .ino sketch and the preprocessed .cpp file type InoMapper struct { - toCpp map[int]int - toIno map[int]int + InoText map[lsp.DocumentURI]*SourceRevision + CppText *SourceRevision + CppFile lsp.DocumentURI + toCpp map[InoLine]int // Converts File.ino:line -> line + toIno map[int]InoLine // Convers line -> File.ino:line +} + +type SourceRevision struct { + Version int + Text string +} + +// InoLine is a line number into an .ino file +type InoLine struct { + File string + Line int } // InoToCppLine converts a source (.ino) line into a target (.cpp) line -func (s *InoMapper) InoToCppLine(sourceLine int) int { - return s.toCpp[sourceLine] +func (s *InoMapper) InoToCppLine(sourceURI lsp.DocumentURI, line int) int { + file := uriToPath(sourceURI) + return s.toCpp[InoLine{file, line}] } -// InoToCppRange converts a source (.ino) lsp.Range into a target (.cpp) lsp.Range -func (s *InoMapper) InoToCppRange(r lsp.Range) lsp.Range { - r.Start.Line = s.InoToCppLine(r.Start.Line) - r.End.Line = s.InoToCppLine(r.End.Line) - return r +func (s *InoMapper) InoToCppLSPRange(sourceURI lsp.DocumentURI, r lsp.Range) lsp.Range { + res := r + res.Start.Line = s.InoToCppLine(sourceURI, r.Start.Line) + res.End.Line = s.InoToCppLine(sourceURI, r.End.Line) + return res } -// CppToInoLine converts a target (.cpp) line into a source (.ino) line -func (s *InoMapper) CppToInoLine(targetLine int) int { - return s.toIno[targetLine] +// CppToInoLine converts a target (.cpp) line into a source.ino:line +func (s *InoMapper) CppToInoLine(targetLine int) (string, int) { + res := s.toIno[targetLine] + return res.File, res.Line } -// CppToInoRange converts a target (.cpp) lsp.Range into a source (.ino) lsp.Range -func (s *InoMapper) CppToInoRange(r lsp.Range) lsp.Range { - r.Start.Line = s.CppToInoLine(r.Start.Line) - r.End.Line = s.CppToInoLine(r.End.Line) - return r +// CppToInoRange converts a target (.cpp) lsp.Range into a source.ino:lsp.Range +func (s *InoMapper) CppToInoRange(r lsp.Range) (string, lsp.Range) { + startFile, startLine := s.CppToInoLine(r.Start.Line) + endFile, endLine := s.CppToInoLine(r.End.Line) + res := r + res.Start.Line = startLine + res.End.Line = endLine + if startFile != endFile { + panic("invalid range conversion") + } + return startFile, res } // CppToInoLineOk converts a target (.cpp) line into a source (.ino) line and // returns true if the conversion is successful -func (s *InoMapper) CppToInoLineOk(targetLine int) (int, bool) { +func (s *InoMapper) CppToInoLineOk(targetLine int) (string, int, bool) { res, ok := s.toIno[targetLine] - return res, ok + return res.File, res.Line, ok } // CreateInoMapper create a InoMapper from the given target file func CreateInoMapper(targetFile io.Reader) *InoMapper { + mapper := &InoMapper{ + toCpp: map[InoLine]int{}, + toIno: map[int]InoLine{}, + } + + sourceFile := "" sourceLine := -1 targetLine := 0 - sourceLineMap := make(map[int]int) - targetLineMap := make(map[int]int) scanner := bufio.NewScanner(targetFile) for scanner.Scan() { lineStr := scanner.Text() if strings.HasPrefix(lineStr, "#line") { - nrEnd := strings.Index(lineStr[6:], " ") - var l int - var err error - if nrEnd > 0 { - l, err = strconv.Atoi(lineStr[6 : nrEnd+6]) - } else { - l, err = strconv.Atoi(lineStr[6:]) - } + tokens := strings.SplitN(lineStr, " ", 3) + l, err := strconv.Atoi(tokens[1]) if err == nil && l > 0 { sourceLine = l - 1 } - } else if sourceLine >= 0 { - sourceLineMap[targetLine] = sourceLine - targetLineMap[sourceLine] = targetLine + sourceFile = unquoteCppString(tokens[2]) + } else if sourceFile != "" { + mapper.toCpp[InoLine{sourceFile, sourceLine}] = targetLine + mapper.toIno[targetLine] = InoLine{sourceFile, sourceLine} sourceLine++ } targetLine++ } - sourceLineMap[targetLine] = sourceLine - targetLineMap[sourceLine] = targetLine - return &InoMapper{ - toIno: sourceLineMap, - toCpp: targetLineMap, + mapper.toCpp[InoLine{sourceFile, sourceLine}] = targetLine + mapper.toIno[targetLine] = InoLine{sourceFile, sourceLine} + return mapper +} + +func unquoteCppString(str string) string { + if len(str) >= 2 && strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) { + str = strings.TrimSuffix(str, `"`)[1:] } + str = strings.Replace(str, "\\\"", "\"", -1) + str = strings.Replace(str, "\\\\", "\\", -1) + return str } // Update performs an update to the SourceMap considering the deleted lines, the // insertion line and the inserted text func (s *InoMapper) Update(deletedLines, insertLine int, insertText string) { - for i := 1; i <= deletedLines; i++ { - sourceLine := insertLine + 1 - targetLine := s.toCpp[sourceLine] - - // Shift up all following lines by one and put them into a new map - newMappings := make(map[int]int) - maxSourceLine, maxTargetLine := 0, 0 - for t, s := range s.toIno { - if t > targetLine && s > sourceLine { - newMappings[t-1] = s - 1 - } else if s > sourceLine { - newMappings[t] = s - 1 - } else if t > targetLine { - newMappings[t-1] = s - } - if s > maxSourceLine { - maxSourceLine = s - } - if t > maxTargetLine { - maxTargetLine = t - } - } - - // Remove mappings for the deleted line - delete(s.toIno, maxTargetLine) - delete(s.toCpp, maxSourceLine) - - // Copy the mappings from the intermediate map - copyMappings(s.toIno, s.toCpp, newMappings) - } - - addedLines := strings.Count(insertText, "\n") - if addedLines > 0 { - targetLine := s.toCpp[insertLine] - - // Shift down all following lines and put them into a new map - newMappings := make(map[int]int) - for t, s := range s.toIno { - if t > targetLine && s > insertLine { - newMappings[t+addedLines] = s + addedLines - } else if s > insertLine { - newMappings[t] = s + addedLines - } else if t > targetLine { - newMappings[t+addedLines] = s - } - } - - // Add mappings for the added lines - for i := 1; i <= addedLines; i++ { - s.toIno[targetLine+i] = insertLine + i - s.toCpp[insertLine+i] = targetLine + i - } - - // Copy the mappings from the intermediate map - copyMappings(s.toIno, s.toCpp, newMappings) - } + // for i := 1; i <= deletedLines; i++ { + // sourceLine := insertLine + 1 + // targetLine := s.toCpp[sourceLine] + + // // Shift up all following lines by one and put them into a new map + // newMappings := make(map[int]int) + // maxSourceLine, maxTargetLine := 0, 0 + // for t, s := range s.toIno { + // if t > targetLine && s > sourceLine { + // newMappings[t-1] = s - 1 + // } else if s > sourceLine { + // newMappings[t] = s - 1 + // } else if t > targetLine { + // newMappings[t-1] = s + // } + // if s > maxSourceLine { + // maxSourceLine = s + // } + // if t > maxTargetLine { + // maxTargetLine = t + // } + // } + + // // Remove mappings for the deleted line + // delete(s.toIno, maxTargetLine) + // delete(s.toCpp, maxSourceLine) + + // // Copy the mappings from the intermediate map + // copyMappings(s.toIno, s.toCpp, newMappings) + // } + + // addedLines := strings.Count(insertText, "\n") + // if addedLines > 0 { + // targetLine := s.toCpp[insertLine] + + // // Shift down all following lines and put them into a new map + // newMappings := make(map[int]int) + // for t, s := range s.toIno { + // if t > targetLine && s > insertLine { + // newMappings[t+addedLines] = s + addedLines + // } else if s > insertLine { + // newMappings[t] = s + addedLines + // } else if t > targetLine { + // newMappings[t+addedLines] = s + // } + // } + + // // Add mappings for the added lines + // for i := 1; i <= addedLines; i++ { + // s.toIno[targetLine+i] = insertLine + i + // s.toCpp[insertLine+i] = targetLine + i + // } + + // // Copy the mappings from the intermediate map + // copyMappings(s.toIno, s.toCpp, newMappings) + // } } func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) { @@ -155,3 +186,26 @@ func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) { } } } + +var expDriveID = regexp.MustCompile("[a-zA-Z]:") + +func uriToPath(uri lsp.DocumentURI) string { + urlObj, err := url.Parse(string(uri)) + if err != nil { + return string(uri) + } + path := "" + segments := strings.Split(urlObj.Path, "/") + for _, segment := range segments { + decoded, err := url.PathUnescape(segment) + if err != nil { + decoded = segment + } + if runtime.GOOS == "windows" && expDriveID.MatchString(decoded) { + path += strings.ToUpper(decoded) + } else if len(decoded) > 0 { + path += string(filepath.Separator) + decoded + } + } + return path +} diff --git a/handler/sourcemapper/ino_test.go b/handler/sourcemapper/ino_test.go index 0fdede0..021e347 100644 --- a/handler/sourcemapper/ino_test.go +++ b/handler/sourcemapper/ino_test.go @@ -1,9 +1,10 @@ package sourcemapper import ( - "reflect" "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestCreateSourceMaps(t *testing.T) { @@ -27,111 +28,224 @@ void loop() { } ` sourceMap := CreateInoMapper(strings.NewReader(input)) - if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ - 3: 0, - 5: 1, - 7: 6, - 9: 1, - 10: 2, - 11: 3, - 12: 4, - 13: 5, - 14: 6, - 15: 7, - 16: 8, - 17: 9, - 18: 10, - }) { - t.Error(sourceMap.toIno) - } - if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ - 0: 3, - 1: 9, - 2: 10, - 3: 11, - 4: 12, - 5: 13, - 6: 14, - 7: 15, - 8: 16, - 9: 17, - 10: 18}, - ) { - t.Error(sourceMap.toCpp) - } + require.EqualValues(t, map[InoLine]int{ + {"sketch_july2a.ino", 0}: 3, + {"sketch_july2a.ino", 1}: 9, + {"sketch_july2a.ino", 2}: 10, + {"sketch_july2a.ino", 3}: 11, + {"sketch_july2a.ino", 4}: 12, + {"sketch_july2a.ino", 5}: 13, + {"sketch_july2a.ino", 6}: 14, + {"sketch_july2a.ino", 7}: 15, + {"sketch_july2a.ino", 8}: 16, + {"sketch_july2a.ino", 9}: 17, + {"sketch_july2a.ino", 10}: 18, + }, sourceMap.toCpp) + require.EqualValues(t, map[int]InoLine{ + 3: {"sketch_july2a.ino", 0}, + 5: {"sketch_july2a.ino", 1}, + 7: {"sketch_july2a.ino", 6}, + 9: {"sketch_july2a.ino", 1}, + 10: {"sketch_july2a.ino", 2}, + 11: {"sketch_july2a.ino", 3}, + 12: {"sketch_july2a.ino", 4}, + 13: {"sketch_july2a.ino", 5}, + 14: {"sketch_july2a.ino", 6}, + 15: {"sketch_july2a.ino", 7}, + 16: {"sketch_july2a.ino", 8}, + 17: {"sketch_july2a.ino", 9}, + 18: {"sketch_july2a.ino", 10}, + }, sourceMap.toIno) +} + +func TestCreateMultifileSourceMap(t *testing.T) { + input := `#include +#line 1 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino" +#include +#include + +#line 4 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino" +void setup(); +#line 9 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino" +void loop(); +#line 23 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino" +void vino(); +#line 2 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino" +void secondFunction(); +#line 4 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino" +void setup() { + // put your setup code here, to run once: + digitalWrite(10, 20); } -func TestUpdateSourceMaps1(t *testing.T) { - sourceMap := &InoMapper{ - toCpp: map[int]int{ - 0: 1, - 1: 2, - 2: 0, - 3: 5, - 4: 3, - 5: 4, - }, - toIno: make(map[int]int), - } - for s, t := range sourceMap.toCpp { - sourceMap.toIno[t] = s - } - sourceMap.Update(0, 1, "foo\nbar\nbaz") - if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ - 0: 1, - 1: 2, - 2: 3, - 3: 4, - 4: 0, - 5: 7, - 6: 5, - 7: 6}, - ) { - t.Error(sourceMap.toCpp) - } - if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ - 0: 4, - 1: 0, - 2: 1, - 3: 2, - 4: 3, - 5: 6, - 6: 7, - 7: 5}, - ) { - t.Error(sourceMap.toIno) - } +void loop() { + // put your main code here, to run repeatedly: + long pippo = Serial.available(); + pippo++; + Serial1.write(pippo); + SPI.begin(); + int ciao = millis(); + Serial.println(ciao, HEX); + if (ciao > 10) { + SerialUSB.println(); + } + Serial.println(); +} + +void vino() { } -func TestUpdateSourceMaps2(t *testing.T) { - sourceMap := &InoMapper{ - toCpp: map[int]int{ - 0: 1, - 1: 2, - 2: 0, - 3: 5, - 4: 3, - 5: 4}, - toIno: make(map[int]int), - } - for s, t := range sourceMap.toCpp { - sourceMap.toIno[t] = s - } - sourceMap.Update(2, 1, "foo") - if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ - 0: 0, - 1: 1, - 2: 2, - 3: 3}, - ) { - t.Error(sourceMap.toCpp) - } - if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ - 0: 0, - 1: 1, - 2: 2, - 3: 3}, - ) { - t.Error(sourceMap.toIno) - } +#line 1 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino" + +void secondFunction() { + +}` + sourceMap := CreateInoMapper(strings.NewReader(input)) + require.EqualValues(t, sourceMap.toCpp, map[InoLine]int{ + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 0}: 2, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 1}: 3, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 2}: 4, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}: 6, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}: 8, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}: 10, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}: 12, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}: 14, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 4}: 15, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 5}: 16, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 6}: 17, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 7}: 18, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}: 19, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 9}: 20, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 10}: 21, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 11}: 22, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 12}: 23, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 13}: 24, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 14}: 25, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 15}: 26, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 16}: 27, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 17}: 28, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 18}: 29, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 19}: 30, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 20}: 31, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 21}: 32, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}: 33, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 23}: 34, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 24}: 35, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 0}: 37, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}: 38, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 2}: 39, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 3}: 40, + {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 4}: 41, + }) + require.EqualValues(t, sourceMap.toIno, map[int]InoLine{ + 2: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 0}, + 3: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 1}, + 4: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 2}, + 6: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, + 8: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, + 10: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, + 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, + 14: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, + 15: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 4}, + 16: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 5}, + 17: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 6}, + 18: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 7}, + 19: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, + 20: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 9}, + 21: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 10}, + 22: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 11}, + 23: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 12}, + 24: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 13}, + 25: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 14}, + 26: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 15}, + 27: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 16}, + 28: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 17}, + 29: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 18}, + 30: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 19}, + 31: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 20}, + 32: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 21}, + 33: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, + 34: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 23}, + 35: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 24}, + 37: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 0}, + 38: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, + 39: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 2}, + 40: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 3}, + 41: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 4}, + }) } + +// func TestUpdateSourceMaps1(t *testing.T) { +// sourceMap := &InoMapper{ +// toCpp: map[int]int{ +// 0: 1, +// 1: 2, +// 2: 0, +// 3: 5, +// 4: 3, +// 5: 4, +// }, +// toIno: make(map[int]int), +// } +// for s, t := range sourceMap.toCpp { +// sourceMap.toIno[t] = s +// } +// sourceMap.Update(0, 1, "foo\nbar\nbaz") +// if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ +// 0: 1, +// 1: 2, +// 2: 3, +// 3: 4, +// 4: 0, +// 5: 7, +// 6: 5, +// 7: 6}, +// ) { +// t.Error(sourceMap.toCpp) +// } +// if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ +// 0: 4, +// 1: 0, +// 2: 1, +// 3: 2, +// 4: 3, +// 5: 6, +// 6: 7, +// 7: 5}, +// ) { +// t.Error(sourceMap.toIno) +// } +// } + +// func TestUpdateSourceMaps2(t *testing.T) { +// sourceMap := &InoMapper{ +// toCpp: map[int]int{ +// 0: 1, +// 1: 2, +// 2: 0, +// 3: 5, +// 4: 3, +// 5: 4}, +// toIno: make(map[int]int), +// } +// for s, t := range sourceMap.toCpp { +// sourceMap.toIno[t] = s +// } +// sourceMap.Update(2, 1, "foo") +// if !reflect.DeepEqual(sourceMap.toCpp, map[int]int{ +// 0: 0, +// 1: 1, +// 2: 2, +// 3: 3}, +// ) { +// t.Error(sourceMap.toCpp) +// } +// if !reflect.DeepEqual(sourceMap.toIno, map[int]int{ +// 0: 0, +// 1: 1, +// 2: 2, +// 3: 3}, +// ) { +// t.Error(sourceMap.toIno) +// } +// } From a01cf523bbd54db0f2e974bc1259ce0625d4334f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 20 Nov 2020 14:24:26 +0100 Subject: [PATCH 10/61] slightly refactored syncer --- handler/syncer.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/handler/syncer.go b/handler/syncer.go index 2cc1b98..198b503 100644 --- a/handler/syncer.go +++ b/handler/syncer.go @@ -27,26 +27,22 @@ type AsyncHandler struct { func (ah AsyncHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { needsWriteLock := req.Method == "textDocument/didOpen" || req.Method == "textDocument/didChange" if needsWriteLock { - ah.synchronizer.FileMux.Lock() - if enableLogging { - log.Println("Message processing locked for", req.Method) - } - go ah.runWrite(ctx, conn, req) + go func() { + ah.synchronizer.FileMux.Lock() + defer ah.synchronizer.FileMux.Unlock() + if enableLogging { + log.Println("Message processing locked for", req.Method) + } + ah.handler.Handle(ctx, conn, req) + if enableLogging { + log.Println("Message processing unlocked for", req.Method) + } + }() } else { - ah.synchronizer.FileMux.RLock() - go ah.runRead(ctx, conn, req) - } -} - -func (ah AsyncHandler) runRead(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { - defer ah.synchronizer.FileMux.RUnlock() - ah.handler.Handle(ctx, conn, req) -} - -func (ah AsyncHandler) runWrite(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { - defer ah.synchronizer.FileMux.Unlock() - ah.handler.Handle(ctx, conn, req) - if enableLogging { - log.Println("Message processing unlocked for", req.Method) + go func() { + ah.synchronizer.FileMux.RLock() + ah.handler.Handle(ctx, conn, req) + ah.synchronizer.FileMux.RUnlock() + }() } } From a9580aaa90b87b77123119b7dee696b621c707fe Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 23 Nov 2020 15:10:11 +0100 Subject: [PATCH 11/61] Big refactoring in LSP handling This is actually much more like a rewriting of the engine than a refactoring. Really too much stuff going on to keep track of them cleanly, a (possibly) short recap may be: - before starting clangd we make a round of arduino-preprocessing - using the new arduino-cli --only-compilation-database flag to generate both compiler flags, preprocessed sketch, etc. - multiple .ino files are now mapped into a single .cpp file - added support for clangd -query-driver flag to query cross-compiler native libraries and includes directories - using type-switch to handle messages in-out from IDE For now the LSP command partially implemented are: 'initialize' 'textDocument/didOpen' --- go.mod | 3 + go.sum | 289 ++++++++++++++++++++++++++++++++++++++++++ handler/builder.go | 229 +++++++++++---------------------- handler/handler.go | 308 ++++++++++++++++++++++++++++++--------------- handler/uri.go | 5 + main.go | 70 ++--------- streams/dumper.go | 26 ++++ 7 files changed, 616 insertions(+), 314 deletions(-) diff --git a/go.mod b/go.mod index 6550e0e..15b1442 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/bcmi-labs/arduino-language-server go 1.12 +replace github.com/arduino/arduino-cli => ../arduino-cli + require ( + github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb github.com/arduino/go-paths-helper v1.3.2 github.com/arduino/go-properties-orderedmap v1.4.0 github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 1346ef7..af5c49b 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,326 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb h1:ZSECb5dRyxpQSiFEuhAXn+3oxeCUBc/PJU3tJenY9Ik= +github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb/go.mod h1:PpHwX4OKp/PFumezvkSRixh5N9uLiCASm3gqK/Da5is= +github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8= github.com/arduino/go-paths-helper v1.0.1 h1:utYXLM2RfFlc9qp/MJTIYp3t6ux/xM6mWjeEb/WLK4Q= github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-paths-helper v1.3.2 h1:5U9TSKQODiwSVgTxskC0PNl0l0Vf40GUlp99Zy2SK8w= github.com/arduino/go-paths-helper v1.3.2/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= +github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cmaglie/go.rice v1.0.3/go.mod h1:AF3bOWkvdOpp8/S3UL8qbQ4N7DiISIbJtj54GWFPAsc= +github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI= +github.com/codeclysm/cc v1.2.2/go.mod h1:XtW4ArCNgQwFphcRGG9+sPX5WM1J6/u0gMy5ZdV3obA= +github.com/codeclysm/extract/v3 v3.0.2 h1:sB4LcE3Php7LkhZwN0n2p8GCwZe92PEQutdbGURf5xc= +github.com/codeclysm/extract/v3 v3.0.2/go.mod h1:NKsw+hqua9H+Rlwy/w/3Qgt9jDonYEgB6wJu+25eOKw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/facchinm/gohex v0.0.0-20201008150446-be2a6be25790/go.mod h1:sErAiirjQXs3P13DBW7oPmJ6Q0WsFjYXVAhz7Xt59UQ= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg= +github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/h2non/filetype v1.0.6/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= +github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14= +github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/imjasonmiller/godice v0.1.2/go.mod h1:8cTkdnVI+NglU2d6sv+ilYcNaJ5VSTBwvMbFULJd/QQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= +github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/retry v0.0.0-20160928201858-1998d01ba1c3/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= +github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbNBraRLZEhoQwFLMrjK8PSlO4D3nDjKYXo= +github.com/juju/utils v0.0.0-20180808125547-9dfc6dbfb02b/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= +github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leonelquinteros/gotext v1.4.0/go.mod h1:yZGXREmoGTtBvZHNcc+Yfug49G/2spuF/i/Qlsvz1Us= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/genetlink v0.0.0-20190313224034-60417448a851/go.mod h1:EsbsAEUEs15qC1cosAwxgCWV0Qhd8TmkxnA9Kw1Vhl4= +github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/taskstats v0.0.0-20190313225729-7cbba52ee072/go.mod h1:sGdS7A6CAETR53zkdjGkgoFlh1vSm7MtX+i8XfEsTMA= +github.com/miekg/dns v1.0.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228/go.mod h1:MGuVJ1+5TX1SCoO2Sx0eAnjpdRytYla2uC1YIZfkC9c= +github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583 h1:ogHi8YLNeIxABOaH6UgtbwkODheuAK+ErP8gWXYQVj0= +github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583/go.mod h1:sFPiU/UgDcsQVu3vkqpZLCXWFwUoQRpHGu9ATihPAl0= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= +github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= +github.com/segmentio/stats/v4 v4.5.3/go.mod h1:LsaahUJR7iiSs8mnkvQvdQ/RLHAS5adGLxuntg0ydGo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d h1:afLbh+ltiygTOB37ymZVwKlJwWZn+86syPTbrrOAydY= github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d/go.mod h1:SULmZY7YNBsvNiQbrb/BEDdEJ84TGnfyUQxaHt8t8rY= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.1-0.20200710201246-675ae5f5a98c/go.mod h1:aeNIJzz/GSSVlS+gpCpQWZ83BKbsoW57mr90+YthtkQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.bug.st/cleanup v1.0.0 h1:XVj1HZxkBXeq3gMT7ijWUpHyIC1j8XAoNSyQ06CskgA= +go.bug.st/cleanup v1.0.0/go.mod h1:EqVmTg2IBk4znLbPD28xne3abjsJftMdqqJEjhn70bk= +go.bug.st/downloader/v2 v2.1.0 h1:VqGOrJrjgz8c0c8ExvF9dvvcpcrbo2IrI+rOoXKD6nQ= +go.bug.st/downloader/v2 v2.1.0/go.mod h1:VZW2V1iGKV8rJL2ZEGIDzzBeKowYv34AedJz13RzVII= +go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18 h1:F1qxtaFuewctYc/SsHRn+Q7Dtwi+yJGPgVq8YLtQz98= +go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18/go.mod h1:Cx1VqMtEhE9pIkEyUj3LVVVPkv89dgW8aCKrRPDR/uE= +go.bug.st/serial v1.1.1/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY= +go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/handler/builder.go b/handler/builder.go index e182f9a..92181f2 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -3,6 +3,7 @@ package handler import ( "bufio" "bytes" + "encoding/json" "io/ioutil" "log" "os" @@ -10,181 +11,95 @@ import ( "path/filepath" "strings" + "github.com/arduino/arduino-cli/arduino/libraries" + "github.com/arduino/arduino-cli/executils" + "github.com/arduino/go-paths-helper" "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" ) -func generateCpp(inoCode []byte, sourcePath, fqbn string) (cppPath string, cppCode []byte, err error) { - // The CLI expects the `theSketchName.ino` file to be in `some/path/theSketchName` folder. - // Expected folder structure: `/path/to/temp/ino2cpp-${random}/theSketchName/theSketchName.ino`. - rawRootTempDir, err := ioutil.TempDir("", "ino2cpp-") - if err != nil { - err = errors.Wrap(err, "Error while creating temporary directory.") - return - } - rootTempDir, err := filepath.EvalSymlinks(rawRootTempDir) - if err != nil { - err = errors.Wrap(err, "Error while resolving symbolic links of temporary directory.") - return - } - - sketchName := filepath.Base(sourcePath) - if strings.HasSuffix(sketchName, ".ino") { - sketchName = sketchName[:len(sketchName)-len(".ino")] - } - sketchTempPath := filepath.Join(rootTempDir, sketchName) - createDirIfNotExist(sketchTempPath) - - // Write source file to temp dir - sketchFileName := sketchName + ".ino" - inoPath := filepath.Join(sketchTempPath, sketchFileName) - err = ioutil.WriteFile(inoPath, inoCode, 0600) - if err != nil { - err = errors.Wrap(err, "Error while writing source file to temporary directory.") - return - } - if enableLogging { - log.Println("Source file written to", inoPath) - } - - // Copy all header files to temp dir - err = copyHeaderFiles(filepath.Dir(sourcePath), rootTempDir) - if err != nil { - return - } - - // Generate compile_flags.txt - cppPath = filepath.Join(sketchTempPath, sketchFileName+".cpp") - flagsPath, err := generateCompileFlags(sketchTempPath, inoPath, sourcePath, fqbn) - if err != nil { - return - } - if enableLogging { - log.Println("Compile flags written to", flagsPath) - } - - // Generate target file - cppCode, err = generateTargetFile(sketchTempPath, inoPath, cppPath, fqbn) - return -} - -func createDirIfNotExist(dir string) { - if _, err := os.Stat(dir); os.IsNotExist(err) { - err = os.MkdirAll(dir, os.ModePerm) - if err != nil { - panic(err) - } - } -} - -func copyHeaderFiles(sourceDir string, destDir string) error { - fileInfos, err := ioutil.ReadDir(sourceDir) - if err != nil { - return err - } - for _, fileInfo := range fileInfos { - if !fileInfo.IsDir() && strings.HasSuffix(fileInfo.Name(), ".h") { - input, err := ioutil.ReadFile(filepath.Join(sourceDir, fileInfo.Name())) - if err != nil { - return err - } - - err = ioutil.WriteFile(filepath.Join(destDir, fileInfo.Name()), input, 0644) - if err != nil { - return err - } - } - } - return nil -} +// func generateCpp(sourcePath, fqbn string) (*paths.Path, []byte, error) { +// // Generate target file +// if cppPath, err := generateBuildEnvironment(paths.New(sourcePath), fqbn); err != nil { +// return nil, nil, err +// } else if cppCode, err := cppPath.ReadFile(); err != nil { +// return nil, nil, err +// } else { +// return cppPath, cppCode, err +// } +// } func updateCpp(inoCode []byte, sourcePath, fqbn string, fqbnChanged bool, cppPath string) (cppCode []byte, err error) { - tempDir := filepath.Dir(cppPath) - inoPath := strings.TrimSuffix(cppPath, ".cpp") - if inoCode != nil { - // Write source file to temp dir - err = ioutil.WriteFile(inoPath, inoCode, 0600) - if err != nil { - err = errors.Wrap(err, "Error while writing source file to temporary directory.") - return - } - if enableLogging { - log.Println("Source file written to", inoPath) - } - } + // tempDir := filepath.Dir(cppPath) + // inoPath := strings.TrimSuffix(cppPath, ".cpp") + // if inoCode != nil { + // // Write source file to temp dir + // err = ioutil.WriteFile(inoPath, inoCode, 0600) + // if err != nil { + // err = errors.Wrap(err, "Error while writing source file to temporary directory.") + // return + // } + // if enableLogging { + // log.Println("Source file written to", inoPath) + // } + // } - if fqbnChanged { - // Generate compile_flags.txt - var flagsPath string - flagsPath, err = generateCompileFlags(tempDir, inoPath, sourcePath, fqbn) - if err != nil { - return - } - if enableLogging { - log.Println("Compile flags written to", flagsPath) - } - } + // if fqbnChanged { + // // Generate compile_flags.txt + // var flagsPath string + // flagsPath, err = generateCompileFlags(tempDir, inoPath, sourcePath, fqbn) + // if err != nil { + // return + // } + // if enableLogging { + // log.Println("Compile flags written to", flagsPath) + // } + // } - // Generate target file - cppCode, err = generateTargetFile(tempDir, inoPath, cppPath, fqbn) + // // Generate target file + // cppCode, err = generateTargetFile(tempDir, inoPath, cppPath, fqbn) return } -func generateCompileFlags(tempDir, inoPath, sourcePath, fqbn string) (string, error) { - var cliArgs []string - if len(fqbn) > 0 { - cliArgs = []string{"compile", "--fqbn", fqbn, "--show-properties", inoPath} - } else { - cliArgs = []string{"compile", "--show-properties", inoPath} - } - propertiesCmd := exec.Command(globalCliPath, cliArgs...) - output, err := propertiesCmd.Output() - if err != nil { - err = logCommandErr(propertiesCmd, output, err, errMsgFilter(tempDir)) - return "", err - } - buildProps, err := properties.LoadFromBytes(output) +func generateBuildEnvironment(sketchDir *paths.Path, fqbn string) (*paths.Path, error) { + // XXX: do this from IDE or via gRPC + args := []string{globalCliPath, + "compile", + "--fqbn", fqbn, + "--only-compilation-database", + "--clean", + "--format", "json", + sketchDir.String(), + } + cmd, err := executils.NewProcess(args...) if err != nil { - return "", errors.Wrap(err, "Error while reading build properties.") + return nil, errors.Errorf("running %s: %s", strings.Join(args, " "), err) } - flagsPath := filepath.Join(tempDir, "compile_flags.txt") - outFile, err := os.OpenFile(flagsPath, os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - return flagsPath, errors.Wrap(err, "Error while creating output file for compile flags.") + cmdOutput := &bytes.Buffer{} + cmd.RedirectStdoutTo(cmdOutput) + cmd.SetDirFromPath(sketchDir) + log.Println("running: ", strings.Join(args, " ")) + if err := cmd.Run(); err != nil { + return nil, errors.Errorf("running %s: %s", strings.Join(args, " "), err) } - defer outFile.Close() - printer := Printer{Writer: bufio.NewWriter(outFile)} - printCompileFlags(buildProps, &printer, fqbn) - printLibraryPaths(sourcePath, &printer) - printer.Flush() - return flagsPath, printer.Err -} - -func generateTargetFile(tempDir, inoPath, cppPath, fqbn string) (cppCode []byte, err error) { - var cliArgs []string - if len(fqbn) > 0 { - cliArgs = []string{"compile", "--fqbn", fqbn, "--preprocess", inoPath} - } else { - cliArgs = []string{"compile", "--preprocess", inoPath} + type cmdBuilderRes struct { + BuildPath *paths.Path `json:"build_path"` + UsedLibraries []*libraries.Library } - preprocessCmd := exec.Command(globalCliPath, cliArgs...) - cppCode, err = preprocessCmd.Output() - if err != nil { - err = logCommandErr(preprocessCmd, cppCode, err, errMsgFilter(tempDir)) - return + type cmdRes struct { + CompilerOut string `json:"compiler_out"` + CompilerErr string `json:"compiler_err"` + BuilderResult cmdBuilderRes `json:"builder_result"` } - - // Filter lines beginning with ERROR or WARNING - cppCode = []byte(filterErrorsAndWarnings(cppCode)) - - err = ioutil.WriteFile(cppPath, cppCode, 0600) - if err != nil { - err = errors.Wrap(err, "Error while writing target file to temporary directory.") - } else if enableLogging { - log.Println("Target file written to", cppPath) + var res cmdRes + if err := json.Unmarshal(cmdOutput.Bytes(), &res); err != nil { + return nil, errors.Errorf("parsing arduino-cli output: %s", err) } - return + + // Return only the build path + log.Println("arduino-cli output:", cmdOutput) + return res.BuilderResult.BuildPath, nil } func filterErrorsAndWarnings(cppCode []byte) string { diff --git a/handler/handler.go b/handler/handler.go index 6c80e76..a2c7990 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -12,20 +12,26 @@ import ( "strings" "time" + "github.com/arduino/arduino-cli/arduino/builder" + "github.com/arduino/arduino-cli/executils" + "github.com/arduino/go-paths-helper" "github.com/bcmi-labs/arduino-language-server/handler/sourcemapper" "github.com/bcmi-labs/arduino-language-server/handler/textutils" + "github.com/bcmi-labs/arduino-language-server/streams" "github.com/pkg/errors" lsp "github.com/sourcegraph/go-lsp" "github.com/sourcegraph/jsonrpc2" ) var globalCliPath string +var globalClangdPath string var enableLogging bool var asyncProcessing bool // Setup initializes global variables. -func Setup(cliPath string, _enableLogging bool, _asyncProcessing bool) { +func Setup(cliPath string, clangdPath string, _enableLogging bool, _asyncProcessing bool) { globalCliPath = cliPath + globalClangdPath = clangdPath enableLogging = _enableLogging asyncProcessing = _asyncProcessing } @@ -34,20 +40,17 @@ func Setup(cliPath string, _enableLogging bool, _asyncProcessing bool) { type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io.ReadCloser) // NewInoHandler creates and configures an InoHandler. -func NewInoHandler(stdio io.ReadWriteCloser, clangdStdio io.ReadWriteCloser, board Board) *InoHandler { +func NewInoHandler(stdio io.ReadWriteCloser, board Board) *InoHandler { handler := &InoHandler{ - data: make(map[lsp.DocumentURI]*FileData), + data: map[lsp.DocumentURI]*FileData{}, + trackedFiles: map[lsp.DocumentURI]lsp.TextDocumentItem{}, config: BoardConfig{ SelectedBoard: board, }, } - clangdStream := jsonrpc2.NewBufferedStream(clangdStdio, jsonrpc2.VSCodeObjectCodec{}) - clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) - handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) - stdStream := jsonrpc2.NewBufferedStream(stdio, jsonrpc2.VSCodeObjectCodec{}) - var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.FromStdio) + var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.HandleMessageFromIDE) if asyncProcessing { stdHandler = AsyncHandler{ handler: stdHandler, @@ -63,8 +66,17 @@ func NewInoHandler(stdio io.ReadWriteCloser, clangdStdio io.ReadWriteCloser, boa // InoHandler is a JSON-RPC handler that delegates messages to clangd. type InoHandler struct { - StdioConn *jsonrpc2.Conn - ClangdConn *jsonrpc2.Conn + StdioConn *jsonrpc2.Conn + ClangdConn *jsonrpc2.Conn + buildPath *paths.Path + buildSketchRoot *paths.Path + buildSketchCpp *paths.Path + sketchRoot *paths.Path + sketchName string + sketchMapper *sourcemapper.InoMapper + sketchTrackedFilesCount int + trackedFiles map[lsp.DocumentURI]lsp.TextDocumentItem + data map[lsp.DocumentURI]*FileData config BoardConfig synchronizer Synchronizer @@ -85,76 +97,49 @@ func (handler *InoHandler) StopClangd() { handler.ClangdConn = nil } -// FromStdio handles a message received from the client (via stdio). -func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) { - params, err := readParams(req.Method, req.Params) - if err != nil { - return +// HandleMessageFromIDE handles a message received from the IDE client (via stdio). +func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) { + needsWriteLock := map[string]bool{ + "textDocument/didOpen": true, + "textDocument/didChange": true, + "textDocument/didClose": true, + } + if needsWriteLock[req.Method] { + // handler.synchronizer.DataMux.Lock() + // defer handler.synchronizer.DataMux.Unlock() + } else { + // handler.synchronizer.DataMux.RLock() + // defer handler.synchronizer.DataMux.RUnlock() } // Handle LSP methods: transform parameters and send to clangd var uri lsp.DocumentURI - if params == nil { - params = req.Params - } else { - uri, err = handler.transformParamsToClangd(ctx, req.Method, params) - } - if err != nil { - return - } - if req.Notif { - err = handler.ClangdConn.Notify(ctx, req.Method, params) - if enableLogging { - log.Println("From stdio:", req.Method) - } - } else { - ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond) - defer cancel() - result, err = sendRequest(ctx, handler.ClangdConn, req.Method, params) - if enableLogging { - log.Println("From stdio:", req.Method, "id", req.ID) - } - } + + params, err := readParams(req.Method, req.Params) if err != nil { - // Exit the process and trigger a restart by the client in case of a severe error - if err.Error() == "context deadline exceeded" { - log.Println("Timeout exceeded while waiting for a reply from clangd.") - handler.exit() - } - if strings.Contains(err.Error(), "non-added document") || strings.Contains(err.Error(), "non-added file") { - log.Println("The clangd process has lost track of the open document.") - handler.exit() - } - return + return nil, err } - - // Transform and return the result - if result != nil { - result = handler.transformClangdResult(req.Method, uri, result) + if params == nil { + params = req.Params } - return -} - -func (handler *InoHandler) exit() { - log.Println("Please restart the language server.") - handler.StopClangd() - os.Exit(1) -} - -func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method string, params interface{}) (uri lsp.DocumentURI, err error) { - needsWriteLock := method == "textDocument/didOpen" || method == "textDocument/didChange" || method == "textDocument/didClose" - if needsWriteLock { - handler.synchronizer.DataMux.Lock() - defer handler.synchronizer.DataMux.Unlock() - } else { + switch p := params.(type) { + case *lsp.InitializeParams: + // method "initialize" handler.synchronizer.DataMux.RLock() - defer handler.synchronizer.DataMux.RUnlock() - } + err = handler.initializeWorkbench(ctx, p) + handler.synchronizer.DataMux.RUnlock() - switch p := params.(type) { - case *lsp.DidOpenTextDocumentParams: // "textDocument/didOpen": + case *lsp.DidOpenTextDocumentParams: + // method "textDocument/didOpen": uri = p.TextDocument.URI - err = handler.ino2cppTextDocumentItem(ctx, &p.TextDocument) + handler.synchronizer.DataMux.Lock() + res, err := handler.didOpen(ctx, p) + handler.synchronizer.DataMux.Unlock() + if res == nil { + log.Println(" notification is not propagated to clangd") + return nil, err // do not propagate to clangd + } + params = res case *lsp.DidChangeTextDocumentParams: // "textDocument/didChange": uri = p.TextDocument.URI err = handler.ino2cppDidChangeTextDocumentParams(ctx, p) @@ -207,29 +192,165 @@ func (handler *InoHandler) transformParamsToClangd(ctx context.Context, method s case *lsp.ExecuteCommandParams: // "workspace/executeCommand": err = handler.ino2cppExecuteCommand(p) } - return + if err != nil { + return nil, err + } + + var result interface{} + if req.Notif { + err = handler.ClangdConn.Notify(ctx, req.Method, params) + if enableLogging { + log.Println(" sent", req.Method, "notification to clangd") + } + } else { + ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond) + defer cancel() + result, err = sendRequest(ctx, handler.ClangdConn, req.Method, params) + if enableLogging { + log.Println(" sent", req.Method, "request id", req.ID, " to clangd") + } + } + if err != nil { + // Exit the process and trigger a restart by the client in case of a severe error + if err.Error() == "context deadline exceeded" { + log.Println("Timeout exceeded while waiting for a reply from clangd.") + handler.exit() + } + if strings.Contains(err.Error(), "non-added document") || strings.Contains(err.Error(), "non-added file") { + log.Println("The clangd process has lost track of the open document.") + handler.exit() + } + } + + // Transform and return the result + if result != nil { + result = handler.transformClangdResult(req.Method, uri, result) + } + return result, err +} + +func (handler *InoHandler) exit() { + log.Println("Please restart the language server.") + handler.StopClangd() + os.Exit(1) +} + +func newPathFromURI(uri lsp.DocumentURI) *paths.Path { + return paths.New(uriToPath(uri)) +} + +func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp.InitializeParams) error { + rootURI := params.RootURI + log.Printf("--> initializeWorkbench(%s)\n", rootURI) + + handler.sketchRoot = newPathFromURI(rootURI) + handler.sketchName = handler.sketchRoot.Base() + if buildPath, err := generateBuildEnvironment(handler.sketchRoot, handler.config.SelectedBoard.Fqbn); err == nil { + handler.buildPath = buildPath + handler.buildSketchRoot = buildPath.Join("sketch") + } else { + return err + } + handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp") + + if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil { + handler.sketchMapper = sourcemapper.CreateInoMapper(bytes.NewReader(cppContent)) + } else { + return errors.WithMessage(err, "reading generated cpp file from sketch") + } + + clangdStdout, clangdStdin, clangdStderr := startClangd(handler.buildPath, handler.buildSketchCpp) + clangdStdio := streams.NewReadWriteCloser(clangdStdin, clangdStdout) + if enableLogging { + clangdStdio = streams.LogReadWriteCloserAs(clangdStdio, "inols-clangd.log") + go io.Copy(streams.OpenLogFileAs("inols-clangd-err.log"), clangdStderr) + } else { + go io.Copy(os.Stderr, clangdStderr) + } + + clangdStream := jsonrpc2.NewBufferedStream(clangdStdio, jsonrpc2.VSCodeObjectCodec{}) + clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) + handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) + + params.RootPath = handler.buildSketchRoot.String() + params.RootURI = pathToURI(handler.buildSketchRoot.String()) + return nil } -func (handler *InoHandler) createFileData(ctx context.Context, inoSourceURI lsp.DocumentURI, sourceText string, version int) (*FileData, []byte, error) { - log.Println("InoHandler: createFileData(", inoSourceURI, sourceText, version) - sourcePath := uriToPath(inoSourceURI) - targetPath, targetBytes, err := generateCpp([]byte(sourceText), sourcePath, handler.config.SelectedBoard.Fqbn) +func startClangd(compileCommandsDir, sketchCpp *paths.Path) (io.WriteCloser, io.ReadCloser, io.ReadCloser) { + // Open compile_commands.json and find the main cross-compiler executable + compileCommands, err := builder.LoadCompilationDatabase(compileCommandsDir.Join("compile_commands.json")) if err != nil { - err = handler.handleError(ctx, err) - return nil, nil, err + panic("could not find compile_commands.json") + } + compiler := "" + for _, cmd := range compileCommands.Contents { + // Accepts only `arguments` fields for now + if !sketchCpp.EquivalentTo(paths.New(cmd.File)) { + continue + } + if len(cmd.Arguments) == 0 { + panic("invalid empty argument field in compile_commands.json") + } + compiler = cmd.Arguments[0] + break + } + if compiler == "" { + panic("main compiler not found") } - targetURI := pathToURI(targetPath) - data := &FileData{ - sourceText, - inoSourceURI, - targetURI, - sourcemapper.CreateInoMapper(bytes.NewReader(targetBytes)), - version, + // Start clangd + args := []string{ + globalClangdPath, + "-log=verbose", + fmt.Sprintf("-query-driver=%s", compiler), + fmt.Sprintf(`--compile-commands-dir=%s`, compileCommandsDir), + } + if enableLogging { + log.Println(" Starting clangd:", strings.Join(args, " ")) + } + if clangdCmd, err := executils.NewProcess(args...); err != nil { + panic("starting clangd: " + err.Error()) + } else if clangdIn, err := clangdCmd.StdinPipe(); err != nil { + panic("getting clangd stdin: " + err.Error()) + } else if clangdOut, err := clangdCmd.StdoutPipe(); err != nil { + panic("getting clangd stdout: " + err.Error()) + } else if clangdErr, err := clangdCmd.StderrPipe(); err != nil { + panic("getting clangd stderr: " + err.Error()) + } else if err := clangdCmd.Start(); err != nil { + panic("running clangd: " + err.Error()) + } else { + return clangdIn, clangdOut, clangdErr + } +} + +func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextDocumentParams) (*lsp.DidOpenTextDocumentParams, error) { + // Add the TextDocumentItem in the tracked files list + doc := params.TextDocument + handler.trackedFiles[doc.URI] = doc + log.Printf("--> didOpen(%s)", doc.URI) + + // If we are tracking a .ino... + if newPathFromURI(doc.URI).Ext() == ".ino" { + handler.sketchTrackedFilesCount++ + log.Printf(" increasing .ino tracked files count: %d", handler.sketchTrackedFilesCount) + + // ...notify clang that sketchCpp is no longer valid on disk + if handler.sketchTrackedFilesCount == 1 { + sketchCpp, err := handler.buildSketchCpp.ReadFile() + newParam := &lsp.DidOpenTextDocumentParams{ + TextDocument: lsp.TextDocumentItem{ + URI: pathToURI(handler.buildSketchCpp.String()), + Text: string(sketchCpp), + LanguageID: "cpp", + Version: 1, + }, + } + log.Printf(" message for clangd: didOpen(%s)", newParam.TextDocument.URI) + return newParam, err + } } - handler.data[inoSourceURI] = data - handler.data[targetURI] = data - return data, targetBytes, nil + return nil, nil } func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, change *lsp.TextDocumentContentChangeEvent) (err error) { @@ -331,19 +452,6 @@ func (handler *InoHandler) ino2cppTextDocumentIdentifier(doc *lsp.TextDocumentId return unknownURI(doc.URI) } -func (handler *InoHandler) ino2cppTextDocumentItem(ctx context.Context, doc *lsp.TextDocumentItem) error { - if strings.HasSuffix(string(doc.URI), ".ino") { - data, targetBytes, err := handler.createFileData(ctx, doc.URI, doc.Text, doc.Version) - if err != nil { - return err - } - doc.LanguageID = "cpp" - doc.URI = data.targetURI - doc.Text = string(targetBytes) - } - return nil -} - func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Context, params *lsp.DidChangeTextDocumentParams) error { handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument.TextDocumentIdentifier) if data, ok := handler.data[params.TextDocument.URI]; ok { diff --git a/handler/uri.go b/handler/uri.go index 0553723..751dc89 100644 --- a/handler/uri.go +++ b/handler/uri.go @@ -7,6 +7,7 @@ import ( "runtime" "strings" + "github.com/arduino/go-paths-helper" "github.com/pkg/errors" lsp "github.com/sourcegraph/go-lsp" ) @@ -34,6 +35,10 @@ func uriToPath(uri lsp.DocumentURI) string { return path } +func ToURI(path *paths.Path) lsp.DocumentURI { + return pathToURI(path.String()) +} + func pathToURI(path string) lsp.DocumentURI { urlObj, err := url.Parse("file://") if err != nil { diff --git a/main.go b/main.go index 2e6cebe..7e7d487 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,12 @@ package main import ( "flag" - "fmt" "io" "log" "os" - "os/exec" - "path/filepath" "runtime/debug" + "github.com/arduino/go-paths-helper" "github.com/bcmi-labs/arduino-language-server/handler" "github.com/bcmi-labs/arduino-language-server/streams" ) @@ -32,8 +30,14 @@ func main() { flag.StringVar(&loggingBasePath, "logpath", ".", "Location where to write logging files to when logging is enabled") flag.Parse() + if loggingBasePath != "" { + streams.GlobalLogDirectory = paths.New(loggingBasePath) + } else if enableLogging { + log.Fatalf("Please specify logpath") + } + if enableLogging { - logfile := openLogFile("inols-err.log") + logfile := streams.OpenLogFileAs("inols-err.log") defer func() { // In case of panic output the stack trace in the log file before exiting if r := recover(); r != nil { @@ -41,69 +45,21 @@ func main() { } logfile.Close() }() - log.SetOutput(logfile) + log.SetOutput(io.MultiWriter(logfile, os.Stderr)) + // log.SetOutput(logfile) } else { log.SetOutput(os.Stderr) } - handler.Setup(cliPath, enableLogging, true) + handler.Setup(cliPath, clangdPath, enableLogging, true) initialBoard := handler.Board{Fqbn: initialFqbn, Name: initialBoardName} - clangdStdout, clangdStdin, clangdStderr := startClangd() - clangdStdio := streams.NewReadWriteCloser(clangdStdin, clangdStdout) - if enableLogging { - logfile := openLogFile("inols-clangd.log") - defer logfile.Close() - clangdStdio = streams.LogReadWriteCloserToFile(clangdStdio, logfile) - - errLogfile := openLogFile("inols-clangd-err.log") - defer errLogfile.Close() - go io.Copy(errLogfile, clangdStderr) - } else { - go io.Copy(os.Stderr, clangdStderr) - } - stdio := streams.NewReadWriteCloser(os.Stdin, os.Stdout) if enableLogging { - logfile := openLogFile("inols.log") - defer logfile.Close() - stdio = streams.LogReadWriteCloserToFile(stdio, logfile) + stdio = streams.LogReadWriteCloserAs(stdio, "inols.log") } - inoHandler := handler.NewInoHandler(stdio, clangdStdio, initialBoard) + inoHandler := handler.NewInoHandler(stdio, initialBoard) defer inoHandler.StopClangd() <-inoHandler.StdioConn.DisconnectNotify() } - -func openLogFile(name string) *os.File { - path := filepath.Join(loggingBasePath, name) - logfile, err := os.Create(path) - if err != nil { - log.Fatalf("Error opening log file: %s", err) - } else { - abs, _ := filepath.Abs(path) - log.Println("logging to " + abs) - } - return logfile -} - -func startClangd() (clangdIn io.WriteCloser, clangdOut io.ReadCloser, clangdErr io.ReadCloser) { - if enableLogging { - log.Println("Starting clangd process:", clangdPath) - } - var clangdCmd *exec.Cmd - if compileCommandsDir != "" { - clangdCmd = exec.Command(clangdPath) - } else { - clangdCmd = exec.Command(clangdPath, fmt.Sprintf(`--compile-commands-dir="%s"`, compileCommandsDir)) - } - clangdIn, _ = clangdCmd.StdinPipe() - clangdOut, _ = clangdCmd.StdoutPipe() - clangdErr, _ = clangdCmd.StderrPipe() - - err := clangdCmd.Start() - if err != nil { - panic(err) - } - return -} diff --git a/streams/dumper.go b/streams/dumper.go index 1cc8359..7e883a5 100644 --- a/streams/dumper.go +++ b/streams/dumper.go @@ -3,15 +3,41 @@ package streams import ( "fmt" "io" + "log" "os" + + "github.com/arduino/go-paths-helper" ) +// GlobalLogDirectory is the directory where logs are created +var GlobalLogDirectory *paths.Path + +// LogReadWriteCloserAs return a proxy for the given upstream io.ReadWriteCloser +// that forward and logs all read/write/close operations on the given filename +// that is created in the GlobalLogDirectory. +func LogReadWriteCloserAs(upstream io.ReadWriteCloser, filename string) io.ReadWriteCloser { + return &dumper{upstream, OpenLogFileAs(filename)} +} + // LogReadWriteCloserToFile return a proxy for the given upstream io.ReadWriteCloser // that forward and logs all read/write/close operations on the given file. func LogReadWriteCloserToFile(upstream io.ReadWriteCloser, file *os.File) io.ReadWriteCloser { return &dumper{upstream, file} } +// OpenLogFileAs creates a log file in GlobalLogDirectory. +func OpenLogFileAs(filename string) *os.File { + path := GlobalLogDirectory.Join(filename) + res, err := path.Create() + if err != nil { + log.Fatalf("Error opening log file: %s", err) + } else { + abs, _ := path.Abs() + log.Printf("logging to %s", abs) + } + return res +} + type dumper struct { upstream io.ReadWriteCloser logfile *os.File From dbbc34fa9f8514cc977f9e08a582ce190c9a3d5f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 23 Nov 2020 18:29:51 +0100 Subject: [PATCH 12/61] Accept all compiler for querying drivers (g++ and gcc for example) --- handler/handler.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index a2c7990..7876028 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -283,19 +283,14 @@ func startClangd(compileCommandsDir, sketchCpp *paths.Path) (io.WriteCloser, io. if err != nil { panic("could not find compile_commands.json") } - compiler := "" + compilers := map[string]bool{} for _, cmd := range compileCommands.Contents { - // Accepts only `arguments` fields for now - if !sketchCpp.EquivalentTo(paths.New(cmd.File)) { - continue - } if len(cmd.Arguments) == 0 { panic("invalid empty argument field in compile_commands.json") } - compiler = cmd.Arguments[0] - break + compilers[cmd.Arguments[0]] = true } - if compiler == "" { + if len(compilers) == 0 { panic("main compiler not found") } @@ -303,9 +298,11 @@ func startClangd(compileCommandsDir, sketchCpp *paths.Path) (io.WriteCloser, io. args := []string{ globalClangdPath, "-log=verbose", - fmt.Sprintf("-query-driver=%s", compiler), fmt.Sprintf(`--compile-commands-dir=%s`, compileCommandsDir), } + for compiler := range compilers { + args = append(args, fmt.Sprintf("-query-driver=%s", compiler)) + } if enableLogging { log.Println(" Starting clangd:", strings.Join(args, " ")) } From f07082210d2fe5da7ea91fbf4e750589eb1c28f6 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 23 Nov 2020 22:27:59 +0100 Subject: [PATCH 13/61] Implemented 'completion' message --- handler/handler.go | 76 ++++++++++++++++++++++++++----------- handler/sourcemapper/ino.go | 7 ++++ 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 7876028..0544ac2 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -140,19 +140,24 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr return nil, err // do not propagate to clangd } params = res + + case *lsp.CompletionParams: // "textDocument/completion": + uri = p.TextDocument.URI + log.Printf("--> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character) + + err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) + log.Printf(" --> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character) + case *lsp.DidChangeTextDocumentParams: // "textDocument/didChange": uri = p.TextDocument.URI err = handler.ino2cppDidChangeTextDocumentParams(ctx, p) case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": uri = p.TextDocument.URI - err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) + err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose": uri = p.TextDocument.URI - err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) + err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) handler.deleteFileData(uri) - case *lsp.CompletionParams: // "textDocument/completion": - uri = p.TextDocument.URI - err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) case *lsp.CodeActionParams: // "textDocument/codeAction": uri = p.TextDocument.URI err = handler.ino2cppCodeActionParams(p) @@ -174,7 +179,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) case *lsp.DocumentFormattingParams: // "textDocument/formatting": uri = p.TextDocument.URI - err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) + err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting": uri = p.TextDocument.URI err = handler.ino2cppDocumentRangeFormattingParams(p) @@ -183,7 +188,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppDocumentOnTypeFormattingParams(p) case *lsp.DocumentSymbolParams: // "textDocument/documentSymbol": uri = p.TextDocument.URI - err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument) + err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.RenameParams: // "textDocument/rename": uri = p.TextDocument.URI err = handler.ino2cppRenameParams(p) @@ -193,6 +198,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppExecuteCommand(p) } if err != nil { + log.Printf(" ~~~ %s", err) return nil, err } @@ -441,16 +447,36 @@ func (handler *InoHandler) handleError(ctx context.Context, err error) error { return errors.New(message) } -func (handler *InoHandler) ino2cppTextDocumentIdentifier(doc *lsp.TextDocumentIdentifier) error { - if data, ok := handler.data[doc.URI]; ok { - doc.URI = data.targetURI - return nil +func (handler *InoHandler) sketchToBuildPathTextDocumentIdentifier(doc *lsp.TextDocumentIdentifier) error { + // Sketchbook/Sketch/Sketch.ino -> build-path/sketch/Sketch.ino.cpp + // Sketchbook/Sketch/AnotherTab.ino -> build-path/sketch/Sketch.ino.cpp (different section from above) + // Sketchbook/Sketch/AnotherFile.cpp -> build-path/sketch/AnotherFile.cpp (1:1) + // another/path/source.cpp -> unchanged + + // Convert sketch path to build path + docFile := newPathFromURI(doc.URI) + newDocFile := docFile + + if docFile.Ext() == ".ino" { + newDocFile = handler.buildSketchCpp + } else if inside, err := docFile.IsInsideDir(handler.sketchRoot); err != nil { + log.Printf(" could not determine if '%s' is inside '%s'", docFile, handler.sketchRoot) + return unknownURI(doc.URI) + } else if !inside { + log.Printf(" passing doc identifier to '%s' as-is", docFile) + } else if rel, err := handler.sketchRoot.RelTo(docFile); err != nil { + log.Printf(" could not determine rel-path of '%s' in '%s", docFile, handler.sketchRoot) + return unknownURI(doc.URI) + } else { + newDocFile = handler.buildSketchRoot.JoinPath(rel) } - return unknownURI(doc.URI) + log.Printf(" URI: '%s' -> '%s'", docFile, newDocFile) + doc.URI = pathToURI(newDocFile.String()) + return nil } func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Context, params *lsp.DidChangeTextDocumentParams) error { - handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument.TextDocumentIdentifier) + handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument.TextDocumentIdentifier) if data, ok := handler.data[params.TextDocument.URI]; ok { for index := range params.ContentChanges { err := handler.updateFileData(ctx, data, ¶ms.ContentChanges[index]) @@ -465,17 +491,21 @@ func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Contex } func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error { - handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) - if data, ok := handler.data[params.TextDocument.URI]; ok { - targetLine := data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) - params.Position.Line = targetLine - return nil + sourceURI := params.TextDocument.URI + if strings.HasSuffix(string(sourceURI), ".ino") { + line, ok := handler.sketchMapper.InoToCppLineOk(sourceURI, params.Position.Line) + if !ok { + log.Printf(" invalid line requested: %s:%d", sourceURI, params.Position.Line) + return unknownURI(params.TextDocument.URI) + } + params.Position.Line = line } - return unknownURI(params.TextDocument.URI) + handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) + return nil } func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) error { - handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) + handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) for index := range params.Context.Diagnostics { @@ -488,7 +518,7 @@ func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) } func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.DocumentRangeFormattingParams) error { - handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) + handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) return nil @@ -497,7 +527,7 @@ func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.Docu } func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.DocumentOnTypeFormattingParams) error { - handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) + handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) return nil @@ -506,7 +536,7 @@ func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.Doc } func (handler *InoHandler) ino2cppRenameParams(params *lsp.RenameParams) error { - handler.ino2cppTextDocumentIdentifier(¶ms.TextDocument) + handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) if data, ok := handler.data[params.TextDocument.URI]; ok { params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) return nil diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 78bda14..90d9852 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -39,6 +39,13 @@ func (s *InoMapper) InoToCppLine(sourceURI lsp.DocumentURI, line int) int { return s.toCpp[InoLine{file, line}] } +// InoToCppLineOk converts a source (.ino) line into a target (.cpp) line +func (s *InoMapper) InoToCppLineOk(sourceURI lsp.DocumentURI, line int) (int, bool) { + file := uriToPath(sourceURI) + res, ok := s.toCpp[InoLine{file, line}] + return res, ok +} + func (s *InoMapper) InoToCppLSPRange(sourceURI lsp.DocumentURI, r lsp.Range) lsp.Range { res := r res.Start.Line = s.InoToCppLine(sourceURI, r.Start.Line) From 84b85b7ebb4dbcc5276a93cce1696ac627b3e784 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 24 Nov 2020 18:08:42 +0100 Subject: [PATCH 14/61] Made a more clean logging --- streams/dumper.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/streams/dumper.go b/streams/dumper.go index 7e883a5..ae66cbf 100644 --- a/streams/dumper.go +++ b/streams/dumper.go @@ -16,13 +16,19 @@ var GlobalLogDirectory *paths.Path // that forward and logs all read/write/close operations on the given filename // that is created in the GlobalLogDirectory. func LogReadWriteCloserAs(upstream io.ReadWriteCloser, filename string) io.ReadWriteCloser { - return &dumper{upstream, OpenLogFileAs(filename)} + return &dumper{ + upstream: upstream, + logfile: OpenLogFileAs(filename), + } } // LogReadWriteCloserToFile return a proxy for the given upstream io.ReadWriteCloser // that forward and logs all read/write/close operations on the given file. func LogReadWriteCloserToFile(upstream io.ReadWriteCloser, file *os.File) io.ReadWriteCloser { - return &dumper{upstream, file} + return &dumper{ + upstream: upstream, + logfile: file, + } } // OpenLogFileAs creates a log file in GlobalLogDirectory. @@ -41,6 +47,8 @@ func OpenLogFileAs(filename string) *os.File { type dumper struct { upstream io.ReadWriteCloser logfile *os.File + reading bool + writing bool } func (d *dumper) Read(buff []byte) (int, error) { @@ -48,7 +56,12 @@ func (d *dumper) Read(buff []byte) (int, error) { if err != nil { d.logfile.Write([]byte(fmt.Sprintf("<<< Read Error: %s\n", err))) } else { - d.logfile.Write([]byte(fmt.Sprintf("<<< Read %d bytes:\n%s\n", n, buff[:n]))) + if !d.reading { + d.reading = true + d.writing = false + d.logfile.Write([]byte("\n<<<\n")) + } + d.logfile.Write(buff[:n]) } return n, err } @@ -58,7 +71,12 @@ func (d *dumper) Write(buff []byte) (int, error) { if err != nil { _, _ = d.logfile.Write([]byte(fmt.Sprintf(">>> Write Error: %s\n", err))) } else { - _, _ = d.logfile.Write([]byte(fmt.Sprintf(">>> Wrote %d bytes:\n%s\n", n, buff[:n]))) + if !d.writing { + d.writing = true + d.reading = false + d.logfile.Write([]byte("\n>>>\n")) + } + _, _ = d.logfile.Write(buff[:n]) } return n, err } From 436052b18252833bb1fca45cd5a34ba4808c710f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 24 Nov 2020 19:47:35 +0100 Subject: [PATCH 15/61] Implemented textDocument/publishDiagnostics --- handler/handler.go | 94 +++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 0544ac2..8a74c85 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -3,7 +3,6 @@ package handler import ( "bytes" "context" - "encoding/json" "fmt" "io" "log" @@ -803,7 +802,58 @@ func (handler *InoHandler) cpp2inoSymbolInformation(syms []*lsp.SymbolInformatio // FromClangd handles a message received from clangd. func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) { - params, _, err := handler.transformParamsToStdio(req.Method, req.Params) + handler.synchronizer.DataMux.RLock() + defer handler.synchronizer.DataMux.RUnlock() + + params, err := readParams(req.Method, req.Params) + if err != nil { + return nil, errors.WithMessage(err, "parsing JSON message from clangd") + } + if params == nil { + // passthrough + params = req.Params + } + switch p := params.(type) { + case *lsp.PublishDiagnosticsParams: + // "textDocument/publishDiagnostics" + if newPathFromURI(p.URI).EquivalentTo(handler.buildSketchCpp) { + // we should transform back N diagnostics of sketch.cpp.ino into + // their .ino counter parts (that may span over multiple files...) + + convertedDiagnostics := map[string][]lsp.Diagnostic{} + for _, cppDiag := range p.Diagnostics { + inoSource, inoRange := handler.sketchMapper.CppToInoRange(cppDiag.Range) + inoDiag := cppDiag + inoDiag.Range = inoRange + if inoDiags, ok := convertedDiagnostics[inoSource]; !ok { + convertedDiagnostics[inoSource] = []lsp.Diagnostic{inoDiag} + } else { + convertedDiagnostics[inoSource] = append(inoDiags, inoDiag) + } + } + + // Push back to IDE the converted diagnostics + for filename, inoDiags := range convertedDiagnostics { + msg := lsp.PublishDiagnosticsParams{ + URI: pathToURI(filename), + Diagnostics: inoDiags, + } + if err := handler.StdioConn.Notify(ctx, req.Method, msg); err != nil { + return nil, err + } + if enableLogging { + log.Println("--> ", req.Method) + } + } + + return nil, err + } + + case *ApplyWorkspaceEditParams: + // "workspace/applyEdit" + p.Edit = *handler.cpp2inoWorkspaceEdit(&p.Edit) + } + if err != nil { log.Println("From clangd: Method:", req.Method, "Error:", err) return nil, err @@ -823,46 +873,6 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. return result, err } -func (handler *InoHandler) transformParamsToStdio(method string, raw *json.RawMessage) (params interface{}, uri lsp.DocumentURI, err error) { - handler.synchronizer.DataMux.RLock() - defer handler.synchronizer.DataMux.RUnlock() - - params, err = readParams(method, raw) - if err != nil { - return - } else if params == nil { - params = raw - return - } - switch method { - case "textDocument/publishDiagnostics": - p := params.(*lsp.PublishDiagnosticsParams) - uri = p.URI - err = handler.cpp2inoPublishDiagnosticsParams(p) - case "workspace/applyEdit": - p := params.(*ApplyWorkspaceEditParams) - p.Edit = *handler.cpp2inoWorkspaceEdit(&p.Edit) - } - return -} - -func (handler *InoHandler) cpp2inoPublishDiagnosticsParams(params *lsp.PublishDiagnosticsParams) error { - if data, ok := handler.data[params.URI]; ok { - params.URI = data.sourceURI - newDiagnostics := make([]lsp.Diagnostic, 0, len(params.Diagnostics)) - for index := range params.Diagnostics { - r := ¶ms.Diagnostics[index].Range - if _, startLine, ok := data.sourceMap.CppToInoLineOk(r.Start.Line); ok { - r.Start.Line = startLine - _, r.End.Line = data.sourceMap.CppToInoLine(r.End.Line) - newDiagnostics = append(newDiagnostics, params.Diagnostics[index]) - } - } - params.Diagnostics = newDiagnostics - } - return nil -} - func (handler *InoHandler) parseCommandArgument(rawArg interface{}) interface{} { if m1, ok := rawArg.(map[string]interface{}); ok && len(m1) == 1 && m1["changes"] != nil { m2 := m1["changes"].(map[string]interface{}) From ed22a4b01c45c162898f62d47cbe4302f953a1b5 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 24 Nov 2020 23:51:55 +0100 Subject: [PATCH 16/61] Added implementation for 'textDocument/hover' message --- handler/handler.go | 11 +++++++++-- handler/protocol.go | 10 +++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 8a74c85..1d317fb 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -147,6 +147,15 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) log.Printf(" --> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character) + case *HoverParams: + // method: "textDocument/hover" + uri = p.TextDocument.URI + doc := &p.TextDocumentPositionParams + log.Printf("--> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character) + + err = handler.ino2cppTextDocumentPositionParams(doc) + log.Printf(" --> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character) + case *lsp.DidChangeTextDocumentParams: // "textDocument/didChange": uri = p.TextDocument.URI err = handler.ino2cppDidChangeTextDocumentParams(ctx, p) @@ -162,8 +171,6 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppCodeActionParams(p) // case "textDocument/signatureHelp": // fallthrough - // case "textDocument/hover": - // fallthrough // case "textDocument/definition": // fallthrough // case "textDocument/typeDefinition": diff --git a/handler/protocol.go b/handler/protocol.go index 177d05e..9e2cba8 100644 --- a/handler/protocol.go +++ b/handler/protocol.go @@ -41,7 +41,9 @@ func readParams(method string, raw *json.RawMessage) (interface{}, error) { case "textDocument/signatureHelp": fallthrough case "textDocument/hover": - fallthrough + params := new(HoverParams) + err := json.Unmarshal(*raw, params) + return params, err case "textDocument/definition": fallthrough case "textDocument/typeDefinition": @@ -222,6 +224,12 @@ func (entry *commandOrCodeAction) MarshalJSON() ([]byte, error) { return nil, nil } +// HoverParams structure according to LSP +type HoverParams struct { + lsp.TextDocumentPositionParams + // lsp.WorkDoneProgressParams +} + // Hover structure according to LSP type Hover struct { Contents MarkupContent `json:"contents"` From 0d5f6106a244e7d5c5fcc6393a3ec21f112af6d5 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 24 Nov 2020 23:52:12 +0100 Subject: [PATCH 17/61] Slightly improved logging --- handler/handler.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 1d317fb..49eba64 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -157,16 +157,20 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Printf(" --> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character) case *lsp.DidChangeTextDocumentParams: // "textDocument/didChange": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppDidChangeTextDocumentParams(ctx, p) case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) handler.deleteFileData(uri) case *lsp.CodeActionParams: // "textDocument/codeAction": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppCodeActionParams(p) // case "textDocument/signatureHelp": @@ -178,29 +182,38 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr // case "textDocument/implementation": // fallthrough case *lsp.TextDocumentPositionParams: // "textDocument/documentHighlight": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(p) case *lsp.ReferenceParams: // "textDocument/references": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) case *lsp.DocumentFormattingParams: // "textDocument/formatting": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppDocumentRangeFormattingParams(p) case *lsp.DocumentOnTypeFormattingParams: // "textDocument/onTypeFormatting": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppDocumentOnTypeFormattingParams(p) case *lsp.DocumentSymbolParams: // "textDocument/documentSymbol": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.RenameParams: // "textDocument/rename": + log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI err = handler.ino2cppRenameParams(p) case *lsp.DidChangeWatchedFilesParams: // "workspace/didChangeWatchedFiles": + log.Printf("UNHANDLED " + req.Method) err = handler.ino2cppDidChangeWatchedFilesParams(p) case *lsp.ExecuteCommandParams: // "workspace/executeCommand": + log.Printf("UNHANDLED " + req.Method) err = handler.ino2cppExecuteCommand(p) } if err != nil { @@ -823,6 +836,11 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. switch p := params.(type) { case *lsp.PublishDiagnosticsParams: // "textDocument/publishDiagnostics" + log.Printf(" <-- publishDiagnostics(%s):", p.URI) + for _, diag := range p.Diagnostics { + log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) + } + if newPathFromURI(p.URI).EquivalentTo(handler.buildSketchCpp) { // we should transform back N diagnostics of sketch.cpp.ino into // their .ino counter parts (that may span over multiple files...) @@ -845,11 +863,14 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. URI: pathToURI(filename), Diagnostics: inoDiags, } - if err := handler.StdioConn.Notify(ctx, req.Method, msg); err != nil { - return nil, err - } if enableLogging { - log.Println("--> ", req.Method) + log.Printf("<-- publishDiagnostics(%s):", msg.URI) + for _, diag := range msg.Diagnostics { + log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) + } + } + if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", msg); err != nil { + return nil, err } } From 5b8ca7135684c4ef7acd22a97d5a961a1b88a755 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 25 Nov 2020 00:25:29 +0100 Subject: [PATCH 18/61] Forked https://github.com/sourcegraph/go-lsp --- go.mod | 5 - go.sum | 33 +- handler/handler.go | 38 +- handler/sourcemapper/ino.go | 2 +- handler/textutils/textutils.go | 2 +- handler/textutils/textutils_test.go | 2 +- handler/uri.go | 2 +- handler/uri_test.go | 2 +- lsp/LICENSE | 19 + lsp/README.md | 10 + lsp/jsonrpc2.go | 52 ++ {handler => lsp}/protocol.go | 123 ++-- lsp/service.go | 919 ++++++++++++++++++++++++++++ lsp/structures.go | 165 +++++ main.go | 3 +- 15 files changed, 1281 insertions(+), 96 deletions(-) create mode 100644 lsp/LICENSE create mode 100644 lsp/README.md create mode 100644 lsp/jsonrpc2.go rename {handler => lsp}/protocol.go (72%) create mode 100644 lsp/service.go create mode 100644 lsp/structures.go diff --git a/go.mod b/go.mod index 15b1442..217ee6b 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,7 @@ require ( github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb github.com/arduino/go-paths-helper v1.3.2 github.com/arduino/go-properties-orderedmap v1.4.0 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.9.1 - github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 github.com/stretchr/testify v1.6.1 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index af5c49b..e251705 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -7,8 +8,6 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb h1:ZSECb5dRyxpQSiFEuhAXn+3oxeCUBc/PJU3tJenY9Ik= -github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb/go.mod h1:PpHwX4OKp/PFumezvkSRixh5N9uLiCASm3gqK/Da5is= github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8= github.com/arduino/go-paths-helper v1.0.1 h1:utYXLM2RfFlc9qp/MJTIYp3t6ux/xM6mWjeEb/WLK4Q= github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= @@ -19,6 +18,7 @@ github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4l github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= +github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -56,6 +56,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg= github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -66,6 +67,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -85,7 +87,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= @@ -96,20 +100,25 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/h2non/filetype v1.0.6/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14= github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imjasonmiller/godice v0.1.2/go.mod h1:8cTkdnVI+NglU2d6sv+ilYcNaJ5VSTBwvMbFULJd/QQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/retry v0.0.0-20160928201858-1998d01ba1c3/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4= +github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0 h1:+WWUkhnTjV6RNOxkcwk79qrjeyHEHvBzlneueBsatX4= github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbNBraRLZEhoQwFLMrjK8PSlO4D3nDjKYXo= github.com/juju/utils v0.0.0-20180808125547-9dfc6dbfb02b/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= @@ -117,6 +126,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -127,8 +137,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leonelquinteros/gotext v1.4.0/go.mod h1:yZGXREmoGTtBvZHNcc+Yfug49G/2spuF/i/Qlsvz1Us= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -137,6 +149,7 @@ github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE github.com/mdlayher/taskstats v0.0.0-20190313225729-7cbba52ee072/go.mod h1:sGdS7A6CAETR53zkdjGkgoFlh1vSm7MtX+i8XfEsTMA= github.com/miekg/dns v1.0.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -145,6 +158,7 @@ github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOW github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228/go.mod h1:MGuVJ1+5TX1SCoO2Sx0eAnjpdRytYla2uC1YIZfkC9c= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -177,20 +191,26 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d h1:afLbh+ltiygTOB37ymZVwKlJwWZn+86syPTbrrOAydY= -github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d/go.mod h1:SULmZY7YNBsvNiQbrb/BEDdEJ84TGnfyUQxaHt8t8rY= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4= github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.1-0.20200710201246-675ae5f5a98c h1:/dP/1GnfVIlWnB0YDImenSmneUCw3wjyq2RMgAG1e2o= github.com/spf13/cobra v1.0.1-0.20200710201246-675ae5f5a98c/go.mod h1:aeNIJzz/GSSVlS+gpCpQWZ83BKbsoW57mr90+YthtkQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -202,6 +222,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -277,6 +298,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -306,8 +328,10 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= @@ -319,6 +343,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler/handler.go b/handler/handler.go index 49eba64..2d22ebd 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -16,9 +16,9 @@ import ( "github.com/arduino/go-paths-helper" "github.com/bcmi-labs/arduino-language-server/handler/sourcemapper" "github.com/bcmi-labs/arduino-language-server/handler/textutils" + "github.com/bcmi-labs/arduino-language-server/lsp" "github.com/bcmi-labs/arduino-language-server/streams" "github.com/pkg/errors" - lsp "github.com/sourcegraph/go-lsp" "github.com/sourcegraph/jsonrpc2" ) @@ -39,11 +39,11 @@ func Setup(cliPath string, clangdPath string, _enableLogging bool, _asyncProcess type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io.ReadCloser) // NewInoHandler creates and configures an InoHandler. -func NewInoHandler(stdio io.ReadWriteCloser, board Board) *InoHandler { +func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ data: map[lsp.DocumentURI]*FileData{}, trackedFiles: map[lsp.DocumentURI]lsp.TextDocumentItem{}, - config: BoardConfig{ + config: lsp.BoardConfig{ SelectedBoard: board, }, } @@ -77,7 +77,7 @@ type InoHandler struct { trackedFiles map[lsp.DocumentURI]lsp.TextDocumentItem data map[lsp.DocumentURI]*FileData - config BoardConfig + config lsp.BoardConfig synchronizer Synchronizer } @@ -114,7 +114,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr // Handle LSP methods: transform parameters and send to clangd var uri lsp.DocumentURI - params, err := readParams(req.Method, req.Params) + params, err := lsp.ReadParams(req.Method, req.Params) if err != nil { return nil, err } @@ -147,7 +147,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) log.Printf(" --> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character) - case *HoverParams: + case *lsp.HoverParams: // method: "textDocument/hover" uri = p.TextDocument.URI doc := &p.TextDocumentPositionParams @@ -230,7 +230,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr } else { ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond) defer cancel() - result, err = sendRequest(ctx, handler.ClangdConn, req.Method, params) + result, err = lsp.SendRequest(ctx, handler.ClangdConn, req.Method, params) if enableLogging { log.Println(" sent", req.Method, "request id", req.ID, " to clangd") } @@ -609,7 +609,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document switch r := result.(type) { case *lsp.CompletionList: // "textDocument/completion": handler.cpp2inoCompletionList(r, uri) - case *[]*commandOrCodeAction: // "textDocument/codeAction": + case *[]*lsp.CommandOrCodeAction: // "textDocument/codeAction": for index := range *r { command := (*r)[index].Command if command != nil { @@ -620,7 +620,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document handler.cpp2inoCodeAction(codeAction, uri) } } - case *Hover: // "textDocument/hover": + case *lsp.Hover: // "textDocument/hover": if len(r.Contents.Value) == 0 { return nil } @@ -647,7 +647,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document for index := range *r { handler.cpp2inoTextEdit(&(*r)[index], uri) } - case *[]*documentSymbolOrSymbolInformation: // "textDocument/documentSymbol": + case *[]*lsp.DocumentSymbolOrSymbolInformation: // "textDocument/documentSymbol": if len(*r) == 0 { return result } @@ -655,7 +655,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document slice := *r if slice[0].DocumentSymbol != nil { // Treat the input as []DocumentSymbol - symbols := make([]DocumentSymbol, len(slice)) + symbols := make([]lsp.DocumentSymbol, len(slice)) for index := range slice { symbols[index] = *slice[index].DocumentSymbol } @@ -694,7 +694,7 @@ func (handler *InoHandler) cpp2inoCompletionList(list *lsp.CompletionList, uri l } } -func (handler *InoHandler) cpp2inoCodeAction(codeAction *CodeAction, uri lsp.DocumentURI) { +func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp.DocumentURI) { codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) if data, ok := handler.data[uri]; ok { for index := range codeAction.Diagnostics { @@ -732,7 +732,7 @@ func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls return &newEdit } -func (handler *InoHandler) cpp2inoHover(hover *Hover, uri lsp.DocumentURI) { +func (handler *InoHandler) cpp2inoHover(hover *lsp.Hover, uri lsp.DocumentURI) { if data, ok := handler.data[uri]; ok { r := hover.Range if r != nil { @@ -760,13 +760,13 @@ func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentU } } -func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, uri lsp.DocumentURI) []DocumentSymbol { +func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, uri lsp.DocumentURI) []lsp.DocumentSymbol { data, ok := handler.data[uri] if !ok || len(origSymbols) == 0 { return origSymbols } - symbolIdx := make(map[string]*DocumentSymbol) + symbolIdx := make(map[string]*lsp.DocumentSymbol) for i := 0; i < len(origSymbols); i++ { symbol := &origSymbols[i] _, symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) @@ -787,7 +787,7 @@ func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, symbolIdx[symbol.Name] = symbol } - newSymbols := make([]DocumentSymbol, len(symbolIdx)) + newSymbols := make([]lsp.DocumentSymbol, len(symbolIdx)) j := 0 for _, s := range symbolIdx { newSymbols[j] = *s @@ -825,7 +825,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. handler.synchronizer.DataMux.RLock() defer handler.synchronizer.DataMux.RUnlock() - params, err := readParams(req.Method, req.Params) + params, err := lsp.ReadParams(req.Method, req.Params) if err != nil { return nil, errors.WithMessage(err, "parsing JSON message from clangd") } @@ -877,7 +877,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. return nil, err } - case *ApplyWorkspaceEditParams: + case *lsp.ApplyWorkspaceEditParams: // "workspace/applyEdit" p.Edit = *handler.cpp2inoWorkspaceEdit(&p.Edit) } @@ -893,7 +893,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. log.Println("From clangd:", req.Method) } } else { - result, err = sendRequest(ctx, handler.StdioConn, req.Method, params) + result, err = lsp.SendRequest(ctx, handler.StdioConn, req.Method, params) if enableLogging { log.Println("From clangd:", req.Method, "id", req.ID) } diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 90d9852..11da6d7 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/sourcegraph/go-lsp" + "github.com/bcmi-labs/arduino-language-server/lsp" ) // InoMapper is a mapping between the .ino sketch and the preprocessed .cpp file diff --git a/handler/textutils/textutils.go b/handler/textutils/textutils.go index d3a24b4..5179779 100644 --- a/handler/textutils/textutils.go +++ b/handler/textutils/textutils.go @@ -3,7 +3,7 @@ package textutils import ( "fmt" - "github.com/sourcegraph/go-lsp" + "github.com/bcmi-labs/arduino-language-server/lsp" ) // ApplyTextChange replaces startingText substring specified by replaceRange with insertText diff --git a/handler/textutils/textutils_test.go b/handler/textutils/textutils_test.go index 032acda..105b71c 100644 --- a/handler/textutils/textutils_test.go +++ b/handler/textutils/textutils_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/sourcegraph/go-lsp" + "github.com/bcmi-labs/arduino-language-server/lsp" ) func TestApplyTextChange(t *testing.T) { diff --git a/handler/uri.go b/handler/uri.go index 751dc89..8c925a1 100644 --- a/handler/uri.go +++ b/handler/uri.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/arduino/go-paths-helper" + "github.com/bcmi-labs/arduino-language-server/lsp" "github.com/pkg/errors" - lsp "github.com/sourcegraph/go-lsp" ) var expDriveID = regexp.MustCompile("[a-zA-Z]:") diff --git a/handler/uri_test.go b/handler/uri_test.go index 3a760f7..d843646 100644 --- a/handler/uri_test.go +++ b/handler/uri_test.go @@ -5,7 +5,7 @@ import ( "runtime" "testing" - lsp "github.com/sourcegraph/go-lsp" + "github.com/bcmi-labs/arduino-language-server/lsp" ) func TestUriToPath(t *testing.T) { diff --git a/lsp/LICENSE b/lsp/LICENSE new file mode 100644 index 0000000..da96b67 --- /dev/null +++ b/lsp/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Sourcegraph + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lsp/README.md b/lsp/README.md new file mode 100644 index 0000000..1a48ef0 --- /dev/null +++ b/lsp/README.md @@ -0,0 +1,10 @@ +This module has been imported from: https://github.com/sourcegraph/go-lsp + +# go-lsp + +Package lsp contains Go types for the messages used in the Language Server +Protocol. + +See +https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md +for more information. diff --git a/lsp/jsonrpc2.go b/lsp/jsonrpc2.go new file mode 100644 index 0000000..11e2b86 --- /dev/null +++ b/lsp/jsonrpc2.go @@ -0,0 +1,52 @@ +package lsp + +import ( + "encoding/json" + "strconv" +) + +// ID represents a JSON-RPC 2.0 request ID, which may be either a +// string or number (or null, which is unsupported). +type ID struct { + // At most one of Num or Str may be nonzero. If both are zero + // valued, then IsNum specifies which field's value is to be used + // as the ID. + Num uint64 + Str string + + // IsString controls whether the Num or Str field's value should be + // used as the ID, when both are zero valued. It must always be + // set to true if the request ID is a string. + IsString bool +} + +func (id ID) String() string { + if id.IsString { + return strconv.Quote(id.Str) + } + return strconv.FormatUint(id.Num, 10) +} + +// MarshalJSON implements json.Marshaler. +func (id ID) MarshalJSON() ([]byte, error) { + if id.IsString { + return json.Marshal(id.Str) + } + return json.Marshal(id.Num) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (id *ID) UnmarshalJSON(data []byte) error { + // Support both uint64 and string IDs. + var v uint64 + if err := json.Unmarshal(data, &v); err == nil { + *id = ID{Num: v} + return nil + } + var v2 string + if err := json.Unmarshal(data, &v2); err != nil { + return err + } + *id = ID{Str: v2, IsString: true} + return nil +} diff --git a/handler/protocol.go b/lsp/protocol.go similarity index 72% rename from handler/protocol.go rename to lsp/protocol.go index 9e2cba8..5c6d09c 100644 --- a/handler/protocol.go +++ b/lsp/protocol.go @@ -1,41 +1,40 @@ -package handler +package lsp import ( "context" "encoding/json" - lsp "github.com/sourcegraph/go-lsp" "github.com/sourcegraph/jsonrpc2" ) -func readParams(method string, raw *json.RawMessage) (interface{}, error) { +func ReadParams(method string, raw *json.RawMessage) (interface{}, error) { switch method { case "initialize": - params := new(lsp.InitializeParams) + params := new(InitializeParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/didOpen": - params := new(lsp.DidOpenTextDocumentParams) + params := new(DidOpenTextDocumentParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/didChange": - params := new(lsp.DidChangeTextDocumentParams) + params := new(DidChangeTextDocumentParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/didSave": - params := new(lsp.DidSaveTextDocumentParams) + params := new(DidSaveTextDocumentParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/didClose": - params := new(lsp.DidCloseTextDocumentParams) + params := new(DidCloseTextDocumentParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/completion": - params := new(lsp.CompletionParams) + params := new(CompletionParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/codeAction": - params := new(lsp.CodeActionParams) + params := new(CodeActionParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/signatureHelp": @@ -51,43 +50,43 @@ func readParams(method string, raw *json.RawMessage) (interface{}, error) { case "textDocument/implementation": fallthrough case "textDocument/documentHighlight": - params := new(lsp.TextDocumentPositionParams) + params := new(TextDocumentPositionParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/references": - params := new(lsp.ReferenceParams) + params := new(ReferenceParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/formatting": - params := new(lsp.DocumentFormattingParams) + params := new(DocumentFormattingParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/rangeFormatting": - params := new(lsp.DocumentRangeFormattingParams) + params := new(DocumentRangeFormattingParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/onTypeFormatting": - params := new(lsp.DocumentOnTypeFormattingParams) + params := new(DocumentOnTypeFormattingParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/documentSymbol": - params := new(lsp.DocumentSymbolParams) + params := new(DocumentSymbolParams) err := json.Unmarshal(*raw, params) return params, err case "textDocument/rename": - params := new(lsp.RenameParams) + params := new(RenameParams) err := json.Unmarshal(*raw, params) return params, err case "workspace/symbol": - params := new(lsp.WorkspaceSymbolParams) + params := new(WorkspaceSymbolParams) err := json.Unmarshal(*raw, params) return params, err case "workspace/didChangeWatchedFiles": - params := new(lsp.DidChangeWatchedFilesParams) + params := new(DidChangeWatchedFilesParams) err := json.Unmarshal(*raw, params) return params, err case "workspace/executeCommand": - params := new(lsp.ExecuteCommandParams) + params := new(ExecuteCommandParams) err := json.Unmarshal(*raw, params) return params, err case "workspace/applyEdit": @@ -95,7 +94,7 @@ func readParams(method string, raw *json.RawMessage) (interface{}, error) { err := json.Unmarshal(*raw, params) return params, err case "textDocument/publishDiagnostics": - params := new(lsp.PublishDiagnosticsParams) + params := new(PublishDiagnosticsParams) err := json.Unmarshal(*raw, params) return params, err case "arduino/selectedBoard": @@ -106,26 +105,26 @@ func readParams(method string, raw *json.RawMessage) (interface{}, error) { return nil, nil } -func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params interface{}) (interface{}, error) { +func SendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params interface{}) (interface{}, error) { switch method { case "initialize": - result := new(lsp.InitializeResult) + result := new(InitializeResult) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/completion": - result := new(lsp.CompletionList) + result := new(CompletionList) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/codeAction": - result := new([]*commandOrCodeAction) + result := new([]*CommandOrCodeAction) err := conn.Call(ctx, method, params, result) return result, err case "completionItem/resolve": - result := new(lsp.CompletionItem) + result := new(CompletionItem) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/signatureHelp": - result := new(lsp.SignatureHelp) + result := new(SignatureHelp) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/hover": @@ -139,11 +138,11 @@ func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params case "textDocument/implementation": fallthrough case "textDocument/references": - result := new([]lsp.Location) + result := new([]Location) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/documentHighlight": - result := new([]lsp.DocumentHighlight) + result := new([]DocumentHighlight) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/formatting": @@ -151,23 +150,23 @@ func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params case "textDocument/rangeFormatting": fallthrough case "textDocument/onTypeFormatting": - result := new([]lsp.TextEdit) + result := new([]TextEdit) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/documentSymbol": - result := new([]*documentSymbolOrSymbolInformation) + result := new([]*DocumentSymbolOrSymbolInformation) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/rename": - result := new(lsp.WorkspaceEdit) + result := new(WorkspaceEdit) err := conn.Call(ctx, method, params, result) return result, err case "workspace/symbol": - result := new([]lsp.SymbolInformation) + result := new([]SymbolInformation) err := conn.Call(ctx, method, params, result) return result, err case "window/showMessageRequest": - result := new(lsp.MessageActionItem) + result := new(MessageActionItem) err := conn.Call(ctx, method, params, result) return result, err case "workspace/executeCommand": @@ -186,20 +185,20 @@ func sendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params // CodeAction structure according to LSP type CodeAction struct { - Title string `json:"title"` - Kind string `json:"kind,omitempty"` - Diagnostics []lsp.Diagnostic `json:"diagnostics,omitempty"` - Edit *lsp.WorkspaceEdit `json:"edit,omitempty"` - Command *lsp.Command `json:"command,omitempty"` + Title string `json:"title"` + Kind string `json:"kind,omitempty"` + Diagnostics []Diagnostic `json:"diagnostics,omitempty"` + Edit *WorkspaceEdit `json:"edit,omitempty"` + Command *Command `json:"command,omitempty"` } -type commandOrCodeAction struct { - Command *lsp.Command +type CommandOrCodeAction struct { + Command *Command CodeAction *CodeAction } -func (entry *commandOrCodeAction) UnmarshalJSON(raw []byte) error { - command := new(lsp.Command) +func (entry *CommandOrCodeAction) UnmarshalJSON(raw []byte) error { + command := new(Command) err := json.Unmarshal(raw, command) if err == nil && len(command.Command) > 0 { entry.Command = command @@ -214,7 +213,7 @@ func (entry *commandOrCodeAction) UnmarshalJSON(raw []byte) error { return nil } -func (entry *commandOrCodeAction) MarshalJSON() ([]byte, error) { +func (entry *CommandOrCodeAction) MarshalJSON() ([]byte, error) { if entry.Command != nil { return json.Marshal(entry.Command) } @@ -224,16 +223,16 @@ func (entry *commandOrCodeAction) MarshalJSON() ([]byte, error) { return nil, nil } -// HoverParams structure according to LSP -type HoverParams struct { - lsp.TextDocumentPositionParams - // lsp.WorkDoneProgressParams -} - // Hover structure according to LSP type Hover struct { Contents MarkupContent `json:"contents"` - Range *lsp.Range `json:"range,omitempty"` + Range *Range `json:"range,omitempty"` +} + +// HoverParams structure according to LSP +type HoverParams struct { + TextDocumentPositionParams + // WorkDoneProgressParams } // MarkupContent structure according to LSP @@ -246,24 +245,24 @@ type MarkupContent struct { type DocumentSymbol struct { Name string `json:"name"` Detail string `json:"detail,omitempty"` - Kind lsp.SymbolKind `json:"kind"` + Kind SymbolKind `json:"kind"` Deprecated bool `json:"deprecated,omitempty"` - Range lsp.Range `json:"range"` - SelectionRange lsp.Range `json:"selectionRange"` + Range Range `json:"range"` + SelectionRange Range `json:"selectionRange"` Children []DocumentSymbol `json:"children,omitempty"` } -type documentSymbolOrSymbolInformation struct { +type DocumentSymbolOrSymbolInformation struct { DocumentSymbol *DocumentSymbol - SymbolInformation *lsp.SymbolInformation + SymbolInformation *SymbolInformation } type documentSymbolOrSymbolInformationDiscriminator struct { - Range *lsp.Range `json:"range,omitempty"` - Location *lsp.Location `json:"location,omitempty"` + Range *Range `json:"range,omitempty"` + Location *Location `json:"location,omitempty"` } -func (entry *documentSymbolOrSymbolInformation) UnmarshalJSON(raw []byte) error { +func (entry *DocumentSymbolOrSymbolInformation) UnmarshalJSON(raw []byte) error { discriminator := new(documentSymbolOrSymbolInformationDiscriminator) err := json.Unmarshal(raw, discriminator) if err != nil { @@ -277,7 +276,7 @@ func (entry *documentSymbolOrSymbolInformation) UnmarshalJSON(raw []byte) error } } if discriminator.Location != nil { - entry.SymbolInformation = new(lsp.SymbolInformation) + entry.SymbolInformation = new(SymbolInformation) err = json.Unmarshal(raw, entry.SymbolInformation) if err != nil { return err @@ -288,8 +287,8 @@ func (entry *documentSymbolOrSymbolInformation) UnmarshalJSON(raw []byte) error // ApplyWorkspaceEditParams structure according to LSP type ApplyWorkspaceEditParams struct { - Label string `json:"label,omitempty"` - Edit lsp.WorkspaceEdit `json:"edit"` + Label string `json:"label,omitempty"` + Edit WorkspaceEdit `json:"edit"` } // ApplyWorkspaceEditResponse structure according to LSP diff --git a/lsp/service.go b/lsp/service.go new file mode 100644 index 0000000..d232c93 --- /dev/null +++ b/lsp/service.go @@ -0,0 +1,919 @@ +package lsp + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/json" + "strings" +) + +type None struct{} + +type InitializeParams struct { + ProcessID int `json:"processId,omitempty"` + + // RootPath is DEPRECATED in favor of the RootURI field. + RootPath string `json:"rootPath,omitempty"` + + RootURI DocumentURI `json:"rootUri,omitempty"` + ClientInfo ClientInfo `json:"clientInfo,omitempty"` + Trace Trace `json:"trace,omitempty"` + InitializationOptions interface{} `json:"initializationOptions,omitempty"` + Capabilities ClientCapabilities `json:"capabilities"` + + WorkDoneToken string `json:"workDoneToken,omitempty"` +} + +// Root returns the RootURI if set, or otherwise the RootPath with 'file://' prepended. +func (p *InitializeParams) Root() DocumentURI { + if p.RootURI != "" { + return p.RootURI + } + if strings.HasPrefix(p.RootPath, "file://") { + return DocumentURI(p.RootPath) + } + return DocumentURI("file://" + p.RootPath) +} + +type DocumentURI string + +type ClientInfo struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +type Trace string + +type ClientCapabilities struct { + Workspace WorkspaceClientCapabilities `json:"workspace,omitempty"` + TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"` + Window WindowClientCapabilities `json:"window,omitempty"` + Experimental interface{} `json:"experimental,omitempty"` + + // Below are Sourcegraph extensions. They do not live in lspext since + // they are extending the field InitializeParams.Capabilities + + // XFilesProvider indicates the client provides support for + // workspace/xfiles. This is a Sourcegraph extension. + XFilesProvider bool `json:"xfilesProvider,omitempty"` + + // XContentProvider indicates the client provides support for + // textDocument/xcontent. This is a Sourcegraph extension. + XContentProvider bool `json:"xcontentProvider,omitempty"` + + // XCacheProvider indicates the client provides support for cache/get + // and cache/set. + XCacheProvider bool `json:"xcacheProvider,omitempty"` +} + +type WorkspaceClientCapabilities struct { + WorkspaceEdit struct { + DocumentChanges bool `json:"documentChanges,omitempty"` + ResourceOperations []string `json:"resourceOperations,omitempty"` + } `json:"workspaceEdit,omitempty"` + + ApplyEdit bool `json:"applyEdit,omitempty"` + + Symbol struct { + SymbolKind struct { + ValueSet []int `json:"valueSet,omitempty"` + } `json:"symbolKind,omitEmpty"` + } `json:"symbol,omitempty"` + + ExecuteCommand *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"executeCommand,omitempty"` + + DidChangeWatchedFiles *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"didChangeWatchedFiles,omitempty"` + + WorkspaceFolders bool `json:"workspaceFolders,omitempty"` + + Configuration bool `json:"configuration,omitempty"` +} + +type TextDocumentClientCapabilities struct { + Declaration *struct { + LinkSupport bool `json:"linkSupport,omitempty"` + } `json:"declaration,omitempty"` + + Definition *struct { + LinkSupport bool `json:"linkSupport,omitempty"` + } `json:"definition,omitempty"` + + Implementation *struct { + LinkSupport bool `json:"linkSupport,omitempty"` + + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"implementation,omitempty"` + + TypeDefinition *struct { + LinkSupport bool `json:"linkSupport,omitempty"` + } `json:"typeDefinition,omitempty"` + + Synchronization *struct { + WillSave bool `json:"willSave,omitempty"` + DidSave bool `json:"didSave,omitempty"` + WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"` + } `json:"synchronization,omitempty"` + + DocumentSymbol struct { + SymbolKind struct { + ValueSet []int `json:"valueSet,omitempty"` + } `json:"symbolKind,omitEmpty"` + + HierarchicalDocumentSymbolSupport bool `json:"hierarchicalDocumentSymbolSupport,omitempty"` + } `json:"documentSymbol,omitempty"` + + Formatting *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"formatting,omitempty"` + + RangeFormatting *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"rangeFormatting,omitempty"` + + Rename *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + + PrepareSupport bool `json:"prepareSupport,omitempty"` + } `json:"rename,omitempty"` + + SemanticHighlightingCapabilities *struct { + SemanticHighlighting bool `json:"semanticHighlighting,omitempty"` + } `json:"semanticHighlightingCapabilities,omitempty"` + + CodeAction struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + + IsPreferredSupport bool `json:"isPreferredSupport,omitempty"` + + CodeActionLiteralSupport struct { + CodeActionKind struct { + ValueSet []CodeActionKind `json:"valueSet,omitempty"` + } `json:"codeActionKind,omitempty"` + } `json:"codeActionLiteralSupport,omitempty"` + } `json:"codeAction,omitempty"` + + Completion struct { + CompletionItem struct { + DocumentationFormat []DocumentationFormat `json:"documentationFormat,omitempty"` + SnippetSupport bool `json:"snippetSupport,omitempty"` + } `json:"completionItem,omitempty"` + + CompletionItemKind struct { + ValueSet []CompletionItemKind `json:"valueSet,omitempty"` + } `json:"completionItemKind,omitempty"` + + ContextSupport bool `json:"contextSupport,omitempty"` + } `json:"completion,omitempty"` + + SignatureHelp *struct { + SignatureInformation struct { + ParameterInformation struct { + LabelOffsetSupport bool `json:"labelOffsetSupport,omitempty"` + } `json:"parameterInformation,omitempty"` + } `json:"signatureInformation,omitempty"` + } `json:"signatureHelp,omitempty"` + + DocumentLink *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + + TooltipSupport bool `json:"tooltipSupport,omitempty"` + } `json:"documentLink,omitempty"` + + Hover *struct { + ContentFormat []string `json:"contentFormat,omitempty"` + } `json:"hover,omitempty"` + + FoldingRange *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + + RangeLimit interface{} `json:"rangeLimit,omitempty"` + + LineFoldingOnly bool `json:"lineFoldingOnly,omitempty"` + } `json:"foldingRange,omitempty"` + + CallHierarchy *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"callHierarchy,omitempty"` + + ColorProvider *struct { + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + } `json:"colorProvider,omitempty"` +} + +type WindowClientCapabilities struct { + WorkDoneProgress bool `json:"workDoneProgress,omitempty"` +} + +type InitializeResult struct { + Capabilities ServerCapabilities `json:"capabilities,omitempty"` +} + +type InitializeError struct { + Retry bool `json:"retry"` +} + +type ResourceOperation string + +const ( + ROCreate ResourceOperation = "create" + RODelete ResourceOperation = "delete" + RORename ResourceOperation = "rename" +) + +// TextDocumentSyncKind is a DEPRECATED way to describe how text +// document syncing works. Use TextDocumentSyncOptions instead (or the +// Options field of TextDocumentSyncOptionsOrKind if you need to +// support JSON-(un)marshaling both). +type TextDocumentSyncKind int + +const ( + TDSKNone TextDocumentSyncKind = 0 + TDSKFull TextDocumentSyncKind = 1 + TDSKIncremental TextDocumentSyncKind = 2 +) + +type TextDocumentSyncOptions struct { + OpenClose bool `json:"openClose,omitempty"` + Change TextDocumentSyncKind `json:"change"` + WillSave bool `json:"willSave,omitempty"` + WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"` + Save *SaveOptions `json:"save,omitempty"` +} + +// TextDocumentSyncOptions holds either a TextDocumentSyncKind or +// TextDocumentSyncOptions. The LSP API allows either to be specified +// in the (ServerCapabilities).TextDocumentSync field. +type TextDocumentSyncOptionsOrKind struct { + Kind *TextDocumentSyncKind + Options *TextDocumentSyncOptions +} + +// MarshalJSON implements json.Marshaler. +func (v *TextDocumentSyncOptionsOrKind) MarshalJSON() ([]byte, error) { + if v == nil { + return []byte("null"), nil + } + if v.Kind != nil { + return json.Marshal(v.Kind) + } + return json.Marshal(v.Options) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (v *TextDocumentSyncOptionsOrKind) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, []byte("null")) { + *v = TextDocumentSyncOptionsOrKind{} + return nil + } + var kind TextDocumentSyncKind + if err := json.Unmarshal(data, &kind); err == nil { + // Create equivalent TextDocumentSyncOptions using the same + // logic as in vscode-languageclient. Also set the Kind field + // so that JSON-marshaling and unmarshaling are inverse + // operations (for backward compatibility, preserving the + // original input but accepting both). + *v = TextDocumentSyncOptionsOrKind{ + Options: &TextDocumentSyncOptions{OpenClose: true, Change: kind}, + Kind: &kind, + } + return nil + } + var tmp TextDocumentSyncOptions + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + *v = TextDocumentSyncOptionsOrKind{Options: &tmp} + return nil +} + +type SaveOptions struct { + IncludeText bool `json:"includeText"` +} + +type ServerCapabilities struct { + TextDocumentSync *TextDocumentSyncOptionsOrKind `json:"textDocumentSync,omitempty"` + HoverProvider bool `json:"hoverProvider,omitempty"` + CompletionProvider *CompletionOptions `json:"completionProvider,omitempty"` + SignatureHelpProvider *SignatureHelpOptions `json:"signatureHelpProvider,omitempty"` + DefinitionProvider bool `json:"definitionProvider,omitempty"` + TypeDefinitionProvider bool `json:"typeDefinitionProvider,omitempty"` + ReferencesProvider bool `json:"referencesProvider,omitempty"` + DocumentHighlightProvider bool `json:"documentHighlightProvider,omitempty"` + DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"` + WorkspaceSymbolProvider bool `json:"workspaceSymbolProvider,omitempty"` + ImplementationProvider bool `json:"implementationProvider,omitempty"` + CodeActionProvider bool `json:"codeActionProvider,omitempty"` + CodeLensProvider *CodeLensOptions `json:"codeLensProvider,omitempty"` + DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"` + DocumentRangeFormattingProvider bool `json:"documentRangeFormattingProvider,omitempty"` + DocumentOnTypeFormattingProvider *DocumentOnTypeFormattingOptions `json:"documentOnTypeFormattingProvider,omitempty"` + RenameProvider bool `json:"renameProvider,omitempty"` + ExecuteCommandProvider *ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` + SemanticHighlighting *SemanticHighlightingOptions `json:"semanticHighlighting,omitempty"` + + // XWorkspaceReferencesProvider indicates the server provides support for + // xworkspace/references. This is a Sourcegraph extension. + XWorkspaceReferencesProvider bool `json:"xworkspaceReferencesProvider,omitempty"` + + // XDefinitionProvider indicates the server provides support for + // textDocument/xdefinition. This is a Sourcegraph extension. + XDefinitionProvider bool `json:"xdefinitionProvider,omitempty"` + + // XWorkspaceSymbolByProperties indicates the server provides support for + // querying symbols by properties with WorkspaceSymbolParams.symbol. This + // is a Sourcegraph extension. + XWorkspaceSymbolByProperties bool `json:"xworkspaceSymbolByProperties,omitempty"` + + Experimental interface{} `json:"experimental,omitempty"` +} + +type CompletionOptions struct { + ResolveProvider bool `json:"resolveProvider,omitempty"` + TriggerCharacters []string `json:"triggerCharacters,omitempty"` +} + +type DocumentOnTypeFormattingOptions struct { + FirstTriggerCharacter string `json:"firstTriggerCharacter"` + MoreTriggerCharacter []string `json:"moreTriggerCharacter,omitempty"` +} + +type CodeLensOptions struct { + ResolveProvider bool `json:"resolveProvider,omitempty"` +} + +type SignatureHelpOptions struct { + TriggerCharacters []string `json:"triggerCharacters,omitempty"` +} + +type ExecuteCommandOptions struct { + Commands []string `json:"commands"` +} + +type ExecuteCommandParams struct { + Command string `json:"command"` + Arguments []interface{} `json:"arguments,omitempty"` +} + +type SemanticHighlightingOptions struct { + Scopes [][]string `json:"scopes,omitempty"` +} + +type CompletionItemKind int + +const ( + _ CompletionItemKind = iota + CIKText + CIKMethod + CIKFunction + CIKConstructor + CIKField + CIKVariable + CIKClass + CIKInterface + CIKModule + CIKProperty + CIKUnit + CIKValue + CIKEnum + CIKKeyword + CIKSnippet + CIKColor + CIKFile + CIKReference + CIKFolder + CIKEnumMember + CIKConstant + CIKStruct + CIKEvent + CIKOperator + CIKTypeParameter +) + +func (c CompletionItemKind) String() string { + return completionItemKindName[c] +} + +var completionItemKindName = map[CompletionItemKind]string{ + CIKText: "text", + CIKMethod: "method", + CIKFunction: "function", + CIKConstructor: "constructor", + CIKField: "field", + CIKVariable: "variable", + CIKClass: "class", + CIKInterface: "interface", + CIKModule: "module", + CIKProperty: "property", + CIKUnit: "unit", + CIKValue: "value", + CIKEnum: "enum", + CIKKeyword: "keyword", + CIKSnippet: "snippet", + CIKColor: "color", + CIKFile: "file", + CIKReference: "reference", + CIKFolder: "folder", + CIKEnumMember: "enumMember", + CIKConstant: "constant", + CIKStruct: "struct", + CIKEvent: "event", + CIKOperator: "operator", + CIKTypeParameter: "typeParameter", +} + +type CompletionItem struct { + Label string `json:"label"` + Kind CompletionItemKind `json:"kind,omitempty"` + Detail string `json:"detail,omitempty"` + Documentation string `json:"documentation,omitempty"` + SortText string `json:"sortText,omitempty"` + FilterText string `json:"filterText,omitempty"` + InsertText string `json:"insertText,omitempty"` + InsertTextFormat InsertTextFormat `json:"insertTextFormat,omitempty"` + TextEdit *TextEdit `json:"textEdit,omitempty"` + Data interface{} `json:"data,omitempty"` +} + +type CompletionList struct { + IsIncomplete bool `json:"isIncomplete"` + Items []CompletionItem `json:"items"` +} + +type CompletionTriggerKind int + +const ( + CTKInvoked CompletionTriggerKind = 1 + CTKTriggerCharacter = 2 +) + +type DocumentationFormat string + +const ( + DFPlainText DocumentationFormat = "plaintext" +) + +type CodeActionKind string + +const ( + CAKEmpty CodeActionKind = "" + CAKQuickFix CodeActionKind = "quickfix" + CAKRefactor CodeActionKind = "refactor" + CAKRefactorExtract CodeActionKind = "refactor.extract" + CAKRefactorInline CodeActionKind = "refactor.inline" + CAKRefactorRewrite CodeActionKind = "refactor.rewrite" + CAKSource CodeActionKind = "source" + CAKSourceOrganizeImports CodeActionKind = "source.organizeImports" +) + +type InsertTextFormat int + +const ( + ITFPlainText InsertTextFormat = 1 + ITFSnippet = 2 +) + +type CompletionContext struct { + TriggerKind CompletionTriggerKind `json:"triggerKind"` + TriggerCharacter string `json:"triggerCharacter,omitempty"` +} + +type CompletionParams struct { + TextDocumentPositionParams + Context CompletionContext `json:"context,omitempty"` +} + +// type Hover struct { +// Contents []MarkedString `json:"contents"` +// Range *Range `json:"range,omitempty"` +// } + +// type hover Hover + +// func (h Hover) MarshalJSON() ([]byte, error) { +// if h.Contents == nil { +// return json.Marshal(hover{ +// Contents: []MarkedString{}, +// Range: h.Range, +// }) +// } +// return json.Marshal(hover(h)) +// } + +type MarkedString markedString + +type markedString struct { + Language string `json:"language"` + Value string `json:"value"` + + isRawString bool +} + +func (m *MarkedString) UnmarshalJSON(data []byte) error { + if d := strings.TrimSpace(string(data)); len(d) > 0 && d[0] == '"' { + // Raw string + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + m.Value = s + m.isRawString = true + return nil + } + // Language string + ms := (*markedString)(m) + return json.Unmarshal(data, ms) +} + +func (m MarkedString) MarshalJSON() ([]byte, error) { + if m.isRawString { + return json.Marshal(m.Value) + } + return json.Marshal((markedString)(m)) +} + +// RawMarkedString returns a MarkedString consisting of only a raw +// string (i.e., "foo" instead of {"value":"foo", "language":"bar"}). +func RawMarkedString(s string) MarkedString { + return MarkedString{Value: s, isRawString: true} +} + +type SignatureHelp struct { + Signatures []SignatureInformation `json:"signatures"` + ActiveSignature int `json:"activeSignature"` + ActiveParameter int `json:"activeParameter"` +} + +type SignatureInformation struct { + Label string `json:"label"` + Documentation string `json:"documentation,omitempty"` + Parameters []ParameterInformation `json:"parameters,omitempty"` +} + +type ParameterInformation struct { + Label string `json:"label"` + Documentation string `json:"documentation,omitempty"` +} + +type ReferenceContext struct { + IncludeDeclaration bool `json:"includeDeclaration"` + + // Sourcegraph extension + XLimit int `json:"xlimit,omitempty"` +} + +type ReferenceParams struct { + TextDocumentPositionParams + Context ReferenceContext `json:"context"` +} + +type DocumentHighlightKind int + +const ( + Text DocumentHighlightKind = 1 + Read = 2 + Write = 3 +) + +type DocumentHighlight struct { + Range Range `json:"range"` + Kind int `json:"kind,omitempty"` +} + +type DocumentSymbolParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` +} + +type SymbolKind int + +// The SymbolKind values are defined at https://microsoft.github.io/language-server-protocol/specification. +const ( + SKFile SymbolKind = 1 + SKModule SymbolKind = 2 + SKNamespace SymbolKind = 3 + SKPackage SymbolKind = 4 + SKClass SymbolKind = 5 + SKMethod SymbolKind = 6 + SKProperty SymbolKind = 7 + SKField SymbolKind = 8 + SKConstructor SymbolKind = 9 + SKEnum SymbolKind = 10 + SKInterface SymbolKind = 11 + SKFunction SymbolKind = 12 + SKVariable SymbolKind = 13 + SKConstant SymbolKind = 14 + SKString SymbolKind = 15 + SKNumber SymbolKind = 16 + SKBoolean SymbolKind = 17 + SKArray SymbolKind = 18 + SKObject SymbolKind = 19 + SKKey SymbolKind = 20 + SKNull SymbolKind = 21 + SKEnumMember SymbolKind = 22 + SKStruct SymbolKind = 23 + SKEvent SymbolKind = 24 + SKOperator SymbolKind = 25 + SKTypeParameter SymbolKind = 26 +) + +func (s SymbolKind) String() string { + return symbolKindName[s] +} + +var symbolKindName = map[SymbolKind]string{ + SKFile: "File", + SKModule: "Module", + SKNamespace: "Namespace", + SKPackage: "Package", + SKClass: "Class", + SKMethod: "Method", + SKProperty: "Property", + SKField: "Field", + SKConstructor: "Constructor", + SKEnum: "Enum", + SKInterface: "Interface", + SKFunction: "Function", + SKVariable: "Variable", + SKConstant: "Constant", + SKString: "String", + SKNumber: "Number", + SKBoolean: "Boolean", + SKArray: "Array", + SKObject: "Object", + SKKey: "Key", + SKNull: "Null", + SKEnumMember: "EnumMember", + SKStruct: "Struct", + SKEvent: "Event", + SKOperator: "Operator", + SKTypeParameter: "TypeParameter", +} + +type SymbolInformation struct { + Name string `json:"name"` + Kind SymbolKind `json:"kind"` + Location Location `json:"location"` + ContainerName string `json:"containerName,omitempty"` +} + +type WorkspaceSymbolParams struct { + Query string `json:"query"` + Limit int `json:"limit"` +} + +type ConfigurationParams struct { + Items []ConfigurationItem `json:"items"` +} + +type ConfigurationItem struct { + ScopeURI string `json:"scopeUri,omitempty"` + Section string `json:"section,omitempty"` +} + +type ConfigurationResult []interface{} + +type CodeActionContext struct { + Diagnostics []Diagnostic `json:"diagnostics"` +} + +type CodeActionParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` + Range Range `json:"range"` + Context CodeActionContext `json:"context"` +} + +type CodeLensParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` +} + +type CodeLens struct { + Range Range `json:"range"` + Command Command `json:"command,omitempty"` + Data interface{} `json:"data,omitempty"` +} + +type DocumentFormattingParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` + Options FormattingOptions `json:"options"` +} + +type FormattingOptions struct { + TabSize int `json:"tabSize"` + InsertSpaces bool `json:"insertSpaces"` + Key string `json:"key"` +} + +type RenameParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` + Position Position `json:"position"` + NewName string `json:"newName"` +} + +type DidOpenTextDocumentParams struct { + TextDocument TextDocumentItem `json:"textDocument"` +} + +type DidChangeTextDocumentParams struct { + TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` + ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"` +} + +type TextDocumentContentChangeEvent struct { + Range *Range `json:"range,omitEmpty"` + RangeLength uint `json:"rangeLength,omitEmpty"` + Text string `json:"text"` +} + +type DidCloseTextDocumentParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` +} + +type DidSaveTextDocumentParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` +} + +type MessageType int + +const ( + MTError MessageType = 1 + MTWarning = 2 + Info = 3 + Log = 4 +) + +type ShowMessageParams struct { + Type MessageType `json:"type"` + Message string `json:"message"` +} + +type MessageActionItem struct { + Title string `json:"title"` +} + +type ShowMessageRequestParams struct { + Type MessageType `json:"type"` + Message string `json:"message"` + Actions []MessageActionItem `json:"actions"` +} + +type LogMessageParams struct { + Type MessageType `json:"type"` + Message string `json:"message"` +} + +type DidChangeConfigurationParams struct { + Settings interface{} `json:"settings"` +} + +type FileChangeType int + +const ( + Created FileChangeType = 1 + Changed = 2 + Deleted = 3 +) + +type FileEvent struct { + URI DocumentURI `json:"uri"` + Type int `json:"type"` +} + +type DidChangeWatchedFilesParams struct { + Changes []FileEvent `json:"changes"` +} + +type PublishDiagnosticsParams struct { + URI DocumentURI `json:"uri"` + Diagnostics []Diagnostic `json:"diagnostics"` +} + +type DocumentRangeFormattingParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` + Range Range `json:"range"` + Options FormattingOptions `json:"options"` +} + +type DocumentOnTypeFormattingParams struct { + TextDocument TextDocumentIdentifier `json:"textDocument"` + Position Position `json:"position"` + Ch string `json:"ch"` + Options FormattingOptions `json:"formattingOptions"` +} + +type CancelParams struct { + ID ID `json:"id"` +} + +type SemanticHighlightingParams struct { + TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` + Lines []SemanticHighlightingInformation `json:"lines"` +} + +// SemanticHighlightingInformation represents a semantic highlighting +// information that has to be applied on a specific line of the text +// document. +type SemanticHighlightingInformation struct { + // Line is the zero-based line position in the text document. + Line int `json:"line"` + + // Tokens is a base64 encoded string representing every single highlighted + // characters with its start position, length and the "lookup table" index of + // the semantic highlighting [TextMate scopes](https://manual.macromates.com/en/language_grammars). + // If the `tokens` is empty or not defined, then no highlighted positions are + // available for the line. + Tokens SemanticHighlightingTokens `json:"tokens,omitempty"` +} + +type semanticHighlightingInformation struct { + Line int `json:"line"` + Tokens *string `json:"tokens"` +} + +// MarshalJSON implements json.Marshaler. +func (v *SemanticHighlightingInformation) MarshalJSON() ([]byte, error) { + tokens := string(v.Tokens.Serialize()) + return json.Marshal(&semanticHighlightingInformation{ + Line: v.Line, + Tokens: &tokens, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (v *SemanticHighlightingInformation) UnmarshalJSON(data []byte) error { + var info semanticHighlightingInformation + err := json.Unmarshal(data, &info) + if err != nil { + return err + } + + if info.Tokens != nil { + v.Tokens, err = DeserializeSemanticHighlightingTokens([]byte(*info.Tokens)) + if err != nil { + return err + } + } + + v.Line = info.Line + return nil +} + +type SemanticHighlightingTokens []SemanticHighlightingToken + +func (v SemanticHighlightingTokens) Serialize() []byte { + var chunks [][]byte + + // Writes each token to `tokens` in the byte format specified by the LSP + // proposal. Described below: + // |<---- 4 bytes ---->|<-- 2 bytes -->|<--- 2 bytes -->| + // | character | length | index | + for _, token := range v { + chunk := make([]byte, 8) + binary.BigEndian.PutUint32(chunk[:4], token.Character) + binary.BigEndian.PutUint16(chunk[4:6], token.Length) + binary.BigEndian.PutUint16(chunk[6:], token.Scope) + chunks = append(chunks, chunk) + } + + src := make([]byte, len(chunks)*8) + for i, chunk := range chunks { + copy(src[i*8:i*8+8], chunk) + } + + dst := make([]byte, base64.StdEncoding.EncodedLen(len(src))) + base64.StdEncoding.Encode(dst, src) + return dst +} + +func DeserializeSemanticHighlightingTokens(src []byte) (SemanticHighlightingTokens, error) { + dst := make([]byte, base64.StdEncoding.DecodedLen(len(src))) + n, err := base64.StdEncoding.Decode(dst, src) + if err != nil { + return nil, err + } + + var chunks [][]byte + for i := 7; i < len(dst[:n]); i += 8 { + chunks = append(chunks, dst[i-7:i+1]) + } + + var tokens SemanticHighlightingTokens + for _, chunk := range chunks { + tokens = append(tokens, SemanticHighlightingToken{ + Character: binary.BigEndian.Uint32(chunk[:4]), + Length: binary.BigEndian.Uint16(chunk[4:6]), + Scope: binary.BigEndian.Uint16(chunk[6:]), + }) + } + + return tokens, nil +} + +type SemanticHighlightingToken struct { + Character uint32 + Length uint16 + Scope uint16 +} diff --git a/lsp/structures.go b/lsp/structures.go new file mode 100644 index 0000000..6d950f8 --- /dev/null +++ b/lsp/structures.go @@ -0,0 +1,165 @@ +package lsp + +import "fmt" + +type Position struct { + /** + * Line position in a document (zero-based). + */ + Line int `json:"line"` + + /** + * Character offset on a line in a document (zero-based). + */ + Character int `json:"character"` +} + +func (p Position) String() string { + return fmt.Sprintf("%d:%d", p.Line, p.Character) +} + +type Range struct { + /** + * The range's start position. + */ + Start Position `json:"start"` + + /** + * The range's end position. + */ + End Position `json:"end"` +} + +func (r Range) String() string { + return fmt.Sprintf("%s-%s", r.Start, r.End) +} + +type Location struct { + URI DocumentURI `json:"uri"` + Range Range `json:"range"` +} + +type Diagnostic struct { + /** + * The range at which the message applies. + */ + Range Range `json:"range"` + + /** + * The diagnostic's severity. Can be omitted. If omitted it is up to the + * client to interpret diagnostics as error, warning, info or hint. + */ + Severity DiagnosticSeverity `json:"severity,omitempty"` + + /** + * The diagnostic's code. Can be omitted. + */ + Code string `json:"code,omitempty"` + + /** + * A human-readable string describing the source of this + * diagnostic, e.g. 'typescript' or 'super lint'. + */ + Source string `json:"source,omitempty"` + + /** + * The diagnostic's message. + */ + Message string `json:"message"` +} + +type DiagnosticSeverity int + +const ( + Error DiagnosticSeverity = 1 + Warning = 2 + Information = 3 + Hint = 4 +) + +type Command struct { + /** + * Title of the command, like `save`. + */ + Title string `json:"title"` + /** + * The identifier of the actual command handler. + */ + Command string `json:"command"` + /** + * Arguments that the command handler should be + * invoked with. + */ + Arguments []interface{} `json:"arguments"` +} + +type TextEdit struct { + /** + * The range of the text document to be manipulated. To insert + * text into a document create a range where start === end. + */ + Range Range `json:"range"` + + /** + * The string to be inserted. For delete operations use an + * empty string. + */ + NewText string `json:"newText"` +} + +type WorkspaceEdit struct { + /** + * Holds changes to existing resources. + */ + Changes map[string][]TextEdit `json:"changes"` +} + +type TextDocumentIdentifier struct { + /** + * The text document's URI. + */ + URI DocumentURI `json:"uri"` +} + +type TextDocumentItem struct { + /** + * The text document's URI. + */ + URI DocumentURI `json:"uri"` + + /** + * The text document's language identifier. + */ + LanguageID string `json:"languageId"` + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + Version int `json:"version"` + + /** + * The content of the opened text document. + */ + Text string `json:"text"` +} + +type VersionedTextDocumentIdentifier struct { + TextDocumentIdentifier + /** + * The version number of this document. + */ + Version int `json:"version"` +} + +type TextDocumentPositionParams struct { + /** + * The text document. + */ + TextDocument TextDocumentIdentifier `json:"textDocument"` + + /** + * The position inside the text document. + */ + Position Position `json:"position"` +} diff --git a/main.go b/main.go index 7e7d487..ebf145f 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "github.com/arduino/go-paths-helper" "github.com/bcmi-labs/arduino-language-server/handler" + "github.com/bcmi-labs/arduino-language-server/lsp" "github.com/bcmi-labs/arduino-language-server/streams" ) @@ -52,7 +53,7 @@ func main() { } handler.Setup(cliPath, clangdPath, enableLogging, true) - initialBoard := handler.Board{Fqbn: initialFqbn, Name: initialBoardName} + initialBoard := lsp.Board{Fqbn: initialFqbn, Name: initialBoardName} stdio := streams.NewReadWriteCloser(os.Stdin, os.Stdout) if enableLogging { From 049d19fc5936678e71c024df5b9d79382b020881 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 25 Nov 2020 01:06:14 +0100 Subject: [PATCH 19/61] Moved URI helper in lsp module --- handler/handler.go | 28 ++++++++++++++-------------- {handler => lsp}/uri.go | 26 ++++++++++++++------------ {handler => lsp}/uri_test.go | 20 +++++++++----------- 3 files changed, 37 insertions(+), 37 deletions(-) rename {handler => lsp}/uri.go (61%) rename {handler => lsp}/uri_test.go (58%) diff --git a/handler/handler.go b/handler/handler.go index 2d22ebd..f07da9e 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -260,15 +260,11 @@ func (handler *InoHandler) exit() { os.Exit(1) } -func newPathFromURI(uri lsp.DocumentURI) *paths.Path { - return paths.New(uriToPath(uri)) -} - func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp.InitializeParams) error { rootURI := params.RootURI log.Printf("--> initializeWorkbench(%s)\n", rootURI) - handler.sketchRoot = newPathFromURI(rootURI) + handler.sketchRoot = rootURI.AsPath() handler.sketchName = handler.sketchRoot.Base() if buildPath, err := generateBuildEnvironment(handler.sketchRoot, handler.config.SelectedBoard.Fqbn); err == nil { handler.buildPath = buildPath @@ -298,7 +294,7 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp. handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) params.RootPath = handler.buildSketchRoot.String() - params.RootURI = pathToURI(handler.buildSketchRoot.String()) + params.RootURI = lsp.NewDocumenteURIFromPath(handler.buildSketchRoot) return nil } @@ -353,7 +349,7 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD log.Printf("--> didOpen(%s)", doc.URI) // If we are tracking a .ino... - if newPathFromURI(doc.URI).Ext() == ".ino" { + if doc.URI.AsPath().Ext() == ".ino" { handler.sketchTrackedFilesCount++ log.Printf(" increasing .ino tracked files count: %d", handler.sketchTrackedFilesCount) @@ -362,7 +358,7 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD sketchCpp, err := handler.buildSketchCpp.ReadFile() newParam := &lsp.DidOpenTextDocumentParams{ TextDocument: lsp.TextDocumentItem{ - URI: pathToURI(handler.buildSketchCpp.String()), + URI: lsp.NewDocumenteURIFromPath(handler.buildSketchCpp), Text: string(sketchCpp), LanguageID: "cpp", Version: 1, @@ -388,11 +384,11 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c return err } } - targetBytes, err := updateCpp([]byte(newSourceText), uriToPath(data.sourceURI), handler.config.SelectedBoard.Fqbn, false, uriToPath(data.targetURI)) + targetBytes, err := updateCpp([]byte(newSourceText), data.sourceURI.Unbox(), handler.config.SelectedBoard.Fqbn, false, data.targetURI.Unbox()) if err != nil { if rang == nil { // Fallback: use the source text unchanged - targetBytes, err = copyIno2Cpp(newSourceText, uriToPath(data.targetURI)) + targetBytes, err = copyIno2Cpp(newSourceText, data.targetURI.Unbox()) if err != nil { return err } @@ -473,7 +469,7 @@ func (handler *InoHandler) sketchToBuildPathTextDocumentIdentifier(doc *lsp.Text // another/path/source.cpp -> unchanged // Convert sketch path to build path - docFile := newPathFromURI(doc.URI) + docFile := doc.URI.AsPath() newDocFile := docFile if docFile.Ext() == ".ino" { @@ -490,7 +486,7 @@ func (handler *InoHandler) sketchToBuildPathTextDocumentIdentifier(doc *lsp.Text newDocFile = handler.buildSketchRoot.JoinPath(rel) } log.Printf(" URI: '%s' -> '%s'", docFile, newDocFile) - doc.URI = pathToURI(newDocFile.String()) + doc.URI = lsp.NewDocumenteURIFromPath(newDocFile) return nil } @@ -841,7 +837,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) } - if newPathFromURI(p.URI).EquivalentTo(handler.buildSketchCpp) { + if p.URI.AsPath().EquivalentTo(handler.buildSketchCpp) { // we should transform back N diagnostics of sketch.cpp.ino into // their .ino counter parts (that may span over multiple files...) @@ -860,7 +856,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. // Push back to IDE the converted diagnostics for filename, inoDiags := range convertedDiagnostics { msg := lsp.PublishDiagnosticsParams{ - URI: pathToURI(filename), + URI: lsp.NewDocumentURI(filename), Diagnostics: inoDiags, } if enableLogging { @@ -936,3 +932,7 @@ func (handler *InoHandler) showMessage(ctx context.Context, msgType lsp.MessageT } handler.StdioConn.Notify(ctx, "window/showMessage", ¶ms) } + +func unknownURI(uri lsp.DocumentURI) error { + return errors.New("Document is not available: " + string(uri)) +} diff --git a/handler/uri.go b/lsp/uri.go similarity index 61% rename from handler/uri.go rename to lsp/uri.go index 8c925a1..9c315a3 100644 --- a/handler/uri.go +++ b/lsp/uri.go @@ -1,4 +1,4 @@ -package handler +package lsp import ( "net/url" @@ -8,13 +8,17 @@ import ( "strings" "github.com/arduino/go-paths-helper" - "github.com/bcmi-labs/arduino-language-server/lsp" - "github.com/pkg/errors" ) var expDriveID = regexp.MustCompile("[a-zA-Z]:") -func uriToPath(uri lsp.DocumentURI) string { +// AsPath convert the DocumentURI to a paths.Path +func (uri DocumentURI) AsPath() *paths.Path { + return paths.New(uri.Unbox()) +} + +// Unbox convert the DocumentURI to a file path string +func (uri DocumentURI) Unbox() string { urlObj, err := url.Parse(string(uri)) if err != nil { return string(uri) @@ -35,11 +39,13 @@ func uriToPath(uri lsp.DocumentURI) string { return path } -func ToURI(path *paths.Path) lsp.DocumentURI { - return pathToURI(path.String()) +// NewDocumenteURIFromPath create a DocumentURI from the given Path object +func NewDocumenteURIFromPath(path *paths.Path) DocumentURI { + return NewDocumentURI(path.String()) } -func pathToURI(path string) lsp.DocumentURI { +// NewDocumentURI create a DocumentURI from the given string path +func NewDocumentURI(path string) DocumentURI { urlObj, err := url.Parse("file://") if err != nil { panic(err) @@ -50,9 +56,5 @@ func pathToURI(path string) lsp.DocumentURI { urlObj.Path += "/" + url.PathEscape(segment) } } - return lsp.DocumentURI(urlObj.String()) -} - -func unknownURI(uri lsp.DocumentURI) error { - return errors.New("Document is not available: " + string(uri)) + return DocumentURI(urlObj.String()) } diff --git a/handler/uri_test.go b/lsp/uri_test.go similarity index 58% rename from handler/uri_test.go rename to lsp/uri_test.go index d843646..98e5fb3 100644 --- a/handler/uri_test.go +++ b/lsp/uri_test.go @@ -1,50 +1,48 @@ -package handler +package lsp import ( "path/filepath" "runtime" "testing" - - "github.com/bcmi-labs/arduino-language-server/lsp" ) func TestUriToPath(t *testing.T) { var path string if runtime.GOOS == "windows" { - path = uriToPath(lsp.DocumentURI("file:///C:/Users/test/Sketch.ino")) + path = DocumentURI("file:///C:/Users/test/Sketch.ino").Unbox() if path != "C:\\Users\\test\\Sketch.ino" { t.Error(path) } - path = uriToPath(lsp.DocumentURI("file:///c%3A/Users/test/Sketch.ino")) + path = DocumentURI("file:///c%3A/Users/test/Sketch.ino").Unbox() if path != "C:\\Users\\test\\Sketch.ino" { t.Error(path) } } else { - path = uriToPath(lsp.DocumentURI("file:///Users/test/Sketch.ino")) + path = DocumentURI("file:///Users/test/Sketch.ino").Unbox() if path != "/Users/test/Sketch.ino" { t.Error(path) } } - path = uriToPath(lsp.DocumentURI("file:///%25F0%259F%2598%259B")) + path = DocumentURI("file:///%25F0%259F%2598%259B").Unbox() if path != string(filepath.Separator)+"\U0001F61B" { t.Error(path) } } func TestPathToUri(t *testing.T) { - var uri lsp.DocumentURI + var uri DocumentURI if runtime.GOOS == "windows" { - uri = pathToURI("C:\\Users\\test\\Sketch.ino") + uri = NewDocumentURI("C:\\Users\\test\\Sketch.ino") if uri != "file:///C:/Users/test/Sketch.ino" { t.Error(uri) } } else { - uri = pathToURI("/Users/test/Sketch.ino") + uri = NewDocumentURI("/Users/test/Sketch.ino") if uri != "file:///Users/test/Sketch.ino" { t.Error(uri) } } - uri = pathToURI("\U0001F61B") + uri = NewDocumentURI("\U0001F61B") if uri != "file:///%25F0%259F%2598%259B" { t.Error(uri) } From 23b6d9c09ab06fd2c594ff14c29ef0d29c82d1b1 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 25 Nov 2020 13:14:58 +0100 Subject: [PATCH 20/61] slightly improved logging --- handler/handler.go | 14 +++++++++----- handler/syncer.go | 5 ++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index f07da9e..92444fa 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -129,18 +129,24 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr handler.synchronizer.DataMux.RUnlock() case *lsp.DidOpenTextDocumentParams: - // method "textDocument/didOpen": + // method "textDocument/didOpen" uri = p.TextDocument.URI + log.Printf("--> didOpen(%s)", uri) + handler.synchronizer.DataMux.Lock() res, err := handler.didOpen(ctx, p) handler.synchronizer.DataMux.Unlock() + if res == nil { - log.Println(" notification is not propagated to clangd") + log.Println(" --X notification is not propagated to clangd") return nil, err // do not propagate to clangd } + + log.Printf(" --> didOpen(%s)", res.TextDocument.URI) params = res - case *lsp.CompletionParams: // "textDocument/completion": + case *lsp.CompletionParams: + // method: "textDocument/completion" uri = p.TextDocument.URI log.Printf("--> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character) @@ -346,7 +352,6 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD // Add the TextDocumentItem in the tracked files list doc := params.TextDocument handler.trackedFiles[doc.URI] = doc - log.Printf("--> didOpen(%s)", doc.URI) // If we are tracking a .ino... if doc.URI.AsPath().Ext() == ".ino" { @@ -364,7 +369,6 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD Version: 1, }, } - log.Printf(" message for clangd: didOpen(%s)", newParam.TextDocument.URI) return newParam, err } } diff --git a/handler/syncer.go b/handler/syncer.go index 198b503..452d3c4 100644 --- a/handler/syncer.go +++ b/handler/syncer.go @@ -2,7 +2,6 @@ package handler import ( "context" - "log" "sync" "github.com/sourcegraph/jsonrpc2" @@ -31,11 +30,11 @@ func (ah AsyncHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jso ah.synchronizer.FileMux.Lock() defer ah.synchronizer.FileMux.Unlock() if enableLogging { - log.Println("Message processing locked for", req.Method) + // log.Println("Message processing locked for", req.Method) } ah.handler.Handle(ctx, conn, req) if enableLogging { - log.Println("Message processing unlocked for", req.Method) + // log.Println("Message processing unlocked for", req.Method) } }() } else { From d07b2ab2593d4bfed0fd80deb7879c435565df7f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 25 Nov 2020 14:43:47 +0100 Subject: [PATCH 21/61] use lsp helper in sourceMapper --- handler/sourcemapper/ino.go | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 11da6d7..24e937b 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -3,10 +3,6 @@ package sourcemapper import ( "bufio" "io" - "net/url" - "path/filepath" - "regexp" - "runtime" "strconv" "strings" @@ -35,14 +31,12 @@ type InoLine struct { // InoToCppLine converts a source (.ino) line into a target (.cpp) line func (s *InoMapper) InoToCppLine(sourceURI lsp.DocumentURI, line int) int { - file := uriToPath(sourceURI) - return s.toCpp[InoLine{file, line}] + return s.toCpp[InoLine{sourceURI.Unbox(), line}] } // InoToCppLineOk converts a source (.ino) line into a target (.cpp) line func (s *InoMapper) InoToCppLineOk(sourceURI lsp.DocumentURI, line int) (int, bool) { - file := uriToPath(sourceURI) - res, ok := s.toCpp[InoLine{file, line}] + res, ok := s.toCpp[InoLine{sourceURI.Unbox(), line}] return res, ok } @@ -193,26 +187,3 @@ func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) { } } } - -var expDriveID = regexp.MustCompile("[a-zA-Z]:") - -func uriToPath(uri lsp.DocumentURI) string { - urlObj, err := url.Parse(string(uri)) - if err != nil { - return string(uri) - } - path := "" - segments := strings.Split(urlObj.Path, "/") - for _, segment := range segments { - decoded, err := url.PathUnescape(segment) - if err != nil { - decoded = segment - } - if runtime.GOOS == "windows" && expDriveID.MatchString(decoded) { - path += strings.ToUpper(decoded) - } else if len(decoded) > 0 { - path += string(filepath.Separator) + decoded - } - } - return path -} From 4401a258bdeaa86a67fedea1901db09d2a48fb30 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 25 Nov 2020 16:29:27 +0100 Subject: [PATCH 22/61] sketchMapper: added info about preprocessed lines --- handler/sourcemapper/ino.go | 31 ++++++++++++++++++++----------- handler/sourcemapper/ino_test.go | 26 ++++++++++++++++---------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 24e937b..a9ed676 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -11,11 +11,12 @@ import ( // InoMapper is a mapping between the .ino sketch and the preprocessed .cpp file type InoMapper struct { - InoText map[lsp.DocumentURI]*SourceRevision - CppText *SourceRevision - CppFile lsp.DocumentURI - toCpp map[InoLine]int // Converts File.ino:line -> line - toIno map[int]InoLine // Convers line -> File.ino:line + InoText map[lsp.DocumentURI]*SourceRevision + CppText *SourceRevision + CppFile lsp.DocumentURI + toCpp map[InoLine]int // Converts File.ino:line -> line + toIno map[int]InoLine // Convers line -> File.ino:line + cppPreprocessed map[int]InoLine // map of the lines added by the preprocessor: preprocessed line -> File.ino:line } type SourceRevision struct { @@ -76,8 +77,9 @@ func (s *InoMapper) CppToInoLineOk(targetLine int) (string, int, bool) { // CreateInoMapper create a InoMapper from the given target file func CreateInoMapper(targetFile io.Reader) *InoMapper { mapper := &InoMapper{ - toCpp: map[InoLine]int{}, - toIno: map[int]InoLine{}, + toCpp: map[InoLine]int{}, + toIno: map[int]InoLine{}, + cppPreprocessed: map[int]InoLine{}, } sourceFile := "" @@ -94,17 +96,24 @@ func CreateInoMapper(targetFile io.Reader) *InoMapper { } sourceFile = unquoteCppString(tokens[2]) } else if sourceFile != "" { - mapper.toCpp[InoLine{sourceFile, sourceLine}] = targetLine - mapper.toIno[targetLine] = InoLine{sourceFile, sourceLine} + mapper.mapLine(sourceFile, sourceLine, targetLine) sourceLine++ } targetLine++ } - mapper.toCpp[InoLine{sourceFile, sourceLine}] = targetLine - mapper.toIno[targetLine] = InoLine{sourceFile, sourceLine} + mapper.mapLine(sourceFile, sourceLine, targetLine) return mapper } +func (s *InoMapper) mapLine(sourceFile string, sourceLine, targetLine int) { + inoLine := InoLine{sourceFile, sourceLine} + if line, ok := s.toCpp[inoLine]; ok { + s.cppPreprocessed[line] = inoLine + } + s.toCpp[inoLine] = targetLine + s.toIno[targetLine] = inoLine +} + func unquoteCppString(str string) string { if len(str) >= 2 && strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) { str = strings.TrimSuffix(str, `"`)[1:] diff --git a/handler/sourcemapper/ino_test.go b/handler/sourcemapper/ino_test.go index 021e347..4f42bad 100644 --- a/handler/sourcemapper/ino_test.go +++ b/handler/sourcemapper/ino_test.go @@ -43,8 +43,8 @@ void loop() { }, sourceMap.toCpp) require.EqualValues(t, map[int]InoLine{ 3: {"sketch_july2a.ino", 0}, - 5: {"sketch_july2a.ino", 1}, - 7: {"sketch_july2a.ino", 6}, + 5: {"sketch_july2a.ino", 1}, // setup + 7: {"sketch_july2a.ino", 6}, // loop 9: {"sketch_july2a.ino", 1}, 10: {"sketch_july2a.ino", 2}, 11: {"sketch_july2a.ino", 3}, @@ -56,6 +56,10 @@ void loop() { 17: {"sketch_july2a.ino", 9}, 18: {"sketch_july2a.ino", 10}, }, sourceMap.toIno) + require.EqualValues(t, map[int]InoLine{ + 5: {"sketch_july2a.ino", 1}, // setup + 7: {"sketch_july2a.ino", 6}, // loop + }, sourceMap.cppPreprocessed) } func TestCreateMultifileSourceMap(t *testing.T) { @@ -105,10 +109,6 @@ void secondFunction() { {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 0}: 2, {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 1}: 3, {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 2}: 4, - {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}: 6, - {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}: 8, - {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}: 10, - {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}: 12, {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}: 14, {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 4}: 15, {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 5}: 16, @@ -141,10 +141,10 @@ void secondFunction() { 2: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 0}, 3: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 1}, 4: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 2}, - 6: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, - 8: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, - 10: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, - 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, + 6: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, // setup + 8: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, // loop + 10: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, // vino + 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, // secondFunction 14: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, 15: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 4}, 16: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 5}, @@ -173,6 +173,12 @@ void secondFunction() { 40: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 3}, 41: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 4}, }) + require.EqualValues(t, map[int]InoLine{ + 6: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, // setup + 8: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, // loop + 10: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, // vino + 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, // secondFunction + }, sourceMap.cppPreprocessed) } // func TestUpdateSourceMaps1(t *testing.T) { From 5f194083395e58572d80bee9eacdeafd3228d00d Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 27 Nov 2020 00:15:34 +0100 Subject: [PATCH 23/61] Added methods to apply text-changes in sketch mapper --- handler/handler.go | 10 +- handler/sourcemapper/ino.go | 260 ++++++++++++++++++++++--------- handler/sourcemapper/ino_test.go | 48 +++++- 3 files changed, 234 insertions(+), 84 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 92444fa..c221994 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -1,13 +1,13 @@ package handler import ( - "bytes" "context" "fmt" "io" "log" "os" "regexp" + "strconv" "strings" "time" @@ -281,7 +281,7 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp. handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp") if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil { - handler.sketchMapper = sourcemapper.CreateInoMapper(bytes.NewReader(cppContent)) + handler.sketchMapper = sourcemapper.CreateInoMapper(cppContent) } else { return errors.WithMessage(err, "reading generated cpp file from sketch") } @@ -399,14 +399,14 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c } else { // Fallback: try to apply a multi-line update data.sourceText = newSourceText - data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text) + //data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text) *rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang) return nil } } data.sourceText = newSourceText - data.sourceMap = sourcemapper.CreateInoMapper(bytes.NewReader(targetBytes)) + data.sourceMap = sourcemapper.CreateInoMapper(targetBytes) change.Text = string(targetBytes) change.Range = nil @@ -417,7 +417,7 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c if err != nil { return err } - data.sourceMap.Update(0, rang.Start.Line, change.Text) + //data.sourceMap.Update(0, rang.Start.Line, change.Text) *rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang) } diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index a9ed676..ebb8a3f 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -2,10 +2,13 @@ package sourcemapper import ( "bufio" - "io" + "bytes" + "fmt" + "sort" "strconv" "strings" + "github.com/bcmi-labs/arduino-language-server/handler/textutils" "github.com/bcmi-labs/arduino-language-server/lsp" ) @@ -16,9 +19,13 @@ type InoMapper struct { CppFile lsp.DocumentURI toCpp map[InoLine]int // Converts File.ino:line -> line toIno map[int]InoLine // Convers line -> File.ino:line + inoPreprocessed map[InoLine]int // map of the lines taken by the preprocessor: File.ino:line -> preprocessed line cppPreprocessed map[int]InoLine // map of the lines added by the preprocessor: preprocessed line -> File.ino:line } +// NotIno are lines that do not belongs to an .ino file +var NotIno = InoLine{"not-ino", 0} + type SourceRevision struct { Version int Text string @@ -41,6 +48,7 @@ func (s *InoMapper) InoToCppLineOk(sourceURI lsp.DocumentURI, line int) (int, bo return res, ok } +// InoToCppLSPRange convert a lsp.Ranger reference to a .ino into a lsp.Range to .cpp func (s *InoMapper) InoToCppLSPRange(sourceURI lsp.DocumentURI, r lsp.Range) lsp.Range { res := r res.Start.Line = s.InoToCppLine(sourceURI, r.Start.Line) @@ -48,6 +56,23 @@ func (s *InoMapper) InoToCppLSPRange(sourceURI lsp.DocumentURI, r lsp.Range) lsp return res } +// InoToCppLSPRangeOk convert a lsp.Ranger reference to a .ino into a lsp.Range to .cpp and returns +// true if the conversion is successful or false if the conversion is invalid. +func (s *InoMapper) InoToCppLSPRangeOk(sourceURI lsp.DocumentURI, r lsp.Range) (lsp.Range, bool) { + res := r + if l, ok := s.InoToCppLineOk(sourceURI, r.Start.Line); ok { + res.Start.Line = l + } else { + return res, false + } + if l, ok := s.InoToCppLineOk(sourceURI, r.End.Line); ok { + res.End.Line = l + } else { + return res, false + } + return res, true +} + // CppToInoLine converts a target (.cpp) line into a source.ino:line func (s *InoMapper) CppToInoLine(targetLine int) (string, int) { res := s.toIno[targetLine] @@ -75,17 +100,22 @@ func (s *InoMapper) CppToInoLineOk(targetLine int) (string, int, bool) { } // CreateInoMapper create a InoMapper from the given target file -func CreateInoMapper(targetFile io.Reader) *InoMapper { +func CreateInoMapper(targetFile []byte) *InoMapper { mapper := &InoMapper{ toCpp: map[InoLine]int{}, toIno: map[int]InoLine{}, + inoPreprocessed: map[InoLine]int{}, cppPreprocessed: map[int]InoLine{}, + CppText: &SourceRevision{ + Version: 1, + Text: string(targetFile), + }, } sourceFile := "" sourceLine := -1 targetLine := 0 - scanner := bufio.NewScanner(targetFile) + scanner := bufio.NewScanner(bytes.NewReader(targetFile)) for scanner.Scan() { lineStr := scanner.Text() if strings.HasPrefix(lineStr, "#line") { @@ -95,9 +125,12 @@ func CreateInoMapper(targetFile io.Reader) *InoMapper { sourceLine = l - 1 } sourceFile = unquoteCppString(tokens[2]) + mapper.toIno[targetLine] = NotIno } else if sourceFile != "" { mapper.mapLine(sourceFile, sourceLine, targetLine) sourceLine++ + } else { + mapper.toIno[targetLine] = NotIno } targetLine++ } @@ -109,6 +142,7 @@ func (s *InoMapper) mapLine(sourceFile string, sourceLine, targetLine int) { inoLine := InoLine{sourceFile, sourceLine} if line, ok := s.toCpp[inoLine]; ok { s.cppPreprocessed[line] = inoLine + s.inoPreprocessed[inoLine] = line } s.toCpp[inoLine] = targetLine s.toIno[targetLine] = inoLine @@ -123,76 +157,156 @@ func unquoteCppString(str string) string { return str } -// Update performs an update to the SourceMap considering the deleted lines, the -// insertion line and the inserted text -func (s *InoMapper) Update(deletedLines, insertLine int, insertText string) { - // for i := 1; i <= deletedLines; i++ { - // sourceLine := insertLine + 1 - // targetLine := s.toCpp[sourceLine] - - // // Shift up all following lines by one and put them into a new map - // newMappings := make(map[int]int) - // maxSourceLine, maxTargetLine := 0, 0 - // for t, s := range s.toIno { - // if t > targetLine && s > sourceLine { - // newMappings[t-1] = s - 1 - // } else if s > sourceLine { - // newMappings[t] = s - 1 - // } else if t > targetLine { - // newMappings[t-1] = s - // } - // if s > maxSourceLine { - // maxSourceLine = s - // } - // if t > maxTargetLine { - // maxTargetLine = t - // } - // } - - // // Remove mappings for the deleted line - // delete(s.toIno, maxTargetLine) - // delete(s.toCpp, maxSourceLine) - - // // Copy the mappings from the intermediate map - // copyMappings(s.toIno, s.toCpp, newMappings) - // } - - // addedLines := strings.Count(insertText, "\n") - // if addedLines > 0 { - // targetLine := s.toCpp[insertLine] - - // // Shift down all following lines and put them into a new map - // newMappings := make(map[int]int) - // for t, s := range s.toIno { - // if t > targetLine && s > insertLine { - // newMappings[t+addedLines] = s + addedLines - // } else if s > insertLine { - // newMappings[t] = s + addedLines - // } else if t > targetLine { - // newMappings[t+addedLines] = s - // } - // } - - // // Add mappings for the added lines - // for i := 1; i <= addedLines; i++ { - // s.toIno[targetLine+i] = insertLine + i - // s.toCpp[insertLine+i] = targetLine + i - // } - - // // Copy the mappings from the intermediate map - // copyMappings(s.toIno, s.toCpp, newMappings) - // } -} - -func copyMappings(sourceLineMap, targetLineMap, newMappings map[int]int) { - for t, s := range newMappings { - sourceLineMap[t] = s - targetLineMap[s] = t - } - for t, s := range newMappings { - // In case multiple target lines are present for a source line, use the last one - if t > targetLineMap[s] { - targetLineMap[s] = t +// ApplyTextChange performs the text change and updates both .ino and .cpp files. +// It returns true if the change is "dirty", this happens when the change alters preprocessed lines +// and a new preprocessing may be probably required. +func (s *InoMapper) ApplyTextChange(inoURI lsp.DocumentURI, inoChange lsp.TextDocumentContentChangeEvent) (dirty bool) { + inoRange := *inoChange.Range + cppRange := s.InoToCppLSPRange(inoURI, inoRange) + deletedLines := inoRange.End.Line - inoRange.Start.Line + + // Apply text changes + newText, err := textutils.ApplyTextChange(s.CppText.Text, cppRange, inoChange.Text) + if err != nil { + panic("error replacing text: " + err.Error()) + } + s.CppText.Text = newText + s.CppText.Version++ + + // Update line references + for deletedLines > 0 { + dirty = dirty || s.deleteCppLine(cppRange.Start.Line) + deletedLines-- + } + addedLines := strings.Count(inoChange.Text, "\n") - 1 + for addedLines > 0 { + dirty = dirty || s.addInoLine(cppRange.Start.Line) + } + if _, is := s.cppPreprocessed[cppRange.Start.Line]; is { + dirty = true + } + return +} + +func (s *InoMapper) addInoLine(cppLine int) (dirty bool) { + preprocessToShiftCpp := map[InoLine]bool{} + + addedInoLine := s.toIno[cppLine] + carry := s.toIno[cppLine] + carry.Line++ + for { + next, ok := s.toIno[cppLine+1] + s.toIno[cppLine+1] = carry + s.toCpp[carry] = cppLine + 1 + if !ok { + break + } + + if next.File == addedInoLine.File && next.Line >= addedInoLine.Line { + if _, is := s.inoPreprocessed[next]; is { + // fmt.Println("Adding", next, "to cpp to shift") + preprocessToShiftCpp[next] = true + } + next.Line++ + } + + carry = next + cppLine++ + } + + // dumpCppToInoMap(s.toIno) + + preprocessToShiftIno := []InoLine{} + for inoPre := range s.inoPreprocessed { + // fmt.Println(">", inoPre, addedInoLine) + if inoPre.File == addedInoLine.File && inoPre.Line >= addedInoLine.Line { + preprocessToShiftIno = append(preprocessToShiftIno, inoPre) } } + for inoPre := range preprocessToShiftCpp { + l := s.inoPreprocessed[inoPre] + delete(s.cppPreprocessed, l) + s.inoPreprocessed[inoPre] = l + 1 + s.cppPreprocessed[l+1] = inoPre + } + for _, inoPre := range preprocessToShiftIno { + l := s.inoPreprocessed[inoPre] + delete(s.inoPreprocessed, inoPre) + inoPre.Line++ + s.inoPreprocessed[inoPre] = l + s.cppPreprocessed[l] = inoPre + s.toIno[l] = inoPre + } + + return +} + +func (s *InoMapper) deleteCppLine(line int) (dirty bool) { + removed := s.toIno[line] + for i := line + 1; ; i++ { + shifted, ok := s.toIno[i] + if !ok { + delete(s.toIno, i-1) + break + } + s.toIno[i-1] = shifted + if shifted != NotIno { + s.toCpp[shifted] = i - 1 + } + } + + if _, ok := s.inoPreprocessed[removed]; ok { + dirty = true + } + + for curr := removed; ; curr.Line++ { + next := curr + next.Line++ + + shifted, ok := s.toCpp[next] + if !ok { + delete(s.toCpp, curr) + break + } + s.toCpp[curr] = shifted + s.toIno[shifted] = curr + + if l, ok := s.inoPreprocessed[next]; ok { + s.inoPreprocessed[curr] = l + s.cppPreprocessed[l] = curr + delete(s.inoPreprocessed, next) + + s.toIno[l] = curr + } + } + return +} + +func dumpCppToInoMap(s map[int]InoLine) { + last := 0 + for cppLine := range s { + if last < cppLine { + last = cppLine + } + } + for line := 0; line <= last; line++ { + target := s[line] + fmt.Printf("%5d -> %s:%d\n", line, target.File, target.Line) + } +} + +func dumpInoToCppMap(s map[InoLine]int) { + keys := []InoLine{} + for k := range s { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].File < keys[j].File || + (keys[i].File == keys[j].File && keys[i].Line < keys[j].Line) + }) + for _, k := range keys { + inoLine := k + cppLine := s[inoLine] + fmt.Printf("%s:%d -> %d\n", inoLine.File, inoLine.Line, cppLine) + } } diff --git a/handler/sourcemapper/ino_test.go b/handler/sourcemapper/ino_test.go index 4f42bad..d6cf23e 100644 --- a/handler/sourcemapper/ino_test.go +++ b/handler/sourcemapper/ino_test.go @@ -1,7 +1,7 @@ package sourcemapper import ( - "strings" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -27,7 +27,7 @@ void loop() { } ` - sourceMap := CreateInoMapper(strings.NewReader(input)) + sourceMap := CreateInoMapper([]byte(input)) require.EqualValues(t, map[InoLine]int{ {"sketch_july2a.ino", 0}: 3, {"sketch_july2a.ino", 1}: 9, @@ -42,9 +42,15 @@ void loop() { {"sketch_july2a.ino", 10}: 18, }, sourceMap.toCpp) require.EqualValues(t, map[int]InoLine{ + 0: NotIno, + 1: NotIno, + 2: NotIno, 3: {"sketch_july2a.ino", 0}, + 4: NotIno, 5: {"sketch_july2a.ino", 1}, // setup + 6: NotIno, 7: {"sketch_july2a.ino", 6}, // loop + 8: NotIno, 9: {"sketch_july2a.ino", 1}, 10: {"sketch_july2a.ino", 2}, 11: {"sketch_july2a.ino", 3}, @@ -60,6 +66,18 @@ void loop() { 5: {"sketch_july2a.ino", 1}, // setup 7: {"sketch_july2a.ino", 6}, // loop }, sourceMap.cppPreprocessed) + + dumpCppToInoMap(sourceMap.toIno) + dumpInoToCppMap(sourceMap.toCpp) + dumpCppToInoMap(sourceMap.cppPreprocessed) + dumpInoToCppMap(sourceMap.inoPreprocessed) + //sourceMap.addInoLine(InoLine{"sketch_july2a.ino", 0}) + sourceMap.addInoLine(3) + fmt.Println("\nAdded line 13") + dumpCppToInoMap(sourceMap.toIno) + dumpInoToCppMap(sourceMap.toCpp) + dumpCppToInoMap(sourceMap.cppPreprocessed) + dumpInoToCppMap(sourceMap.inoPreprocessed) } func TestCreateMultifileSourceMap(t *testing.T) { @@ -104,7 +122,7 @@ void vino() { void secondFunction() { }` - sourceMap := CreateInoMapper(strings.NewReader(input)) + sourceMap := CreateInoMapper([]byte(input)) require.EqualValues(t, sourceMap.toCpp, map[InoLine]int{ {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 0}: 2, {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 1}: 3, @@ -138,13 +156,20 @@ void secondFunction() { {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 4}: 41, }) require.EqualValues(t, sourceMap.toIno, map[int]InoLine{ + 0: NotIno, + 1: NotIno, 2: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 0}, 3: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 1}, 4: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 2}, - 6: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, // setup - 8: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, // loop + 5: NotIno, + 6: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, // setup + 7: NotIno, + 8: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 8}, // loop + 9: NotIno, 10: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, // vino - 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, // secondFunction + 11: NotIno, + 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, // secondFunction + 13: NotIno, 14: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 3}, 15: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 4}, 16: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 5}, @@ -167,6 +192,7 @@ void secondFunction() { 33: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, 34: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 23}, 35: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 24}, + 36: {"not-ino", 0}, 37: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 0}, 38: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, 39: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 2}, @@ -179,6 +205,16 @@ void secondFunction() { 10: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino", 22}, // vino 12: {"/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino", 1}, // secondFunction }, sourceMap.cppPreprocessed) + dumpCppToInoMap(sourceMap.toIno) + dumpInoToCppMap(sourceMap.toCpp) + dumpCppToInoMap(sourceMap.cppPreprocessed) + dumpInoToCppMap(sourceMap.inoPreprocessed) + sourceMap.deleteCppLine(21) + fmt.Println("\nRemoved line 21") + dumpCppToInoMap(sourceMap.toIno) + dumpInoToCppMap(sourceMap.toCpp) + dumpCppToInoMap(sourceMap.cppPreprocessed) + dumpInoToCppMap(sourceMap.inoPreprocessed) } // func TestUpdateSourceMaps1(t *testing.T) { From 8d099f97dd50040fbccb40ce7b60260f6206a7ef Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 27 Nov 2020 00:17:43 +0100 Subject: [PATCH 24/61] track version in tracked files --- handler/handler.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index c221994..00a7286 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -42,7 +42,7 @@ type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ data: map[lsp.DocumentURI]*FileData{}, - trackedFiles: map[lsp.DocumentURI]lsp.TextDocumentItem{}, + trackedFiles: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, config: lsp.BoardConfig{ SelectedBoard: board, }, @@ -70,11 +70,12 @@ type InoHandler struct { buildPath *paths.Path buildSketchRoot *paths.Path buildSketchCpp *paths.Path + buildSketchCppVersion int sketchRoot *paths.Path sketchName string sketchMapper *sourcemapper.InoMapper sketchTrackedFilesCount int - trackedFiles map[lsp.DocumentURI]lsp.TextDocumentItem + trackedFiles map[lsp.DocumentURI]*lsp.TextDocumentItem data map[lsp.DocumentURI]*FileData config lsp.BoardConfig @@ -131,7 +132,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr case *lsp.DidOpenTextDocumentParams: // method "textDocument/didOpen" uri = p.TextDocument.URI - log.Printf("--> didOpen(%s)", uri) + log.Printf("--> didOpen(%s@%d as '%s')", p.TextDocument.URI, p.TextDocument.Version, p.TextDocument.LanguageID) handler.synchronizer.DataMux.Lock() res, err := handler.didOpen(ctx, p) @@ -142,7 +143,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr return nil, err // do not propagate to clangd } - log.Printf(" --> didOpen(%s)", res.TextDocument.URI) + log.Printf(" --> didOpen(%s@%d as '%s')", res.TextDocument.URI, res.TextDocument.Version, p.TextDocument.LanguageID) params = res case *lsp.CompletionParams: @@ -279,6 +280,7 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp. return err } handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp") + handler.buildSketchCppVersion = 1 if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil { handler.sketchMapper = sourcemapper.CreateInoMapper(cppContent) @@ -351,7 +353,7 @@ func startClangd(compileCommandsDir, sketchCpp *paths.Path) (io.WriteCloser, io. func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextDocumentParams) (*lsp.DidOpenTextDocumentParams, error) { // Add the TextDocumentItem in the tracked files list doc := params.TextDocument - handler.trackedFiles[doc.URI] = doc + handler.trackedFiles[doc.URI] = &doc // If we are tracking a .ino... if doc.URI.AsPath().Ext() == ".ino" { @@ -366,7 +368,7 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD URI: lsp.NewDocumenteURIFromPath(handler.buildSketchCpp), Text: string(sketchCpp), LanguageID: "cpp", - Version: 1, + Version: handler.buildSketchCppVersion, }, } return newParam, err From 039617bddb790f6a76f7f5fb5bccde300b9d9476 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 27 Nov 2020 00:18:59 +0100 Subject: [PATCH 25/61] first implementation of textDocument/didChange --- handler/handler.go | 108 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 19 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 00a7286..d2fe348 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -146,6 +146,32 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Printf(" --> didOpen(%s@%d as '%s')", res.TextDocument.URI, res.TextDocument.Version, p.TextDocument.LanguageID) params = res + case *lsp.DidChangeTextDocumentParams: + // notification "textDocument/didChange" + uri = p.TextDocument.URI + log.Printf("--> didChange(%s@%d)", p.TextDocument.URI, p.TextDocument.Version) + for _, change := range p.ContentChanges { + log.Printf(" > %s -> '%s'", change.Range, strconv.Quote(change.Text)) + } + + res, err := handler.didChange(ctx, p) + if err != nil { + log.Printf(" --E error: %s", err) + return nil, err + } + if res == nil { + log.Println(" --X notification is not propagated to clangd") + return nil, err // do not propagate to clangd + } + + p = res + log.Printf(" --> didChange(%s@%d)", p.TextDocument.URI, p.TextDocument.Version) + for _, change := range p.ContentChanges { + log.Printf(" > %s -> '%s'", change.Range, strconv.Quote(change.Text)) + } + err = handler.ClangdConn.Notify(ctx, req.Method, p) + return nil, err + case *lsp.CompletionParams: // method: "textDocument/completion" uri = p.TextDocument.URI @@ -163,10 +189,6 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppTextDocumentPositionParams(doc) log.Printf(" --> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character) - case *lsp.DidChangeTextDocumentParams: // "textDocument/didChange": - log.Printf("UNHANDLED " + req.Method) - uri = p.TextDocument.URI - err = handler.ino2cppDidChangeTextDocumentParams(ctx, p) case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": log.Printf("UNHANDLED " + req.Method) uri = p.TextDocument.URI @@ -377,6 +399,69 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD return nil, nil } +func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeTextDocumentParams) (*lsp.DidChangeTextDocumentParams, error) { + doc := req.TextDocument + + trackedDoc, ok := handler.trackedFiles[doc.URI] + if !ok { + return nil, unknownURI(doc.URI) + } + if trackedDoc.Version+1 != doc.Version { + return nil, errors.Errorf("document out-of-sync: expected version %d but got %d", trackedDoc.Version+1, doc.Version) + } + trackedDoc.Version++ + + if doc.URI.AsPath().Ext() == ".ino" { + // If changes are applied to a .ino file we increment the global .ino.cpp versioning + // for each increment of the single .ino file. + + cppChanges := []lsp.TextDocumentContentChangeEvent{} + for _, inoChange := range req.ContentChanges { + dirty := handler.sketchMapper.ApplyTextChange(doc.URI, inoChange) + if dirty { + // TODO: Detect changes in critical lines (for example function definitions) + // and trigger arduino-preprocessing + clangd restart. + + log.Println(" uh oh DIRTY CHANGE!") + } + + // log.Println("New version:----------") + // log.Println(handler.sketchMapper.CppText.Text) + // log.Println("----------------------") + + cppRange, ok := handler.sketchMapper.InoToCppLSPRangeOk(doc.URI, *inoChange.Range) + if !ok { + return nil, errors.Errorf("invalid change range %s:%s", doc.URI, *inoChange.Range) + } + cppChange := lsp.TextDocumentContentChangeEvent{ + Range: &cppRange, + RangeLength: inoChange.RangeLength, + Text: inoChange.Text, + } + cppChanges = append(cppChanges, cppChange) + } + + // build a cpp equivalent didChange request + cppReq := &lsp.DidChangeTextDocumentParams{ + ContentChanges: cppChanges, + TextDocument: lsp.VersionedTextDocumentIdentifier{ + TextDocumentIdentifier: lsp.TextDocumentIdentifier{ + URI: lsp.NewDocumenteURIFromPath(handler.buildSketchCpp), + }, + Version: handler.sketchMapper.CppText.Version, + }, + } + return cppReq, nil + } else { + + // TODO + return nil, unknownURI(doc.URI) + + } + + return nil, unknownURI(doc.URI) +} + func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, change *lsp.TextDocumentContentChangeEvent) (err error) { rang := change.Range if rang == nil || rang.Start.Line != rang.End.Line { @@ -496,21 +581,6 @@ func (handler *InoHandler) sketchToBuildPathTextDocumentIdentifier(doc *lsp.Text return nil } -func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Context, params *lsp.DidChangeTextDocumentParams) error { - handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument.TextDocumentIdentifier) - if data, ok := handler.data[params.TextDocument.URI]; ok { - for index := range params.ContentChanges { - err := handler.updateFileData(ctx, data, ¶ms.ContentChanges[index]) - if err != nil { - return err - } - } - data.version = params.TextDocument.Version - return nil - } - return unknownURI(params.TextDocument.URI) -} - func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error { sourceURI := params.TextDocument.URI if strings.HasSuffix(string(sourceURI), ".ino") { From b27fb0f8d628b8013f4c08e9e3c85ba6f7850244 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 27 Nov 2020 00:19:12 +0100 Subject: [PATCH 26/61] improved logging --- handler/handler.go | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index d2fe348..075ad32 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -190,16 +190,19 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Printf(" --> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character) case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) handler.deleteFileData(uri) case *lsp.CodeActionParams: // "textDocument/codeAction": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.ino2cppCodeActionParams(p) // case "textDocument/signatureHelp": @@ -211,42 +214,51 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr // case "textDocument/implementation": // fallthrough case *lsp.TextDocumentPositionParams: // "textDocument/documentHighlight": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(p) case *lsp.ReferenceParams: // "textDocument/references": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) case *lsp.DocumentFormattingParams: // "textDocument/formatting": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.ino2cppDocumentRangeFormattingParams(p) case *lsp.DocumentOnTypeFormattingParams: // "textDocument/onTypeFormatting": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.ino2cppDocumentOnTypeFormattingParams(p) case *lsp.DocumentSymbolParams: // "textDocument/documentSymbol": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.RenameParams: // "textDocument/rename": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil uri = p.TextDocument.URI err = handler.ino2cppRenameParams(p) case *lsp.DidChangeWatchedFilesParams: // "workspace/didChangeWatchedFiles": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil err = handler.ino2cppDidChangeWatchedFilesParams(p) case *lsp.ExecuteCommandParams: // "workspace/executeCommand": - log.Printf("UNHANDLED " + req.Method) + log.Printf("--X " + req.Method) + return nil, nil err = handler.ino2cppExecuteCommand(p) } if err != nil { - log.Printf(" ~~~ %s", err) + log.Printf(" --E %s", err) return nil, err } From 736739af24260ae1e0f30c55747cb81a4ea1789a Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Fri, 27 Nov 2020 23:30:16 +0100 Subject: [PATCH 27/61] implement cleaning of diagnostics --- handler/handler.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/handler/handler.go b/handler/handler.go index 075ad32..803c37d 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -929,6 +929,23 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. // we should transform back N diagnostics of sketch.cpp.ino into // their .ino counter parts (that may span over multiple files...) + // Remove diagnostics from all .ino if there are no errors coming from clang + if len(p.Diagnostics) == 0 { + // XXX: Optimize this to publish "empty diagnostics" only to .ino that are + // currently showing previous diagnostics. + + for sourceURI := range handler.trackedFiles { + msg := lsp.PublishDiagnosticsParams{ + URI: sourceURI, + Diagnostics: []lsp.Diagnostic{}, + } + if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", msg); err != nil { + return nil, err + } + } + return nil, nil + } + convertedDiagnostics := map[string][]lsp.Diagnostic{} for _, cppDiag := range p.Diagnostics { inoSource, inoRange := handler.sketchMapper.CppToInoRange(cppDiag.Range) From 474f2e7682bb3c0997a084f5edbd3477dc52f363 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 29 Nov 2020 01:38:44 +0100 Subject: [PATCH 28/61] inlined Hover response handler --- handler/handler.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 803c37d..8d533d0 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -691,6 +691,15 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document defer handler.synchronizer.DataMux.RUnlock() switch r := result.(type) { + case *lsp.Hover: + // method "textDocument/hover" + if len(r.Contents.Value) == 0 { + return nil + } + if uri.AsPath().EquivalentTo(handler.buildSketchCpp) { + _, *r.Range = handler.sketchMapper.CppToInoRange(*r.Range) + } + case *lsp.CompletionList: // "textDocument/completion": handler.cpp2inoCompletionList(r, uri) case *[]*lsp.CommandOrCodeAction: // "textDocument/codeAction": @@ -704,11 +713,6 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document handler.cpp2inoCodeAction(codeAction, uri) } } - case *lsp.Hover: // "textDocument/hover": - if len(r.Contents.Value) == 0 { - return nil - } - handler.cpp2inoHover(r, uri) // case "textDocument/definition": // fallthrough // case "textDocument/typeDefinition": @@ -816,15 +820,6 @@ func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls return &newEdit } -func (handler *InoHandler) cpp2inoHover(hover *lsp.Hover, uri lsp.DocumentURI) { - if data, ok := handler.data[uri]; ok { - r := hover.Range - if r != nil { - _, *r = data.sourceMap.CppToInoRange(*r) - } - } -} - func (handler *InoHandler) cpp2inoLocation(location *lsp.Location) { if data, ok := handler.data[location.URI]; ok { location.URI = data.sourceURI From bf332d76d905720ed5e7bcac4721165fc61628c6 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 29 Nov 2020 01:39:12 +0100 Subject: [PATCH 29/61] let's panic on un-handled functions --- handler/handler.go | 389 +++++++++++++++++++++++---------------------- main.go | 3 +- 2 files changed, 204 insertions(+), 188 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 8d533d0..4ef3f2a 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -41,7 +41,7 @@ type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io // NewInoHandler creates and configures an InoHandler. func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ - data: map[lsp.DocumentURI]*FileData{}, + //data: map[lsp.DocumentURI]*FileData{}, trackedFiles: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, config: lsp.BoardConfig{ SelectedBoard: board, @@ -77,7 +77,7 @@ type InoHandler struct { sketchTrackedFilesCount int trackedFiles map[lsp.DocumentURI]*lsp.TextDocumentItem - data map[lsp.DocumentURI]*FileData + //data map[lsp.DocumentURI]*FileData config lsp.BoardConfig synchronizer Synchronizer } @@ -524,10 +524,10 @@ func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, c } func (handler *InoHandler) deleteFileData(sourceURI lsp.DocumentURI) { - if data, ok := handler.data[sourceURI]; ok { - delete(handler.data, data.sourceURI) - delete(handler.data, data.targetURI) - } + // if data, ok := handler.data[sourceURI]; ok { + // delete(handler.data, data.sourceURI) + // delete(handler.data, data.targetURI) + // } } func (handler *InoHandler) handleError(ctx context.Context, err error) error { @@ -608,81 +608,88 @@ func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDoc } func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) error { - handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) - if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) - for index := range params.Context.Diagnostics { - r := ¶ms.Context.Diagnostics[index].Range - *r = data.sourceMap.InoToCppLSPRange(data.sourceURI, *r) - } - return nil - } + panic("not implemented") + // handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) + // if data, ok := handler.data[params.TextDocument.URI]; ok { + // params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) + // for index := range params.Context.Diagnostics { + // r := ¶ms.Context.Diagnostics[index].Range + // *r = data.sourceMap.InoToCppLSPRange(data.sourceURI, *r) + // } + // return nil + // } return unknownURI(params.TextDocument.URI) } func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.DocumentRangeFormattingParams) error { - handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) - if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) - return nil - } + panic("not implemented") + // handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) + // if data, ok := handler.data[params.TextDocument.URI]; ok { + // params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) + // return nil + // } return unknownURI(params.TextDocument.URI) } func (handler *InoHandler) ino2cppDocumentOnTypeFormattingParams(params *lsp.DocumentOnTypeFormattingParams) error { - handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) - if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) - return nil - } + panic("not implemented") + // handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) + // if data, ok := handler.data[params.TextDocument.URI]; ok { + // params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) + // return nil + // } return unknownURI(params.TextDocument.URI) } func (handler *InoHandler) ino2cppRenameParams(params *lsp.RenameParams) error { - handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) - if data, ok := handler.data[params.TextDocument.URI]; ok { - params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) - return nil - } + panic("not implemented") + // handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) + // if data, ok := handler.data[params.TextDocument.URI]; ok { + // params.Position.Line = data.sourceMap.InoToCppLine(data.sourceURI, params.Position.Line) + // return nil + // } return unknownURI(params.TextDocument.URI) } func (handler *InoHandler) ino2cppDidChangeWatchedFilesParams(params *lsp.DidChangeWatchedFilesParams) error { - for index := range params.Changes { - fileEvent := ¶ms.Changes[index] - if data, ok := handler.data[fileEvent.URI]; ok { - fileEvent.URI = data.targetURI - } - } + panic("not implemented") + // for index := range params.Changes { + // fileEvent := ¶ms.Changes[index] + // if data, ok := handler.data[fileEvent.URI]; ok { + // fileEvent.URI = data.targetURI + // } + // } return nil } func (handler *InoHandler) ino2cppExecuteCommand(executeCommand *lsp.ExecuteCommandParams) error { - if len(executeCommand.Arguments) == 1 { - arg := handler.parseCommandArgument(executeCommand.Arguments[0]) - if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { - executeCommand.Arguments[0] = handler.ino2cppWorkspaceEdit(workspaceEdit) - } - } + panic("not implemented") + // if len(executeCommand.Arguments) == 1 { + // arg := handler.parseCommandArgument(executeCommand.Arguments[0]) + // if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { + // executeCommand.Arguments[0] = handler.ino2cppWorkspaceEdit(workspaceEdit) + // } + // } return nil } func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit { + panic("not implemented") newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} - for uri, edit := range origEdit.Changes { - if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { - newValue := make([]lsp.TextEdit, len(edit)) - for index := range edit { - newValue[index] = lsp.TextEdit{ - NewText: edit[index].NewText, - Range: data.sourceMap.InoToCppLSPRange(data.sourceURI, edit[index].Range), - } - } - newEdit.Changes[string(data.targetURI)] = newValue - } else { - newEdit.Changes[uri] = edit - } - } + // for uri, edit := range origEdit.Changes { + // if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { + // newValue := make([]lsp.TextEdit, len(edit)) + // for index := range edit { + // newValue[index] = lsp.TextEdit{ + // NewText: edit[index].NewText, + // Range: data.sourceMap.InoToCppLSPRange(data.sourceURI, edit[index].Range), + // } + // } + // newEdit.Changes[string(data.targetURI)] = newValue + // } else { + // newEdit.Changes[uri] = edit + // } + // } return &newEdit } @@ -768,135 +775,144 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document } func (handler *InoHandler) cpp2inoCompletionList(list *lsp.CompletionList, uri lsp.DocumentURI) { - if data, ok := handler.data[uri]; ok { - newItems := make([]lsp.CompletionItem, 0, len(list.Items)) - for _, item := range list.Items { - if !strings.HasPrefix(item.InsertText, "_") { - if item.TextEdit != nil { - _, item.TextEdit.Range = data.sourceMap.CppToInoRange(item.TextEdit.Range) - } - newItems = append(newItems, item) - } - } - list.Items = newItems - } + panic("not implemented") + // if data, ok := handler.data[uri]; ok { + // newItems := make([]lsp.CompletionItem, 0, len(list.Items)) + // for _, item := range list.Items { + // if !strings.HasPrefix(item.InsertText, "_") { + // if item.TextEdit != nil { + // _, item.TextEdit.Range = data.sourceMap.CppToInoRange(item.TextEdit.Range) + // } + // newItems = append(newItems, item) + // } + // } + // list.Items = newItems + // } } func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp.DocumentURI) { - codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) - if data, ok := handler.data[uri]; ok { - for index := range codeAction.Diagnostics { - _, codeAction.Diagnostics[index].Range = data.sourceMap.CppToInoRange(codeAction.Diagnostics[index].Range) - } - } + panic("not implemented") + // codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) + // if data, ok := handler.data[uri]; ok { + // for index := range codeAction.Diagnostics { + // _, codeAction.Diagnostics[index].Range = data.sourceMap.CppToInoRange(codeAction.Diagnostics[index].Range) + // } + // } } func (handler *InoHandler) cpp2inoCommand(command *lsp.Command) { - if len(command.Arguments) == 1 { - arg := handler.parseCommandArgument(command.Arguments[0]) - if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { - command.Arguments[0] = handler.cpp2inoWorkspaceEdit(workspaceEdit) - } - } + panic("not implemented") + // if len(command.Arguments) == 1 { + // arg := handler.parseCommandArgument(command.Arguments[0]) + // if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { + // command.Arguments[0] = handler.cpp2inoWorkspaceEdit(workspaceEdit) + // } + // } } func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit { - newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} - for uri, edit := range origEdit.Changes { - if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { - newValue := make([]lsp.TextEdit, len(edit)) - for index := range edit { - _, newRange := data.sourceMap.CppToInoRange(edit[index].Range) - newValue[index] = lsp.TextEdit{ - NewText: edit[index].NewText, - Range: newRange, - } - } - newEdit.Changes[string(data.sourceURI)] = newValue - } else { - newEdit.Changes[uri] = edit - } - } - return &newEdit + panic("not implemented") + // newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} + // for uri, edit := range origEdit.Changes { + // if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { + // newValue := make([]lsp.TextEdit, len(edit)) + // for index := range edit { + // _, newRange := data.sourceMap.CppToInoRange(edit[index].Range) + // newValue[index] = lsp.TextEdit{ + // NewText: edit[index].NewText, + // Range: newRange, + // } + // } + // newEdit.Changes[string(data.sourceURI)] = newValue + // } else { + // newEdit.Changes[uri] = edit + // } + // } + // return &newEdit } func (handler *InoHandler) cpp2inoLocation(location *lsp.Location) { - if data, ok := handler.data[location.URI]; ok { - location.URI = data.sourceURI - _, location.Range = data.sourceMap.CppToInoRange(location.Range) - } + panic("not implemented") + // if data, ok := handler.data[location.URI]; ok { + // location.URI = data.sourceURI + // _, location.Range = data.sourceMap.CppToInoRange(location.Range) + // } } func (handler *InoHandler) cpp2inoDocumentHighlight(highlight *lsp.DocumentHighlight, uri lsp.DocumentURI) { - if data, ok := handler.data[uri]; ok { - _, highlight.Range = data.sourceMap.CppToInoRange(highlight.Range) - } + panic("not implemented") + // if data, ok := handler.data[uri]; ok { + // _, highlight.Range = data.sourceMap.CppToInoRange(highlight.Range) + // } } func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentURI) { - if data, ok := handler.data[uri]; ok { - _, edit.Range = data.sourceMap.CppToInoRange(edit.Range) - } + panic("not implemented") + // if data, ok := handler.data[uri]; ok { + // _, edit.Range = data.sourceMap.CppToInoRange(edit.Range) + // } } func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, uri lsp.DocumentURI) []lsp.DocumentSymbol { - data, ok := handler.data[uri] - if !ok || len(origSymbols) == 0 { - return origSymbols - } - - symbolIdx := make(map[string]*lsp.DocumentSymbol) - for i := 0; i < len(origSymbols); i++ { - symbol := &origSymbols[i] - _, symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) - - duplicate := false - other, duplicate := symbolIdx[symbol.Name] - if duplicate { - // We prefer symbols later in the file due to the function header generation. E.g. if one has a function `void foo() {}` somehwre in the code - // the code generation will add a `void foo();` header at the beginning of the cpp file. We care about the function body later in the file, not - // the header early on. - if other.Range.Start.Line < symbol.Range.Start.Line { - continue - } - } - - _, symbol.SelectionRange = data.sourceMap.CppToInoRange(symbol.SelectionRange) - symbol.Children = handler.cpp2inoDocumentSymbols(symbol.Children, uri) - symbolIdx[symbol.Name] = symbol - } - - newSymbols := make([]lsp.DocumentSymbol, len(symbolIdx)) - j := 0 - for _, s := range symbolIdx { - newSymbols[j] = *s - j++ - } - return newSymbols + panic("not implemented") + // data, ok := handler.data[uri] + // if !ok || len(origSymbols) == 0 { + // return origSymbols + // } + + // symbolIdx := make(map[string]*lsp.DocumentSymbol) + // for i := 0; i < len(origSymbols); i++ { + // symbol := &origSymbols[i] + // _, symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) + + // duplicate := false + // other, duplicate := symbolIdx[symbol.Name] + // if duplicate { + // // We prefer symbols later in the file due to the function header generation. E.g. if one has a function `void foo() {}` somehwre in the code + // // the code generation will add a `void foo();` header at the beginning of the cpp file. We care about the function body later in the file, not + // // the header early on. + // if other.Range.Start.Line < symbol.Range.Start.Line { + // continue + // } + // } + + // _, symbol.SelectionRange = data.sourceMap.CppToInoRange(symbol.SelectionRange) + // symbol.Children = handler.cpp2inoDocumentSymbols(symbol.Children, uri) + // symbolIdx[symbol.Name] = symbol + // } + + // newSymbols := make([]lsp.DocumentSymbol, len(symbolIdx)) + // j := 0 + // for _, s := range symbolIdx { + // newSymbols[j] = *s + // j++ + // } + // return newSymbols } func (handler *InoHandler) cpp2inoSymbolInformation(syms []*lsp.SymbolInformation) []lsp.SymbolInformation { - // Much like in cpp2inoDocumentSymbols we de-duplicate symbols based on file in-file location. - idx := make(map[string]*lsp.SymbolInformation) - for _, sym := range syms { - handler.cpp2inoLocation(&sym.Location) - - nme := fmt.Sprintf("%s::%s", sym.ContainerName, sym.Name) - other, duplicate := idx[nme] - if duplicate && other.Location.Range.Start.Line < sym.Location.Range.Start.Line { - continue - } - - idx[nme] = sym - } - - var j int - symbols := make([]lsp.SymbolInformation, len(idx)) - for _, sym := range idx { - symbols[j] = *sym - j++ - } - return symbols + panic("not implemented") + // // Much like in cpp2inoDocumentSymbols we de-duplicate symbols based on file in-file location. + // idx := make(map[string]*lsp.SymbolInformation) + // for _, sym := range syms { + // handler.cpp2inoLocation(&sym.Location) + + // nme := fmt.Sprintf("%s::%s", sym.ContainerName, sym.Name) + // other, duplicate := idx[nme] + // if duplicate && other.Location.Range.Start.Line < sym.Location.Range.Start.Line { + // continue + // } + + // idx[nme] = sym + // } + + // var j int + // symbols := make([]lsp.SymbolInformation, len(idx)) + // for _, sym := range idx { + // symbols[j] = *sym + // j++ + // } + // return symbols } // FromClangd handles a message received from clangd. @@ -998,31 +1014,32 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. } func (handler *InoHandler) parseCommandArgument(rawArg interface{}) interface{} { - if m1, ok := rawArg.(map[string]interface{}); ok && len(m1) == 1 && m1["changes"] != nil { - m2 := m1["changes"].(map[string]interface{}) - workspaceEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} - for uri, rawValue := range m2 { - rawTextEdits := rawValue.([]interface{}) - textEdits := make([]lsp.TextEdit, len(rawTextEdits)) - for index := range rawTextEdits { - m3 := rawTextEdits[index].(map[string]interface{}) - rawRange := m3["range"] - m4 := rawRange.(map[string]interface{}) - rawStart := m4["start"] - m5 := rawStart.(map[string]interface{}) - textEdits[index].Range.Start.Line = int(m5["line"].(float64)) - textEdits[index].Range.Start.Character = int(m5["character"].(float64)) - rawEnd := m4["end"] - m6 := rawEnd.(map[string]interface{}) - textEdits[index].Range.End.Line = int(m6["line"].(float64)) - textEdits[index].Range.End.Character = int(m6["character"].(float64)) - textEdits[index].NewText = m3["newText"].(string) - } - workspaceEdit.Changes[uri] = textEdits - } - return &workspaceEdit - } - return nil + panic("not implemented") + // if m1, ok := rawArg.(map[string]interface{}); ok && len(m1) == 1 && m1["changes"] != nil { + // m2 := m1["changes"].(map[string]interface{}) + // workspaceEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} + // for uri, rawValue := range m2 { + // rawTextEdits := rawValue.([]interface{}) + // textEdits := make([]lsp.TextEdit, len(rawTextEdits)) + // for index := range rawTextEdits { + // m3 := rawTextEdits[index].(map[string]interface{}) + // rawRange := m3["range"] + // m4 := rawRange.(map[string]interface{}) + // rawStart := m4["start"] + // m5 := rawStart.(map[string]interface{}) + // textEdits[index].Range.Start.Line = int(m5["line"].(float64)) + // textEdits[index].Range.Start.Character = int(m5["character"].(float64)) + // rawEnd := m4["end"] + // m6 := rawEnd.(map[string]interface{}) + // textEdits[index].Range.End.Line = int(m6["line"].(float64)) + // textEdits[index].Range.End.Character = int(m6["character"].(float64)) + // textEdits[index].NewText = m3["newText"].(string) + // } + // workspaceEdit.Changes[uri] = textEdits + // } + // return &workspaceEdit + // } + // return nil } func (handler *InoHandler) showMessage(ctx context.Context, msgType lsp.MessageType, message string) { diff --git a/main.go b/main.go index ebf145f..6657f64 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,9 @@ package main import ( "flag" - "io" "log" "os" - "runtime/debug" + "syscall" "github.com/arduino/go-paths-helper" "github.com/bcmi-labs/arduino-language-server/handler" From b67523360aa6a50b21ceca7abbb39d084fa0fa14 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 29 Nov 2020 01:39:24 +0100 Subject: [PATCH 30/61] Log by redirecting stderr, this helps to log panics in goroutines https://stackoverflow.com/a/34773942/1655275 --- main.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 6657f64..7f5bb73 100644 --- a/main.go +++ b/main.go @@ -38,18 +38,22 @@ func main() { if enableLogging { logfile := streams.OpenLogFileAs("inols-err.log") + // log.SetOutput(io.MultiWriter(logfile, os.Stderr)) defer func() { - // In case of panic output the stack trace in the log file before exiting - if r := recover(); r != nil { - log.Println(string(debug.Stack())) - } + // // In case of panic output the stack trace in the log file before exiting + // if r := recover(); r != nil { + // log.Println(string(debug.Stack())) + // } + logfile.Close() }() - log.SetOutput(io.MultiWriter(logfile, os.Stderr)) + err := syscall.Dup2(int(logfile.Fd()), int(os.Stderr.Fd())) + if err != nil { + log.Fatalf("Failed to redirect stderr to file: %v", err) + } // log.SetOutput(logfile) - } else { - log.SetOutput(os.Stderr) } + log.SetOutput(os.Stderr) handler.Setup(cliPath, clangdPath, enableLogging, true) initialBoard := lsp.Board{Fqbn: initialFqbn, Name: initialBoardName} From e2271599bd0e50b796a1305dbf1af6f559835cb7 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 29 Nov 2020 01:57:56 +0100 Subject: [PATCH 31/61] inline completionList --- handler/handler.go | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 4ef3f2a..f2b9c36 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -697,18 +697,36 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document handler.synchronizer.DataMux.RLock() defer handler.synchronizer.DataMux.RUnlock() + cppToIno := uri != "" && uri.AsPath().EquivalentTo(handler.buildSketchCpp) + switch r := result.(type) { case *lsp.Hover: // method "textDocument/hover" if len(r.Contents.Value) == 0 { return nil } - if uri.AsPath().EquivalentTo(handler.buildSketchCpp) { + if cppToIno { _, *r.Range = handler.sketchMapper.CppToInoRange(*r.Range) } + log.Printf("<-- hover(%s)", strconv.Quote(r.Contents.Value)) + return r + + case *lsp.CompletionList: + // method "textDocument/completion" + newItems := make([]lsp.CompletionItem, 0) + + for _, item := range r.Items { + if !strings.HasPrefix(item.InsertText, "_") { + if cppToIno && item.TextEdit != nil { + _, item.TextEdit.Range = handler.sketchMapper.CppToInoRange(item.TextEdit.Range) + } + newItems = append(newItems, item) + } + } + r.Items = newItems + log.Printf("<-- completion(%d items)", len(r.Items)) + return r - case *lsp.CompletionList: // "textDocument/completion": - handler.cpp2inoCompletionList(r, uri) case *[]*lsp.CommandOrCodeAction: // "textDocument/codeAction": for index := range *r { command := (*r)[index].Command @@ -774,22 +792,6 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document return result } -func (handler *InoHandler) cpp2inoCompletionList(list *lsp.CompletionList, uri lsp.DocumentURI) { - panic("not implemented") - // if data, ok := handler.data[uri]; ok { - // newItems := make([]lsp.CompletionItem, 0, len(list.Items)) - // for _, item := range list.Items { - // if !strings.HasPrefix(item.InsertText, "_") { - // if item.TextEdit != nil { - // _, item.TextEdit.Range = data.sourceMap.CppToInoRange(item.TextEdit.Range) - // } - // newItems = append(newItems, item) - // } - // } - // list.Items = newItems - // } -} - func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp.DocumentURI) { panic("not implemented") // codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) From 57a46b38a4c239516c2c0bf9bc28d3bf0934ed5a Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 30 Nov 2020 16:42:50 +0100 Subject: [PATCH 32/61] partial implementation of codeAction --- handler/handler.go | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index f2b9c36..7cf2984 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -180,6 +180,23 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams) log.Printf(" --> completion(%s:%d:%d)\n", p.TextDocument.URI, p.Position.Line, p.Position.Character) + case *lsp.CodeActionParams: + // method "textDocument/codeAction" + uri = p.TextDocument.URI + log.Printf("--> codeAction(%s:%s)", p.TextDocument.URI, p.Range.Start) + + if err := handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument); err != nil { + break + } + if p.TextDocument.URI.AsPath().EquivalentTo(handler.buildSketchCpp) { + p.Range = handler.sketchMapper.InoToCppLSPRange(uri, p.Range) + for index := range p.Context.Diagnostics { + r := &p.Context.Diagnostics[index].Range + *r = handler.sketchMapper.InoToCppLSPRange(uri, *r) + } + } + log.Printf(" --> codeAction(%s:%s)", p.TextDocument.URI, p.Range.Start) + case *lsp.HoverParams: // method: "textDocument/hover" uri = p.TextDocument.URI @@ -200,11 +217,6 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr uri = p.TextDocument.URI err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) handler.deleteFileData(uri) - case *lsp.CodeActionParams: // "textDocument/codeAction": - log.Printf("--X " + req.Method) - return nil, nil - uri = p.TextDocument.URI - err = handler.ino2cppCodeActionParams(p) // case "textDocument/signatureHelp": // fallthrough // case "textDocument/definition": @@ -607,20 +619,6 @@ func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDoc return nil } -func (handler *InoHandler) ino2cppCodeActionParams(params *lsp.CodeActionParams) error { - panic("not implemented") - // handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) - // if data, ok := handler.data[params.TextDocument.URI]; ok { - // params.Range = data.sourceMap.InoToCppLSPRange(data.sourceURI, params.Range) - // for index := range params.Context.Diagnostics { - // r := ¶ms.Context.Diagnostics[index].Range - // *r = data.sourceMap.InoToCppLSPRange(data.sourceURI, *r) - // } - // return nil - // } - return unknownURI(params.TextDocument.URI) -} - func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.DocumentRangeFormattingParams) error { panic("not implemented") // handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) @@ -727,7 +725,12 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document log.Printf("<-- completion(%d items)", len(r.Items)) return r - case *[]*lsp.CommandOrCodeAction: // "textDocument/codeAction": + case *[]*lsp.CommandOrCodeAction: + // method "textDocument/codeAction" + // TODO: implement response + r = &[]*lsp.CommandOrCodeAction{} + log.Printf("<-- codeAction(empty)") + break for index := range *r { command := (*r)[index].Command if command != nil { @@ -738,6 +741,7 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document handler.cpp2inoCodeAction(codeAction, uri) } } + // case "textDocument/definition": // fallthrough // case "textDocument/typeDefinition": From 91241a36eb97c69a5cca0ae42bc6ea09f2324b49 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 1 Dec 2020 15:20:24 +0100 Subject: [PATCH 33/61] Implemented textDocument/documentSymbol --- handler/handler.go | 117 ++++++++++++++++++------------------ handler/sourcemapper/ino.go | 8 +++ lsp/protocol.go | 47 ++++++++------- 3 files changed, 91 insertions(+), 81 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 7cf2984..673892e 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -206,6 +206,14 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr err = handler.ino2cppTextDocumentPositionParams(doc) log.Printf(" --> hover(%s:%d:%d)\n", doc.TextDocument.URI, doc.Position.Line, doc.Position.Character) + case *lsp.DocumentSymbolParams: + // method "textDocument/documentSymbol" + uri = p.TextDocument.URI + log.Printf("--> documentSymbol(%s)", p.TextDocument.URI) + + err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) + log.Printf(" --> documentSymbol(%s)", p.TextDocument.URI) + case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": log.Printf("--X " + req.Method) return nil, nil @@ -250,11 +258,6 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr return nil, nil uri = p.TextDocument.URI err = handler.ino2cppDocumentOnTypeFormattingParams(p) - case *lsp.DocumentSymbolParams: // "textDocument/documentSymbol": - log.Printf("--X " + req.Method) - return nil, nil - uri = p.TextDocument.URI - err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) case *lsp.RenameParams: // "textDocument/rename": log.Printf("--X " + req.Method) return nil, nil @@ -725,6 +728,19 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document log.Printf("<-- completion(%d items)", len(r.Items)) return r + case *lsp.DocumentSymbolArrayOrSymbolInformationArray: + // method "textDocument/documentSymbol" + + if r.DocumentSymbolArray != nil { + // Treat the input as []DocumentSymbol + return handler.cpp2inoDocumentSymbols(*r.DocumentSymbolArray, uri) + } else if r.SymbolInformationArray != nil { + // Treat the input as []SymbolInformation + return handler.cpp2inoSymbolInformation(*r.SymbolInformationArray) + } else { + // Treat the input as null + } + case *[]*lsp.CommandOrCodeAction: // method "textDocument/codeAction" // TODO: implement response @@ -764,28 +780,6 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document for index := range *r { handler.cpp2inoTextEdit(&(*r)[index], uri) } - case *[]*lsp.DocumentSymbolOrSymbolInformation: // "textDocument/documentSymbol": - if len(*r) == 0 { - return result - } - - slice := *r - if slice[0].DocumentSymbol != nil { - // Treat the input as []DocumentSymbol - symbols := make([]lsp.DocumentSymbol, len(slice)) - for index := range slice { - symbols[index] = *slice[index].DocumentSymbol - } - return handler.cpp2inoDocumentSymbols(symbols, uri) - } - if slice[0].SymbolInformation != nil { - // Treat the input as []SymbolInformation - symbols := make([]*lsp.SymbolInformation, len(slice)) - for i, s := range slice { - symbols[i] = s.SymbolInformation - } - return handler.cpp2inoSymbolInformation(symbols) - } case *lsp.WorkspaceEdit: // "textDocument/rename": return handler.cpp2inoWorkspaceEdit(r) case *[]lsp.SymbolInformation: // "workspace/symbol": @@ -859,44 +853,47 @@ func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentU // } } -func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, uri lsp.DocumentURI) []lsp.DocumentSymbol { - panic("not implemented") - // data, ok := handler.data[uri] - // if !ok || len(origSymbols) == 0 { - // return origSymbols - // } +func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, origURI lsp.DocumentURI) []lsp.DocumentSymbol { + if origURI.AsPath().Ext() != ".ino" || len(origSymbols) == 0 { + return origSymbols + } - // symbolIdx := make(map[string]*lsp.DocumentSymbol) - // for i := 0; i < len(origSymbols); i++ { - // symbol := &origSymbols[i] - // _, symbol.Range = data.sourceMap.CppToInoRange(symbol.Range) - - // duplicate := false - // other, duplicate := symbolIdx[symbol.Name] - // if duplicate { - // // We prefer symbols later in the file due to the function header generation. E.g. if one has a function `void foo() {}` somehwre in the code - // // the code generation will add a `void foo();` header at the beginning of the cpp file. We care about the function body later in the file, not - // // the header early on. - // if other.Range.Start.Line < symbol.Range.Start.Line { - // continue - // } - // } + inoSymbols := []lsp.DocumentSymbol{} + for _, symbol := range origSymbols { + if handler.sketchMapper.IsPreprocessedCppLine(symbol.Range.Start.Line) { + continue + } - // _, symbol.SelectionRange = data.sourceMap.CppToInoRange(symbol.SelectionRange) - // symbol.Children = handler.cpp2inoDocumentSymbols(symbol.Children, uri) - // symbolIdx[symbol.Name] = symbol - // } + inoFile, inoRange := handler.sketchMapper.CppToInoRange(symbol.Range) + inoSelectionURI, inoSelectionRange := handler.sketchMapper.CppToInoRange(symbol.SelectionRange) - // newSymbols := make([]lsp.DocumentSymbol, len(symbolIdx)) - // j := 0 - // for _, s := range symbolIdx { - // newSymbols[j] = *s - // j++ - // } - // return newSymbols + if inoFile != inoSelectionURI { + log.Printf(" ERROR: symbol range and selection belongs to different URI!") + log.Printf(" > %s != %s", symbol.Range, symbol.SelectionRange) + log.Printf(" > %s:%s != %s:%s", inoFile, inoRange, inoSelectionURI, inoSelectionRange) + continue + } + + if inoFile != origURI.Unbox() { + //log.Printf(" skipping symbol related to %s", inoFile) + continue + } + + inoSymbols = append(inoSymbols, lsp.DocumentSymbol{ + Name: symbol.Name, + Detail: symbol.Detail, + Deprecated: symbol.Deprecated, + Kind: symbol.Kind, + Range: inoRange, + SelectionRange: inoSelectionRange, + Children: handler.cpp2inoDocumentSymbols(symbol.Children, origURI), + }) + } + + return inoSymbols } -func (handler *InoHandler) cpp2inoSymbolInformation(syms []*lsp.SymbolInformation) []lsp.SymbolInformation { +func (handler *InoHandler) cpp2inoSymbolInformation(syms []lsp.SymbolInformation) []lsp.SymbolInformation { panic("not implemented") // // Much like in cpp2inoDocumentSymbols we de-duplicate symbols based on file in-file location. // idx := make(map[string]*lsp.SymbolInformation) diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index ebb8a3f..804b693 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -99,6 +99,14 @@ func (s *InoMapper) CppToInoLineOk(targetLine int) (string, int, bool) { return res.File, res.Line, ok } +// IsPreprocessedCppLine returns true if the give .cpp line is part of the +// section added by the arduino preprocessor. +func (s *InoMapper) IsPreprocessedCppLine(cppLine int) bool { + _, preprocessed := s.cppPreprocessed[cppLine] + _, mapsToIno := s.toIno[cppLine] + return preprocessed || !mapsToIno +} + // CreateInoMapper create a InoMapper from the given target file func CreateInoMapper(targetFile []byte) *InoMapper { mapper := &InoMapper{ diff --git a/lsp/protocol.go b/lsp/protocol.go index 5c6d09c..932377b 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -154,7 +154,7 @@ func SendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params err := conn.Call(ctx, method, params, result) return result, err case "textDocument/documentSymbol": - result := new([]*DocumentSymbolOrSymbolInformation) + result := new(DocumentSymbolArrayOrSymbolInformationArray) err := conn.Call(ctx, method, params, result) return result, err case "textDocument/rename": @@ -252,35 +252,40 @@ type DocumentSymbol struct { Children []DocumentSymbol `json:"children,omitempty"` } -type DocumentSymbolOrSymbolInformation struct { - DocumentSymbol *DocumentSymbol - SymbolInformation *SymbolInformation +type DocumentSymbolArrayOrSymbolInformationArray struct { + DocumentSymbolArray *[]DocumentSymbol + SymbolInformationArray *[]SymbolInformation } -type documentSymbolOrSymbolInformationDiscriminator struct { - Range *Range `json:"range,omitempty"` - Location *Location `json:"location,omitempty"` -} - -func (entry *DocumentSymbolOrSymbolInformation) UnmarshalJSON(raw []byte) error { - discriminator := new(documentSymbolOrSymbolInformationDiscriminator) - err := json.Unmarshal(raw, discriminator) - if err != nil { +func (entry *DocumentSymbolArrayOrSymbolInformationArray) UnmarshalJSON(raw []byte) error { + intermediate := []json.RawMessage{} + if err := json.Unmarshal(raw, &intermediate); err != nil { + return err + } + discriminator := struct { + Range *Range `json:"range,omitempty"` + Location *Location `json:"location,omitempty"` + }{} + if err := json.Unmarshal(intermediate[0], &discriminator); err != nil { return err } if discriminator.Range != nil { - entry.DocumentSymbol = new(DocumentSymbol) - err = json.Unmarshal(raw, entry.DocumentSymbol) - if err != nil { - return err + res := make([]DocumentSymbol, len(intermediate)) + for i, item := range intermediate { + if err := json.Unmarshal(item, &res[i]); err != nil { + return err + } } + entry.DocumentSymbolArray = &res } if discriminator.Location != nil { - entry.SymbolInformation = new(SymbolInformation) - err = json.Unmarshal(raw, entry.SymbolInformation) - if err != nil { - return err + res := make([]SymbolInformation, len(intermediate)) + for i, item := range intermediate { + if err := json.Unmarshal(item, &res[i]); err != nil { + return err + } } + entry.SymbolInformationArray = &res } return nil } From 749ab911682d05d6eb76308657c5a8c0898a58df Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 1 Dec 2020 16:34:20 +0100 Subject: [PATCH 34/61] Append to logs instead of overwriting --- go.mod | 4 ++-- go.sum | 2 ++ streams/dumper.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 217ee6b..46ebfa1 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.12 replace github.com/arduino/arduino-cli => ../arduino-cli require ( - github.com/arduino/arduino-cli v0.0.0-20201118111649-5edef82c60fb - github.com/arduino/go-paths-helper v1.3.2 + github.com/arduino/arduino-cli v0.0.0-20201201130510-05ce1509a4f1 + github.com/arduino/go-paths-helper v1.3.3 github.com/arduino/go-properties-orderedmap v1.4.0 github.com/pkg/errors v0.9.1 github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 diff --git a/go.sum b/go.sum index e251705..a3f518f 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3 github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-paths-helper v1.3.2 h1:5U9TSKQODiwSVgTxskC0PNl0l0Vf40GUlp99Zy2SK8w= github.com/arduino/go-paths-helper v1.3.2/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.3.3 h1:r51Mix4GkYqiaF9IuRTtRE3Y/WFA0C47WwG/VZAkbcI= +github.com/arduino/go-paths-helper v1.3.3/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= diff --git a/streams/dumper.go b/streams/dumper.go index ae66cbf..a948aa1 100644 --- a/streams/dumper.go +++ b/streams/dumper.go @@ -34,7 +34,7 @@ func LogReadWriteCloserToFile(upstream io.ReadWriteCloser, file *os.File) io.Rea // OpenLogFileAs creates a log file in GlobalLogDirectory. func OpenLogFileAs(filename string) *os.File { path := GlobalLogDirectory.Join(filename) - res, err := path.Create() + res, err := path.Append() if err != nil { log.Fatalf("Error opening log file: %s", err) } else { From f9b9ad6d9a5afee056c3ceb8041eb9b1002847e6 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 1 Dec 2020 16:34:43 +0100 Subject: [PATCH 35/61] Added lsp protocol decoding tests --- lsp/protocol_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lsp/protocol_test.go diff --git a/lsp/protocol_test.go b/lsp/protocol_test.go new file mode 100644 index 0000000..4986bdf --- /dev/null +++ b/lsp/protocol_test.go @@ -0,0 +1,66 @@ +package lsp + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDocumentSymbolParse(t *testing.T) { + docin := ` + [ + { + "kind":12, + "name":"setup", + "range": {"end": {"character":11,"line":6},"start": {"character":0,"line":6}}, + "selectionRange":{"end":{"character":10,"line":6},"start":{"character":5,"line":6}} + },{ + "kind":12, + "name":"newfunc", + "range":{"end":{"character":13,"line":8},"start":{"character":0,"line":8}}, + "selectionRange":{"end":{"character":12,"line":8},"start":{"character":5,"line":8}} + },{ + "kind":12, + "name":"loop", + "range":{"end":{"character":10,"line":10},"start":{"character":0,"line":10}}, + "selectionRange":{"end":{"character":9,"line":10},"start":{"character":5,"line":10}} + },{ + "kind":12, + "name":"secondFunction", + "range":{"end":{"character":20,"line":12},"start":{"character":0,"line":12}}, + "selectionRange":{"end":{"character":19,"line":12},"start":{"character":5,"line":12}} + },{ + "kind":12, + "name":"setup", + "range":{"end":{"character":0,"line":21},"start":{"character":0,"line":14}}, + "selectionRange":{"end":{"character":10,"line":14},"start":{"character":5,"line":14}} + },{ + "kind":12, + "name":"newfunc", + "range":{"end":{"character":16,"line":23},"start":{"character":0,"line":23}}, + "selectionRange":{"end":{"character":12,"line":23},"start":{"character":5,"line":23}} + },{ + "kind":12, + "name":"loop", + "range":{"end":{"character":0,"line":26},"start":{"character":0,"line":24}}, + "selectionRange":{"end":{"character":9,"line":24},"start":{"character":5,"line":24}} + },{ + "kind":12, + "name":"secondFunction", + "range":{"end":{"character":0,"line":32},"start":{"character":0,"line":30}}, + "selectionRange":{"end":{"character":19,"line":30},"start":{"character":5,"line":30}} + } + ]` + var res DocumentSymbolArrayOrSymbolInformationArray + err := json.Unmarshal([]byte(docin), &res) + require.NoError(t, err) + require.NotNil(t, res.DocumentSymbolArray) + symbols := *res.DocumentSymbolArray + require.Equal(t, SymbolKind(12), symbols[2].Kind) + require.Equal(t, "loop", symbols[2].Name) + require.Equal(t, "10:0-10:10", symbols[2].Range.String()) + require.Equal(t, "10:5-10:9", symbols[2].SelectionRange.String()) + fmt.Printf("%+v\n", res) +} From aac7cdc5d5d64d9a3a6b2c6e40566c7684449505 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 2 Dec 2020 11:18:17 +0100 Subject: [PATCH 36/61] re-enable global message handling synchronization --- handler/handler.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 673892e..c1b95f2 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -105,11 +105,11 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr "textDocument/didClose": true, } if needsWriteLock[req.Method] { - // handler.synchronizer.DataMux.Lock() - // defer handler.synchronizer.DataMux.Unlock() + handler.synchronizer.DataMux.Lock() + defer handler.synchronizer.DataMux.Unlock() } else { - // handler.synchronizer.DataMux.RLock() - // defer handler.synchronizer.DataMux.RUnlock() + handler.synchronizer.DataMux.RLock() + defer handler.synchronizer.DataMux.RUnlock() } // Handle LSP methods: transform parameters and send to clangd @@ -125,18 +125,14 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr switch p := params.(type) { case *lsp.InitializeParams: // method "initialize" - handler.synchronizer.DataMux.RLock() err = handler.initializeWorkbench(ctx, p) - handler.synchronizer.DataMux.RUnlock() case *lsp.DidOpenTextDocumentParams: // method "textDocument/didOpen" uri = p.TextDocument.URI log.Printf("--> didOpen(%s@%d as '%s')", p.TextDocument.URI, p.TextDocument.Version, p.TextDocument.LanguageID) - handler.synchronizer.DataMux.Lock() res, err := handler.didOpen(ctx, p) - handler.synchronizer.DataMux.Unlock() if res == nil { log.Println(" --X notification is not propagated to clangd") From 7943ae1ce37273fdb994f76dea423c266b7d0b86 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 2 Dec 2020 11:19:15 +0100 Subject: [PATCH 37/61] partial implementation of documentSymbol --- handler/handler.go | 126 ++++++++++++++++++++++-------------- handler/sourcemapper/ino.go | 1 - lsp/protocol.go | 2 +- lsp/structures.go | 2 +- 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index c1b95f2..96f98d0 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -672,7 +672,7 @@ func (handler *InoHandler) ino2cppExecuteCommand(executeCommand *lsp.ExecuteComm func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit { panic("not implemented") - newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} + newEdit := lsp.WorkspaceEdit{Changes: make(map[lsp.DocumentURI][]lsp.TextEdit)} // for uri, edit := range origEdit.Changes { // if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { // newValue := make([]lsp.TextEdit, len(edit)) @@ -737,22 +737,22 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document // Treat the input as null } - case *[]*lsp.CommandOrCodeAction: + case *[]lsp.CommandOrCodeAction: // method "textDocument/codeAction" - // TODO: implement response - r = &[]*lsp.CommandOrCodeAction{} - log.Printf("<-- codeAction(empty)") - break - for index := range *r { - command := (*r)[index].Command - if command != nil { - handler.cpp2inoCommand(command) + log.Printf(" <-- codeAction(%d elements)", len(*r)) + for i, item := range *r { + (*r)[i] = lsp.CommandOrCodeAction{ + Command: handler.cpp2inoCommand(item.Command), + CodeAction: handler.cpp2inoCodeAction(item.CodeAction, uri), + } + if item.Command != nil { + log.Printf(" > Command: %s", item.Command.Title) } - codeAction := (*r)[index].CodeAction - if codeAction != nil { - handler.cpp2inoCodeAction(codeAction, uri) + if item.CodeAction != nil { + log.Printf(" > CodeAction: %s", item.CodeAction.Title) } } + log.Printf("<-- codeAction(%d elements)", len(*r)) // case "textDocument/definition": // fallthrough @@ -786,45 +786,73 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document return result } -func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp.DocumentURI) { - panic("not implemented") - // codeAction.Edit = handler.cpp2inoWorkspaceEdit(codeAction.Edit) - // if data, ok := handler.data[uri]; ok { - // for index := range codeAction.Diagnostics { - // _, codeAction.Diagnostics[index].Range = data.sourceMap.CppToInoRange(codeAction.Diagnostics[index].Range) - // } - // } +func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp.DocumentURI) *lsp.CodeAction { + if codeAction == nil { + return nil + } + inoCodeAction := &lsp.CodeAction{ + Title: codeAction.Title, + Kind: codeAction.Kind, + Edit: handler.cpp2inoWorkspaceEdit(codeAction.Edit), + Diagnostics: codeAction.Diagnostics, + Command: handler.cpp2inoCommand(codeAction.Command), + } + if uri.AsPath().Ext() == ".ino" { + for i, diag := range inoCodeAction.Diagnostics { + _, inoCodeAction.Diagnostics[i].Range = handler.sketchMapper.CppToInoRange(diag.Range) + } + } + return inoCodeAction } -func (handler *InoHandler) cpp2inoCommand(command *lsp.Command) { - panic("not implemented") - // if len(command.Arguments) == 1 { - // arg := handler.parseCommandArgument(command.Arguments[0]) - // if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { - // command.Arguments[0] = handler.cpp2inoWorkspaceEdit(workspaceEdit) - // } - // } +func (handler *InoHandler) cpp2inoCommand(command *lsp.Command) *lsp.Command { + if command == nil { + return nil + } + inoCommand := &lsp.Command{ + Title: command.Title, + Command: command.Command, + Arguments: command.Arguments, + } + if len(command.Arguments) == 1 { + arg := handler.parseCommandArgument(inoCommand.Arguments[0]) + if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { + inoCommand.Arguments[0] = handler.cpp2inoWorkspaceEdit(workspaceEdit) + } + } + return inoCommand } -func (handler *InoHandler) cpp2inoWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit { - panic("not implemented") - // newEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} - // for uri, edit := range origEdit.Changes { - // if data, ok := handler.data[lsp.DocumentURI(uri)]; ok { - // newValue := make([]lsp.TextEdit, len(edit)) - // for index := range edit { - // _, newRange := data.sourceMap.CppToInoRange(edit[index].Range) - // newValue[index] = lsp.TextEdit{ - // NewText: edit[index].NewText, - // Range: newRange, - // } - // } - // newEdit.Changes[string(data.sourceURI)] = newValue - // } else { - // newEdit.Changes[uri] = edit - // } - // } - // return &newEdit +func (handler *InoHandler) cpp2inoWorkspaceEdit(origWorkspaceEdit *lsp.WorkspaceEdit) *lsp.WorkspaceEdit { + if origWorkspaceEdit == nil { + return nil + } + resWorkspaceEdit := &lsp.WorkspaceEdit{ + Changes: map[lsp.DocumentURI][]lsp.TextEdit{}, + } + for editURI, edits := range origWorkspaceEdit.Changes { + // if the edits are not relative to sketch file... + if !editURI.AsPath().EquivalentTo(handler.buildSketchCpp) { + // ...pass them through... + resWorkspaceEdit.Changes[editURI] = edits + continue + } + + // ...otherwise convert edits to the sketch.ino.cpp into multilpe .ino edits + for _, edit := range edits { + cppRange := edit.Range + inoFile, inoRange := handler.sketchMapper.CppToInoRange(cppRange) + inoURI := lsp.NewDocumentURI(inoFile) + if _, have := resWorkspaceEdit.Changes[inoURI]; !have { + resWorkspaceEdit.Changes[inoURI] = []lsp.TextEdit{} + } + resWorkspaceEdit.Changes[inoURI] = append(resWorkspaceEdit.Changes[inoURI], lsp.TextEdit{ + NewText: edit.NewText, + Range: inoRange, + }) + } + } + return resWorkspaceEdit } func (handler *InoHandler) cpp2inoLocation(location *lsp.Location) { @@ -1013,7 +1041,9 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. } func (handler *InoHandler) parseCommandArgument(rawArg interface{}) interface{} { + log.Printf(" TRY TO PARSE: %+v", rawArg) panic("not implemented") + return nil // if m1, ok := rawArg.(map[string]interface{}); ok && len(m1) == 1 && m1["changes"] != nil { // m2 := m1["changes"].(map[string]interface{}) // workspaceEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 804b693..ddf3716 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -16,7 +16,6 @@ import ( type InoMapper struct { InoText map[lsp.DocumentURI]*SourceRevision CppText *SourceRevision - CppFile lsp.DocumentURI toCpp map[InoLine]int // Converts File.ino:line -> line toIno map[int]InoLine // Convers line -> File.ino:line inoPreprocessed map[InoLine]int // map of the lines taken by the preprocessor: File.ino:line -> preprocessed line diff --git a/lsp/protocol.go b/lsp/protocol.go index 932377b..66bb87d 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -116,7 +116,7 @@ func SendRequest(ctx context.Context, conn *jsonrpc2.Conn, method string, params err := conn.Call(ctx, method, params, result) return result, err case "textDocument/codeAction": - result := new([]*CommandOrCodeAction) + result := new([]CommandOrCodeAction) err := conn.Call(ctx, method, params, result) return result, err case "completionItem/resolve": diff --git a/lsp/structures.go b/lsp/structures.go index 6d950f8..4b40a91 100644 --- a/lsp/structures.go +++ b/lsp/structures.go @@ -111,7 +111,7 @@ type WorkspaceEdit struct { /** * Holds changes to existing resources. */ - Changes map[string][]TextEdit `json:"changes"` + Changes map[DocumentURI][]TextEdit `json:"changes"` } type TextDocumentIdentifier struct { From 8a67a1800650c11a1480e5d4e9093e7a5717844b Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 2 Dec 2020 14:32:47 +0100 Subject: [PATCH 38/61] didChange now keeps track of changes in the text --- handler/handler.go | 30 ++++++++++++++++-------------- handler/textutils/textutils.go | 11 +++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 96f98d0..71cdf17 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -150,17 +150,16 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Printf(" > %s -> '%s'", change.Range, strconv.Quote(change.Text)) } - res, err := handler.didChange(ctx, p) - if err != nil { + if res, err := handler.didChange(ctx, p); err != nil { log.Printf(" --E error: %s", err) return nil, err - } - if res == nil { + } else if res == nil { log.Println(" --X notification is not propagated to clangd") return nil, err // do not propagate to clangd + } else { + p = res } - p = res log.Printf(" --> didChange(%s@%d)", p.TextDocument.URI, p.TextDocument.Version) for _, change := range p.ContentChanges { log.Printf(" > %s -> '%s'", change.Range, strconv.Quote(change.Text)) @@ -432,11 +431,13 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText if trackedDoc.Version+1 != doc.Version { return nil, errors.Errorf("document out-of-sync: expected version %d but got %d", trackedDoc.Version+1, doc.Version) } - trackedDoc.Version++ + for _, change := range req.ContentChanges { + textutils.ApplyLSPTextDocumentContentChangeEvent(trackedDoc, &change) + } + // If changes are applied to a .ino file we increment the global .ino.cpp versioning + // for each increment of the single .ino file. if doc.URI.AsPath().Ext() == ".ino" { - // If changes are applied to a .ino file we increment the global .ino.cpp versioning - // for each increment of the single .ino file. cppChanges := []lsp.TextDocumentContentChangeEvent{} for _, inoChange := range req.ContentChanges { @@ -475,14 +476,15 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText }, } return cppReq, nil - } else { - - // TODO - return nil, unknownURI(doc.URI) - } - return nil, unknownURI(doc.URI) + // If changes are applied to other files pass them by converting just the URI + cppReq := &lsp.DidChangeTextDocumentParams{ + TextDocument: req.TextDocument, + ContentChanges: req.ContentChanges, + } + err := handler.sketchToBuildPathTextDocumentIdentifier(&cppReq.TextDocument.TextDocumentIdentifier) + return cppReq, err } func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, change *lsp.TextDocumentContentChangeEvent) (err error) { diff --git a/handler/textutils/textutils.go b/handler/textutils/textutils.go index 5179779..b0af538 100644 --- a/handler/textutils/textutils.go +++ b/handler/textutils/textutils.go @@ -6,6 +6,17 @@ import ( "github.com/bcmi-labs/arduino-language-server/lsp" ) +// ApplyLSPTextDocumentContentChangeEvent applies the LSP change in the given text +func ApplyLSPTextDocumentContentChangeEvent(textDoc *lsp.TextDocumentItem, change *lsp.TextDocumentContentChangeEvent) error { + newText, err := ApplyTextChange(textDoc.Text, *change.Range, change.Text) + if err != nil { + return err + } + textDoc.Text = newText + textDoc.Version++ + return nil +} + // ApplyTextChange replaces startingText substring specified by replaceRange with insertText func ApplyTextChange(startingText string, replaceRange lsp.Range, insertText string) (res string, err error) { start, err := getOffset(startingText, replaceRange.Start) From c3eac7ac0788614d6b81434e7402452f54512b87 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 3 Dec 2020 00:20:54 +0100 Subject: [PATCH 39/61] Added InitializedParams lsp structure --- handler/handler.go | 4 ++++ lsp/protocol.go | 2 ++ lsp/service.go | 2 ++ 3 files changed, 8 insertions(+) diff --git a/handler/handler.go b/handler/handler.go index 71cdf17..c9dd82f 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -127,6 +127,10 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr // method "initialize" err = handler.initializeWorkbench(ctx, p) + case *lsp.InitializedParams: + // method "initialized" + log.Println("--> initialized") + case *lsp.DidOpenTextDocumentParams: // method "textDocument/didOpen" uri = p.TextDocument.URI diff --git a/lsp/protocol.go b/lsp/protocol.go index 66bb87d..6af5eeb 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -13,6 +13,8 @@ func ReadParams(method string, raw *json.RawMessage) (interface{}, error) { params := new(InitializeParams) err := json.Unmarshal(*raw, params) return params, err + case "initialized": + return &InitializedParams{}, nil case "textDocument/didOpen": params := new(DidOpenTextDocumentParams) err := json.Unmarshal(*raw, params) diff --git a/lsp/service.go b/lsp/service.go index d232c93..f1067d2 100644 --- a/lsp/service.go +++ b/lsp/service.go @@ -25,6 +25,8 @@ type InitializeParams struct { WorkDoneToken string `json:"workDoneToken,omitempty"` } +type InitializedParams struct{} + // Root returns the RootURI if set, or otherwise the RootPath with 'file://' prepended. func (p *InitializeParams) Root() DocumentURI { if p.RootURI != "" { From 943cfa8c7c629a6bf2b6fd60632bd02caba483a7 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 3 Dec 2020 00:27:29 +0100 Subject: [PATCH 40/61] removed wrong synchronization directives --- handler/handler.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index c9dd82f..be1121f 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -100,6 +100,7 @@ func (handler *InoHandler) StopClangd() { // HandleMessageFromIDE handles a message received from the IDE client (via stdio). func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) { needsWriteLock := map[string]bool{ + "initialize": true, "textDocument/didOpen": true, "textDocument/didChange": true, "textDocument/didClose": true, @@ -697,9 +698,6 @@ func (handler *InoHandler) ino2cppWorkspaceEdit(origEdit *lsp.WorkspaceEdit) *ls } func (handler *InoHandler) transformClangdResult(method string, uri lsp.DocumentURI, result interface{}) interface{} { - handler.synchronizer.DataMux.RLock() - defer handler.synchronizer.DataMux.RUnlock() - cppToIno := uri != "" && uri.AsPath().EquivalentTo(handler.buildSketchCpp) switch r := result.(type) { From 556b426e011decf848d3d8daf8fe4e149c30d134 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 3 Dec 2020 01:02:24 +0100 Subject: [PATCH 41/61] removed unused field --- handler/handler.go | 1 - 1 file changed, 1 deletion(-) diff --git a/handler/handler.go b/handler/handler.go index be1121f..4c4f0f5 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -77,7 +77,6 @@ type InoHandler struct { sketchTrackedFilesCount int trackedFiles map[lsp.DocumentURI]*lsp.TextDocumentItem - //data map[lsp.DocumentURI]*FileData config lsp.BoardConfig synchronizer Synchronizer } From 10355fa79761cacb3a3de95780307768bc2fc32f Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 3 Dec 2020 01:02:58 +0100 Subject: [PATCH 42/61] Fixed content tracking version mismatch warning --- handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler/handler.go b/handler/handler.go index 4c4f0f5..2d6b232 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -432,7 +432,7 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText if !ok { return nil, unknownURI(doc.URI) } - if trackedDoc.Version+1 != doc.Version { + if trackedDoc.Version+len(req.ContentChanges) != doc.Version { return nil, errors.Errorf("document out-of-sync: expected version %d but got %d", trackedDoc.Version+1, doc.Version) } for _, change := range req.ContentChanges { From fdd4a7ff21947aa123d0353dce0f455510252180 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 3 Dec 2020 01:03:24 +0100 Subject: [PATCH 43/61] fixed dirty change detection --- handler/sourcemapper/ino.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index ddf3716..3eaac4f 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -180,6 +180,10 @@ func (s *InoMapper) ApplyTextChange(inoURI lsp.DocumentURI, inoChange lsp.TextDo s.CppText.Text = newText s.CppText.Version++ + if _, is := s.inoPreprocessed[s.toIno[cppRange.Start.Line]]; is { + dirty = true + } + // Update line references for deletedLines > 0 { dirty = dirty || s.deleteCppLine(cppRange.Start.Line) @@ -189,9 +193,6 @@ func (s *InoMapper) ApplyTextChange(inoURI lsp.DocumentURI, inoChange lsp.TextDo for addedLines > 0 { dirty = dirty || s.addInoLine(cppRange.Start.Line) } - if _, is := s.cppPreprocessed[cppRange.Start.Line]; is { - dirty = true - } return } From f8b79eab641ba30c7622bf95b65d30490ca758ac Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 3 Dec 2020 01:05:11 +0100 Subject: [PATCH 44/61] implemented documentSymbol caching --- handler/handler.go | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 2d6b232..c7e9814 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -67,10 +67,14 @@ func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { type InoHandler struct { StdioConn *jsonrpc2.Conn ClangdConn *jsonrpc2.Conn + lspInitializeParams *lsp.InitializeParams buildPath *paths.Path buildSketchRoot *paths.Path buildSketchCpp *paths.Path buildSketchCppVersion int + buildSketchSymbols []lsp.DocumentSymbol + buildSketchSymbolsLoad bool + buildSketchSymbolsCheck bool sketchRoot *paths.Path sketchName string sketchMapper *sourcemapper.InoMapper @@ -125,7 +129,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr switch p := params.(type) { case *lsp.InitializeParams: // method "initialize" - err = handler.initializeWorkbench(ctx, p) + err = handler.initializeWorkbench(p) case *lsp.InitializedParams: // method "initialized" @@ -290,6 +294,11 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Println(" sent", req.Method, "request id", req.ID, " to clangd") } } + if err == nil && handler.buildSketchSymbolsLoad { + handler.buildSketchSymbolsLoad = false + log.Println("Resfreshing document symbols") + err = handler.refreshCppDocumentSymbols() + } if err != nil { // Exit the process and trigger a restart by the client in case of a severe error if err.Error() == "context deadline exceeded" { @@ -315,10 +324,11 @@ func (handler *InoHandler) exit() { os.Exit(1) } -func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp.InitializeParams) error { +func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) error { rootURI := params.RootURI log.Printf("--> initializeWorkbench(%s)\n", rootURI) + handler.lspInitializeParams = params handler.sketchRoot = rootURI.AsPath() handler.sketchName = handler.sketchRoot.Base() if buildPath, err := generateBuildEnvironment(handler.sketchRoot, handler.config.SelectedBoard.Fqbn); err == nil { @@ -329,6 +339,8 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp. } handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp") handler.buildSketchCppVersion = 1 + handler.lspInitializeParams.RootPath = handler.buildSketchRoot.String() + handler.lspInitializeParams.RootURI = lsp.NewDocumenteURIFromPath(handler.buildSketchRoot) if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil { handler.sketchMapper = sourcemapper.CreateInoMapper(cppContent) @@ -349,8 +361,26 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp. clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) - params.RootPath = handler.buildSketchRoot.String() - params.RootURI = lsp.NewDocumenteURIFromPath(handler.buildSketchRoot) + return nil +} + +func (handler *InoHandler) refreshCppDocumentSymbols() error { + // Query source code symbols + cppURI := lsp.NewDocumenteURIFromPath(handler.buildSketchCpp) + log.Printf(" --> documentSymbol(%s)", cppURI) + result, err := lsp.SendRequest(context.Background(), handler.ClangdConn, "textDocument/documentSymbol", &lsp.DocumentSymbolParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: cppURI}, + }) + if err != nil { + return errors.WithMessage(err, "quering source code symbols") + } + result = handler.transformClangdResult("textDocument/documentSymbol", cppURI, result) + if symbols, ok := result.([]lsp.DocumentSymbol); !ok { + return errors.WithMessage(err, "quering source code symbols (2)") + } else { + handler.buildSketchSymbols = symbols + log.Printf("%+v\n", symbols) + } return nil } @@ -419,6 +449,9 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD Version: handler.buildSketchCppVersion, }, } + + // Trigger a documentSymbol load + handler.buildSketchSymbolsLoad = true return newParam, err } } From 80eb6f3e0c4bc3efb3301a831c3da38d6b80f4a7 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 6 Dec 2020 00:36:00 +0100 Subject: [PATCH 45/61] removed no more used functions --- handler/builder.go | 175 --------------------------------------------- handler/handler.go | 63 +--------------- 2 files changed, 3 insertions(+), 235 deletions(-) diff --git a/handler/builder.go b/handler/builder.go index 92181f2..53502bd 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -101,178 +101,3 @@ func generateBuildEnvironment(sketchDir *paths.Path, fqbn string) (*paths.Path, log.Println("arduino-cli output:", cmdOutput) return res.BuilderResult.BuildPath, nil } - -func filterErrorsAndWarnings(cppCode []byte) string { - var sb strings.Builder - scanner := bufio.NewScanner(bytes.NewReader(cppCode)) - for scanner.Scan() { - lineStr := scanner.Text() - if !(strings.HasPrefix(lineStr, "ERROR:") || strings.HasPrefix(lineStr, "WARNING:")) { - sb.WriteString(lineStr) - sb.WriteRune('\n') - } - } - return sb.String() -} - -func copyIno2Cpp(inoCode string, cppPath string) (cppCode []byte, err error) { - inoPath := strings.TrimSuffix(cppPath, ".cpp") - filePrefix := "#include \n#line 1 \"" + inoPath + "\"\n" - cppCode = []byte(filePrefix + inoCode) - err = ioutil.WriteFile(cppPath, cppCode, 0600) - if err != nil { - err = errors.Wrap(err, "Error while writing target file to temporary directory.") - return - } - if enableLogging { - log.Println("Target file written to", cppPath) - } - return -} - -func printCompileFlags(buildProps *properties.Map, printer *Printer, fqbn string) { - if strings.Contains(fqbn, ":avr:") { - printer.Println("--target=avr") - } else if strings.Contains(fqbn, ":sam:") { - printer.Println("--target=arm-none-eabi") - } - cppFlags := buildProps.ExpandPropsInString(buildProps.Get("compiler.cpp.flags")) - printer.Println(splitFlags(cppFlags)) - mcu := buildProps.ExpandPropsInString(buildProps.Get("build.mcu")) - if strings.Contains(fqbn, ":avr:") { - printer.Println("-mmcu=", mcu) - } else if strings.Contains(fqbn, ":sam:") { - printer.Println("-mcpu=", mcu) - } - fcpu := buildProps.ExpandPropsInString(buildProps.Get("build.f_cpu")) - printer.Println("-DF_CPU=", fcpu) - ideVersion := buildProps.ExpandPropsInString(buildProps.Get("runtime.ide.version")) - printer.Println("-DARDUINO=", ideVersion) - board := buildProps.ExpandPropsInString(buildProps.Get("build.board")) - printer.Println("-DARDUINO_", board) - arch := buildProps.ExpandPropsInString(buildProps.Get("build.arch")) - printer.Println("-DARDUINO_ARCH_", arch) - if strings.Contains(fqbn, ":sam:") { - libSamFlags := buildProps.ExpandPropsInString(buildProps.Get("compiler.libsam.c.flags")) - printer.Println(splitFlags(libSamFlags)) - } - extraFlags := buildProps.ExpandPropsInString(buildProps.Get("build.extra_flags")) - printer.Println(splitFlags(extraFlags)) - corePath := buildProps.ExpandPropsInString(buildProps.Get("build.core.path")) - printer.Println("-I", corePath) - variantPath := buildProps.ExpandPropsInString(buildProps.Get("build.variant.path")) - printer.Println("-I", variantPath) - if strings.Contains(fqbn, ":avr:") { - avrgccPath := buildProps.ExpandPropsInString(buildProps.Get("runtime.tools.avr-gcc.path")) - printer.Println("-I", filepath.Join(avrgccPath, "avr", "include")) - } - - printLibraryPaths(corePath, printer) -} - -func printLibraryPaths(basePath string, printer *Printer) { - parentDir := filepath.Dir(basePath) - if strings.HasSuffix(parentDir, string(filepath.Separator)) || strings.HasSuffix(parentDir, ".") { - return - } - libsDir := filepath.Join(parentDir, "libraries") - if libraries, err := ioutil.ReadDir(libsDir); err == nil { - for _, libInfo := range libraries { - if libInfo.IsDir() { - srcDir := filepath.Join(libsDir, libInfo.Name(), "src") - if srcInfo, err := os.Stat(srcDir); err == nil && srcInfo.IsDir() { - printer.Println("-I", srcDir) - } else { - printer.Println("-I", filepath.Join(libsDir, libInfo.Name())) - } - } - } - } - printLibraryPaths(parentDir, printer) -} - -// Printer prints to a Writer and stores the first error. -type Printer struct { - Writer *bufio.Writer - Err error -} - -// Println prints the given strings followed by a line break. -func (printer *Printer) Println(text ...string) { - totalLen := 0 - for i := range text { - if len(text[i]) > 0 { - _, err := printer.Writer.WriteString(text[i]) - if err != nil && printer.Err == nil { - printer.Err = err - } - totalLen += len(text[i]) - } - } - if totalLen > 0 { - _, err := printer.Writer.WriteString("\n") - if err != nil && printer.Err == nil { - printer.Err = err - } - } -} - -// Flush flushes the underlying writer. -func (printer *Printer) Flush() { - err := printer.Writer.Flush() - if err != nil && printer.Err == nil { - printer.Err = err - } -} - -func splitFlags(flags string) string { - flagsBytes := []byte(flags) - result := make([]byte, len(flagsBytes)) - inSingleQuotes := false - inDoubleQuotes := false - for i, b := range flagsBytes { - if b == '\'' && !inDoubleQuotes { - inSingleQuotes = !inSingleQuotes - } - if b == '"' && !inSingleQuotes { - inDoubleQuotes = !inDoubleQuotes - } - if b == ' ' && !inSingleQuotes && !inDoubleQuotes { - result[i] = '\n' - } else { - result[i] = b - } - } - return string(result) -} - -func logCommandErr(command *exec.Cmd, stdout []byte, err error, filter func(string) string) error { - message := "" - log.Println("Command error:", command.Args, err) - if len(stdout) > 0 { - stdoutStr := string(stdout) - log.Println("------------------------------BEGIN STDOUT\n", stdoutStr, "------------------------------END STDOUT") - message += filter(stdoutStr) - } - if exitErr, ok := err.(*exec.ExitError); ok { - stderr := exitErr.Stderr - if len(stderr) > 0 { - stderrStr := string(stderr) - log.Println("------------------------------BEGIN STDERR\n", stderrStr, "------------------------------END STDERR") - message += filter(stderrStr) - } - } - if len(message) == 0 { - return err - } - return errors.New(message) -} - -func errMsgFilter(tempDir string) func(string) string { - if !strings.HasSuffix(tempDir, string(filepath.Separator)) { - tempDir += string(filepath.Separator) - } - return func(s string) string { - return strings.ReplaceAll(s, tempDir, "") - } -} diff --git a/handler/handler.go b/handler/handler.go index c7e9814..c7a9bcf 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -41,7 +41,6 @@ type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io // NewInoHandler creates and configures an InoHandler. func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ - //data: map[lsp.DocumentURI]*FileData{}, trackedFiles: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, config: lsp.BoardConfig{ SelectedBoard: board, @@ -225,9 +224,9 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose": log.Printf("--X " + req.Method) return nil, nil - uri = p.TextDocument.URI - err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) - handler.deleteFileData(uri) + // uri = p.TextDocument.URI + // err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) + // handler.deleteFileData(uri) // case "textDocument/signatureHelp": // fallthrough // case "textDocument/definition": @@ -524,62 +523,6 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText return cppReq, err } -func (handler *InoHandler) updateFileData(ctx context.Context, data *FileData, change *lsp.TextDocumentContentChangeEvent) (err error) { - rang := change.Range - if rang == nil || rang.Start.Line != rang.End.Line { - // Update the source text and regenerate the cpp code - var newSourceText string - if rang == nil { - newSourceText = change.Text - } else { - newSourceText, err = textutils.ApplyTextChange(data.sourceText, *rang, change.Text) - if err != nil { - return err - } - } - targetBytes, err := updateCpp([]byte(newSourceText), data.sourceURI.Unbox(), handler.config.SelectedBoard.Fqbn, false, data.targetURI.Unbox()) - if err != nil { - if rang == nil { - // Fallback: use the source text unchanged - targetBytes, err = copyIno2Cpp(newSourceText, data.targetURI.Unbox()) - if err != nil { - return err - } - } else { - // Fallback: try to apply a multi-line update - data.sourceText = newSourceText - //data.sourceMap.Update(rang.End.Line-rang.Start.Line, rang.Start.Line, change.Text) - *rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang) - return nil - } - } - - data.sourceText = newSourceText - data.sourceMap = sourcemapper.CreateInoMapper(targetBytes) - - change.Text = string(targetBytes) - change.Range = nil - change.RangeLength = 0 - } else { - // Apply an update to a single line both to the source and the target text - data.sourceText, err = textutils.ApplyTextChange(data.sourceText, *rang, change.Text) - if err != nil { - return err - } - //data.sourceMap.Update(0, rang.Start.Line, change.Text) - - *rang = data.sourceMap.InoToCppLSPRange(data.sourceURI, *rang) - } - return nil -} - -func (handler *InoHandler) deleteFileData(sourceURI lsp.DocumentURI) { - // if data, ok := handler.data[sourceURI]; ok { - // delete(handler.data, data.sourceURI) - // delete(handler.data, data.targetURI) - // } -} - func (handler *InoHandler) handleError(ctx context.Context, err error) error { errorStr := err.Error() var message string From ff6c799800cf57061431db8cd853fbdce0291f7c Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 6 Dec 2020 00:36:19 +0100 Subject: [PATCH 46/61] fixed case in 'omitempty' directive --- lsp/service.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lsp/service.go b/lsp/service.go index f1067d2..b08d5a4 100644 --- a/lsp/service.go +++ b/lsp/service.go @@ -80,7 +80,7 @@ type WorkspaceClientCapabilities struct { Symbol struct { SymbolKind struct { ValueSet []int `json:"valueSet,omitempty"` - } `json:"symbolKind,omitEmpty"` + } `json:"symbolKind,omitempty"` } `json:"symbol,omitempty"` ExecuteCommand *struct { @@ -124,7 +124,7 @@ type TextDocumentClientCapabilities struct { DocumentSymbol struct { SymbolKind struct { ValueSet []int `json:"valueSet,omitempty"` - } `json:"symbolKind,omitEmpty"` + } `json:"symbolKind,omitempty"` HierarchicalDocumentSymbolSupport bool `json:"hierarchicalDocumentSymbolSupport,omitempty"` } `json:"documentSymbol,omitempty"` @@ -725,8 +725,8 @@ type DidChangeTextDocumentParams struct { } type TextDocumentContentChangeEvent struct { - Range *Range `json:"range,omitEmpty"` - RangeLength uint `json:"rangeLength,omitEmpty"` + Range *Range `json:"range,omitempty"` + RangeLength uint `json:"rangeLength,omitempty"` Text string `json:"text"` } From 804136d79ff9b2a71fc4c2995dc61413632221ca Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 6 Dec 2020 00:42:21 +0100 Subject: [PATCH 47/61] Implemented sketch rebuild --- go.mod | 2 + handler/builder.go | 107 ++++++++++++++++++++++++++------------------- handler/handler.go | 99 ++++++++++++++++++++++++++++------------- 3 files changed, 132 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index 46ebfa1..0fa5e7a 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.12 replace github.com/arduino/arduino-cli => ../arduino-cli +replace github.com/arduino/go-paths-helper => ../go-paths-helper + require ( github.com/arduino/arduino-cli v0.0.0-20201201130510-05ce1509a4f1 github.com/arduino/go-paths-helper v1.3.3 diff --git a/handler/builder.go b/handler/builder.go index 53502bd..2e96a34 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -1,73 +1,90 @@ package handler import ( - "bufio" "bytes" "encoding/json" - "io/ioutil" "log" - "os" - "os/exec" - "path/filepath" "strings" + "time" "github.com/arduino/arduino-cli/arduino/libraries" "github.com/arduino/arduino-cli/executils" "github.com/arduino/go-paths-helper" - "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" ) -// func generateCpp(sourcePath, fqbn string) (*paths.Path, []byte, error) { -// // Generate target file -// if cppPath, err := generateBuildEnvironment(paths.New(sourcePath), fqbn); err != nil { -// return nil, nil, err -// } else if cppCode, err := cppPath.ReadFile(); err != nil { -// return nil, nil, err -// } else { -// return cppPath, cppCode, err -// } -// } +func (handler *InoHandler) scheduleRebuildEnvironment() { + handler.rebuildSketchDeadlineMutex.Lock() + defer handler.rebuildSketchDeadlineMutex.Unlock() + d := time.Now().Add(time.Second) + handler.rebuildSketchDeadline = &d +} + +func (handler *InoHandler) rebuildEnvironmentLoop() { + grabDeadline := func() *time.Time { + handler.rebuildSketchDeadlineMutex.Lock() + defer handler.rebuildSketchDeadlineMutex.Unlock() + + res := handler.rebuildSketchDeadline + handler.rebuildSketchDeadline = nil + return res + } + + for { + // Wait for someone to schedule a preprocessing... + time.Sleep(100 * time.Millisecond) + deadline := grabDeadline() + if deadline == nil { + continue + } -func updateCpp(inoCode []byte, sourcePath, fqbn string, fqbnChanged bool, cppPath string) (cppCode []byte, err error) { - // tempDir := filepath.Dir(cppPath) - // inoPath := strings.TrimSuffix(cppPath, ".cpp") - // if inoCode != nil { - // // Write source file to temp dir - // err = ioutil.WriteFile(inoPath, inoCode, 0600) - // if err != nil { - // err = errors.Wrap(err, "Error while writing source file to temporary directory.") - // return - // } - // if enableLogging { - // log.Println("Source file written to", inoPath) - // } - // } + for time.Now().Before(*deadline) { + time.Sleep(100 * time.Millisecond) - // if fqbnChanged { - // // Generate compile_flags.txt - // var flagsPath string - // flagsPath, err = generateCompileFlags(tempDir, inoPath, sourcePath, fqbn) - // if err != nil { - // return - // } - // if enableLogging { - // log.Println("Compile flags written to", flagsPath) - // } - // } + if d := grabDeadline(); d != nil { + deadline = d + } + } - // // Generate target file - // cppCode, err = generateTargetFile(tempDir, inoPath, cppPath, fqbn) - return + // Regenerate preprocessed sketch! + handler.synchronizer.DataMux.Lock() + handler.initializeWorkbench(nil) + handler.synchronizer.DataMux.Unlock() + } } -func generateBuildEnvironment(sketchDir *paths.Path, fqbn string) (*paths.Path, error) { +func (handler *InoHandler) generateBuildEnvironment() (*paths.Path, error) { + sketchDir := handler.sketchRoot + fqbn := handler.config.SelectedBoard.Fqbn + + // Export temporary files + type overridesFile struct { + Overrides map[string]string `json:"overrides"` + } + data := overridesFile{Overrides: map[string]string{}} + for uri, trackedFile := range handler.trackedFiles { + rel, err := uri.AsPath().RelFrom(handler.sketchRoot) + if err != nil { + return nil, errors.WithMessage(err, "dumping tracked files") + } + data.Overrides[rel.String()] = trackedFile.Text + } + var overridesJSON string + if jsonBytes, err := json.MarshalIndent(data, "", " "); err != nil { + return nil, errors.WithMessage(err, "dumping tracked files") + } else if tmpFile, err := paths.WriteToTempFile(jsonBytes, nil, ""); err != nil { + return nil, errors.WithMessage(err, "dumping tracked files") + } else { + overridesJSON = tmpFile.String() + } + // XXX: do this from IDE or via gRPC args := []string{globalCliPath, "compile", "--fqbn", fqbn, "--only-compilation-database", "--clean", + "--source-override", overridesJSON, "--format", "json", sketchDir.String(), } diff --git a/handler/handler.go b/handler/handler.go index c7a9bcf..011d269 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -9,6 +9,7 @@ import ( "regexp" "strconv" "strings" + "sync" "time" "github.com/arduino/arduino-cli/arduino/builder" @@ -59,26 +60,30 @@ func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { if enableLogging { log.Println("Initial board configuration:", board) } + + go handler.rebuildEnvironmentLoop() return handler } // InoHandler is a JSON-RPC handler that delegates messages to clangd. type InoHandler struct { - StdioConn *jsonrpc2.Conn - ClangdConn *jsonrpc2.Conn - lspInitializeParams *lsp.InitializeParams - buildPath *paths.Path - buildSketchRoot *paths.Path - buildSketchCpp *paths.Path - buildSketchCppVersion int - buildSketchSymbols []lsp.DocumentSymbol - buildSketchSymbolsLoad bool - buildSketchSymbolsCheck bool - sketchRoot *paths.Path - sketchName string - sketchMapper *sourcemapper.InoMapper - sketchTrackedFilesCount int - trackedFiles map[lsp.DocumentURI]*lsp.TextDocumentItem + StdioConn *jsonrpc2.Conn + ClangdConn *jsonrpc2.Conn + lspInitializeParams *lsp.InitializeParams + buildPath *paths.Path + buildSketchRoot *paths.Path + buildSketchCpp *paths.Path + buildSketchCppVersion int + buildSketchSymbols []lsp.DocumentSymbol + buildSketchSymbolsLoad bool + buildSketchSymbolsCheck bool + rebuildSketchDeadline *time.Time + rebuildSketchDeadlineMutex sync.Mutex + sketchRoot *paths.Path + sketchName string + sketchMapper *sourcemapper.InoMapper + sketchTrackedFilesCount int + trackedFiles map[lsp.DocumentURI]*lsp.TextDocumentItem config lsp.BoardConfig synchronizer Synchronizer @@ -324,13 +329,18 @@ func (handler *InoHandler) exit() { } func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) error { - rootURI := params.RootURI - log.Printf("--> initializeWorkbench(%s)\n", rootURI) + currCppTextVersion := 0 + if params != nil { + log.Printf("--> initialize(%s)\n", params.RootURI) + handler.lspInitializeParams = params + handler.sketchRoot = params.RootURI.AsPath() + handler.sketchName = handler.sketchRoot.Base() + } else { + currCppTextVersion = handler.sketchMapper.CppText.Version + log.Printf("--> RE-initialize()\n") + } - handler.lspInitializeParams = params - handler.sketchRoot = rootURI.AsPath() - handler.sketchName = handler.sketchRoot.Base() - if buildPath, err := generateBuildEnvironment(handler.sketchRoot, handler.config.SelectedBoard.Fqbn); err == nil { + if buildPath, err := handler.generateBuildEnvironment(); err == nil { handler.buildPath = buildPath handler.buildSketchRoot = buildPath.Join("sketch") } else { @@ -343,22 +353,48 @@ func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) err if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil { handler.sketchMapper = sourcemapper.CreateInoMapper(cppContent) + handler.sketchMapper.CppText.Version = currCppTextVersion + 1 } else { return errors.WithMessage(err, "reading generated cpp file from sketch") } - clangdStdout, clangdStdin, clangdStderr := startClangd(handler.buildPath, handler.buildSketchCpp) - clangdStdio := streams.NewReadWriteCloser(clangdStdin, clangdStdout) - if enableLogging { - clangdStdio = streams.LogReadWriteCloserAs(clangdStdio, "inols-clangd.log") - go io.Copy(streams.OpenLogFileAs("inols-clangd-err.log"), clangdStderr) + if params == nil { + // If we are restarting re-synchronize clangd + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + cppURI := lsp.NewDocumenteURIFromPath(handler.buildSketchCpp) + cppTextDocumentIdentifier := lsp.TextDocumentIdentifier{URI: cppURI} + + syncEvent := &lsp.DidChangeTextDocumentParams{ + TextDocument: lsp.VersionedTextDocumentIdentifier{ + TextDocumentIdentifier: cppTextDocumentIdentifier, + Version: handler.sketchMapper.CppText.Version, + }, + ContentChanges: []lsp.TextDocumentContentChangeEvent{ + {Text: handler.sketchMapper.CppText.Text}, // Full text change + }, + } + + if err := handler.ClangdConn.Notify(ctx, "textDocument/didChange", syncEvent); err != nil { + log.Println(" error reinitilizing clangd:", err) + return err + } } else { - go io.Copy(os.Stderr, clangdStderr) - } + // Otherwise start clangd! + clangdStdout, clangdStdin, clangdStderr := startClangd(handler.buildPath, handler.buildSketchCpp) + clangdStdio := streams.NewReadWriteCloser(clangdStdin, clangdStdout) + if enableLogging { + clangdStdio = streams.LogReadWriteCloserAs(clangdStdio, "inols-clangd.log") + go io.Copy(streams.OpenLogFileAs("inols-clangd-err.log"), clangdStderr) + } else { + go io.Copy(os.Stderr, clangdStderr) + } - clangdStream := jsonrpc2.NewBufferedStream(clangdStdio, jsonrpc2.VSCodeObjectCodec{}) - clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) - handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) + clangdStream := jsonrpc2.NewBufferedStream(clangdStdio, jsonrpc2.VSCodeObjectCodec{}) + clangdHandler := jsonrpc2.AsyncHandler(jsonrpc2.HandlerWithError(handler.FromClangd)) + handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler) + } return nil } @@ -483,6 +519,7 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText // and trigger arduino-preprocessing + clangd restart. log.Println(" uh oh DIRTY CHANGE!") + handler.scheduleRebuildEnvironment() } // log.Println("New version:----------") From 611bf6bfc32cb774f5467c3e41b48a986c7e8886 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 7 Dec 2020 16:13:32 +0100 Subject: [PATCH 48/61] Updated go-paths-helper --- go.mod | 5 +---- go.sum | 7 +++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 0fa5e7a..02a2f1a 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,9 @@ go 1.12 replace github.com/arduino/arduino-cli => ../arduino-cli -replace github.com/arduino/go-paths-helper => ../go-paths-helper - require ( github.com/arduino/arduino-cli v0.0.0-20201201130510-05ce1509a4f1 - github.com/arduino/go-paths-helper v1.3.3 - github.com/arduino/go-properties-orderedmap v1.4.0 + github.com/arduino/go-paths-helper v1.4.0 github.com/pkg/errors v0.9.1 github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 github.com/stretchr/testify v1.6.1 diff --git a/go.sum b/go.sum index a3f518f..9a22923 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,10 @@ github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3 github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-paths-helper v1.3.2 h1:5U9TSKQODiwSVgTxskC0PNl0l0Vf40GUlp99Zy2SK8w= github.com/arduino/go-paths-helper v1.3.2/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= -github.com/arduino/go-paths-helper v1.3.3 h1:r51Mix4GkYqiaF9IuRTtRE3Y/WFA0C47WwG/VZAkbcI= -github.com/arduino/go-paths-helper v1.3.3/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= +github.com/arduino/go-paths-helper v1.4.0 h1:ilnseAdxmN1bFnLxxXHRtcdmt9jBf3O4jtYfWfqule4= +github.com/arduino/go-paths-helper v1.4.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU= +github.com/arduino/go-properties-orderedmap v1.3.0 h1:4No/vQopB36e7WUIk6H6TxiSEJPiMrVOCZylYmua39o= github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= -github.com/arduino/go-properties-orderedmap v1.4.0 h1:YEbbzPqm1gXWDM/Jaq8tlvmh09z2qeHPJTUw9/VA4Dk= -github.com/arduino/go-properties-orderedmap v1.4.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= From 459045821110be59f1d1e8f32d38e02960056858 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 7 Dec 2020 20:45:27 +0100 Subject: [PATCH 49/61] Added some Commands.Arguments --- handler/handler.go | 71 ++++++++++++++++++++-------------------------- lsp/structures.go | 7 +++-- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 011d269..f330eb1 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -2,6 +2,7 @@ package handler import ( "context" + "encoding/json" "fmt" "io" "log" @@ -757,16 +758,16 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document // method "textDocument/codeAction" log.Printf(" <-- codeAction(%d elements)", len(*r)) for i, item := range *r { - (*r)[i] = lsp.CommandOrCodeAction{ - Command: handler.cpp2inoCommand(item.Command), - CodeAction: handler.cpp2inoCodeAction(item.CodeAction, uri), - } if item.Command != nil { log.Printf(" > Command: %s", item.Command.Title) } if item.CodeAction != nil { log.Printf(" > CodeAction: %s", item.CodeAction.Title) } + (*r)[i] = lsp.CommandOrCodeAction{ + Command: handler.Cpp2InoCommand(item.Command), + CodeAction: handler.cpp2inoCodeAction(item.CodeAction, uri), + } } log.Printf("<-- codeAction(%d elements)", len(*r)) @@ -811,7 +812,7 @@ func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp Kind: codeAction.Kind, Edit: handler.cpp2inoWorkspaceEdit(codeAction.Edit), Diagnostics: codeAction.Diagnostics, - Command: handler.cpp2inoCommand(codeAction.Command), + Command: handler.Cpp2InoCommand(codeAction.Command), } if uri.AsPath().Ext() == ".ino" { for i, diag := range inoCodeAction.Diagnostics { @@ -821,7 +822,7 @@ func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp return inoCodeAction } -func (handler *InoHandler) cpp2inoCommand(command *lsp.Command) *lsp.Command { +func (handler *InoHandler) Cpp2InoCommand(command *lsp.Command) *lsp.Command { if command == nil { return nil } @@ -830,10 +831,29 @@ func (handler *InoHandler) cpp2inoCommand(command *lsp.Command) *lsp.Command { Command: command.Command, Arguments: command.Arguments, } - if len(command.Arguments) == 1 { - arg := handler.parseCommandArgument(inoCommand.Arguments[0]) - if workspaceEdit, ok := arg.(*lsp.WorkspaceEdit); ok { - inoCommand.Arguments[0] = handler.cpp2inoWorkspaceEdit(workspaceEdit) + if command.Command == "clangd.applyTweak" { + for i := range command.Arguments { + v := struct { + TweakID string `json:"tweakID"` + File lsp.DocumentURI `json:"file"` + Selection lsp.Range `json:"selection"` + }{} + if err := json.Unmarshal(command.Arguments[0], &v); err == nil { + if v.TweakID == "ExtractVariable" { + log.Println(" > converted clangd ExtractVariable") + if v.File.AsPath().EquivalentTo(handler.buildSketchCpp) { + inoFile, inoSelection := handler.sketchMapper.CppToInoRange(v.Selection) + v.File = lsp.NewDocumentURI(inoFile) + v.Selection = inoSelection + } + } + } + + converted, err := json.Marshal(v) + if err != nil { + panic("Internal Error: json conversion of codeAcion command arguments") + } + inoCommand.Arguments[i] = converted } } return inoCommand @@ -1056,37 +1076,6 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. return result, err } -func (handler *InoHandler) parseCommandArgument(rawArg interface{}) interface{} { - log.Printf(" TRY TO PARSE: %+v", rawArg) - panic("not implemented") - return nil - // if m1, ok := rawArg.(map[string]interface{}); ok && len(m1) == 1 && m1["changes"] != nil { - // m2 := m1["changes"].(map[string]interface{}) - // workspaceEdit := lsp.WorkspaceEdit{Changes: make(map[string][]lsp.TextEdit)} - // for uri, rawValue := range m2 { - // rawTextEdits := rawValue.([]interface{}) - // textEdits := make([]lsp.TextEdit, len(rawTextEdits)) - // for index := range rawTextEdits { - // m3 := rawTextEdits[index].(map[string]interface{}) - // rawRange := m3["range"] - // m4 := rawRange.(map[string]interface{}) - // rawStart := m4["start"] - // m5 := rawStart.(map[string]interface{}) - // textEdits[index].Range.Start.Line = int(m5["line"].(float64)) - // textEdits[index].Range.Start.Character = int(m5["character"].(float64)) - // rawEnd := m4["end"] - // m6 := rawEnd.(map[string]interface{}) - // textEdits[index].Range.End.Line = int(m6["line"].(float64)) - // textEdits[index].Range.End.Character = int(m6["character"].(float64)) - // textEdits[index].NewText = m3["newText"].(string) - // } - // workspaceEdit.Changes[uri] = textEdits - // } - // return &workspaceEdit - // } - // return nil -} - func (handler *InoHandler) showMessage(ctx context.Context, msgType lsp.MessageType, message string) { params := lsp.ShowMessageParams{ Type: msgType, diff --git a/lsp/structures.go b/lsp/structures.go index 4b40a91..e7a93b6 100644 --- a/lsp/structures.go +++ b/lsp/structures.go @@ -1,6 +1,9 @@ package lsp -import "fmt" +import ( + "encoding/json" + "fmt" +) type Position struct { /** @@ -90,7 +93,7 @@ type Command struct { * Arguments that the command handler should be * invoked with. */ - Arguments []interface{} `json:"arguments"` + Arguments []json.RawMessage `json:"arguments"` } type TextEdit struct { From 54bfcf1e42844718105fe0ac6e4dfefeeaa91439 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 9 Dec 2020 11:07:51 +0100 Subject: [PATCH 50/61] re-preprocess sketch if there are changes in the c++ functions outline --- handler/handler.go | 54 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index f330eb1..1dcab19 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -301,9 +301,14 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr } if err == nil && handler.buildSketchSymbolsLoad { handler.buildSketchSymbolsLoad = false - log.Println("Resfreshing document symbols") + log.Println("--! Resfreshing document symbols") err = handler.refreshCppDocumentSymbols() } + if err == nil && handler.buildSketchSymbolsCheck { + handler.buildSketchSymbolsCheck = false + log.Println("--! Resfreshing document symbols") + err = handler.checkCppDocumentSymbols() + } if err != nil { // Exit the process and trigger a restart by the client in case of a severe error if err.Error() == "context deadline exceeded" { @@ -414,8 +419,40 @@ func (handler *InoHandler) refreshCppDocumentSymbols() error { if symbols, ok := result.([]lsp.DocumentSymbol); !ok { return errors.WithMessage(err, "quering source code symbols (2)") } else { + // Filter non-functions symbols + i := 0 + for _, symbol := range symbols { + if symbol.Kind != lsp.SKFunction { + continue + } + symbols[i] = symbol + i++ + } + symbols = symbols[:i] + for _, symbol := range symbols { + log.Printf(" symbol: %s %s", symbol.Kind, symbol.Name) + } handler.buildSketchSymbols = symbols - log.Printf("%+v\n", symbols) + } + return nil +} + +func (handler *InoHandler) checkCppDocumentSymbols() error { + oldSymbols := handler.buildSketchSymbols + if err := handler.refreshCppDocumentSymbols(); err != nil { + return err + } + if len(oldSymbols) != len(handler.buildSketchSymbols) { + log.Println("--! new symbols detected, triggering sketch rebuild!") + handler.scheduleRebuildEnvironment() + return nil + } + for i, old := range oldSymbols { + if newName := handler.buildSketchSymbols[i].Name; old.Name != newName { + log.Printf("--! symbols changed, triggering sketch rebuild: '%s' -> '%s'", old.Name, newName) + handler.scheduleRebuildEnvironment() + return nil + } } return nil } @@ -519,7 +556,7 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText // TODO: Detect changes in critical lines (for example function definitions) // and trigger arduino-preprocessing + clangd restart. - log.Println(" uh oh DIRTY CHANGE!") + log.Println("--! DIRTY CHANGE, force sketch rebuild!") handler.scheduleRebuildEnvironment() } @@ -1044,6 +1081,17 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) } } + + // If we have an "undefined reference" in the .ino code trigger a + // check for newly created symbols (that in turn may trigger a + // new arduino-preprocessing of the sketch). + if msg.URI.AsPath().Ext() == ".ino" { + for _, diag := range msg.Diagnostics { + if diag.Code == "undeclared_var_use_suggest" { + handler.buildSketchSymbolsCheck = true + } + } + } if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", msg); err != nil { return nil, err } From da6892e1beb2884c099ae5ae35a4b5ec9efbefbb Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 13 Dec 2020 07:58:18 +0100 Subject: [PATCH 51/61] Fixed doc sync infinite loop bug --- handler/sourcemapper/ino.go | 1 + 1 file changed, 1 insertion(+) diff --git a/handler/sourcemapper/ino.go b/handler/sourcemapper/ino.go index 3eaac4f..a921b40 100644 --- a/handler/sourcemapper/ino.go +++ b/handler/sourcemapper/ino.go @@ -192,6 +192,7 @@ func (s *InoMapper) ApplyTextChange(inoURI lsp.DocumentURI, inoChange lsp.TextDo addedLines := strings.Count(inoChange.Text, "\n") - 1 for addedLines > 0 { dirty = dirty || s.addInoLine(cppRange.Start.Line) + addedLines-- } return } From 476eabc9944524dbf15987701557ff276309a9dc Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 13 Dec 2020 07:58:29 +0100 Subject: [PATCH 52/61] Added String method to DocumentURI --- lsp/uri.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lsp/uri.go b/lsp/uri.go index 9c315a3..f1b6fac 100644 --- a/lsp/uri.go +++ b/lsp/uri.go @@ -39,6 +39,10 @@ func (uri DocumentURI) Unbox() string { return path } +func (uri DocumentURI) String() string { + return string(uri) +} + // NewDocumenteURIFromPath create a DocumentURI from the given Path object func NewDocumenteURIFromPath(path *paths.Path) DocumentURI { return NewDocumentURI(path.String()) From 28368325c76db514efcc140a362d94b088903e77 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 13 Dec 2020 08:19:05 +0100 Subject: [PATCH 53/61] renamed InoHandler.trackedFiles -> docs --- handler/builder.go | 2 +- handler/handler.go | 56 +++++++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/handler/builder.go b/handler/builder.go index 2e96a34..bb34be3 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -62,7 +62,7 @@ func (handler *InoHandler) generateBuildEnvironment() (*paths.Path, error) { Overrides map[string]string `json:"overrides"` } data := overridesFile{Overrides: map[string]string{}} - for uri, trackedFile := range handler.trackedFiles { + for uri, trackedFile := range handler.docs { rel, err := uri.AsPath().RelFrom(handler.sketchRoot) if err != nil { return nil, errors.WithMessage(err, "dumping tracked files") diff --git a/handler/handler.go b/handler/handler.go index 1dcab19..7efc0b5 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -40,10 +40,34 @@ func Setup(cliPath string, clangdPath string, _enableLogging bool, _asyncProcess // CLangdStarter starts clangd and returns its stdin/out/err type CLangdStarter func() (stdin io.WriteCloser, stdout io.ReadCloser, stderr io.ReadCloser) +// InoHandler is a JSON-RPC handler that delegates messages to clangd. +type InoHandler struct { + StdioConn *jsonrpc2.Conn + ClangdConn *jsonrpc2.Conn + lspInitializeParams *lsp.InitializeParams + buildPath *paths.Path + buildSketchRoot *paths.Path + buildSketchCpp *paths.Path + buildSketchCppVersion int + buildSketchSymbols []lsp.DocumentSymbol + buildSketchSymbolsLoad bool + buildSketchSymbolsCheck bool + rebuildSketchDeadline *time.Time + rebuildSketchDeadlineMutex sync.Mutex + sketchRoot *paths.Path + sketchName string + sketchMapper *sourcemapper.InoMapper + sketchTrackedFilesCount int + docs map[lsp.DocumentURI]*lsp.TextDocumentItem + + config lsp.BoardConfig + synchronizer Synchronizer +} + // NewInoHandler creates and configures an InoHandler. func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ - trackedFiles: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, + docs: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, config: lsp.BoardConfig{ SelectedBoard: board, }, @@ -66,30 +90,6 @@ func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { return handler } -// InoHandler is a JSON-RPC handler that delegates messages to clangd. -type InoHandler struct { - StdioConn *jsonrpc2.Conn - ClangdConn *jsonrpc2.Conn - lspInitializeParams *lsp.InitializeParams - buildPath *paths.Path - buildSketchRoot *paths.Path - buildSketchCpp *paths.Path - buildSketchCppVersion int - buildSketchSymbols []lsp.DocumentSymbol - buildSketchSymbolsLoad bool - buildSketchSymbolsCheck bool - rebuildSketchDeadline *time.Time - rebuildSketchDeadlineMutex sync.Mutex - sketchRoot *paths.Path - sketchName string - sketchMapper *sourcemapper.InoMapper - sketchTrackedFilesCount int - trackedFiles map[lsp.DocumentURI]*lsp.TextDocumentItem - - config lsp.BoardConfig - synchronizer Synchronizer -} - // FileData gathers information on a .ino source file. type FileData struct { sourceText string @@ -504,7 +504,7 @@ func startClangd(compileCommandsDir, sketchCpp *paths.Path) (io.WriteCloser, io. func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextDocumentParams) (*lsp.DidOpenTextDocumentParams, error) { // Add the TextDocumentItem in the tracked files list doc := params.TextDocument - handler.trackedFiles[doc.URI] = &doc + handler.docs[doc.URI] = &doc // If we are tracking a .ino... if doc.URI.AsPath().Ext() == ".ino" { @@ -534,7 +534,7 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeTextDocumentParams) (*lsp.DidChangeTextDocumentParams, error) { doc := req.TextDocument - trackedDoc, ok := handler.trackedFiles[doc.URI] + trackedDoc, ok := handler.docs[doc.URI] if !ok { return nil, unknownURI(doc.URI) } @@ -1045,7 +1045,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. // XXX: Optimize this to publish "empty diagnostics" only to .ino that are // currently showing previous diagnostics. - for sourceURI := range handler.trackedFiles { + for sourceURI := range handler.docs { msg := lsp.PublishDiagnosticsParams{ URI: sourceURI, Diagnostics: []lsp.Diagnostic{}, From 414f11bfcb5da15aaff768232d5b936e881cccbd Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Sun, 13 Dec 2020 08:19:27 +0100 Subject: [PATCH 54/61] correctly handle diagnostics update --- handler/handler.go | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 7efc0b5..6731569 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -59,6 +59,7 @@ type InoHandler struct { sketchMapper *sourcemapper.InoMapper sketchTrackedFilesCount int docs map[lsp.DocumentURI]*lsp.TextDocumentItem + docHasDiagnostics map[lsp.DocumentURI]bool config lsp.BoardConfig synchronizer Synchronizer @@ -68,6 +69,7 @@ type InoHandler struct { func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ docs: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, + docHasDiagnostics: map[lsp.DocumentURI]bool{}, config: lsp.BoardConfig{ SelectedBoard: board, }, @@ -1040,23 +1042,6 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. // we should transform back N diagnostics of sketch.cpp.ino into // their .ino counter parts (that may span over multiple files...) - // Remove diagnostics from all .ino if there are no errors coming from clang - if len(p.Diagnostics) == 0 { - // XXX: Optimize this to publish "empty diagnostics" only to .ino that are - // currently showing previous diagnostics. - - for sourceURI := range handler.docs { - msg := lsp.PublishDiagnosticsParams{ - URI: sourceURI, - Diagnostics: []lsp.Diagnostic{}, - } - if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", msg); err != nil { - return nil, err - } - } - return nil, nil - } - convertedDiagnostics := map[string][]lsp.Diagnostic{} for _, cppDiag := range p.Diagnostics { inoSource, inoRange := handler.sketchMapper.CppToInoRange(cppDiag.Range) @@ -1070,11 +1055,13 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. } // Push back to IDE the converted diagnostics + docsWithDiagnostics := map[lsp.DocumentURI]bool{} for filename, inoDiags := range convertedDiagnostics { msg := lsp.PublishDiagnosticsParams{ URI: lsp.NewDocumentURI(filename), Diagnostics: inoDiags, } + docsWithDiagnostics[msg.URI] = true if enableLogging { log.Printf("<-- publishDiagnostics(%s):", msg.URI) for _, diag := range msg.Diagnostics { @@ -1097,6 +1084,30 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. } } + // Remove diagnostics from all .ino where there are no errors coming from clang + for sourceURI := range handler.docs { + if !handler.docHasDiagnostics[sourceURI] { + // skip if the document didn't have previously sent diagnostics + continue + } + if docsWithDiagnostics[sourceURI] { + // skip if we already sent updated diagnostics + continue + } + // otherwise clear previous diagnostics + msg := lsp.PublishDiagnosticsParams{ + URI: sourceURI, + Diagnostics: []lsp.Diagnostic{}, + } + if enableLogging { + log.Printf("<-- publishDiagnostics(%s):", msg.URI) + } + if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", msg); err != nil { + return nil, err + } + } + + handler.docHasDiagnostics = docsWithDiagnostics return nil, err } From 9cd59436956bf6f7d02159e6d2e9eb785c0e2559 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 15 Dec 2020 13:11:54 +0100 Subject: [PATCH 55/61] updated arduino-cli version --- go.mod | 4 +--- go.sum | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 02a2f1a..aceee61 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,8 @@ module github.com/bcmi-labs/arduino-language-server go 1.12 -replace github.com/arduino/arduino-cli => ../arduino-cli - require ( - github.com/arduino/arduino-cli v0.0.0-20201201130510-05ce1509a4f1 + github.com/arduino/arduino-cli v0.0.0-20201215104024-6a177ebf56f2 github.com/arduino/go-paths-helper v1.4.0 github.com/pkg/errors v0.9.1 github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 diff --git a/go.sum b/go.sum index 9a22923..2eacd34 100644 --- a/go.sum +++ b/go.sum @@ -8,16 +8,18 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/arduino/arduino-cli v0.0.0-20201215104024-6a177ebf56f2 h1:J+EUtAwSXK7AFSkK0Tbw85rLbcGf1ykuerssDUD9LxY= +github.com/arduino/arduino-cli v0.0.0-20201215104024-6a177ebf56f2/go.mod h1:RZsAJUrAIHFaSj71SNJ/hSRUqNrjDw+3WFT2xw9NnRM= +github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c h1:agh2JT96G8egU7FEb13L4dq3fnCN7lxXhJ86t69+W7s= github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8= github.com/arduino/go-paths-helper v1.0.1 h1:utYXLM2RfFlc9qp/MJTIYp3t6ux/xM6mWjeEb/WLK4Q= github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= -github.com/arduino/go-paths-helper v1.3.2 h1:5U9TSKQODiwSVgTxskC0PNl0l0Vf40GUlp99Zy2SK8w= -github.com/arduino/go-paths-helper v1.3.2/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-paths-helper v1.4.0 h1:ilnseAdxmN1bFnLxxXHRtcdmt9jBf3O4jtYfWfqule4= github.com/arduino/go-paths-helper v1.4.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU= github.com/arduino/go-properties-orderedmap v1.3.0 h1:4No/vQopB36e7WUIk6H6TxiSEJPiMrVOCZylYmua39o= github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= +github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b h1:9hDi4F2st6dbLC3y4i02zFT5quS4X6iioWifGlVwfy4= github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= @@ -28,8 +30,11 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cmaglie/go.rice v1.0.3 h1:ZBLmBdQp6ejc+n8eMNH0uuRSKkg6kKe6ORjXKnyHBYw= github.com/cmaglie/go.rice v1.0.3/go.mod h1:AF3bOWkvdOpp8/S3UL8qbQ4N7DiISIbJtj54GWFPAsc= +github.com/cmaglie/pb v1.0.27 h1:ynGj8vBXR+dtj4B7Q/W/qGt31771Ux5iFfRQBnwdQiA= github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI= +github.com/codeclysm/cc v1.2.2 h1:1ChS4EvWTjw6bH2sd6QiMcmih0itVVrWdh9MmOliX/I= github.com/codeclysm/cc v1.2.2/go.mod h1:XtW4ArCNgQwFphcRGG9+sPX5WM1J6/u0gMy5ZdV3obA= github.com/codeclysm/extract/v3 v3.0.2 h1:sB4LcE3Php7LkhZwN0n2p8GCwZe92PEQutdbGURf5xc= github.com/codeclysm/extract/v3 v3.0.2/go.mod h1:NKsw+hqua9H+Rlwy/w/3Qgt9jDonYEgB6wJu+25eOKw= @@ -38,10 +43,13 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -49,12 +57,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/facchinm/gohex v0.0.0-20201008150446-be2a6be25790 h1:GTx/F+6TdhQAMtaDrpGDgnAzxYJhTVuTdSKpMsUbMNA= github.com/facchinm/gohex v0.0.0-20201008150446-be2a6be25790/go.mod h1:sErAiirjQXs3P13DBW7oPmJ6Q0WsFjYXVAhz7Xt59UQ= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2 h1:C6sOwknxwWfLBEQ91zhmptlfxf7pVEs5s6wOnDxNpS4= github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg= +github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5 h1:R8jFW6G/bjoXjWPFrEfw9G5YQDlYhwV4AC+Eonu6wmk= github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -65,6 +78,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -103,9 +117,11 @@ github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14= github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/imjasonmiller/godice v0.1.2 h1:T1/sW/HoDzFeuwzOOuQjmeMELz9CzZ53I2CnD+08zD4= github.com/imjasonmiller/godice v0.1.2/go.mod h1:8cTkdnVI+NglU2d6sv+ilYcNaJ5VSTBwvMbFULJd/QQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -124,6 +140,7 @@ github.com/juju/testing v0.0.0-20200510222523-6c8c298c77a0/go.mod h1:hpGvhGHPVbN github.com/juju/utils v0.0.0-20180808125547-9dfc6dbfb02b/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk= github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -136,19 +153,24 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leonelquinteros/gotext v1.4.0 h1:2NHPCto5IoMXbrT0bldPrxj0qM5asOCwtb1aUQZ1tys= github.com/leonelquinteros/gotext v1.4.0/go.mod h1:yZGXREmoGTtBvZHNcc+Yfug49G/2spuF/i/Qlsvz1Us= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/genetlink v0.0.0-20190313224034-60417448a851/go.mod h1:EsbsAEUEs15qC1cosAwxgCWV0Qhd8TmkxnA9Kw1Vhl4= github.com/mdlayher/netlink v0.0.0-20190313131330-258ea9dff42c/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/taskstats v0.0.0-20190313225729-7cbba52ee072/go.mod h1:sGdS7A6CAETR53zkdjGkgoFlh1vSm7MtX+i8XfEsTMA= +github.com/miekg/dns v1.0.5 h1:MQBGf2JEJDu0rg9WOpQZzeO+zW8UKwgkvP3R1dUU1Yw= github.com/miekg/dns v1.0.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -157,6 +179,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228 h1:Cvfd2dOlXIPTeEkOT/h8PyK4phBngOM4at9/jlgy7d4= github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228/go.mod h1:MGuVJ1+5TX1SCoO2Sx0eAnjpdRytYla2uC1YIZfkC9c= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= @@ -179,15 +202,22 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= +github.com/segmentio/stats/v4 v4.5.3 h1:Y/DSUWZ4c8ICgqJ9rQohzKvGqGWbLPWad5zmxVoKN+Y= github.com/segmentio/stats/v4 v4.5.3/go.mod h1:LsaahUJR7iiSs8mnkvQvdQ/RLHAS5adGLxuntg0ydGo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -213,6 +243,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -229,6 +260,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -238,7 +270,9 @@ go.bug.st/downloader/v2 v2.1.0 h1:VqGOrJrjgz8c0c8ExvF9dvvcpcrbo2IrI+rOoXKD6nQ= go.bug.st/downloader/v2 v2.1.0/go.mod h1:VZW2V1iGKV8rJL2ZEGIDzzBeKowYv34AedJz13RzVII= go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18 h1:F1qxtaFuewctYc/SsHRn+Q7Dtwi+yJGPgVq8YLtQz98= go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18/go.mod h1:Cx1VqMtEhE9pIkEyUj3LVVVPkv89dgW8aCKrRPDR/uE= +go.bug.st/serial v1.1.1 h1:5J1DpaIaSIruBi7jVnKXnhRS+YQ9+2PLJMtIZKoIgnc= go.bug.st/serial v1.1.1/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY= +go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI= go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -249,6 +283,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -335,9 +370,12 @@ gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3M gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= From 91d23769eaf40cf754c6c82c3459e67b99c5b149 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 15 Dec 2020 17:29:11 +0100 Subject: [PATCH 56/61] Fixed typo --- handler/handler.go | 12 ++++++------ lsp/uri.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 6731569..ced2b06 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -357,7 +357,7 @@ func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) err handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp") handler.buildSketchCppVersion = 1 handler.lspInitializeParams.RootPath = handler.buildSketchRoot.String() - handler.lspInitializeParams.RootURI = lsp.NewDocumenteURIFromPath(handler.buildSketchRoot) + handler.lspInitializeParams.RootURI = lsp.NewDocumentURIFromPath(handler.buildSketchRoot) if cppContent, err := handler.buildSketchCpp.ReadFile(); err == nil { handler.sketchMapper = sourcemapper.CreateInoMapper(cppContent) @@ -371,7 +371,7 @@ func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) err ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - cppURI := lsp.NewDocumenteURIFromPath(handler.buildSketchCpp) + cppURI := lsp.NewDocumentURIFromPath(handler.buildSketchCpp) cppTextDocumentIdentifier := lsp.TextDocumentIdentifier{URI: cppURI} syncEvent := &lsp.DidChangeTextDocumentParams{ @@ -409,7 +409,7 @@ func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) err func (handler *InoHandler) refreshCppDocumentSymbols() error { // Query source code symbols - cppURI := lsp.NewDocumenteURIFromPath(handler.buildSketchCpp) + cppURI := lsp.NewDocumentURIFromPath(handler.buildSketchCpp) log.Printf(" --> documentSymbol(%s)", cppURI) result, err := lsp.SendRequest(context.Background(), handler.ClangdConn, "textDocument/documentSymbol", &lsp.DocumentSymbolParams{ TextDocument: lsp.TextDocumentIdentifier{URI: cppURI}, @@ -518,7 +518,7 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD sketchCpp, err := handler.buildSketchCpp.ReadFile() newParam := &lsp.DidOpenTextDocumentParams{ TextDocument: lsp.TextDocumentItem{ - URI: lsp.NewDocumenteURIFromPath(handler.buildSketchCpp), + URI: lsp.NewDocumentURIFromPath(handler.buildSketchCpp), Text: string(sketchCpp), LanguageID: "cpp", Version: handler.buildSketchCppVersion, @@ -583,7 +583,7 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText ContentChanges: cppChanges, TextDocument: lsp.VersionedTextDocumentIdentifier{ TextDocumentIdentifier: lsp.TextDocumentIdentifier{ - URI: lsp.NewDocumenteURIFromPath(handler.buildSketchCpp), + URI: lsp.NewDocumentURIFromPath(handler.buildSketchCpp), }, Version: handler.sketchMapper.CppText.Version, }, @@ -659,7 +659,7 @@ func (handler *InoHandler) sketchToBuildPathTextDocumentIdentifier(doc *lsp.Text newDocFile = handler.buildSketchRoot.JoinPath(rel) } log.Printf(" URI: '%s' -> '%s'", docFile, newDocFile) - doc.URI = lsp.NewDocumenteURIFromPath(newDocFile) + doc.URI = lsp.NewDocumentURIFromPath(newDocFile) return nil } diff --git a/lsp/uri.go b/lsp/uri.go index f1b6fac..3bdfa2b 100644 --- a/lsp/uri.go +++ b/lsp/uri.go @@ -43,8 +43,8 @@ func (uri DocumentURI) String() string { return string(uri) } -// NewDocumenteURIFromPath create a DocumentURI from the given Path object -func NewDocumenteURIFromPath(path *paths.Path) DocumentURI { +// NewDocumentURIFromPath create a DocumentURI from the given Path object +func NewDocumentURIFromPath(path *paths.Path) DocumentURI { return NewDocumentURI(path.String()) } From c51689a1be5c6af1048654902cb010cd95073372 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 16 Dec 2020 12:57:27 +0100 Subject: [PATCH 57/61] Added .Ext() method on DocumentURI --- handler/handler.go | 10 +++++----- lsp/uri.go | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index ced2b06..e924280 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -509,7 +509,7 @@ func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextD handler.docs[doc.URI] = &doc // If we are tracking a .ino... - if doc.URI.AsPath().Ext() == ".ino" { + if doc.URI.Ext() == ".ino" { handler.sketchTrackedFilesCount++ log.Printf(" increasing .ino tracked files count: %d", handler.sketchTrackedFilesCount) @@ -549,7 +549,7 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText // If changes are applied to a .ino file we increment the global .ino.cpp versioning // for each increment of the single .ino file. - if doc.URI.AsPath().Ext() == ".ino" { + if doc.URI.Ext() == ".ino" { cppChanges := []lsp.TextDocumentContentChangeEvent{} for _, inoChange := range req.ContentChanges { @@ -853,7 +853,7 @@ func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp Diagnostics: codeAction.Diagnostics, Command: handler.Cpp2InoCommand(codeAction.Command), } - if uri.AsPath().Ext() == ".ino" { + if uri.Ext() == ".ino" { for i, diag := range inoCodeAction.Diagnostics { _, inoCodeAction.Diagnostics[i].Range = handler.sketchMapper.CppToInoRange(diag.Range) } @@ -953,7 +953,7 @@ func (handler *InoHandler) cpp2inoTextEdit(edit *lsp.TextEdit, uri lsp.DocumentU } func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, origURI lsp.DocumentURI) []lsp.DocumentSymbol { - if origURI.AsPath().Ext() != ".ino" || len(origSymbols) == 0 { + if origURI.Ext() != ".ino" || len(origSymbols) == 0 { return origSymbols } @@ -1072,7 +1072,7 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. // If we have an "undefined reference" in the .ino code trigger a // check for newly created symbols (that in turn may trigger a // new arduino-preprocessing of the sketch). - if msg.URI.AsPath().Ext() == ".ino" { + if msg.URI.Ext() == ".ino" { for _, diag := range msg.Diagnostics { if diag.Code == "undeclared_var_use_suggest" { handler.buildSketchSymbolsCheck = true diff --git a/lsp/uri.go b/lsp/uri.go index 3bdfa2b..9c84adf 100644 --- a/lsp/uri.go +++ b/lsp/uri.go @@ -43,6 +43,11 @@ func (uri DocumentURI) String() string { return string(uri) } +// Ext returns the extension of the file pointed by the URI +func (uri DocumentURI) Ext() string { + return filepath.Ext(string(uri)) +} + // NewDocumentURIFromPath create a DocumentURI from the given Path object func NewDocumentURIFromPath(path *paths.Path) DocumentURI { return NewDocumentURI(path.String()) From d9eeb0ccc4c7d814ad4eaca23e60a681644919ee Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 16 Dec 2020 13:11:22 +0100 Subject: [PATCH 58/61] didOpen now handles .cpp / .h files too --- handler/handler.go | 123 +++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 44 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index e924280..51e5951 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -195,7 +195,8 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr uri = p.TextDocument.URI log.Printf("--> codeAction(%s:%s)", p.TextDocument.URI, p.Range.Start) - if err := handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument); err != nil { + p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument) + if err != nil { break } if p.TextDocument.URI.AsPath().EquivalentTo(handler.buildSketchCpp) { @@ -221,14 +222,14 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr uri = p.TextDocument.URI log.Printf("--> documentSymbol(%s)", p.TextDocument.URI) - err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) + p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument) log.Printf(" --> documentSymbol(%s)", p.TextDocument.URI) case *lsp.DidSaveTextDocumentParams: // "textDocument/didSave": log.Printf("--X " + req.Method) return nil, nil uri = p.TextDocument.URI - err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) + p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument) case *lsp.DidCloseTextDocumentParams: // "textDocument/didClose": log.Printf("--X " + req.Method) return nil, nil @@ -257,7 +258,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Printf("--X " + req.Method) return nil, nil uri = p.TextDocument.URI - err = handler.sketchToBuildPathTextDocumentIdentifier(&p.TextDocument) + p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument) case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting": log.Printf("--X " + req.Method) return nil, nil @@ -503,34 +504,48 @@ func startClangd(compileCommandsDir, sketchCpp *paths.Path) (io.WriteCloser, io. } } -func (handler *InoHandler) didOpen(ctx context.Context, params *lsp.DidOpenTextDocumentParams) (*lsp.DidOpenTextDocumentParams, error) { +func (handler *InoHandler) didOpen(ctx context.Context, inoDidOpen *lsp.DidOpenTextDocumentParams) (*lsp.DidOpenTextDocumentParams, error) { // Add the TextDocumentItem in the tracked files list - doc := params.TextDocument - handler.docs[doc.URI] = &doc + inoItem := inoDidOpen.TextDocument + handler.docs[inoItem.URI] = &inoItem // If we are tracking a .ino... - if doc.URI.Ext() == ".ino" { + if inoItem.URI.Ext() == ".ino" { handler.sketchTrackedFilesCount++ log.Printf(" increasing .ino tracked files count: %d", handler.sketchTrackedFilesCount) - // ...notify clang that sketchCpp is no longer valid on disk - if handler.sketchTrackedFilesCount == 1 { - sketchCpp, err := handler.buildSketchCpp.ReadFile() - newParam := &lsp.DidOpenTextDocumentParams{ - TextDocument: lsp.TextDocumentItem{ - URI: lsp.NewDocumentURIFromPath(handler.buildSketchCpp), - Text: string(sketchCpp), - LanguageID: "cpp", - Version: handler.buildSketchCppVersion, - }, - } - - // Trigger a documentSymbol load - handler.buildSketchSymbolsLoad = true - return newParam, err + // notify clang that sketchCpp has been opened only once + if handler.sketchTrackedFilesCount != 1 { + return nil, nil } + + // trigger a documentSymbol load + handler.buildSketchSymbolsLoad = true } - return nil, nil + + cppItem, err := handler.ino2cppTextDocumentItem(inoItem) + return &lsp.DidOpenTextDocumentParams{ + TextDocument: cppItem, + }, err +} + +func (handler *InoHandler) ino2cppTextDocumentItem(inoItem lsp.TextDocumentItem) (cppItem lsp.TextDocumentItem, err error) { + cppURI, err := handler.ino2cppDocumentURI(inoItem.URI) + if err != nil { + return cppItem, err + } + cppItem.URI = cppURI + + if cppURI.AsPath().EquivalentTo(handler.buildSketchCpp) { + cppItem.LanguageID = "cpp" + cppItem.Text = handler.sketchMapper.CppText.Text + cppItem.Version = handler.sketchMapper.CppText.Version + } else { + cppItem.Text = handler.docs[inoItem.URI].Text + cppItem.Version = handler.docs[inoItem.URI].Version + } + + return cppItem, nil } func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeTextDocumentParams) (*lsp.DidChangeTextDocumentParams, error) { @@ -592,11 +607,14 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText } // If changes are applied to other files pass them by converting just the URI + cppDoc, err := handler.ino2cppVersionedTextDocumentIdentifier(req.TextDocument) + if err != nil { + return nil, err + } cppReq := &lsp.DidChangeTextDocumentParams{ - TextDocument: req.TextDocument, + TextDocument: cppDoc, ContentChanges: req.ContentChanges, } - err := handler.sketchToBuildPathTextDocumentIdentifier(&cppReq.TextDocument.TextDocumentIdentifier) return cppReq, err } @@ -635,32 +653,45 @@ func (handler *InoHandler) handleError(ctx context.Context, err error) error { return errors.New(message) } -func (handler *InoHandler) sketchToBuildPathTextDocumentIdentifier(doc *lsp.TextDocumentIdentifier) error { +func (handler *InoHandler) ino2cppVersionedTextDocumentIdentifier(doc lsp.VersionedTextDocumentIdentifier) (lsp.VersionedTextDocumentIdentifier, error) { + cppURI, err := handler.ino2cppDocumentURI(doc.URI) + res := doc + res.URI = cppURI + return res, err +} + +func (handler *InoHandler) ino2cppTextDocumentIdentifier(doc lsp.TextDocumentIdentifier) (lsp.TextDocumentIdentifier, error) { + cppURI, err := handler.ino2cppDocumentURI(doc.URI) + res := doc + res.URI = cppURI + return res, err +} + +func (handler *InoHandler) ino2cppDocumentURI(uri lsp.DocumentURI) (lsp.DocumentURI, error) { // Sketchbook/Sketch/Sketch.ino -> build-path/sketch/Sketch.ino.cpp // Sketchbook/Sketch/AnotherTab.ino -> build-path/sketch/Sketch.ino.cpp (different section from above) // Sketchbook/Sketch/AnotherFile.cpp -> build-path/sketch/AnotherFile.cpp (1:1) // another/path/source.cpp -> unchanged // Convert sketch path to build path - docFile := doc.URI.AsPath() - newDocFile := docFile - - if docFile.Ext() == ".ino" { - newDocFile = handler.buildSketchCpp - } else if inside, err := docFile.IsInsideDir(handler.sketchRoot); err != nil { - log.Printf(" could not determine if '%s' is inside '%s'", docFile, handler.sketchRoot) - return unknownURI(doc.URI) + inoPath := uri.AsPath() + cppPath := inoPath + + if inoPath.Ext() == ".ino" { + cppPath = handler.buildSketchCpp + } else if inside, err := inoPath.IsInsideDir(handler.sketchRoot); err != nil { + log.Printf(" could not determine if '%s' is inside '%s'", inoPath, handler.sketchRoot) + return "", unknownURI(uri) } else if !inside { - log.Printf(" passing doc identifier to '%s' as-is", docFile) - } else if rel, err := handler.sketchRoot.RelTo(docFile); err != nil { - log.Printf(" could not determine rel-path of '%s' in '%s", docFile, handler.sketchRoot) - return unknownURI(doc.URI) + log.Printf(" passing doc identifier to '%s' as-is", inoPath) + } else if rel, err := handler.sketchRoot.RelTo(inoPath); err != nil { + log.Printf(" could not determine rel-path of '%s' in '%s", inoPath, handler.sketchRoot) + return "", unknownURI(uri) } else { - newDocFile = handler.buildSketchRoot.JoinPath(rel) + cppPath = handler.buildSketchRoot.JoinPath(rel) } - log.Printf(" URI: '%s' -> '%s'", docFile, newDocFile) - doc.URI = lsp.NewDocumentURIFromPath(newDocFile) - return nil + log.Printf(" URI: '%s' -> '%s'", inoPath, cppPath) + return lsp.NewDocumentURIFromPath(cppPath), nil } func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error { @@ -673,7 +704,11 @@ func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDoc } params.Position.Line = line } - handler.sketchToBuildPathTextDocumentIdentifier(¶ms.TextDocument) + cppDoc, err := handler.ino2cppTextDocumentIdentifier(params.TextDocument) + if err != nil { + return err + } + params.TextDocument = cppDoc return nil } From e5c82d643f06bc5abc2dd4246415021bafd354b9 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 16 Dec 2020 16:57:45 +0100 Subject: [PATCH 59/61] Fixed publishDiagnostics for .cpp files --- handler/handler.go | 204 +++++++++++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 64 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 51e5951..def7a51 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -59,7 +59,7 @@ type InoHandler struct { sketchMapper *sourcemapper.InoMapper sketchTrackedFilesCount int docs map[lsp.DocumentURI]*lsp.TextDocumentItem - docHasDiagnostics map[lsp.DocumentURI]bool + inoDocsWithDiagnostics map[lsp.DocumentURI]bool config lsp.BoardConfig synchronizer Synchronizer @@ -68,8 +68,8 @@ type InoHandler struct { // NewInoHandler creates and configures an InoHandler. func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler { handler := &InoHandler{ - docs: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, - docHasDiagnostics: map[lsp.DocumentURI]bool{}, + docs: map[lsp.DocumentURI]*lsp.TextDocumentItem{}, + inoDocsWithDiagnostics: map[lsp.DocumentURI]bool{}, config: lsp.BoardConfig{ SelectedBoard: board, }, @@ -667,31 +667,71 @@ func (handler *InoHandler) ino2cppTextDocumentIdentifier(doc lsp.TextDocumentIde return res, err } -func (handler *InoHandler) ino2cppDocumentURI(uri lsp.DocumentURI) (lsp.DocumentURI, error) { +func (handler *InoHandler) ino2cppDocumentURI(inoURI lsp.DocumentURI) (lsp.DocumentURI, error) { // Sketchbook/Sketch/Sketch.ino -> build-path/sketch/Sketch.ino.cpp // Sketchbook/Sketch/AnotherTab.ino -> build-path/sketch/Sketch.ino.cpp (different section from above) // Sketchbook/Sketch/AnotherFile.cpp -> build-path/sketch/AnotherFile.cpp (1:1) // another/path/source.cpp -> unchanged // Convert sketch path to build path - inoPath := uri.AsPath() - cppPath := inoPath - + inoPath := inoURI.AsPath() if inoPath.Ext() == ".ino" { - cppPath = handler.buildSketchCpp - } else if inside, err := inoPath.IsInsideDir(handler.sketchRoot); err != nil { + return lsp.NewDocumentURIFromPath(handler.buildSketchCpp), nil + } + + inside, err := inoPath.IsInsideDir(handler.sketchRoot) + if err != nil { log.Printf(" could not determine if '%s' is inside '%s'", inoPath, handler.sketchRoot) - return "", unknownURI(uri) - } else if !inside { + return "", unknownURI(inoURI) + } + if !inside { log.Printf(" passing doc identifier to '%s' as-is", inoPath) - } else if rel, err := handler.sketchRoot.RelTo(inoPath); err != nil { - log.Printf(" could not determine rel-path of '%s' in '%s", inoPath, handler.sketchRoot) - return "", unknownURI(uri) - } else { - cppPath = handler.buildSketchRoot.JoinPath(rel) + return inoURI, nil + } + + rel, err := handler.sketchRoot.RelTo(inoPath) + if err == nil { + cppPath := handler.buildSketchRoot.JoinPath(rel) + log.Printf(" URI: '%s' -> '%s'", inoPath, cppPath) + return lsp.NewDocumentURIFromPath(cppPath), nil } - log.Printf(" URI: '%s' -> '%s'", inoPath, cppPath) - return lsp.NewDocumentURIFromPath(cppPath), nil + + log.Printf(" could not determine rel-path of '%s' in '%s': %s", inoPath, handler.sketchRoot, err) + return "", err +} + +func (handler *InoHandler) cpp2inoDocumentURI(cppURI lsp.DocumentURI, cppRange lsp.Range) (lsp.DocumentURI, lsp.Range, error) { + // Sketchbook/Sketch/Sketch.ino <- build-path/sketch/Sketch.ino.cpp + // Sketchbook/Sketch/AnotherTab.ino <- build-path/sketch/Sketch.ino.cpp (different section from above) + // Sketchbook/Sketch/AnotherFile.cpp <- build-path/sketch/AnotherFile.cpp (1:1) + // another/path/source.cpp <- unchanged + + // Convert build path to sketch path + cppPath := cppURI.AsPath() + if cppPath.EquivalentTo(handler.buildSketchCpp) { + inoPath, inoRange := handler.sketchMapper.CppToInoRange(cppRange) + return lsp.NewDocumentURI(inoPath), inoRange, nil + } + + inside, err := cppPath.IsInsideDir(handler.buildSketchRoot) + if err != nil { + log.Printf(" could not determine if '%s' is inside '%s'", cppPath, handler.buildSketchRoot) + return "", lsp.Range{}, err + } + if !inside { + log.Printf(" keep doc identifier to '%s' as-is", cppPath) + return cppURI, cppRange, nil + } + + rel, err := handler.buildSketchRoot.RelTo(cppPath) + if err == nil { + inoPath := handler.sketchRoot.JoinPath(rel) + log.Printf(" URI: '%s' -> '%s'", cppPath, inoPath) + return lsp.NewDocumentURIFromPath(inoPath), cppRange, nil + } + + log.Printf(" could not determine rel-path of '%s' in '%s': %s", cppPath, handler.buildSketchRoot, err) + return "", lsp.Range{}, err } func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error { @@ -1052,6 +1092,52 @@ func (handler *InoHandler) cpp2inoSymbolInformation(syms []lsp.SymbolInformation // return symbols } +func (handler *InoHandler) cpp2inoDiagnostics(cppDiags *lsp.PublishDiagnosticsParams) ([]*lsp.PublishDiagnosticsParams, error) { + + if len(cppDiags.Diagnostics) == 0 { + // If we receive the empty diagnostic on the preprocessed sketch, + // just return an empty diagnostic array. + if cppDiags.URI.AsPath().EquivalentTo(handler.buildSketchCpp) { + return []*lsp.PublishDiagnosticsParams{}, nil + } + + inoURI, _, err := handler.cpp2inoDocumentURI(cppDiags.URI, lsp.Range{}) + return []*lsp.PublishDiagnosticsParams{ + { + URI: inoURI, + Diagnostics: []lsp.Diagnostic{}, + }, + }, err + } + + convertedDiagnostics := map[lsp.DocumentURI]*lsp.PublishDiagnosticsParams{} + for _, cppDiag := range cppDiags.Diagnostics { + inoURI, inoRange, err := handler.cpp2inoDocumentURI(cppDiags.URI, cppDiag.Range) + if err != nil { + return nil, err + } + + inoDiagParam, created := convertedDiagnostics[inoURI] + if !created { + inoDiagParam = &lsp.PublishDiagnosticsParams{ + URI: inoURI, + Diagnostics: []lsp.Diagnostic{}, + } + convertedDiagnostics[inoURI] = inoDiagParam + } + + inoDiag := cppDiag + inoDiag.Range = inoRange + inoDiagParam.Diagnostics = append(inoDiagParam.Diagnostics, inoDiag) + } + + inoDiagParams := []*lsp.PublishDiagnosticsParams{} + for _, v := range convertedDiagnostics { + inoDiagParams = append(inoDiagParams, v) + } + return inoDiagParams, nil +} + // FromClangd handles a message received from clangd. func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) { handler.synchronizer.DataMux.RLock() @@ -1073,59 +1159,49 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) } - if p.URI.AsPath().EquivalentTo(handler.buildSketchCpp) { - // we should transform back N diagnostics of sketch.cpp.ino into - // their .ino counter parts (that may span over multiple files...) - - convertedDiagnostics := map[string][]lsp.Diagnostic{} - for _, cppDiag := range p.Diagnostics { - inoSource, inoRange := handler.sketchMapper.CppToInoRange(cppDiag.Range) - inoDiag := cppDiag - inoDiag.Range = inoRange - if inoDiags, ok := convertedDiagnostics[inoSource]; !ok { - convertedDiagnostics[inoSource] = []lsp.Diagnostic{inoDiag} - } else { - convertedDiagnostics[inoSource] = append(inoDiags, inoDiag) + // the diagnostics on sketch.cpp.ino once mapped into their + // .ino counter parts may span over multiple .ino files... + inoDiagnostics, err := handler.cpp2inoDiagnostics(p) + if err != nil { + return nil, err + } + cleanUpInoDiagnostics := false + if len(inoDiagnostics) == 0 { + cleanUpInoDiagnostics = true + } + + // Push back to IDE the converted diagnostics + inoDocsWithDiagnostics := map[lsp.DocumentURI]bool{} + for _, inoDiag := range inoDiagnostics { + if enableLogging { + log.Printf("<-- publishDiagnostics(%s):", inoDiag.URI) + for _, diag := range inoDiag.Diagnostics { + log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) } } - // Push back to IDE the converted diagnostics - docsWithDiagnostics := map[lsp.DocumentURI]bool{} - for filename, inoDiags := range convertedDiagnostics { - msg := lsp.PublishDiagnosticsParams{ - URI: lsp.NewDocumentURI(filename), - Diagnostics: inoDiags, - } - docsWithDiagnostics[msg.URI] = true - if enableLogging { - log.Printf("<-- publishDiagnostics(%s):", msg.URI) - for _, diag := range msg.Diagnostics { - log.Printf(" > %d:%d - %v: %s", diag.Range.Start.Line, diag.Range.Start.Character, diag.Severity, diag.Code) + // If we have an "undefined reference" in the .ino code trigger a + // check for newly created symbols (that in turn may trigger a + // new arduino-preprocessing of the sketch). + if inoDiag.URI.Ext() == ".ino" { + inoDocsWithDiagnostics[inoDiag.URI] = true + cleanUpInoDiagnostics = true + for _, diag := range inoDiag.Diagnostics { + if diag.Code == "undeclared_var_use_suggest" { + handler.buildSketchSymbolsCheck = true } } + } - // If we have an "undefined reference" in the .ino code trigger a - // check for newly created symbols (that in turn may trigger a - // new arduino-preprocessing of the sketch). - if msg.URI.Ext() == ".ino" { - for _, diag := range msg.Diagnostics { - if diag.Code == "undeclared_var_use_suggest" { - handler.buildSketchSymbolsCheck = true - } - } - } - if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", msg); err != nil { - return nil, err - } + if err := handler.StdioConn.Notify(ctx, "textDocument/publishDiagnostics", inoDiag); err != nil { + return nil, err } + } + if cleanUpInoDiagnostics { // Remove diagnostics from all .ino where there are no errors coming from clang - for sourceURI := range handler.docs { - if !handler.docHasDiagnostics[sourceURI] { - // skip if the document didn't have previously sent diagnostics - continue - } - if docsWithDiagnostics[sourceURI] { + for sourceURI := range handler.inoDocsWithDiagnostics { + if inoDocsWithDiagnostics[sourceURI] { // skip if we already sent updated diagnostics continue } @@ -1142,9 +1218,9 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. } } - handler.docHasDiagnostics = docsWithDiagnostics - return nil, err + handler.inoDocsWithDiagnostics = inoDocsWithDiagnostics } + return nil, err case *lsp.ApplyWorkspaceEditParams: // "workspace/applyEdit" From 796a4631cb571030e7e3a9033b366c53e33505be Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 17 Dec 2020 00:15:35 +0100 Subject: [PATCH 60/61] Fixed text synchronization issues Text versioning is not linear with the number of changes: we just use the version number passed by the client and hope for the best. --- handler/handler.go | 19 +++++-------------- handler/textutils/textutils.go | 14 +++++++++----- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index def7a51..ed9cf8b 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -162,7 +162,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr uri = p.TextDocument.URI log.Printf("--> didChange(%s@%d)", p.TextDocument.URI, p.TextDocument.Version) for _, change := range p.ContentChanges { - log.Printf(" > %s -> '%s'", change.Range, strconv.Quote(change.Text)) + log.Printf(" > %s -> %s", change.Range, strconv.Quote(change.Text)) } if res, err := handler.didChange(ctx, p); err != nil { @@ -177,7 +177,7 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr log.Printf(" --> didChange(%s@%d)", p.TextDocument.URI, p.TextDocument.Version) for _, change := range p.ContentChanges { - log.Printf(" > %s -> '%s'", change.Range, strconv.Quote(change.Text)) + log.Printf(" > %s -> %s", change.Range, strconv.Quote(change.Text)) } err = handler.ClangdConn.Notify(ctx, req.Method, p) return nil, err @@ -291,16 +291,12 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr var result interface{} if req.Notif { err = handler.ClangdConn.Notify(ctx, req.Method, params) - if enableLogging { - log.Println(" sent", req.Method, "notification to clangd") - } + // log.Println(" sent", req.Method, "notification to clangd") } else { ctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond) defer cancel() result, err = lsp.SendRequest(ctx, handler.ClangdConn, req.Method, params) - if enableLogging { - log.Println(" sent", req.Method, "request id", req.ID, " to clangd") - } + // log.Println(" sent", req.Method, "request id", req.ID, " to clangd") } if err == nil && handler.buildSketchSymbolsLoad { handler.buildSketchSymbolsLoad = false @@ -555,12 +551,7 @@ func (handler *InoHandler) didChange(ctx context.Context, req *lsp.DidChangeText if !ok { return nil, unknownURI(doc.URI) } - if trackedDoc.Version+len(req.ContentChanges) != doc.Version { - return nil, errors.Errorf("document out-of-sync: expected version %d but got %d", trackedDoc.Version+1, doc.Version) - } - for _, change := range req.ContentChanges { - textutils.ApplyLSPTextDocumentContentChangeEvent(trackedDoc, &change) - } + textutils.ApplyLSPTextDocumentContentChangeEvent(trackedDoc, req.ContentChanges, doc.Version) // If changes are applied to a .ino file we increment the global .ino.cpp versioning // for each increment of the single .ino file. diff --git a/handler/textutils/textutils.go b/handler/textutils/textutils.go index b0af538..dbcf0b8 100644 --- a/handler/textutils/textutils.go +++ b/handler/textutils/textutils.go @@ -7,13 +7,17 @@ import ( ) // ApplyLSPTextDocumentContentChangeEvent applies the LSP change in the given text -func ApplyLSPTextDocumentContentChangeEvent(textDoc *lsp.TextDocumentItem, change *lsp.TextDocumentContentChangeEvent) error { - newText, err := ApplyTextChange(textDoc.Text, *change.Range, change.Text) - if err != nil { - return err +func ApplyLSPTextDocumentContentChangeEvent(textDoc *lsp.TextDocumentItem, changes []lsp.TextDocumentContentChangeEvent, version int) error { + newText := textDoc.Text + for _, change := range changes { + if t, err := ApplyTextChange(newText, *change.Range, change.Text); err == nil { + newText = t + } else { + return err + } } textDoc.Text = newText - textDoc.Version++ + textDoc.Version = version return nil } From c2c6d968a3ae0f9d5e4127b2ea4f615eda8c142a Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 17 Dec 2020 10:09:17 +0100 Subject: [PATCH 61/61] Improved panic log handling --- handler/builder.go | 3 +++ handler/handler.go | 6 ++++++ main.go | 21 +++++---------------- streams/panics.go | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 streams/panics.go diff --git a/handler/builder.go b/handler/builder.go index bb34be3..db03d53 100644 --- a/handler/builder.go +++ b/handler/builder.go @@ -10,6 +10,7 @@ import ( "github.com/arduino/arduino-cli/arduino/libraries" "github.com/arduino/arduino-cli/executils" "github.com/arduino/go-paths-helper" + "github.com/bcmi-labs/arduino-language-server/streams" "github.com/pkg/errors" ) @@ -21,6 +22,8 @@ func (handler *InoHandler) scheduleRebuildEnvironment() { } func (handler *InoHandler) rebuildEnvironmentLoop() { + defer streams.CatchAndLogPanic() + grabDeadline := func() *time.Time { handler.rebuildSketchDeadlineMutex.Lock() defer handler.rebuildSketchDeadlineMutex.Unlock() diff --git a/handler/handler.go b/handler/handler.go index ed9cf8b..293ddd3 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -109,6 +109,8 @@ func (handler *InoHandler) StopClangd() { // HandleMessageFromIDE handles a message received from the IDE client (via stdio). func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) { + defer streams.CatchAndLogPanic() + needsWriteLock := map[string]bool{ "initialize": true, "textDocument/didOpen": true, @@ -1131,6 +1133,8 @@ func (handler *InoHandler) cpp2inoDiagnostics(cppDiags *lsp.PublishDiagnosticsPa // FromClangd handles a message received from clangd. func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) { + defer streams.CatchAndLogPanic() + handler.synchronizer.DataMux.RLock() defer handler.synchronizer.DataMux.RUnlock() @@ -1238,6 +1242,8 @@ func (handler *InoHandler) FromClangd(ctx context.Context, connection *jsonrpc2. } func (handler *InoHandler) showMessage(ctx context.Context, msgType lsp.MessageType, message string) { + defer streams.CatchAndLogPanic() + params := lsp.ShowMessageParams{ Type: msgType, Message: message, diff --git a/main.go b/main.go index 7f5bb73..d76c81a 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,9 @@ package main import ( "flag" + "io" "log" "os" - "syscall" "github.com/arduino/go-paths-helper" "github.com/bcmi-labs/arduino-language-server/handler" @@ -38,22 +38,11 @@ func main() { if enableLogging { logfile := streams.OpenLogFileAs("inols-err.log") - // log.SetOutput(io.MultiWriter(logfile, os.Stderr)) - defer func() { - // // In case of panic output the stack trace in the log file before exiting - // if r := recover(); r != nil { - // log.Println(string(debug.Stack())) - // } - - logfile.Close() - }() - err := syscall.Dup2(int(logfile.Fd()), int(os.Stderr.Fd())) - if err != nil { - log.Fatalf("Failed to redirect stderr to file: %v", err) - } - // log.SetOutput(logfile) + log.SetOutput(io.MultiWriter(logfile, os.Stderr)) + defer streams.CatchAndLogPanic() + } else { + log.SetOutput(os.Stderr) } - log.SetOutput(os.Stderr) handler.Setup(cliPath, clangdPath, enableLogging, true) initialBoard := lsp.Board{Fqbn: initialFqbn, Name: initialBoardName} diff --git a/streams/panics.go b/streams/panics.go new file mode 100644 index 0000000..c4b7d0d --- /dev/null +++ b/streams/panics.go @@ -0,0 +1,17 @@ +package streams + +import ( + "fmt" + "log" + "runtime/debug" +) + +// CatchAndLogPanic will recover a panic, log it on standard logger, and rethrow it +// to continue stack unwinding. +func CatchAndLogPanic() { + if r := recover(); r != nil { + reason := fmt.Sprintf("%v", r) + log.Println(fmt.Sprintf("Panic: %s\n\n%s", reason, string(debug.Stack()))) + panic(reason) + } +}