diff --git a/go.mod b/go.mod
index ad27295..aceee61 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,9 @@ module github.com/bcmi-labs/arduino-language-server
 go 1.12
 
 require (
-	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/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
+	github.com/stretchr/testify v1.6.1
 )
diff --git a/go.sum b/go.sum
index 386ebdb..2eacd34 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,390 @@
-github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+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=
+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-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.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=
+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 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=
+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 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=
+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=
+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 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=
+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=
+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 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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+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 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=
+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=
+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=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/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=
+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 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=
+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/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/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 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=
+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/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 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=
+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 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=
+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=
+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 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=
+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 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=
+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 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=
+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 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 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=
+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=
+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 170bbd2..db03d53 100644
--- a/handler/builder.go
+++ b/handler/builder.go
@@ -1,362 +1,123 @@
 package handler
 
 import (
-	"bufio"
 	"bytes"
-	"io/ioutil"
+	"encoding/json"
 	"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/bcmi-labs/arduino-language-server/streams"
 	"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
-	}
+func (handler *InoHandler) scheduleRebuildEnvironment() {
+	handler.rebuildSketchDeadlineMutex.Lock()
+	defer handler.rebuildSketchDeadlineMutex.Unlock()
+	d := time.Now().Add(time.Second)
+	handler.rebuildSketchDeadline = &d
+}
 
-	sketchName := filepath.Base(sourcePath)
-	if strings.HasSuffix(sketchName, ".ino") {
-		sketchName = sketchName[:len(sketchName)-len(".ino")]
-	}
-	sketchTempPath := filepath.Join(rootTempDir, sketchName)
-	createDirIfNotExist(sketchTempPath)
+func (handler *InoHandler) rebuildEnvironmentLoop() {
+	defer streams.CatchAndLogPanic()
 
-	// 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)
-	}
+	grabDeadline := func() *time.Time {
+		handler.rebuildSketchDeadlineMutex.Lock()
+		defer handler.rebuildSketchDeadlineMutex.Unlock()
 
-	// Copy all header files to temp dir
-	err = copyHeaderFiles(filepath.Dir(sourcePath), rootTempDir)
-	if err != nil {
-		return
+		res := handler.rebuildSketchDeadline
+		handler.rebuildSketchDeadline = nil
+		return res
 	}
 
-	// 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)
+	for {
+		// Wait for someone to schedule a preprocessing...
+		time.Sleep(100 * time.Millisecond)
+		deadline := grabDeadline()
+		if deadline == nil {
+			continue
 		}
-	}
-}
 
-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
-			}
+		for time.Now().Before(*deadline) {
+			time.Sleep(100 * time.Millisecond)
 
-			err = ioutil.WriteFile(filepath.Join(destDir, fileInfo.Name()), input, 0644)
-			if err != nil {
-				return err
+			if d := grabDeadline(); d != nil {
+				deadline = d
 			}
 		}
+
+		// Regenerate preprocessed sketch!
+		handler.synchronizer.DataMux.Lock()
+		handler.initializeWorkbench(nil)
+		handler.synchronizer.DataMux.Unlock()
 	}
-	return nil
 }
 
-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)
-		}
-	}
+func (handler *InoHandler) generateBuildEnvironment() (*paths.Path, error) {
+	sketchDir := handler.sketchRoot
+	fqbn := handler.config.SelectedBoard.Fqbn
 
-	if fqbnChanged {
-		// Generate compile_flags.txt
-		var flagsPath string
-		flagsPath, err = generateCompileFlags(tempDir, inoPath, sourcePath, fqbn)
+	// Export temporary files
+	type overridesFile struct {
+		Overrides map[string]string `json:"overrides"`
+	}
+	data := overridesFile{Overrides: map[string]string{}}
+	for uri, trackedFile := range handler.docs {
+		rel, err := uri.AsPath().RelFrom(handler.sketchRoot)
 		if err != nil {
-			return
-		}
-		if enableLogging {
-			log.Println("Compile flags written to", flagsPath)
+			return nil, errors.WithMessage(err, "dumping tracked files")
 		}
+		data.Overrides[rel.String()] = trackedFile.Text
 	}
-
-	// 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}
+	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 {
-		cliArgs = []string{"compile", "--show-properties", inoPath}
-	}
-	propertiesCmd := exec.Command(globalCliPath, cliArgs...)
-	output, err := propertiesCmd.Output()
-	if err != nil {
-		err = logCommandErr(globalCliPath, output, err, errMsgFilter(tempDir))
-		return "", err
+		overridesJSON = tmpFile.String()
 	}
-	properties, err := readProperties(bytes.NewReader(output))
-	if err != nil {
-		return "", errors.Wrap(err, "Error while reading build properties.")
-	}
-	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.")
-	}
-	defer outFile.Close()
-
-	printer := Printer{Writer: bufio.NewWriter(outFile)}
-	printCompileFlags(properties, &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}
+	// 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(),
 	}
-	preprocessCmd := exec.Command(globalCliPath, cliArgs...)
-	cppCode, err = preprocessCmd.Output()
+	cmd, err := executils.NewProcess(args...)
 	if err != nil {
-		err = logCommandErr(globalCliPath, cppCode, err, errMsgFilter(tempDir))
-		return
+		return nil, errors.Errorf("running %s: %s", strings.Join(args, " "), err)
 	}
-
-	// 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)
+	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)
 	}
-	return
-}
 
-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 <Arduino.h>\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(properties map[string]string, 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")
-	printer.Println(splitFlags(cppFlags))
-	mcu := expandProperty(properties, "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")
-	printer.Println("-DF_CPU=", fcpu)
-	ideVersion := expandProperty(properties, "runtime.ide.version")
-	printer.Println("-DARDUINO=", ideVersion)
-	board := expandProperty(properties, "build.board")
-	printer.Println("-DARDUINO_", board)
-	arch := expandProperty(properties, "build.arch")
-	printer.Println("-DARDUINO_ARCH_", arch)
-	if strings.Contains(fqbn, ":sam:") {
-		libSamFlags := expandProperty(properties, "compiler.libsam.c.flags")
-		printer.Println(splitFlags(libSamFlags))
-	}
-	extraFlags := expandProperty(properties, "build.extra_flags")
-	printer.Println(splitFlags(extraFlags))
-	corePath := expandProperty(properties, "build.core.path")
-	printer.Println("-I", corePath)
-	variantPath := expandProperty(properties, "build.variant.path")
-	printer.Println("-I", variantPath)
-	if strings.Contains(fqbn, ":avr:") {
-		avrgccPath := expandProperty(properties, "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
-		}
+	type cmdBuilderRes struct {
+		BuildPath     *paths.Path `json:"build_path"`
+		UsedLibraries []*libraries.Library
 	}
-	return string(result)
-}
-
-func logCommandErr(command string, stdout []byte, err error, filter func(string) string) error {
-	message := ""
-	log.Println("Command error:", command, err)
-	if len(stdout) > 0 {
-		stdoutStr := string(stdout)
-		log.Println("------------------------------BEGIN STDOUT\n", stdoutStr, "------------------------------END STDOUT")
-		message += filter(stdoutStr)
+	type cmdRes struct {
+		CompilerOut   string        `json:"compiler_out"`
+		CompilerErr   string        `json:"compiler_err"`
+		BuilderResult cmdBuilderRes `json:"builder_result"`
 	}
-	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
+	var res cmdRes
+	if err := json.Unmarshal(cmdOutput.Bytes(), &res); err != nil {
+		return nil, errors.Errorf("parsing arduino-cli output: %s", 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, "")
-	}
+	// Return only the build path
+	log.Println("arduino-cli output:", cmdOutput)
+	return res.BuilderResult.BuildPath, nil
 }
diff --git a/handler/handler.go b/handler/handler.go
index 4141da6..293ddd3 100644
--- a/handler/handler.go
+++ b/handler/handler.go
@@ -1,30 +1,38 @@
 package handler
 
 import (
-	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"regexp"
+	"strconv"
 	"strings"
+	"sync"
 	"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/lsp"
+	"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
 }
@@ -32,21 +40,43 @@ func Setup(cliPath string, _enableLogging bool, _asyncProcessing bool) {
 // 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
+	inoDocsWithDiagnostics     map[lsp.DocumentURI]bool
+
+	config       lsp.BoardConfig
+	synchronizer Synchronizer
+}
+
 // 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, board lsp.Board) *InoHandler {
 	handler := &InoHandler{
-		clangdProc: ClangdProc{
-			Start: startClangd,
-			Logs:  logStreams,
-		},
-		data: make(map[lsp.DocumentURI]*FileData),
-		config: BoardConfig{
+		docs:                   map[lsp.DocumentURI]*lsp.TextDocumentItem{},
+		inoDocsWithDiagnostics: map[lsp.DocumentURI]bool{},
+		config: lsp.BoardConfig{
 			SelectedBoard: board,
 		},
 	}
-	handler.startClangd()
-	stdStream := jsonrpc2.NewBufferedStream(logStreams.AttachStdInOut(stdin, stdout), jsonrpc2.VSCodeObjectCodec{})
-	var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.FromStdio)
+
+	stdStream := jsonrpc2.NewBufferedStream(stdio, jsonrpc2.VSCodeObjectCodec{})
+	var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.HandleMessageFromIDE)
 	if asyncProcessing {
 		stdHandler = AsyncHandler{
 			handler:      stdHandler,
@@ -57,47 +87,18 @@ func NewInoHandler(stdin io.ReadCloser, stdout io.WriteCloser, logStreams *Strea
 	if enableLogging {
 		log.Println("Initial board configuration:", board)
 	}
-	return handler
-}
 
-// InoHandler is a JSON-RPC handler that delegates messages to clangd.
-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
+	go handler.rebuildEnvironmentLoop()
+	return handler
 }
 
 // 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
-}
-
-// 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)
+	sourceText string
+	sourceURI  lsp.DocumentURI
+	targetURI  lsp.DocumentURI
+	sourceMap  *sourcemapper.InoMapper
+	version    int
 }
 
 // StopClangd closes the connection to the clangd process.
@@ -106,35 +107,208 @@ 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) {
+	defer streams.CatchAndLogPanic()
+
+	needsWriteLock := map[string]bool{
+		"initialize":             true,
+		"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
+
+	params, err := lsp.ReadParams(req.Method, req.Params)
+	if err != nil {
+		return nil, err
+	}
 	if params == nil {
 		params = req.Params
-	} else {
-		uri, err = handler.transformParamsToClangd(ctx, req.Method, params)
+	}
+	switch p := params.(type) {
+	case *lsp.InitializeParams:
+		// method "initialize"
+		err = handler.initializeWorkbench(p)
+
+	case *lsp.InitializedParams:
+		// method "initialized"
+		log.Println("--> initialized")
+
+	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)
+
+		res, err := handler.didOpen(ctx, p)
+
+		if res == nil {
+			log.Println("    --X notification is not propagated to clangd")
+			return nil, err // do not propagate to clangd
+		}
+
+		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))
+		}
+
+		if res, err := handler.didChange(ctx, p); err != nil {
+			log.Printf("    --E error: %s", err)
+			return nil, err
+		} else if res == nil {
+			log.Println("    --X notification is not propagated to clangd")
+			return nil, err // do not propagate to clangd
+		} else {
+			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
+		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.CodeActionParams:
+		// method "textDocument/codeAction"
+		uri = p.TextDocument.URI
+		log.Printf("--> codeAction(%s:%s)", p.TextDocument.URI, p.Range.Start)
+
+		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
+		if 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
+		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.DocumentSymbolParams:
+		// method "textDocument/documentSymbol"
+		uri = p.TextDocument.URI
+		log.Printf("--> documentSymbol(%s)", p.TextDocument.URI)
+
+		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
+		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
+	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)
+	// case "textDocument/signatureHelp":
+	// 	fallthrough
+	// case "textDocument/definition":
+	// 	fallthrough
+	// case "textDocument/typeDefinition":
+	// 	fallthrough
+	// case "textDocument/implementation":
+	// 	fallthrough
+	case *lsp.TextDocumentPositionParams: // "textDocument/documentHighlight":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		uri = p.TextDocument.URI
+		err = handler.ino2cppTextDocumentPositionParams(p)
+	case *lsp.ReferenceParams: // "textDocument/references":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		uri = p.TextDocument.URI
+		err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams)
+	case *lsp.DocumentFormattingParams: // "textDocument/formatting":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		uri = p.TextDocument.URI
+		p.TextDocument, err = handler.ino2cppTextDocumentIdentifier(p.TextDocument)
+	case *lsp.DocumentRangeFormattingParams: // "textDocument/rangeFormatting":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		uri = p.TextDocument.URI
+		err = handler.ino2cppDocumentRangeFormattingParams(p)
+	case *lsp.DocumentOnTypeFormattingParams: // "textDocument/onTypeFormatting":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		uri = p.TextDocument.URI
+		err = handler.ino2cppDocumentOnTypeFormattingParams(p)
+	case *lsp.RenameParams: // "textDocument/rename":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		uri = p.TextDocument.URI
+		err = handler.ino2cppRenameParams(p)
+	case *lsp.DidChangeWatchedFilesParams: // "workspace/didChangeWatchedFiles":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		err = handler.ino2cppDidChangeWatchedFilesParams(p)
+	case *lsp.ExecuteCommandParams: // "workspace/executeCommand":
+		log.Printf("--X " + req.Method)
+		return nil, nil
+		err = handler.ino2cppExecuteCommand(p)
 	}
 	if err != nil {
-		return
+		log.Printf("    --E %s", err)
+		return nil, err
 	}
+
+	var result interface{}
 	if req.Notif {
 		err = handler.ClangdConn.Notify(ctx, req.Method, params)
-		if enableLogging {
-			log.Println("From stdio:", req.Method)
-		}
+		// 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("From stdio:", req.Method, "id", req.ID)
-		}
+		result, err = lsp.SendRequest(ctx, handler.ClangdConn, req.Method, params)
+		// 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 && 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
@@ -146,14 +320,13 @@ func (handler *InoHandler) FromStdio(ctx context.Context, conn *jsonrpc2.Conn, r
 			log.Println("The clangd process has lost track of the open document.")
 			handler.exit()
 		}
-		return
 	}
 
 	// Transform and return the result
 	if result != nil {
 		result = handler.transformClangdResult(req.Method, uri, result)
 	}
-	return
+	return result, err
 }
 
 func (handler *InoHandler) exit() {
@@ -162,181 +335,280 @@ func (handler *InoHandler) exit() {
 	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()
+func (handler *InoHandler) initializeWorkbench(params *lsp.InitializeParams) error {
+	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 {
-		handler.synchronizer.DataMux.RLock()
-		defer handler.synchronizer.DataMux.RUnlock()
+		currCppTextVersion = handler.sketchMapper.CppText.Version
+		log.Printf("--> RE-initialize()\n")
 	}
 
-	switch method {
-	case "textDocument/didOpen":
-		p := params.(*lsp.DidOpenTextDocumentParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentItem(ctx, &p.TextDocument)
-	case "textDocument/didChange":
-		p := params.(*lsp.DidChangeTextDocumentParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppDidChangeTextDocumentParams(ctx, p)
-	case "textDocument/didSave":
-		p := params.(*lsp.DidSaveTextDocumentParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument)
-	case "textDocument/didClose":
-		p := params.(*lsp.DidCloseTextDocumentParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument)
-		handler.deleteFileData(uri)
-	case "textDocument/completion":
-		p := params.(*lsp.CompletionParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams)
-	case "textDocument/codeAction":
-		p := params.(*lsp.CodeActionParams)
-		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)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentPositionParams(p)
-	case "textDocument/references":
-		p := params.(*lsp.ReferenceParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentPositionParams(&p.TextDocumentPositionParams)
-	case "textDocument/formatting":
-		p := params.(*lsp.DocumentFormattingParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument)
-	case "textDocument/rangeFormatting":
-		p := params.(*lsp.DocumentRangeFormattingParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppDocumentRangeFormattingParams(p)
-	case "textDocument/onTypeFormatting":
-		p := params.(*lsp.DocumentOnTypeFormattingParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppDocumentOnTypeFormattingParams(p)
-	case "textDocument/documentSymbol":
-		p := params.(*lsp.DocumentSymbolParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppTextDocumentIdentifier(&p.TextDocument)
-	case "textDocument/rename":
-		p := params.(*lsp.RenameParams)
-		uri = p.TextDocument.URI
-		err = handler.ino2cppRenameParams(p)
-	case "workspace/didChangeWatchedFiles":
-		p := params.(*lsp.DidChangeWatchedFilesParams)
-		err = handler.ino2cppDidChangeWatchedFilesParams(p)
-	case "workspace/executeCommand":
-		p := params.(*lsp.ExecuteCommandParams)
-		err = handler.ino2cppExecuteCommand(p)
+	if buildPath, err := handler.generateBuildEnvironment(); err == nil {
+		handler.buildPath = buildPath
+		handler.buildSketchRoot = buildPath.Join("sketch")
+	} else {
+		return err
+	}
+	handler.buildSketchCpp = handler.buildSketchRoot.Join(handler.sketchName + ".ino.cpp")
+	handler.buildSketchCppVersion = 1
+	handler.lspInitializeParams.RootPath = handler.buildSketchRoot.String()
+	handler.lspInitializeParams.RootURI = lsp.NewDocumentURIFromPath(handler.buildSketchRoot)
+
+	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")
+	}
+
+	if params == nil {
+		// If we are restarting re-synchronize clangd
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+
+		cppURI := lsp.NewDocumentURIFromPath(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 {
+		// 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)
 	}
-	return
+
+	return nil
 }
 
-func (handler *InoHandler) createFileData(ctx context.Context, sourceURI lsp.DocumentURI, sourceText string, version int) (*FileData, []byte, error) {
-	sourcePath := uriToPath(sourceURI)
-	targetPath, targetBytes, err := generateCpp([]byte(sourceText), sourcePath, handler.config.SelectedBoard.Fqbn)
+func (handler *InoHandler) refreshCppDocumentSymbols() error {
+	// Query source code symbols
+	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},
+	})
 	if err != nil {
-		err = handler.handleError(ctx, err)
-		if len(targetPath) == 0 {
-			return nil, nil, err
+		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 {
+		// Filter non-functions symbols
+		i := 0
+		for _, symbol := range symbols {
+			if symbol.Kind != lsp.SKFunction {
+				continue
+			}
+			symbols[i] = symbol
+			i++
 		}
-		// Fallback: use the source text unchanged
-		targetBytes, err = copyIno2Cpp(sourceText, targetPath)
-		if err != nil {
-			return nil, nil, err
+		symbols = symbols[:i]
+		for _, symbol := range symbols {
+			log.Printf("    symbol: %s %s", symbol.Kind, symbol.Name)
 		}
+		handler.buildSketchSymbols = symbols
 	}
+	return nil
+}
 
-	targetURI := pathToURI(targetPath)
-	sourceLineMap, targetLineMap := createSourceMaps(bytes.NewReader(targetBytes))
-	data := &FileData{
-		sourceText,
-		sourceURI,
-		targetURI,
-		sourceLineMap,
-		targetLineMap,
-		version,
+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
+		}
 	}
-	handler.data[sourceURI] = data
-	handler.data[targetURI] = data
-	return data, targetBytes, nil
+	return nil
 }
 
-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 = applyTextChange(data.sourceText, *rang, change.Text)
-			if err != nil {
-				return err
-			}
+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 {
+		panic("could not find compile_commands.json")
+	}
+	compilers := map[string]bool{}
+	for _, cmd := range compileCommands.Contents {
+		if len(cmd.Arguments) == 0 {
+			panic("invalid empty argument field in compile_commands.json")
 		}
-		targetBytes, err := updateCpp([]byte(newSourceText), uriToPath(data.sourceURI), handler.config.SelectedBoard.Fqbn, false, uriToPath(data.targetURI))
-		if err != nil {
-			if rang == nil {
-				// Fallback: use the source text unchanged
-				targetBytes, err = copyIno2Cpp(newSourceText, uriToPath(data.targetURI))
-				if err != nil {
-					return err
-				}
-			} 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
-				return nil
-			}
+		compilers[cmd.Arguments[0]] = true
+	}
+	if len(compilers) == 0 {
+		panic("main compiler not found")
+	}
+
+	// Start clangd
+	args := []string{
+		globalClangdPath,
+		"-log=verbose",
+		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, " "))
+	}
+	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, inoDidOpen *lsp.DidOpenTextDocumentParams) (*lsp.DidOpenTextDocumentParams, error) {
+	// Add the TextDocumentItem in the tracked files list
+	inoItem := inoDidOpen.TextDocument
+	handler.docs[inoItem.URI] = &inoItem
+
+	// If we are tracking a .ino...
+	if inoItem.URI.Ext() == ".ino" {
+		handler.sketchTrackedFilesCount++
+		log.Printf("    increasing .ino tracked files count: %d", handler.sketchTrackedFilesCount)
+
+		// notify clang that sketchCpp has been opened only once
+		if handler.sketchTrackedFilesCount != 1 {
+			return nil, nil
 		}
 
-		sourceLineMap, targetLineMap := createSourceMaps(bytes.NewReader(targetBytes))
-		data.sourceText = newSourceText
-		data.sourceLineMap = sourceLineMap
-		data.targetLineMap = targetLineMap
+		// trigger a documentSymbol load
+		handler.buildSketchSymbolsLoad = true
+	}
+
+	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
 
-		change.Text = string(targetBytes)
-		change.Range = nil
-		change.RangeLength = 0
+	if cppURI.AsPath().EquivalentTo(handler.buildSketchCpp) {
+		cppItem.LanguageID = "cpp"
+		cppItem.Text = handler.sketchMapper.CppText.Text
+		cppItem.Version = handler.sketchMapper.CppText.Version
 	} 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)
-		if err != nil {
-			return err
+		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) {
+	doc := req.TextDocument
+
+	trackedDoc, ok := handler.docs[doc.URI]
+	if !ok {
+		return nil, unknownURI(doc.URI)
+	}
+	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.
+	if doc.URI.Ext() == ".ino" {
+
+		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("--! DIRTY CHANGE, force sketch rebuild!")
+				handler.scheduleRebuildEnvironment()
+			}
+
+			// 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)
 		}
-		updateSourceMaps(data.sourceLineMap, data.targetLineMap, 0, rang.Start.Line, change.Text)
 
-		rang.Start.Line = targetLine
-		rang.End.Line = targetLine
+		// build a cpp equivalent didChange request
+		cppReq := &lsp.DidChangeTextDocumentParams{
+			ContentChanges: cppChanges,
+			TextDocument: lsp.VersionedTextDocumentIdentifier{
+				TextDocumentIdentifier: lsp.TextDocumentIdentifier{
+					URI: lsp.NewDocumentURIFromPath(handler.buildSketchCpp),
+				},
+				Version: handler.sketchMapper.CppText.Version,
+			},
+		}
+		return cppReq, nil
 	}
-	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)
+	// 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:   cppDoc,
+		ContentChanges: req.ContentChanges,
+	}
+	return cppReq, err
 }
 
 func (handler *InoHandler) handleError(ctx context.Context, err error) error {
@@ -374,218 +646,263 @@ 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
-	}
-	return unknownURI(doc.URI)
+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) 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) ino2cppTextDocumentIdentifier(doc lsp.TextDocumentIdentifier) (lsp.TextDocumentIdentifier, error) {
+	cppURI, err := handler.ino2cppDocumentURI(doc.URI)
+	res := doc
+	res.URI = cppURI
+	return res, err
 }
 
-func (handler *InoHandler) ino2cppDidChangeTextDocumentParams(ctx context.Context, params *lsp.DidChangeTextDocumentParams) error {
-	handler.ino2cppTextDocumentIdentifier(&params.TextDocument.TextDocumentIdentifier)
-	if data, ok := handler.data[params.TextDocument.URI]; ok {
-		for index := range params.ContentChanges {
-			err := handler.updateFileData(ctx, data, &params.ContentChanges[index])
-			if err != nil {
-				return err
-			}
-		}
-		data.version = params.TextDocument.Version
-		return nil
+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 := inoURI.AsPath()
+	if inoPath.Ext() == ".ino" {
+		return lsp.NewDocumentURIFromPath(handler.buildSketchCpp), nil
 	}
-	return unknownURI(params.TextDocument.URI)
+
+	inside, err := inoPath.IsInsideDir(handler.sketchRoot)
+	if err != nil {
+		log.Printf("    could not determine if '%s' is inside '%s'", inoPath, handler.sketchRoot)
+		return "", unknownURI(inoURI)
+	}
+	if !inside {
+		log.Printf("    passing doc identifier to '%s' as-is", inoPath)
+		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("    could not determine rel-path of '%s' in '%s': %s", inoPath, handler.sketchRoot, err)
+	return "", err
 }
 
-func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error {
-	handler.ino2cppTextDocumentIdentifier(&params.TextDocument)
-	if data, ok := handler.data[params.TextDocument.URI]; ok {
-		targetLine := data.targetLineMap[params.Position.Line]
-		params.Position.Line = targetLine
-		return nil
+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
 	}
-	return unknownURI(params.TextDocument.URI)
+
+	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) ino2cppCodeActionParams(params *lsp.CodeActionParams) error {
-	handler.ino2cppTextDocumentIdentifier(&params.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]
-		for index := range params.Context.Diagnostics {
-			r := &params.Context.Diagnostics[index].Range
-			r.Start.Line = data.targetLineMap[r.Start.Line]
-			r.End.Line = data.targetLineMap[r.End.Line]
+func (handler *InoHandler) ino2cppTextDocumentPositionParams(params *lsp.TextDocumentPositionParams) error {
+	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)
 		}
-		return nil
+		params.Position.Line = line
 	}
-	return unknownURI(params.TextDocument.URI)
+	cppDoc, err := handler.ino2cppTextDocumentIdentifier(params.TextDocument)
+	if err != nil {
+		return err
+	}
+	params.TextDocument = cppDoc
+	return nil
 }
 
 func (handler *InoHandler) ino2cppDocumentRangeFormattingParams(params *lsp.DocumentRangeFormattingParams) error {
-	handler.ino2cppTextDocumentIdentifier(&params.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]
-		return nil
-	}
+	panic("not implemented")
+	// handler.sketchToBuildPathTextDocumentIdentifier(&params.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.ino2cppTextDocumentIdentifier(&params.TextDocument)
-	if data, ok := handler.data[params.TextDocument.URI]; ok {
-		params.Position.Line = data.targetLineMap[params.Position.Line]
-		return nil
-	}
+	panic("not implemented")
+	// handler.sketchToBuildPathTextDocumentIdentifier(&params.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.ino2cppTextDocumentIdentifier(&params.TextDocument)
-	if data, ok := handler.data[params.TextDocument.URI]; ok {
-		params.Position.Line = data.targetLineMap[params.Position.Line]
-		return nil
-	}
+	panic("not implemented")
+	// handler.sketchToBuildPathTextDocumentIdentifier(&params.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 := &params.Changes[index]
-		if data, ok := handler.data[fileEvent.URI]; ok {
-			fileEvent.URI = data.targetURI
-		}
-	}
+	panic("not implemented")
+	// for index := range params.Changes {
+	// 	fileEvent := &params.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 {
-	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 {
-				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},
-					},
-				}
-			}
-			newEdit.Changes[string(data.targetURI)] = newValue
-		} else {
-			newEdit.Changes[uri] = edit
-		}
-	}
+	panic("not implemented")
+	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))
+	// 		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
 }
 
 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 method {
-	case "textDocument/completion":
-		r := result.(*lsp.CompletionList)
-		handler.cpp2inoCompletionList(r, uri)
-	case "textDocument/codeAction":
-		r := result.(*[]*commandOrCodeAction)
-		for index := range *r {
-			command := (*r)[index].Command
-			if command != nil {
-				handler.cpp2inoCommand(command)
-			}
-			codeAction := (*r)[index].CodeAction
-			if codeAction != nil {
-				handler.cpp2inoCodeAction(codeAction, uri)
-			}
-		}
-	case "textDocument/hover":
-		r := result.(*Hover)
+	switch r := result.(type) {
+	case *lsp.Hover:
+		// method "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)
+		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.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"
+		log.Printf("    <-- codeAction(%d elements)", len(*r))
+		for i, item := range *r {
+			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))
+
+	// 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 {
-			return result
-		}
-
-		slice := *r
-		if slice[0].DocumentSymbol != nil {
-			// Treat the input as []DocumentSymbol
-			symbols := make([]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 "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)
 		}
@@ -593,163 +910,318 @@ func (handler *InoHandler) transformClangdResult(method string, uri lsp.Document
 	return result
 }
 
-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 {
-					r := &item.TextEdit.Range
-					r.Start.Line = data.sourceLineMap[r.Start.Line]
-					r.End.Line = data.sourceLineMap[r.End.Line]
-				}
-				newItems = append(newItems, item)
-			}
-		}
-		list.Items = newItems
+func (handler *InoHandler) cpp2inoCodeAction(codeAction *lsp.CodeAction, uri lsp.DocumentURI) *lsp.CodeAction {
+	if codeAction == nil {
+		return nil
 	}
-}
-
-func (handler *InoHandler) cpp2inoCodeAction(codeAction *CodeAction, uri lsp.DocumentURI) {
-	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]
-		}
+	inoCodeAction := &lsp.CodeAction{
+		Title:       codeAction.Title,
+		Kind:        codeAction.Kind,
+		Edit:        handler.cpp2inoWorkspaceEdit(codeAction.Edit),
+		Diagnostics: codeAction.Diagnostics,
+		Command:     handler.Cpp2InoCommand(codeAction.Command),
 	}
-}
-
-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)
+	if uri.Ext() == ".ino" {
+		for i, diag := range inoCodeAction.Diagnostics {
+			_, inoCodeAction.Diagnostics[i].Range = handler.sketchMapper.CppToInoRange(diag.Range)
 		}
 	}
+	return inoCodeAction
 }
 
-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 {
-				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},
-					},
+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 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
+					}
 				}
 			}
-			newEdit.Changes[string(data.sourceURI)] = newValue
-		} else {
-			newEdit.Changes[uri] = edit
+
+			converted, err := json.Marshal(v)
+			if err != nil {
+				panic("Internal Error: json conversion of codeAcion command arguments")
+			}
+			inoCommand.Arguments[i] = converted
 		}
 	}
-	return &newEdit
+	return inoCommand
 }
 
-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]
+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) {
-	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]
-	}
+	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.Start.Line = data.sourceLineMap[highlight.Range.Start.Line]
-		highlight.Range.End.Line = data.sourceLineMap[highlight.Range.End.Line]
-	}
+	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.Start.Line = data.sourceLineMap[edit.Range.Start.Line]
-		edit.Range.End.Line = data.sourceLineMap[edit.Range.End.Line]
-	}
+	panic("not implemented")
+	// if data, ok := handler.data[uri]; ok {
+	// 	_, edit.Range = data.sourceMap.CppToInoRange(edit.Range)
+	// }
 }
 
-func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []DocumentSymbol, uri lsp.DocumentURI) []DocumentSymbol {
-	data, ok := handler.data[uri]
-	if !ok || len(origSymbols) == 0 {
+func (handler *InoHandler) cpp2inoDocumentSymbols(origSymbols []lsp.DocumentSymbol, origURI lsp.DocumentURI) []lsp.DocumentSymbol {
+	if origURI.Ext() != ".ino" || len(origSymbols) == 0 {
 		return origSymbols
 	}
 
-	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]
+	inoSymbols := []lsp.DocumentSymbol{}
+	for _, symbol := range origSymbols {
+		if handler.sketchMapper.IsPreprocessedCppLine(symbol.Range.Start.Line) {
+			continue
+		}
+
+		inoFile, inoRange := handler.sketchMapper.CppToInoRange(symbol.Range)
+		inoSelectionURI, inoSelectionRange := handler.sketchMapper.CppToInoRange(symbol.SelectionRange)
 
-		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
-			}
+		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
 		}
 
-		symbol.SelectionRange.Start.Line = data.sourceLineMap[symbol.SelectionRange.Start.Line]
-		symbol.SelectionRange.End.Line = data.sourceLineMap[symbol.SelectionRange.End.Line]
-		symbol.Children = handler.cpp2inoDocumentSymbols(symbol.Children, uri)
-		symbolIdx[symbol.Name] = symbol
-	}
+		if inoFile != origURI.Unbox() {
+			//log.Printf("    skipping symbol related to %s", inoFile)
+			continue
+		}
 
-	newSymbols := make([]DocumentSymbol, len(symbolIdx))
-	j := 0
-	for _, s := range symbolIdx {
-		newSymbols[j] = *s
-		j++
+		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 newSymbols
+
+	return inoSymbols
 }
 
-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)
+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)
+	// 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
+	// 	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
+}
+
+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
 		}
 
-		idx[nme] = sym
+		inoURI, _, err := handler.cpp2inoDocumentURI(cppDiags.URI, lsp.Range{})
+		return []*lsp.PublishDiagnosticsParams{
+			{
+				URI:         inoURI,
+				Diagnostics: []lsp.Diagnostic{},
+			},
+		}, err
 	}
 
-	var j int
-	symbols := make([]lsp.SymbolInformation, len(idx))
-	for _, sym := range idx {
-		symbols[j] = *sym
-		j++
+	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 symbols
+	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) {
-	params, _, err := handler.transformParamsToStdio(req.Method, req.Params)
+	defer streams.CatchAndLogPanic()
+
+	handler.synchronizer.DataMux.RLock()
+	defer handler.synchronizer.DataMux.RUnlock()
+
+	params, err := lsp.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"
+		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)
+		}
+
+		// 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)
+				}
+			}
+
+			// 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 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.inoDocsWithDiagnostics {
+				if inoDocsWithDiagnostics[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.inoDocsWithDiagnostics = inoDocsWithDiagnostics
+		}
+		return nil, err
+
+	case *lsp.ApplyWorkspaceEditParams:
+		// "workspace/applyEdit"
+		p.Edit = *handler.cpp2inoWorkspaceEdit(&p.Edit)
+	}
+
 	if err != nil {
 		log.Println("From clangd: Method:", req.Method, "Error:", err)
 		return nil, err
@@ -761,7 +1233,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)
 		}
@@ -769,78 +1241,16 @@ 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 := &params.Diagnostics[index].Range
-			if startLine, ok := data.sourceLineMap[r.Start.Line]; ok {
-				r.Start.Line = startLine
-				r.End.Line = data.sourceLineMap[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{})
-		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) {
+	defer streams.CatchAndLogPanic()
+
 	params := lsp.ShowMessageParams{
 		Type:    msgType,
 		Message: message,
 	}
 	handler.StdioConn.Notify(ctx, "window/showMessage", &params)
 }
+
+func unknownURI(uri lsp.DocumentURI) error {
+	return errors.New("Document is not available: " + string(uri))
+}
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)
-	}
-}
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..a921b40
--- /dev/null
+++ b/handler/sourcemapper/ino.go
@@ -0,0 +1,321 @@
+package sourcemapper
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+
+	"github.com/bcmi-labs/arduino-language-server/handler/textutils"
+	"github.com/bcmi-labs/arduino-language-server/lsp"
+)
+
+// InoMapper is a mapping between the .ino sketch and the preprocessed .cpp file
+type InoMapper struct {
+	InoText         map[lsp.DocumentURI]*SourceRevision
+	CppText         *SourceRevision
+	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
+}
+
+// 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(sourceURI lsp.DocumentURI, line int) int {
+	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) {
+	res, ok := s.toCpp[InoLine{sourceURI.Unbox(), line}]
+	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)
+	res.End.Line = s.InoToCppLine(sourceURI, r.End.Line)
+	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]
+	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) (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) (string, int, bool) {
+	res, ok := s.toIno[targetLine]
+	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{
+		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(bytes.NewReader(targetFile))
+	for scanner.Scan() {
+		lineStr := scanner.Text()
+		if strings.HasPrefix(lineStr, "#line") {
+			tokens := strings.SplitN(lineStr, " ", 3)
+			l, err := strconv.Atoi(tokens[1])
+			if err == nil && l > 0 {
+				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++
+	}
+	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.inoPreprocessed[inoLine] = line
+	}
+	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:]
+	}
+	str = strings.Replace(str, "\\\"", "\"", -1)
+	str = strings.Replace(str, "\\\\", "\\", -1)
+	return str
+}
+
+// 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++
+
+	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)
+		deletedLines--
+	}
+	addedLines := strings.Count(inoChange.Text, "\n") - 1
+	for addedLines > 0 {
+		dirty = dirty || s.addInoLine(cppRange.Start.Line)
+		addedLines--
+	}
+	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
new file mode 100644
index 0000000..d6cf23e
--- /dev/null
+++ b/handler/sourcemapper/ino_test.go
@@ -0,0 +1,293 @@
+package sourcemapper
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestCreateSourceMaps(t *testing.T) {
+	input := `#include <Arduino.h>
+#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([]byte(input))
+	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{
+		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},
+		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)
+	require.EqualValues(t, map[int]InoLine{
+		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) {
+	input := `#include <Arduino.h>
+#line 1 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/Prova_Spazio.ino"
+#include <SPI.h>
+#include <Audio.h>
+
+#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);
+}
+
+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() {
+}
+
+#line 1 "/home/megabug/Workspace/sketchbook-cores-beta/Prova_Spazio/SecondTab.ino"
+
+void secondFunction() {
+
+}`
+	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,
+		{"/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}:  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{
+		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},
+		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
+		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},
+		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},
+		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},
+		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)
+	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) {
+// 	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/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/handler/syncer.go b/handler/syncer.go
index 2cc1b98..452d3c4 100644
--- a/handler/syncer.go
+++ b/handler/syncer.go
@@ -2,7 +2,6 @@ package handler
 
 import (
 	"context"
-	"log"
 	"sync"
 
 	"github.com/sourcegraph/jsonrpc2"
@@ -27,26 +26,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()
+		}()
 	}
 }
diff --git a/handler/textutils/textutils.go b/handler/textutils/textutils.go
new file mode 100644
index 0000000..dbcf0b8
--- /dev/null
+++ b/handler/textutils/textutils.go
@@ -0,0 +1,112 @@
+package textutils
+
+import (
+	"fmt"
+
+	"github.com/bcmi-labs/arduino-language-server/lsp"
+)
+
+// ApplyLSPTextDocumentContentChangeEvent applies the LSP change in the given text
+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 = 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)
+	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..105b71c 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/bcmi-labs/arduino-language-server/lsp"
 )
 
-func TestCreateSourceMaps(t *testing.T) {
-	input := `#include <Arduino.h>
-#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"))
 		}
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 64%
rename from handler/protocol.go
rename to lsp/protocol.go
index 177d05e..6af5eeb 100644
--- a/handler/protocol.go
+++ b/lsp/protocol.go
@@ -1,47 +1,50 @@
-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 "initialized":
+		return &InitializedParams{}, nil
 	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":
 		fallthrough
 	case "textDocument/hover":
-		fallthrough
+		params := new(HoverParams)
+		err := json.Unmarshal(*raw, params)
+		return params, err
 	case "textDocument/definition":
 		fallthrough
 	case "textDocument/typeDefinition":
@@ -49,43 +52,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":
@@ -93,7 +96,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":
@@ -104,26 +107,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":
@@ -137,11 +140,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":
@@ -149,23 +152,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(DocumentSymbolArrayOrSymbolInformationArray)
 		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":
@@ -184,20 +187,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
@@ -212,7 +215,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)
 	}
@@ -225,7 +228,13 @@ func (entry *commandOrCodeAction) MarshalJSON() ([]byte, error) {
 // 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
@@ -238,50 +247,55 @@ 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 {
-	DocumentSymbol    *DocumentSymbol
-	SymbolInformation *lsp.SymbolInformation
+type DocumentSymbolArrayOrSymbolInformationArray struct {
+	DocumentSymbolArray    *[]DocumentSymbol
+	SymbolInformationArray *[]SymbolInformation
 }
 
-type documentSymbolOrSymbolInformationDiscriminator struct {
-	Range    *lsp.Range    `json:"range,omitempty"`
-	Location *lsp.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(lsp.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
 }
 
 // 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/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)
+}
diff --git a/lsp/service.go b/lsp/service.go
new file mode 100644
index 0000000..b08d5a4
--- /dev/null
+++ b/lsp/service.go
@@ -0,0 +1,921 @@
+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"`
+}
+
+type InitializedParams struct{}
+
+// 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..e7a93b6
--- /dev/null
+++ b/lsp/structures.go
@@ -0,0 +1,168 @@
+package lsp
+
+import (
+	"encoding/json"
+	"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 []json.RawMessage `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[DocumentURI][]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/handler/uri.go b/lsp/uri.go
similarity index 51%
rename from handler/uri.go
rename to lsp/uri.go
index 0553723..9c84adf 100644
--- a/handler/uri.go
+++ b/lsp/uri.go
@@ -1,4 +1,4 @@
-package handler
+package lsp
 
 import (
 	"net/url"
@@ -7,13 +7,18 @@ import (
 	"runtime"
 	"strings"
 
-	"github.com/pkg/errors"
-	lsp "github.com/sourcegraph/go-lsp"
+	"github.com/arduino/go-paths-helper"
 )
 
 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)
@@ -34,7 +39,22 @@ func uriToPath(uri lsp.DocumentURI) string {
 	return path
 }
 
-func pathToURI(path string) lsp.DocumentURI {
+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())
+}
+
+// NewDocumentURI create a DocumentURI from the given string path
+func NewDocumentURI(path string) DocumentURI {
 	urlObj, err := url.Parse("file://")
 	if err != nil {
 		panic(err)
@@ -45,9 +65,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 59%
rename from handler/uri_test.go
rename to lsp/uri_test.go
index 3a760f7..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"
-
-	lsp "github.com/sourcegraph/go-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)
 	}
diff --git a/main.go b/main.go
index 9980174..d76c81a 100644
--- a/main.go
+++ b/main.go
@@ -2,13 +2,14 @@ package main
 
 import (
 	"flag"
-	"fmt"
 	"io"
 	"log"
 	"os"
-	"os/exec"
 
+	"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"
 )
 
 var clangdPath string
@@ -29,46 +30,29 @@ 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()
+	if loggingBasePath != "" {
+		streams.GlobalLogDirectory = paths.New(loggingBasePath)
+	} else if enableLogging {
+		log.Fatalf("Please specify logpath")
+	}
 
-		log.SetOutput(logStreams.Default)
+	if enableLogging {
+		logfile := streams.OpenLogFileAs("inols-err.log")
+		log.SetOutput(io.MultiWriter(logfile, os.Stderr))
+		defer streams.CatchAndLogPanic()
 	} 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)
-	defer inoHandler.StopClangd()
-	<-inoHandler.StdioConn.DisconnectNotify()
-}
+	handler.Setup(cliPath, clangdPath, enableLogging, true)
+	initialBoard := lsp.Board{Fqbn: initialFqbn, Name: initialBoardName}
 
-func startClangd() (clangdIn io.WriteCloser, clangdOut io.ReadCloser, clangdErr io.ReadCloser) {
+	stdio := streams.NewReadWriteCloser(os.Stdin, os.Stdout)
 	if enableLogging {
-		log.Println("Starting clangd process:", clangdPath)
+		stdio = streams.LogReadWriteCloserAs(stdio, "inols.log")
 	}
-	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
+	inoHandler := handler.NewInoHandler(stdio, initialBoard)
+	defer inoHandler.StopClangd()
+	<-inoHandler.StdioConn.DisconnectNotify()
 }
diff --git a/streams/dumper.go b/streams/dumper.go
new file mode 100644
index 0000000..a948aa1
--- /dev/null
+++ b/streams/dumper.go
@@ -0,0 +1,89 @@
+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: 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: upstream,
+		logfile:  file,
+	}
+}
+
+// OpenLogFileAs creates a log file in GlobalLogDirectory.
+func OpenLogFileAs(filename string) *os.File {
+	path := GlobalLogDirectory.Join(filename)
+	res, err := path.Append()
+	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
+	reading  bool
+	writing  bool
+}
+
+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 {
+		if !d.reading {
+			d.reading = true
+			d.writing = false
+			d.logfile.Write([]byte("\n<<<\n"))
+		}
+		d.logfile.Write(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 {
+		if !d.writing {
+			d.writing = true
+			d.reading = false
+			d.logfile.Write([]byte("\n>>>\n"))
+		}
+		_, _ = d.logfile.Write(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/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)
+	}
+}
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
+}